summaryrefslogtreecommitdiffstats
path: root/toolkit
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:14:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:14:29 +0000
commitfbaf0bb26397aa498eb9156f06d5a6fe34dd7dd8 (patch)
tree4c1ccaf5486d4f2009f9a338a98a83e886e29c97 /toolkit
parentReleasing progress-linux version 124.0.1-1~progress7.99u1. (diff)
downloadfirefox-fbaf0bb26397aa498eb9156f06d5a6fe34dd7dd8.tar.xz
firefox-fbaf0bb26397aa498eb9156f06d5a6fe34dd7dd8.zip
Merging upstream version 125.0.1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit')
-rw-r--r--toolkit/actors/AutoCompleteParent.sys.mjs3
-rw-r--r--toolkit/actors/AutoScrollChild.sys.mjs30
-rw-r--r--toolkit/actors/FindBarChild.sys.mjs7
-rw-r--r--toolkit/actors/PictureInPictureChild.sys.mjs12
-rw-r--r--toolkit/actors/SelectChild.sys.mjs12
-rw-r--r--toolkit/actors/SelectParent.sys.mjs2
-rw-r--r--toolkit/components/aboutconfig/content/aboutconfig.js1
-rw-r--r--toolkit/components/aboutconfig/test/browser/browser.toml3
-rw-r--r--toolkit/components/aboutconfig/test/browser/browser_clipboard.js17
-rw-r--r--toolkit/components/aboutconfig/test/browser/browser_edit.js21
-rw-r--r--toolkit/components/aboutmemory/content/aboutMemory.js6
-rw-r--r--toolkit/components/antitracking/ContentBlockingAllowList.cpp69
-rw-r--r--toolkit/components/antitracking/ContentBlockingAllowList.h56
-rw-r--r--toolkit/components/antitracking/ContentBlockingAllowList.sys.mjs2
-rw-r--r--toolkit/components/antitracking/PurgeTrackerService.sys.mjs2
-rw-r--r--toolkit/components/antitracking/URLDecorationAnnotationsService.sys.mjs2
-rw-r--r--toolkit/components/antitracking/URLQueryStringStripper.cpp4
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingProtection.cpp123
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingProtection.h28
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingRecord.cpp13
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingRecord.h15
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingState.cpp17
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingState.h6
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingStateGlobal.cpp32
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingStateGlobal.h11
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/nsIBounceTrackingProtection.idl4
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_purge.js66
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/test/xpcshell/test_bouncetracking_clearExpiredUserActivation.js85
-rw-r--r--toolkit/components/antitracking/bouncetrackingprotection/test/xpcshell/xpcshell.toml2
-rw-r--r--toolkit/components/antitracking/test/browser/3rdPartySVG.html2
-rw-r--r--toolkit/components/antitracking/test/browser/3rdPartyWorker.html2
-rw-r--r--toolkit/components/antitracking/test/browser/antitracking_head.js12
-rw-r--r--toolkit/components/antitracking/test/browser/blobPartitionPage.html2
-rw-r--r--toolkit/components/antitracking/test/browser/browser-blocking.toml6
-rw-r--r--toolkit/components/antitracking/test/browser/browser_aboutblank.js4
-rw-r--r--toolkit/components/antitracking/test/browser/browser_addonHostPermissionIgnoredInTP.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_allowListNotifications.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_allowListNotifications_alwaysPartition.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_allowListSeparationInPrivateAndNormalWindows.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_allowPermissionForTracker.js4
-rw-r--r--toolkit/components/antitracking/test/browser/browser_backgroundImageAssertion.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_blockingCookies.js4
-rw-r--r--toolkit/components/antitracking/test/browser/browser_blockingDOMCache.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_blockingDOMCacheAlwaysPartition.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_blockingDOMCacheAlwaysPartitionSAA.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_blockingDOMCacheSAA.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_blockingIndexedDb.js4
-rw-r--r--toolkit/components/antitracking/test/browser/browser_blockingIndexedDbInWorkers.js6
-rw-r--r--toolkit/components/antitracking/test/browser/browser_blockingIndexedDbInWorkers2.js10
-rw-r--r--toolkit/components/antitracking/test/browser/browser_blockingLocalStorage.js4
-rw-r--r--toolkit/components/antitracking/test/browser/browser_blockingMessaging.js20
-rw-r--r--toolkit/components/antitracking/test/browser/browser_blockingNoOpener.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_blockingServiceWorkers.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_blockingServiceWorkersStorageAccessAPI.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_blockingSessionStorage.js4
-rw-r--r--toolkit/components/antitracking/test/browser/browser_blockingSharedWorkers.js4
-rw-r--r--toolkit/components/antitracking/test/browser/browser_contentBlockingAllowListPrincipal.js14
-rw-r--r--toolkit/components/antitracking/test/browser/browser_contentBlockingTelemetry.js3
-rw-r--r--toolkit/components/antitracking/test/browser/browser_cookieBetweenTabs.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_denyPermissionForTracker.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_doublyNestedTracker.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_existingCookiesForSubresources.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_firstPartyCookieRejectionHonoursAllowList.js4
-rw-r--r--toolkit/components/antitracking/test/browser/browser_hasStorageAccess.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_hasStorageAccess_alwaysPartition.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_localStorageEvents.js4
-rw-r--r--toolkit/components/antitracking/test/browser/browser_onModifyRequestNotificationForTrackingResources.js4
-rw-r--r--toolkit/components/antitracking/test/browser/browser_partitionedConsoleMessage.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_partitionedCookies.js20
-rw-r--r--toolkit/components/antitracking/test/browser/browser_partitionedDOMCache.js14
-rw-r--r--toolkit/components/antitracking/test/browser/browser_partitionedIndexedDB.js6
-rw-r--r--toolkit/components/antitracking/test/browser/browser_partitionedLocalStorage.js4
-rw-r--r--toolkit/components/antitracking/test/browser/browser_partitionedLocalStorage_events.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_partitionedLockManager.js4
-rw-r--r--toolkit/components/antitracking/test/browser/browser_partitionedMessaging.js4
-rw-r--r--toolkit/components/antitracking/test/browser/browser_partitionedServiceWorkers.js40
-rw-r--r--toolkit/components/antitracking/test/browser/browser_partitionedSharedWorkers.js4
-rw-r--r--toolkit/components/antitracking/test/browser/browser_permissionInNormalWindows.js5
-rw-r--r--toolkit/components/antitracking/test/browser/browser_permissionInNormalWindows_alwaysPartition.js6
-rw-r--r--toolkit/components/antitracking/test/browser/browser_permissionInPrivateWindows.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_permissionInPrivateWindows_alwaysPartition.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_permissionPropagation.js6
-rw-r--r--toolkit/components/antitracking/test/browser/browser_referrerDefaultPolicy.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_script.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_siteSpecificWorkArounds.js8
-rw-r--r--toolkit/components/antitracking/test/browser/browser_socialtracking_save_image.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_staticPartition_cache.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_staticPartition_network.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_staticPartition_saveAs.js6
-rw-r--r--toolkit/components/antitracking/test/browser/browser_staticPartition_tls_session.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccessDoorHanger.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccessPrivilegeAPI.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccessPromiseRejectHandlerUserInteraction.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccessPromiseRejectHandlerUserInteraction_alwaysPartition.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccessPromiseResolveHandlerUserInteraction.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccessRemovalNavigateSubframe.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccessRemovalNavigateSubframe_alwaysPartition.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccessRemovalNavigateTopframe.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccessRemovalNavigateTopframe_alwaysPartition.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccessSandboxed.js6
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccessSandboxed_alwaysPartition.js6
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccessThirdPartyChecks.js6
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccessThirdPartyChecks_alwaysPartition.js6
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccessWithDynamicFpi.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccessWithHeuristics.js22
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_Arguments.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_CookieBehavior.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_CookiePermission.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_CrossOriginSameSite.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_Doorhanger.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_Embed.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_Enable.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_RequireIntermediatePermission.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_StorageAccessPermission.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_UserActivation.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_subResources.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_subResourcesPartitioned.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_subResourcesPartitioned_alwaysPartition.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_thirdPartyStorageRejectionForCORS.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_urlDecorationStripping.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_urlDecorationStripping_alwaysPartition.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_urlQueryStringStripping.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_urlQueryStringStripping_allowList.js2
-rw-r--r--toolkit/components/antitracking/test/browser/browser_userInteraction.js8
-rw-r--r--toolkit/components/antitracking/test/browser/browser_workerPropagation.js2
-rw-r--r--toolkit/components/antitracking/test/browser/file_saveAsPageInfo.html4
-rw-r--r--toolkit/components/antitracking/test/browser/head.js2
-rw-r--r--toolkit/components/antitracking/test/browser/imageCacheWorker.js2
-rw-r--r--toolkit/components/antitracking/test/browser/localStorage.html2
-rw-r--r--toolkit/components/antitracking/test/browser/partitionedstorage_head.js17
-rw-r--r--toolkit/components/antitracking/test/browser/storage_access_head.js2
-rw-r--r--toolkit/components/antitracking/test/browser/tracker.js2
-rw-r--r--toolkit/components/antitracking/test/browser/workerIframe.html2
-rw-r--r--toolkit/components/antitracking/test/xpcshell/test_staticPartition_authhttp.js2
-rw-r--r--toolkit/components/antitracking/test/xpcshell/test_staticPartition_prefetch.js2
-rw-r--r--toolkit/components/antitracking/test/xpcshell/test_staticPartition_preload.js2
-rw-r--r--toolkit/components/antitracking/test/xpcshell/xpcshell.toml7
-rw-r--r--toolkit/components/apppicker/content/appPicker.js6
-rw-r--r--toolkit/components/asyncshutdown/AsyncShutdown.sys.mjs3
-rw-r--r--toolkit/components/asyncshutdown/nsIAsyncShutdown.idl4
-rw-r--r--toolkit/components/backgroundhangmonitor/ThreadStackHelper.cpp3
-rw-r--r--toolkit/components/backgroundhangmonitor/ThreadStackHelper.h3
-rw-r--r--toolkit/components/bitsdownload/nsIBits.idl7
-rw-r--r--toolkit/components/cleardata/PrincipalsCollector.sys.mjs2
-rw-r--r--toolkit/components/cleardata/ServiceWorkerCleanUp.sys.mjs2
-rw-r--r--toolkit/components/cleardata/nsIClearDataService.idl2
-rw-r--r--toolkit/components/cleardata/tests/marionette/test_moved_origin_directory_cleanup.py11
-rw-r--r--toolkit/components/cleardata/tests/unit/test_cookies.js71
-rw-r--r--toolkit/components/contentanalysis/ContentAnalysis.cpp437
-rw-r--r--toolkit/components/contentanalysis/ContentAnalysis.h42
-rw-r--r--toolkit/components/contentanalysis/moz.build124
-rw-r--r--toolkit/components/contentanalysis/nsIContentAnalysis.idl18
-rw-r--r--toolkit/components/contentanalysis/tests/browser/browser.toml3
-rw-r--r--toolkit/components/contentanalysis/tests/browser/browser_content_analysis_policies.js127
-rw-r--r--toolkit/components/contentanalysis/tests/browser/moz.build7
-rw-r--r--toolkit/components/contentanalysis/tests/gtest/TestContentAnalysis.cpp214
-rw-r--r--toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisAgent.cpp132
-rw-r--r--toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisAgent.h (renamed from toolkit/components/contentanalysis/tests/gtest/TestContentAnalysis.h)0
-rw-r--r--toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisMisbehaving.cpp2
-rw-r--r--toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisUtils.cpp2
-rw-r--r--toolkit/components/contentanalysis/tests/gtest/moz.build4
-rw-r--r--toolkit/components/contentanalysis/tests/moz.build7
-rw-r--r--toolkit/components/contextualidentity/ContextualIdentityService.sys.mjs2
-rw-r--r--toolkit/components/corroborator/Corroborate.sys.mjs12
-rw-r--r--toolkit/components/crashes/CrashManager.in.sys.mjs3
-rw-r--r--toolkit/components/credentialmanagement/IdentityCredentialPromptService.sys.mjs12
-rw-r--r--toolkit/components/credentialmanagement/IdentityCredentialStorageService.cpp19
-rw-r--r--toolkit/components/downloads/DownloadCore.sys.mjs4
-rw-r--r--toolkit/components/downloads/DownloadHistory.sys.mjs4
-rw-r--r--toolkit/components/downloads/DownloadIntegration.sys.mjs2
-rw-r--r--toolkit/components/downloads/DownloadLegacy.sys.mjs14
-rw-r--r--toolkit/components/downloads/DownloadList.sys.mjs2
-rw-r--r--toolkit/components/downloads/test/unit/common_test_Download.js35
-rw-r--r--toolkit/components/downloads/test/unit/head.js10
-rw-r--r--toolkit/components/downloads/test/unit/test_DownloadHistory.js2
-rw-r--r--toolkit/components/downloads/test/unit/test_DownloadLegacy.js2
-rw-r--r--toolkit/components/downloads/test/unit/test_DownloadList.js4
-rw-r--r--toolkit/components/downloads/test/unit/test_Download_noext_win.js2
-rw-r--r--toolkit/components/enterprisepolicies/WindowsGPOParser.sys.mjs2
-rw-r--r--toolkit/components/enterprisepolicies/macOSPoliciesParser.sys.mjs2
-rw-r--r--toolkit/components/extensions/.eslintrc.js8
-rw-r--r--toolkit/components/extensions/ConduitsChild.sys.mjs2
-rw-r--r--toolkit/components/extensions/Extension.sys.mjs45
-rw-r--r--toolkit/components/extensions/ExtensionActions.sys.mjs32
-rw-r--r--toolkit/components/extensions/ExtensionChild.sys.mjs10
-rw-r--r--toolkit/components/extensions/ExtensionCommon.sys.mjs74
-rw-r--r--toolkit/components/extensions/ExtensionContent.sys.mjs4
-rw-r--r--toolkit/components/extensions/ExtensionDNR.sys.mjs18
-rw-r--r--toolkit/components/extensions/ExtensionPageChild.sys.mjs4
-rw-r--r--toolkit/components/extensions/ExtensionParent.sys.mjs26
-rw-r--r--toolkit/components/extensions/ExtensionPermissions.sys.mjs5
-rw-r--r--toolkit/components/extensions/ExtensionPolicyService.cpp9
-rw-r--r--toolkit/components/extensions/ExtensionProcessScript.sys.mjs4
-rw-r--r--toolkit/components/extensions/ExtensionScriptingStore.sys.mjs10
-rw-r--r--toolkit/components/extensions/ExtensionStorage.sys.mjs2
-rw-r--r--toolkit/components/extensions/ExtensionStorageIDB.sys.mjs2
-rw-r--r--toolkit/components/extensions/ExtensionStorageSync.sys.mjs2
-rw-r--r--toolkit/components/extensions/ExtensionStorageSyncKinto.sys.mjs6
-rw-r--r--toolkit/components/extensions/ExtensionTelemetry.sys.mjs4
-rw-r--r--toolkit/components/extensions/ExtensionTestCommon.sys.mjs2
-rw-r--r--toolkit/components/extensions/ExtensionUtils.sys.mjs2
-rw-r--r--toolkit/components/extensions/ExtensionWorkerChild.sys.mjs2
-rw-r--r--toolkit/components/extensions/ExtensionXPCShellUtils.sys.mjs6
-rw-r--r--toolkit/components/extensions/ExtensionsParent.cpp4
-rw-r--r--toolkit/components/extensions/MessageChannel.sys.mjs10
-rw-r--r--toolkit/components/extensions/MessageManagerProxy.sys.mjs2
-rw-r--r--toolkit/components/extensions/ProxyChannelFilter.sys.mjs8
-rw-r--r--toolkit/components/extensions/Schemas.sys.mjs44
-rw-r--r--toolkit/components/extensions/WebNavigation.sys.mjs3
-rw-r--r--toolkit/components/extensions/child/ext-declarativeNetRequest.js2
-rw-r--r--toolkit/components/extensions/child/ext-storage.js8
-rw-r--r--toolkit/components/extensions/child/ext-test.js4
-rw-r--r--toolkit/components/extensions/child/ext-userScripts-content.js2
-rw-r--r--toolkit/components/extensions/docs/webext-storage.rst2
-rw-r--r--toolkit/components/extensions/docs/webidl_bindings.rst2
-rw-r--r--toolkit/components/extensions/metrics.yaml24
-rw-r--r--toolkit/components/extensions/parent/ext-alarms.js4
-rw-r--r--toolkit/components/extensions/parent/ext-backgroundPage.js4
-rw-r--r--toolkit/components/extensions/parent/ext-browsingData.js4
-rw-r--r--toolkit/components/extensions/parent/ext-captivePortal.js10
-rw-r--r--toolkit/components/extensions/parent/ext-clipboard.js2
-rw-r--r--toolkit/components/extensions/parent/ext-contextualIdentities.js2
-rw-r--r--toolkit/components/extensions/parent/ext-declarativeNetRequest.js2
-rw-r--r--toolkit/components/extensions/parent/ext-dns.js2
-rw-r--r--toolkit/components/extensions/parent/ext-downloads.js11
-rw-r--r--toolkit/components/extensions/parent/ext-geckoProfiler.js2
-rw-r--r--toolkit/components/extensions/parent/ext-identity.js4
-rw-r--r--toolkit/components/extensions/parent/ext-idle.js2
-rw-r--r--toolkit/components/extensions/parent/ext-management.js2
-rw-r--r--toolkit/components/extensions/parent/ext-networkStatus.js2
-rw-r--r--toolkit/components/extensions/parent/ext-protocolHandlers.js2
-rw-r--r--toolkit/components/extensions/parent/ext-runtime.js4
-rw-r--r--toolkit/components/extensions/parent/ext-scripting.js6
-rw-r--r--toolkit/components/extensions/parent/ext-storage.js4
-rw-r--r--toolkit/components/extensions/parent/ext-tabs-base.js77
-rw-r--r--toolkit/components/extensions/parent/ext-theme.js2
-rw-r--r--toolkit/components/extensions/parent/ext-webNavigation.js2
-rw-r--r--toolkit/components/extensions/storage/webext_storage_bridge/src/lib.rs4
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_background_serviceworker.js2
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_downloads_filters.js2
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_downloads_referrer.js6
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_extension_page_tab_navigated.js2
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_management_themes.js2
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_themes_arrowpanels.js4
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_themes_incognito.js2
-rw-r--r--toolkit/components/extensions/test/browser/browser_ext_themes_ntp_colors_perwindow.js3
-rw-r--r--toolkit/components/extensions/test/mochitest/chrome_cleanup_script.js2
-rw-r--r--toolkit/components/extensions/test/mochitest/file_indexedDB.html2
-rw-r--r--toolkit/components/extensions/test/mochitest/file_simple_iframe_worker.html4
-rw-r--r--toolkit/components/extensions/test/mochitest/file_simple_webrequest_worker.html4
-rw-r--r--toolkit/components/extensions/test/mochitest/head_cookies.js2
-rw-r--r--toolkit/components/extensions/test/mochitest/head_webrequest.js8
-rw-r--r--toolkit/components/extensions/test/mochitest/mochitest-common.toml1
-rw-r--r--toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_saveAs.html2
-rw-r--r--toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_uniquify.html2
-rw-r--r--toolkit/components/extensions/test/mochitest/test_chrome_ext_permissions.html2
-rw-r--r--toolkit/components/extensions/test/mochitest/test_chrome_ext_webrequest_background_events.html4
-rw-r--r--toolkit/components/extensions/test/mochitest/test_chrome_ext_webrequest_mozextension.html2
-rw-r--r--toolkit/components/extensions/test/mochitest/test_ext_activityLog.html4
-rw-r--r--toolkit/components/extensions/test/mochitest/test_ext_async_clipboard.html8
-rw-r--r--toolkit/components/extensions/test/mochitest/test_ext_browsingData_indexedDB.html2
-rw-r--r--toolkit/components/extensions/test/mochitest/test_ext_browsingData_localStorage.html2
-rw-r--r--toolkit/components/extensions/test/mochitest/test_ext_browsingData_serviceWorkers.html2
-rw-r--r--toolkit/components/extensions/test/mochitest/test_ext_browsingData_settings.html8
-rw-r--r--toolkit/components/extensions/test/mochitest/test_ext_exclude_include_globs.html4
-rw-r--r--toolkit/components/extensions/test/mochitest/test_ext_extension_iframe_messaging.html2
-rw-r--r--toolkit/components/extensions/test/mochitest/test_ext_redirect_jar.html2
-rw-r--r--toolkit/components/extensions/test/mochitest/test_ext_request_urlClassification.html2
-rw-r--r--toolkit/components/extensions/test/mochitest/test_ext_sendmessage_doublereply.html2
-rw-r--r--toolkit/components/extensions/test/mochitest/test_ext_sendmessage_reply2.html4
-rw-r--r--toolkit/components/extensions/test/mochitest/test_ext_storage_cleanup.html2
-rw-r--r--toolkit/components/extensions/test/mochitest/test_ext_streamfilter_multiple.html2
-rw-r--r--toolkit/components/extensions/test/mochitest/test_ext_streamfilter_processswitch.html2
-rw-r--r--toolkit/components/extensions/test/mochitest/test_ext_subframes_privileges.html2
-rw-r--r--toolkit/components/extensions/test/mochitest/test_ext_tabs_executeScript_good.html2
-rw-r--r--toolkit/components/extensions/test/mochitest/test_ext_tabs_permissions.html7
-rw-r--r--toolkit/components/extensions/test/mochitest/test_ext_web_accessible_incognito.html2
-rw-r--r--toolkit/components/extensions/test/mochitest/test_ext_web_accessible_resources.html2
-rw-r--r--toolkit/components/extensions/test/mochitest/test_ext_webrequest_auth.html10
-rw-r--r--toolkit/components/extensions/test/mochitest/test_ext_webrequest_background_events.html2
-rw-r--r--toolkit/components/extensions/test/mochitest/test_ext_webrequest_basic.html2
-rw-r--r--toolkit/components/extensions/test/mochitest/test_ext_webrequest_errors.html2
-rw-r--r--toolkit/components/extensions/test/mochitest/test_ext_webrequest_hsts.html10
-rw-r--r--toolkit/components/extensions/test/mochitest/test_ext_webrequest_redirect_bypass_cors.html2
-rw-r--r--toolkit/components/extensions/test/mochitest/webrequest_chromeworker.js2
-rw-r--r--toolkit/components/extensions/test/xpcshell/data/file_page_xhr.html2
-rw-r--r--toolkit/components/extensions/test/xpcshell/data/file_permission_xhr.html2
-rw-r--r--toolkit/components/extensions/test/xpcshell/head.js22
-rw-r--r--toolkit/components/extensions/test/xpcshell/head_schemas.js4
-rw-r--r--toolkit/components/extensions/test/xpcshell/head_service_worker.js2
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_csp_validator.js6
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_alarms_does_not_fire.js2
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_api_events_listener_calls_exceptions.js1
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_captivePortal.js5
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_contentScripts_register.js2
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_contentscript.js2
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_contentscript_context.js2
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_contentscript_create_iframe.js2
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_contentscript_importmap.js2
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_contentscript_module_import.js2
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_contentscript_triggeringPrincipal.js2
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_contexts.js4
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_contexts_gc.js4
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_dnr_allowAllRequests.js2
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_dnr_download.js2
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_dnr_modifyHeaders.js2
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_dnr_webrequest.js4
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_dns.js2
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_downloads_download.js2
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_downloads_partitionKey.js2
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_downloads_search.js2
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_downloads_urlencoded.js2
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_eventpage_messaging.js4
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_experiments.js8
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_i18n.js8
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_indexedDB_principal.js12
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_management.js2
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_management_uninstall_self.js6
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_messaging_startup.js4
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_native_messaging.js6
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_notifications_incognito.js2
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_permissions.js26
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_persistent_events.js4
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_privacy_disable.js2
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_proxy_authorization_via_proxyinfo.js2
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_proxy_onauthrequired.js8
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_proxy_settings.js4
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_proxy_socks.js4
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_proxy_startup.js4
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_redirects.js10
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_runtime_onInstalled_and_onStartup.js2
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_runtime_ports.js2
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_runtime_ports_gc.js2
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_runtime_sendMessage.js4
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_same_site_redirects.js2
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_schemas.js6
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_schemas_interactive.js4
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_schemas_manifest_permissions.js2
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_schemas_privileged.js2
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_schemas_roots.js2
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_scripting_persistAcrossSessions.js12
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_storage_idb_data_migration.js3
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_storage_sanitizer.js2
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_storage_sync_kinto.js117
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_storage_tab.js2
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_userScripts_exports.js4
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_webRequest_auth.js2
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_webRequest_cancelWithReason.js4
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_webRequest_download.js2
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_webRequest_filterResponseData.js2
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_webRequest_permission.js4
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_webRequest_redirectProperty.js4
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_webRequest_responseBody.js40
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_webRequest_startup.js22
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_webRequest_suspend.js4
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_webRequest_viewsource.js2
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_webRequest_webSocket.js10
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_web_accessible_resources.js8
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_native_manifests.js2
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_proxy_failover.js6
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_proxy_info_results.js29
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_proxy_listener.js6
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_webRequest_filtering.js2
-rw-r--r--toolkit/components/extensions/test/xpcshell/webidl-api/test_ext_webidl_api.js18
-rw-r--r--toolkit/components/extensions/test/xpcshell/webidl-api/test_ext_webidl_api_event_callback.js20
-rw-r--r--toolkit/components/extensions/test/xpcshell/webidl-api/test_ext_webidl_api_request_handler.js2
-rw-r--r--toolkit/components/extensions/test/xpcshell/webidl-api/test_ext_webidl_api_schema_errors.js2
-rw-r--r--toolkit/components/extensions/test/xpcshell/webidl-api/test_ext_webidl_api_schema_formatters.js2
-rw-r--r--toolkit/components/extensions/test/xpcshell/webidl-api/test_ext_webidl_runtime_port.js2
-rw-r--r--toolkit/components/extensions/webidl-api/ExtensionAPIRequestForwarder.cpp6
-rw-r--r--toolkit/components/extensions/webidl-api/GenerateWebIDLBindings.py4
-rw-r--r--toolkit/components/extensions/webrequest/WebRequest.sys.mjs4
-rw-r--r--toolkit/components/find/nsFind.cpp1
-rw-r--r--toolkit/components/find/nsWebBrowserFind.cpp1
-rw-r--r--toolkit/components/formautofill/AutofillProfileAutoComplete.sys.mjs2
-rw-r--r--toolkit/components/formautofill/Constants.ios.mjs3
-rw-r--r--toolkit/components/formautofill/FormAutofill.ios.sys.mjs2
-rw-r--r--toolkit/components/formautofill/FormAutofill.sys.mjs4
-rw-r--r--toolkit/components/formautofill/FormAutofillChild.ios.sys.mjs14
-rw-r--r--toolkit/components/formautofill/FormAutofillChild.sys.mjs524
-rw-r--r--toolkit/components/formautofill/FormAutofillContent.sys.mjs367
-rw-r--r--toolkit/components/formautofill/FormAutofillNative.cpp20
-rw-r--r--toolkit/components/formautofill/FormAutofillParent.sys.mjs6
-rw-r--r--toolkit/components/formautofill/FormAutofillStorageBase.sys.mjs193
-rw-r--r--toolkit/components/formautofill/FormAutofillSync.sys.mjs2
-rw-r--r--toolkit/components/formautofill/Helpers.ios.mjs100
-rw-r--r--toolkit/components/formautofill/Overrides.ios.js1
-rw-r--r--toolkit/components/formautofill/ProfileAutoCompleteResult.sys.mjs62
-rw-r--r--toolkit/components/formautofill/android/FormAutofillPrompter.sys.mjs8
-rw-r--r--toolkit/components/formautofill/android/FormAutofillStorage.sys.mjs10
-rw-r--r--toolkit/components/formautofill/default/FormAutofillPrompter.sys.mjs6
-rw-r--r--toolkit/components/formautofill/moz.build5
-rw-r--r--toolkit/components/formautofill/shared/AddressComponent.sys.mjs10
-rw-r--r--toolkit/components/formautofill/shared/AddressParser.sys.mjs2
-rw-r--r--toolkit/components/formautofill/shared/AddressRecord.sys.mjs119
-rw-r--r--toolkit/components/formautofill/shared/AutofillTelemetry.sys.mjs (renamed from toolkit/components/formautofill/AutofillTelemetry.sys.mjs)8
-rw-r--r--toolkit/components/formautofill/shared/FieldScanner.sys.mjs13
-rw-r--r--toolkit/components/formautofill/shared/FormAutofillHeuristics.sys.mjs72
-rw-r--r--toolkit/components/formautofill/shared/FormAutofillSection.sys.mjs24
-rw-r--r--toolkit/components/formautofill/shared/FormAutofillUtils.sys.mjs39
-rw-r--r--toolkit/components/formautofill/shared/FormStateManager.sys.mjs2
-rw-r--r--toolkit/components/formautofill/shared/PhoneNumber.sys.mjs (renamed from toolkit/components/formautofill/phonenumberutils/PhoneNumber.sys.mjs)4
-rw-r--r--toolkit/components/formautofill/shared/PhoneNumberMetaData.sys.mjs (renamed from toolkit/components/formautofill/phonenumberutils/PhoneNumberMetaData.sys.mjs)0
-rw-r--r--toolkit/components/formautofill/shared/PhoneNumberNormalizer.sys.mjs (renamed from toolkit/components/formautofill/phonenumberutils/PhoneNumberNormalizer.sys.mjs)0
-rw-r--r--toolkit/components/glean/Cargo.toml6
-rw-r--r--toolkit/components/glean/api/Cargo.toml3
-rw-r--r--toolkit/components/glean/api/src/ffi/custom_distribution.rs2
-rw-r--r--toolkit/components/glean/api/src/ffi/memory_distribution.rs2
-rw-r--r--toolkit/components/glean/api/src/ffi/mod.rs1
-rw-r--r--toolkit/components/glean/api/src/ffi/object.rs68
-rw-r--r--toolkit/components/glean/api/src/ffi/timing_distribution.rs2
-rw-r--r--toolkit/components/glean/api/src/pings.rs2
-rw-r--r--toolkit/components/glean/api/src/private/custom_distribution.rs4
-rw-r--r--toolkit/components/glean/api/src/private/mod.rs2
-rw-r--r--toolkit/components/glean/api/src/private/object.rs83
-rw-r--r--toolkit/components/glean/api/src/private/ping.rs4
-rw-r--r--toolkit/components/glean/api/src/private/timing_distribution.rs4
-rw-r--r--toolkit/components/glean/bindings/GleanMetric.h1
-rw-r--r--toolkit/components/glean/bindings/MetricTypes.h1
-rw-r--r--toolkit/components/glean/bindings/jog/src/lib.rs6
-rw-r--r--toolkit/components/glean/bindings/private/CustomDistribution.cpp8
-rw-r--r--toolkit/components/glean/bindings/private/DistributionData.h6
-rw-r--r--toolkit/components/glean/bindings/private/MemoryDistribution.cpp8
-rw-r--r--toolkit/components/glean/bindings/private/Object.cpp80
-rw-r--r--toolkit/components/glean/bindings/private/Object.h94
-rw-r--r--toolkit/components/glean/bindings/private/Timespan.cpp1
-rw-r--r--toolkit/components/glean/bindings/private/TimingDistribution.cpp29
-rw-r--r--toolkit/components/glean/build_scripts/glean_parser_ext/jog.py1
-rw-r--r--toolkit/components/glean/build_scripts/glean_parser_ext/run_glean_parser.py27
-rw-r--r--toolkit/components/glean/build_scripts/glean_parser_ext/rust.py37
-rw-r--r--toolkit/components/glean/build_scripts/glean_parser_ext/templates/cpp.jinja22
-rw-r--r--toolkit/components/glean/build_scripts/glean_parser_ext/templates/jog_factory.jinja215
-rw-r--r--toolkit/components/glean/build_scripts/glean_parser_ext/templates/ohttp.jinja213
-rw-r--r--toolkit/components/glean/build_scripts/glean_parser_ext/templates/rust.jinja2106
-rw-r--r--toolkit/components/glean/build_scripts/glean_parser_ext/templates/rust_pings.jinja21
-rw-r--r--toolkit/components/glean/build_scripts/mach_commands.py17
-rw-r--r--toolkit/components/glean/metrics_index.py3
-rw-r--r--toolkit/components/glean/moz.build13
-rw-r--r--toolkit/components/glean/pings.yaml5
-rw-r--r--toolkit/components/glean/src/init/viaduct_uploader.rs205
-rw-r--r--toolkit/components/glean/src/lib.rs10
-rw-r--r--toolkit/components/glean/src/ohttp_pings.rs13
-rw-r--r--toolkit/components/glean/tags.yaml20
-rw-r--r--toolkit/components/glean/tests/gtest/TestFog.cpp6
-rw-r--r--toolkit/components/glean/tests/pytest/gifft_output_Event4
-rw-r--r--toolkit/components/glean/tests/pytest/gifft_output_Scalar6
-rw-r--r--toolkit/components/glean/tests/pytest/jogfile_output21
-rw-r--r--toolkit/components/glean/tests/pytest/metrics_test.yaml20
-rw-r--r--toolkit/components/glean/tests/pytest/metrics_test_output138
-rw-r--r--toolkit/components/glean/tests/pytest/metrics_test_output_cpp19
-rw-r--r--toolkit/components/glean/tests/pytest/metrics_test_output_js_cpp104
-rw-r--r--toolkit/components/glean/tests/pytest/metrics_test_output_js_h2
-rw-r--r--toolkit/components/glean/tests/pytest/pings_test.yaml18
-rw-r--r--toolkit/components/glean/tests/pytest/pings_test_output19
-rw-r--r--toolkit/components/glean/tests/pytest/pings_test_output_cpp7
-rw-r--r--toolkit/components/glean/tests/pytest/pings_test_output_js_cpp10
-rw-r--r--toolkit/components/glean/tests/pytest/pings_test_output_js_h2
-rw-r--r--toolkit/components/glean/tests/test_metrics.yaml19
-rw-r--r--toolkit/components/glean/tests/test_pings.yaml19
-rw-r--r--toolkit/components/glean/tests/xpcshell/head.js152
-rw-r--r--toolkit/components/glean/tests/xpcshell/test_GIFFT.js10
-rw-r--r--toolkit/components/glean/tests/xpcshell/test_GIFFTIPC.js7
-rw-r--r--toolkit/components/glean/tests/xpcshell/test_Glean.js118
-rw-r--r--toolkit/components/glean/tests/xpcshell/test_GleanIPC.js5
-rw-r--r--toolkit/components/glean/tests/xpcshell/test_JOG.js27
-rw-r--r--toolkit/components/glean/tests/xpcshell/test_JOGIPC.js5
-rw-r--r--toolkit/components/glean/tests/xpcshell/test_OHTTP.js32
-rw-r--r--toolkit/components/glean/tests/xpcshell/xpcshell.toml3
-rw-r--r--toolkit/components/glean/xpcom/FOG.cpp7
-rw-r--r--toolkit/components/glean/xpcom/nsIFOG.idl3
-rw-r--r--toolkit/components/kvstore/kvstore.sys.mjs24
-rw-r--r--toolkit/components/kvstore/nsIKeyValue.idl16
-rw-r--r--toolkit/components/kvstore/src/lib.rs44
-rw-r--r--toolkit/components/kvstore/src/task.rs26
-rw-r--r--toolkit/components/kvstore/test/xpcshell/test_kvstore.js74
-rw-r--r--toolkit/components/messaging-system/lib/SpecialMessageActions.sys.mjs1
-rw-r--r--toolkit/components/messaging-system/schemas/TriggerActionSchemas/index.md2
-rw-r--r--toolkit/components/messaging-system/schemas/index.rst4
-rw-r--r--toolkit/components/ml/actors/MLEngineChild.sys.mjs325
-rw-r--r--toolkit/components/ml/actors/MLEngineParent.sys.mjs403
-rw-r--r--toolkit/components/ml/actors/moz.build8
-rw-r--r--toolkit/components/ml/content/EngineProcess.sys.mjs241
-rw-r--r--toolkit/components/ml/content/MLEngine.html16
-rw-r--r--toolkit/components/ml/content/MLEngine.worker.mjs91
-rw-r--r--toolkit/components/ml/content/SummarizerModel.sys.mjs160
-rw-r--r--toolkit/components/ml/docs/index.md44
-rw-r--r--toolkit/components/ml/jar.mn9
-rw-r--r--toolkit/components/ml/moz.build17
-rw-r--r--toolkit/components/ml/tests/browser/browser.toml5
-rw-r--r--toolkit/components/ml/tests/browser/browser_ml_engine.js219
-rw-r--r--toolkit/components/ml/tests/browser/head.js155
-rw-r--r--toolkit/components/moz.build1
-rw-r--r--toolkit/components/mozintl/mozIntl.sys.mjs2
-rw-r--r--toolkit/components/mozintl/test/test_mozintl.js8
-rw-r--r--toolkit/components/narrate/NarrateControls.sys.mjs92
-rw-r--r--toolkit/components/narrate/test/browser_narrate_toggle.js25
-rw-r--r--toolkit/components/nimbus/FeatureManifest.yaml321
-rw-r--r--toolkit/components/nimbus/generate/generate_feature_manifest.py25
-rw-r--r--toolkit/components/nimbus/lib/ExperimentManager.sys.mjs76
-rw-r--r--toolkit/components/nimbus/lib/ExperimentStore.sys.mjs2
-rw-r--r--toolkit/components/nimbus/lib/RemoteSettingsExperimentLoader.sys.mjs2
-rw-r--r--toolkit/components/nimbus/metrics.yaml38
-rw-r--r--toolkit/components/nimbus/schemas/ExperimentFeature.schema.json125
-rw-r--r--toolkit/components/nimbus/schemas/ExperimentFeatureManifest.schema.json123
-rw-r--r--toolkit/components/nimbus/test/unit/test_ExperimentManager_unenroll.js112
-rw-r--r--toolkit/components/nimbus/test/unit/test_SharedDataMap.js5
-rw-r--r--toolkit/components/normandy/Normandy.sys.mjs2
-rw-r--r--toolkit/components/normandy/actions/BaseAction.sys.mjs2
-rw-r--r--toolkit/components/normandy/actions/BranchedAddonStudyAction.sys.mjs2
-rw-r--r--toolkit/components/normandy/actions/PreferenceExperimentAction.sys.mjs2
-rw-r--r--toolkit/components/normandy/actions/ShowHeartbeatAction.sys.mjs5
-rw-r--r--toolkit/components/normandy/lib/AddonStudies.sys.mjs2
-rw-r--r--toolkit/components/normandy/lib/LegacyHeartbeat.sys.mjs1
-rw-r--r--toolkit/components/normandy/lib/PreferenceExperiments.sys.mjs4
-rw-r--r--toolkit/components/normandy/lib/RecipeRunner.sys.mjs4
-rw-r--r--toolkit/components/normandy/test/browser/browser_ActionsManager.js2
-rw-r--r--toolkit/components/normandy/test/browser/browser_BaseAction.js4
-rw-r--r--toolkit/components/normandy/test/browser/browser_LegacyHeartbeat.js138
-rw-r--r--toolkit/components/normandy/test/browser/browser_PreferenceExperiments.js21
-rw-r--r--toolkit/components/normandy/test/browser/browser_RecipeRunner.js12
-rw-r--r--toolkit/components/normandy/test/browser/browser_actions_BranchedAddonStudyAction.js2
-rw-r--r--toolkit/components/normandy/test/browser/browser_actions_PreferenceExperimentAction.js1
-rw-r--r--toolkit/components/normandy/test/unit/test_NormandyApi.js4
-rw-r--r--toolkit/components/passwordmgr/LoginAutoComplete.sys.mjs2
-rw-r--r--toolkit/components/passwordmgr/LoginHelper.sys.mjs2
-rw-r--r--toolkit/components/passwordmgr/LoginManager.shared.mjs2
-rw-r--r--toolkit/components/passwordmgr/LoginManager.sys.mjs2
-rw-r--r--toolkit/components/passwordmgr/LoginManagerAuthPrompter.sys.mjs6
-rw-r--r--toolkit/components/passwordmgr/LoginManagerChild.sys.mjs48
-rw-r--r--toolkit/components/passwordmgr/LoginManagerContextMenu.sys.mjs4
-rw-r--r--toolkit/components/passwordmgr/LoginManagerPrompter.sys.mjs183
-rw-r--r--toolkit/components/passwordmgr/nsILoginInfo.idl6
-rw-r--r--toolkit/components/passwordmgr/nsILoginManagerAuthPrompter.idl5
-rw-r--r--toolkit/components/passwordmgr/nsILoginManagerPrompter.idl5
-rw-r--r--toolkit/components/passwordmgr/storage-geckoview.sys.mjs14
-rw-r--r--toolkit/components/passwordmgr/test/browser/browser.toml49
-rw-r--r--toolkit/components/passwordmgr/test/browser/browser_autocomplete_primary_password.js2
-rw-r--r--toolkit/components/passwordmgr/test/browser/browser_basicAuth_rateLimit.js16
-rw-r--r--toolkit/components/passwordmgr/test/browser/browser_deleteLoginsBackup.js2
-rw-r--r--toolkit/components/passwordmgr/test/browser/browser_doorhanger_crossframe.js2
-rw-r--r--toolkit/components/passwordmgr/test/browser/browser_doorhanger_generated_password.js4
-rw-r--r--toolkit/components/passwordmgr/test/browser/browser_doorhanger_remembering.js52
-rw-r--r--toolkit/components/passwordmgr/test/browser/browser_doorhanger_window_open.js4
-rw-r--r--toolkit/components/passwordmgr/test/browser/browser_entry_point_telemetry.js2
-rw-r--r--toolkit/components/passwordmgr/test/browser/browser_formless_submit_chrome.js10
-rw-r--r--toolkit/components/passwordmgr/test/browser/browser_message_onFormSubmit.js2
-rw-r--r--toolkit/components/passwordmgr/test/browser/browser_preselect_login.js2
-rw-r--r--toolkit/components/passwordmgr/test/browser/browser_proxyAuth_prompt.js8
-rw-r--r--toolkit/components/passwordmgr/test/browser/browser_relay_telemetry.js2
-rw-r--r--toolkit/components/passwordmgr/test/browser/browser_username_only_form_telemetry.js14
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/pwmgr_common.js25
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/pwmgr_common_parent.js6
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_autofill_https_downgrade.html2
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_bug_627616.html46
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_dismissed_doorhanger_in_shadow_DOM.html8
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_formLike_rootElement_with_Shadow_DOM.html8
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_formless_autofill.html2
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_formless_submit_form_removal.html14
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_formless_submit_form_removal_negative.html4
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_formless_submit_navigation.html6
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_formless_submit_navigation_negative.html10
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_munged_values.html8
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_one_doorhanger_per_un_pw.html5
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_password_length.html12
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_prompt_async.html6
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_prompt_promptAuth_proxy.html6
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_submit_without_field_modifications.html8
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_xhr.html2
-rw-r--r--toolkit/components/passwordmgr/test/unit/test_LoginManagerParent_onPasswordEditedOrGenerated.js11
-rw-r--r--toolkit/components/passwordmgr/test/unit/test_logins_change.js2
-rw-r--r--toolkit/components/pdfjs/content/GeckoViewPdfjsChild.sys.mjs4
-rw-r--r--toolkit/components/pdfjs/content/GeckoViewPdfjsParent.sys.mjs18
-rw-r--r--toolkit/components/pdfjs/content/PdfJsDefaultPreferences.sys.mjs1
-rw-r--r--toolkit/components/pdfjs/content/PdfStreamConverter.sys.mjs49
-rw-r--r--toolkit/components/pdfjs/content/PdfjsChild.sys.mjs19
-rw-r--r--toolkit/components/pdfjs/content/build/pdf.mjs726
-rw-r--r--toolkit/components/pdfjs/content/build/pdf.scripting.mjs4
-rw-r--r--toolkit/components/pdfjs/content/build/pdf.worker.mjs114
-rw-r--r--toolkit/components/pdfjs/content/web/images/gv-toolbarButton-openinapp.svg11
-rw-r--r--toolkit/components/pdfjs/content/web/viewer-geckoview.css8
-rw-r--r--toolkit/components/pdfjs/content/web/viewer-geckoview.html3
-rw-r--r--toolkit/components/pdfjs/content/web/viewer-geckoview.mjs439
-rw-r--r--toolkit/components/pdfjs/content/web/viewer.css292
-rw-r--r--toolkit/components/pdfjs/content/web/viewer.html19
-rw-r--r--toolkit/components/pdfjs/content/web/viewer.mjs451
-rw-r--r--toolkit/components/pdfjs/jar.mn1
-rw-r--r--toolkit/components/pdfjs/moz.yaml4
-rw-r--r--toolkit/components/pdfjs/test/browser.toml2
-rw-r--r--toolkit/components/pdfjs/test/browser_pdfjs_caret_browsing_mode.js82
-rw-r--r--toolkit/components/pdfjs/test/browser_pdfjs_download_button.js2
-rw-r--r--toolkit/components/pdfjs/test/browser_pdfjs_editing_contextmenu.js140
-rw-r--r--toolkit/components/pdfjs/test/browser_pdfjs_nonpdf_filename.js2
-rw-r--r--toolkit/components/pdfjs/test/browser_pdfjs_octet_stream.js2
-rw-r--r--toolkit/components/pdfjs/test/browser_pdfjs_saveas.js2
-rw-r--r--toolkit/components/pdfjs/test/browser_pdfjs_stamp_telemetry.js2
-rw-r--r--toolkit/components/pdfjs/test/head.js22
-rw-r--r--toolkit/components/pictureinpicture/content/player.js14
-rw-r--r--toolkit/components/pictureinpicture/content/player.xhtml1
-rw-r--r--toolkit/components/pictureinpicture/tests/browser_cannotTriggerFromContent.js2
-rw-r--r--toolkit/components/pictureinpicture/tests/browser_closePip_pageNavigationChanges.js4
-rw-r--r--toolkit/components/pictureinpicture/tests/browser_preserveTabPipIconOverlay.js4
-rw-r--r--toolkit/components/pictureinpicture/tests/browser_removeVideoElement.js2
-rw-r--r--toolkit/components/pictureinpicture/tests/browser_reversePiP.js25
-rw-r--r--toolkit/components/pictureinpicture/tests/browser_touch_toggle_enablepip.js2
-rw-r--r--toolkit/components/places/BookmarkHTMLUtils.sys.mjs2
-rw-r--r--toolkit/components/places/BookmarkJSONUtils.sys.mjs23
-rw-r--r--toolkit/components/places/BookmarkList.sys.mjs262
-rw-r--r--toolkit/components/places/Bookmarks.sys.mjs158
-rw-r--r--toolkit/components/places/Database.cpp20
-rw-r--r--toolkit/components/places/Database.h1
-rw-r--r--toolkit/components/places/Helpers.cpp22
-rw-r--r--toolkit/components/places/Helpers.h8
-rw-r--r--toolkit/components/places/History.cpp78
-rw-r--r--toolkit/components/places/History.sys.mjs27
-rw-r--r--toolkit/components/places/PlacesBackups.sys.mjs2
-rw-r--r--toolkit/components/places/PlacesDBUtils.sys.mjs4
-rw-r--r--toolkit/components/places/PlacesExpiration.sys.mjs4
-rw-r--r--toolkit/components/places/PlacesFrecencyRecalculator.sys.mjs55
-rw-r--r--toolkit/components/places/PlacesPreviews.sys.mjs43
-rw-r--r--toolkit/components/places/PlacesSyncUtils.sys.mjs4
-rw-r--r--toolkit/components/places/PlacesTransactions.sys.mjs8
-rw-r--r--toolkit/components/places/PlacesUtils.sys.mjs81
-rw-r--r--toolkit/components/places/SQLFunctions.cpp20
-rw-r--r--toolkit/components/places/SQLFunctions.h8
-rw-r--r--toolkit/components/places/Shutdown.h4
-rw-r--r--toolkit/components/places/SyncedBookmarksMirror.sys.mjs2
-rw-r--r--toolkit/components/places/TaggingService.sys.mjs2
-rw-r--r--toolkit/components/places/moz.build1
-rw-r--r--toolkit/components/places/mozIAsyncHistory.idl4
-rw-r--r--toolkit/components/places/nsFaviconService.cpp64
-rw-r--r--toolkit/components/places/nsINavHistoryService.idl2
-rw-r--r--toolkit/components/places/nsITaggingService.idl6
-rw-r--r--toolkit/components/places/nsPlacesTriggers.h2
-rw-r--r--toolkit/components/places/tests/PlacesTestUtils.sys.mjs22
-rw-r--r--toolkit/components/places/tests/bookmarks/test_818584-discard-duplicate-backups.js2
-rw-r--r--toolkit/components/places/tests/bookmarks/test_818593-store-backup-metadata.js4
-rw-r--r--toolkit/components/places/tests/bookmarks/test_insert_thousands_bookmarks.js24
-rw-r--r--toolkit/components/places/tests/bookmarks/xpcshell.toml2
-rw-r--r--toolkit/components/places/tests/browser/browser.toml4
-rw-r--r--toolkit/components/places/tests/browser/browser_double_redirect.js2
-rw-r--r--toolkit/components/places/tests/browser/browser_favicon.js74
-rw-r--r--toolkit/components/places/tests/browser/browser_favicon_privatebrowsing_perwindowpb.js2
-rw-r--r--toolkit/components/places/tests/browser/browser_history_post.js2
-rw-r--r--toolkit/components/places/tests/browser/browser_notfound.js2
-rw-r--r--toolkit/components/places/tests/browser/browser_redirect_self.js2
-rw-r--r--toolkit/components/places/tests/browser/browser_visited_notfound.js2
-rw-r--r--toolkit/components/places/tests/browser/browser_visituri.js238
-rw-r--r--toolkit/components/places/tests/browser/browser_visituri_privatebrowsing_perwindowpb.js2
-rw-r--r--toolkit/components/places/tests/browser/favicon.html5
-rw-r--r--toolkit/components/places/tests/browser/previews/browser.toml2
-rw-r--r--toolkit/components/places/tests/browser/userpass.html13
-rw-r--r--toolkit/components/places/tests/chrome/test_371798.xhtml2
-rw-r--r--toolkit/components/places/tests/expiration/test_notifications.js2
-rw-r--r--toolkit/components/places/tests/favicons/head_favicons.js11
-rw-r--r--toolkit/components/places/tests/favicons/test_cached-favicon_mime_type.js60
-rw-r--r--toolkit/components/places/tests/favicons/test_page-icon_protocol.js62
-rw-r--r--toolkit/components/places/tests/favicons/test_setAndFetchFaviconForPage.js16
-rw-r--r--toolkit/components/places/tests/history/test_async_history_api.js10
-rw-r--r--toolkit/components/places/tests/history/test_insertMany.js21
-rw-r--r--toolkit/components/places/tests/history/test_removeByFilter.js4
-rw-r--r--toolkit/components/places/tests/history/test_removeVisitsByFilter.js2
-rw-r--r--toolkit/components/places/tests/history/test_updatePlaces_embed.js8
-rw-r--r--toolkit/components/places/tests/migration/places_v77.sqlite (renamed from toolkit/components/places/tests/migration/places_v75.sqlite)bin1507328 -> 1507328 bytes
-rw-r--r--toolkit/components/places/tests/migration/test_current_from_v74.js26
-rw-r--r--toolkit/components/places/tests/migration/xpcshell.toml2
-rw-r--r--toolkit/components/places/tests/moz.build1
-rw-r--r--toolkit/components/places/tests/queries/test_async.js2
-rw-r--r--toolkit/components/places/tests/queries/test_containersQueries_sorting.js4
-rw-r--r--toolkit/components/places/tests/queries/test_querySerialization.js46
-rw-r--r--toolkit/components/places/tests/queries/test_redirects.js4
-rw-r--r--toolkit/components/places/tests/queries/test_tags.js6
-rw-r--r--toolkit/components/places/tests/sync/head_sync.js6
-rw-r--r--toolkit/components/places/tests/sync/test_bookmark_abort_merging.js4
-rw-r--r--toolkit/components/places/tests/sync/test_bookmark_observer_recorder.js4
-rw-r--r--toolkit/components/places/tests/unit/test_asyncExecuteLegacyQueries.js4
-rw-r--r--toolkit/components/places/tests/unit/test_async_transactions.js2
-rw-r--r--toolkit/components/places/tests/unit/test_bookmark_list.js115
-rw-r--r--toolkit/components/places/tests/unit/test_bookmarks_html.js2
-rw-r--r--toolkit/components/places/tests/unit/test_bookmarks_html_corrupt.js2
-rw-r--r--toolkit/components/places/tests/unit/test_bookmarks_restore_notification.js2
-rw-r--r--toolkit/components/places/tests/unit/test_frecency_decay.js2
-rw-r--r--toolkit/components/places/tests/unit/test_nsINavHistoryViewer.js8
-rw-r--r--toolkit/components/places/tests/unit/test_origins.js79
-rw-r--r--toolkit/components/places/tests/unit/test_origins_parsing.js11
-rw-r--r--toolkit/components/places/tests/unit/test_tag_autocomplete_search.js2
-rw-r--r--toolkit/components/places/tests/unit/xpcshell.toml2
-rw-r--r--toolkit/components/printing/content/print.js2
-rw-r--r--toolkit/components/printing/tests/browser_preview_navigation.js2
-rw-r--r--toolkit/components/printing/tests/head.js4
-rw-r--r--toolkit/components/processtools/ProcInfo.h12
-rw-r--r--toolkit/components/processtools/ProcInfo.mm45
-rw-r--r--toolkit/components/processtools/moz.build2
-rw-r--r--toolkit/components/promiseworker/PromiseWorker.sys.mjs175
-rw-r--r--toolkit/components/promiseworker/tests/xpcshell/data/worker.js13
-rw-r--r--toolkit/components/promiseworker/tests/xpcshell/test_Promise.js48
-rw-r--r--toolkit/components/promiseworker/worker/PromiseWorker.template.worker.js40
-rw-r--r--toolkit/components/prompts/content/commonDialog.css9
-rw-r--r--toolkit/components/prompts/content/commonDialog.js1
-rw-r--r--toolkit/components/prompts/content/commonDialog.xhtml10
-rw-r--r--toolkit/components/prompts/docs/nsIPromptService-reference.rst2
-rw-r--r--toolkit/components/prompts/src/CommonDialog.sys.mjs4
-rw-r--r--toolkit/components/prompts/src/Prompter.sys.mjs5
-rw-r--r--toolkit/components/prompts/test/PromptTestUtils.sys.mjs112
-rw-r--r--toolkit/components/prompts/test/chromeScript.js40
-rw-r--r--toolkit/components/prompts/test/prompt_common.js19
-rw-r--r--toolkit/components/remote/nsDBusRemoteServer.cpp10
-rw-r--r--toolkit/components/remote/nsDBusRemoteServer.h2
-rw-r--r--toolkit/components/reportbrokensite/ReportBrokenSiteChild.sys.mjs118
-rw-r--r--toolkit/components/reportbrokensite/ReportBrokenSiteParent.sys.mjs310
-rw-r--r--toolkit/components/reputationservice/ApplicationReputation.cpp28
-rw-r--r--toolkit/components/reputationservice/ApplicationReputation.h4
-rw-r--r--toolkit/components/resistfingerprinting/RFPHelper.sys.mjs2
-rw-r--r--toolkit/components/resistfingerprinting/metrics.yaml384
-rw-r--r--toolkit/components/resistfingerprinting/moz.build12
-rw-r--r--toolkit/components/resistfingerprinting/nsRFPService.cpp38
-rw-r--r--toolkit/components/resistfingerprinting/nsUserCharacteristics.cpp297
-rw-r--r--toolkit/components/resistfingerprinting/nsUserCharacteristics.h28
-rw-r--r--toolkit/components/resistfingerprinting/pings.yaml19
-rw-r--r--toolkit/components/resistfingerprinting/tests/gtest/moz.build1
-rw-r--r--toolkit/components/resistfingerprinting/tests/gtest/test_usercharping.cpp143
-rw-r--r--toolkit/components/satchel/FormHandlerChild.sys.mjs72
-rw-r--r--toolkit/components/satchel/FormHistoryChild.sys.mjs7
-rw-r--r--toolkit/components/satchel/FormHistoryStartup.sys.mjs2
-rw-r--r--toolkit/components/satchel/moz.build1
-rw-r--r--toolkit/components/satchel/nsFormFillController.cpp2
-rw-r--r--toolkit/components/satchel/test/browser/browser_close_tab.js5
-rw-r--r--toolkit/components/satchel/test/browser/browser_privbrowsing_perwindowpb.js2
-rw-r--r--toolkit/components/satchel/test/test_form_submission.html16
-rw-r--r--toolkit/components/satchel/test/test_popup_enter_event.html4
-rw-r--r--toolkit/components/satchel/test/unit/test_db_corrupt.js2
-rw-r--r--toolkit/components/search/AppProvidedSearchEngine.sys.mjs185
-rw-r--r--toolkit/components/search/SearchEngine.sys.mjs4
-rw-r--r--toolkit/components/search/SearchService.sys.mjs207
-rw-r--r--toolkit/components/search/SearchSuggestionController.sys.mjs4
-rw-r--r--toolkit/components/search/SearchUtils.sys.mjs4
-rw-r--r--toolkit/components/search/nsISearchService.idl2
-rw-r--r--toolkit/components/search/schema/search-config-overrides-ui-schema.json3
-rw-r--r--toolkit/components/search/schema/search-config-overrides-v2-schema.json1
-rw-r--r--toolkit/components/search/schema/search-config-overrides-v2-ui-schema.json9
-rw-r--r--toolkit/components/search/schema/search-default-override-allowlist-ui-schema.json2
-rw-r--r--toolkit/components/search/tests/SearchTestUtils.sys.mjs10
-rw-r--r--toolkit/components/search/tests/xpcshell/data/search-config-v2-no-order-hint.json207
-rw-r--r--toolkit/components/search/tests/xpcshell/data/search-config-v2.json223
-rw-r--r--toolkit/components/search/tests/xpcshell/method-extensions/search-config-v2.json83
-rw-r--r--toolkit/components/search/tests/xpcshell/searchconfigs/test_searchconfig_ui_schemas_valid.js36
-rw-r--r--toolkit/components/search/tests/xpcshell/searchconfigs/test_searchconfig_validates.js191
-rw-r--r--toolkit/components/search/tests/xpcshell/searchconfigs/test_searchicons_validates.js20
-rw-r--r--toolkit/components/search/tests/xpcshell/searchconfigs/xpcshell.toml16
-rw-r--r--toolkit/components/search/tests/xpcshell/test-extensions/search-config-v2.json139
-rw-r--r--toolkit/components/search/tests/xpcshell/test_appProvided_icons.js211
-rw-r--r--toolkit/components/search/tests/xpcshell/test_defaultPrivateEngine.js4
-rw-r--r--toolkit/components/search/tests/xpcshell/test_engine_ids.js49
-rw-r--r--toolkit/components/search/tests/xpcshell/test_engine_set_alias.js2
-rw-r--r--toolkit/components/search/tests/xpcshell/test_getSubmission_params_pref.js9
-rw-r--r--toolkit/components/search/tests/xpcshell/test_getSubmission_params_prefNimbus.js17
-rw-r--r--toolkit/components/search/tests/xpcshell/test_getSubmission_params_prefNimbus_invalid.js5
-rw-r--r--toolkit/components/search/tests/xpcshell/test_initialization.js51
-rw-r--r--toolkit/components/search/tests/xpcshell/test_initialization_with_region.js87
-rw-r--r--toolkit/components/search/tests/xpcshell/test_maybereloadengine_order.js54
-rw-r--r--toolkit/components/search/tests/xpcshell/test_missing_engine.js52
-rw-r--r--toolkit/components/search/tests/xpcshell/test_opensearch_icon.js18
-rw-r--r--toolkit/components/search/tests/xpcshell/test_opensearch_icons_invalid.js6
-rw-r--r--toolkit/components/search/tests/xpcshell/test_override_allowlist_switch.js12
-rw-r--r--toolkit/components/search/tests/xpcshell/test_reload_engines.js322
-rw-r--r--toolkit/components/search/tests/xpcshell/test_reload_engines_experiment.js126
-rw-r--r--toolkit/components/search/tests/xpcshell/test_reload_engines_locales.js93
-rw-r--r--toolkit/components/search/tests/xpcshell/test_remove_engine_notification_box.js112
-rw-r--r--toolkit/components/search/tests/xpcshell/test_searchSuggest.js2
-rw-r--r--toolkit/components/search/tests/xpcshell/test_searchSuggest_cookies.js2
-rw-r--r--toolkit/components/search/tests/xpcshell/test_searchSuggest_extraParams.js56
-rw-r--r--toolkit/components/search/tests/xpcshell/test_searchTermFromResult.js169
-rw-r--r--toolkit/components/search/tests/xpcshell/test_settings_persist.js62
-rw-r--r--toolkit/components/search/tests/xpcshell/test_sort_orders-no-hints.js8
-rw-r--r--toolkit/components/search/tests/xpcshell/test_telemetry_event_default.js206
-rw-r--r--toolkit/components/search/tests/xpcshell/test_validate_engines.js56
-rw-r--r--toolkit/components/search/tests/xpcshell/test_webextensions_install.js22
-rw-r--r--toolkit/components/search/tests/xpcshell/test_webextensions_startup_duplicate.js6
-rw-r--r--toolkit/components/search/tests/xpcshell/xpcshell.toml13
-rw-r--r--toolkit/components/sessionstore/SessionStoreChangeListener.cpp11
-rw-r--r--toolkit/components/taskscheduler/TaskScheduler.sys.mjs2
-rw-r--r--toolkit/components/taskscheduler/TaskSchedulerMacOSImpl.sys.mjs2
-rw-r--r--toolkit/components/taskscheduler/TaskSchedulerWinImpl.sys.mjs2
-rw-r--r--toolkit/components/telemetry/Events.yaml48
-rw-r--r--toolkit/components/telemetry/Histograms.json94
-rw-r--r--toolkit/components/telemetry/Scalars.yaml40
-rw-r--r--toolkit/components/telemetry/TelemetryStartup.sys.mjs4
-rw-r--r--toolkit/components/telemetry/app/TelemetryControllerContent.sys.mjs2
-rw-r--r--toolkit/components/telemetry/app/TelemetryEnvironment.sys.mjs2
-rw-r--r--toolkit/components/telemetry/app/TelemetryReportingPolicy.sys.mjs5
-rw-r--r--toolkit/components/telemetry/app/TelemetryScheduler.sys.mjs2
-rw-r--r--toolkit/components/telemetry/app/TelemetryStorage.sys.mjs6
-rw-r--r--toolkit/components/telemetry/dap/DAPTelemetrySender.sys.mjs2
-rw-r--r--toolkit/components/telemetry/dap/DAPVisitCounter.sys.mjs2
-rw-r--r--toolkit/components/telemetry/docs/data/health-ping.rst2
-rw-r--r--toolkit/components/telemetry/docs/obsolete/fhr/architecture.rst6
-rw-r--r--toolkit/components/telemetry/docs/obsolete/fhr/dataformat.rst2
-rw-r--r--toolkit/components/telemetry/pings/EventPing.sys.mjs2
-rw-r--r--toolkit/components/telemetry/pings/TelemetrySession.sys.mjs2
-rw-r--r--toolkit/components/telemetry/tests/browser/browser_DynamicScalars.js2
-rw-r--r--toolkit/components/telemetry/tests/integration/tests/conftest.py8
-rw-r--r--toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/runner.py3
-rw-r--r--toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/testcase.py12
-rw-r--r--toolkit/components/telemetry/tests/marionette/tests/client/test_deletion_request_ping.py4
-rw-r--r--toolkit/components/telemetry/tests/marionette/tests/client/test_main_tab_scalars.py4
-rw-r--r--toolkit/components/telemetry/tests/unit/data/search-extensions/search-config-v2.json40
-rw-r--r--toolkit/components/telemetry/tests/unit/head.js6
-rw-r--r--toolkit/components/telemetry/tests/unit/test_EventPing.js8
-rw-r--r--toolkit/components/telemetry/tests/unit/test_TelemetryController.js2
-rw-r--r--toolkit/components/telemetry/tests/unit/test_TelemetryEnvironment.js17
-rw-r--r--toolkit/components/telemetry/tests/unit/test_TelemetryEnvironment_search.js13
-rw-r--r--toolkit/components/telemetry/tests/unit/test_TelemetryEvents.js2
-rw-r--r--toolkit/components/telemetry/tests/unit/test_TelemetryHistograms.js6
-rw-r--r--toolkit/components/telemetry/tests/unit/test_TelemetryLateWrites.js8
-rw-r--r--toolkit/components/telemetry/tests/unit/test_TelemetrySendOldPings.js2
-rw-r--r--toolkit/components/telemetry/tests/unit/test_TelemetrySession.js10
-rw-r--r--toolkit/components/telemetry/tests/unit/xpcshell.toml1
-rw-r--r--toolkit/components/terminator/nsTerminator.cpp6
-rw-r--r--toolkit/components/tooltiptext/TooltipTextProvider.sys.mjs227
-rw-r--r--toolkit/components/tooltiptext/tests/browser.toml2
-rw-r--r--toolkit/components/tooltiptext/tests/browser_input_file_tooltips.js4
-rw-r--r--toolkit/components/tooltiptext/tests/browser_nac_tooltip.js66
-rw-r--r--toolkit/components/tooltiptext/tests/browser_shadow_dom_tooltip.js2
-rw-r--r--toolkit/components/translations/TranslationsTelemetry.sys.mjs3
-rw-r--r--toolkit/components/translations/actors/AboutTranslationsChild.sys.mjs12
-rw-r--r--toolkit/components/translations/actors/AboutTranslationsParent.sys.mjs6
-rw-r--r--toolkit/components/translations/actors/TranslationsChild.sys.mjs1
-rw-r--r--toolkit/components/translations/actors/TranslationsEngineChild.sys.mjs6
-rw-r--r--toolkit/components/translations/actors/TranslationsEngineParent.sys.mjs13
-rw-r--r--toolkit/components/translations/actors/TranslationsParent.sys.mjs219
-rw-r--r--toolkit/components/translations/content/translations-document.sys.mjs74
-rw-r--r--toolkit/components/translations/content/translations-engine.sys.mjs8
-rw-r--r--toolkit/components/translations/content/translations.mjs7
-rw-r--r--toolkit/components/translations/tests/browser/browser_translations_translation_document.js100
-rw-r--r--toolkit/components/translations/tests/browser/shared-head.js142
-rw-r--r--toolkit/components/translations/tests/browser/translations-test.mjs2
-rw-r--r--toolkit/components/translations/translations.d.ts2
-rw-r--r--toolkit/components/uniffi-js/UniFFIPointer.cpp38
-rw-r--r--toolkit/components/url-classifier/UrlClassifierHashCompleter.sys.mjs4
-rw-r--r--toolkit/components/url-classifier/UrlClassifierLib.sys.mjs2
-rw-r--r--toolkit/components/url-classifier/UrlClassifierListManager.sys.mjs2
-rw-r--r--toolkit/components/url-classifier/UrlClassifierRemoteSettingsService.sys.mjs2
-rw-r--r--toolkit/components/url-classifier/nsIUrlListManager.idl2
-rw-r--r--toolkit/components/url-classifier/tests/UrlClassifierTestUtils.sys.mjs8
-rw-r--r--toolkit/components/url-classifier/tests/mochitest/allowlistAnnotatedFrame.html2
-rw-r--r--toolkit/components/url-classifier/tests/mochitest/classifiedAnnotatedFrame.html2
-rw-r--r--toolkit/components/url-classifier/tests/mochitest/classifierCommon.js6
-rw-r--r--toolkit/components/url-classifier/tests/mochitest/classifierHelper.js4
-rw-r--r--toolkit/components/url-classifier/tests/mochitest/dnt.html2
-rw-r--r--toolkit/components/url-classifier/tests/mochitest/features.js2
-rw-r--r--toolkit/components/url-classifier/tests/mochitest/head.js2
-rw-r--r--toolkit/components/url-classifier/tests/mochitest/test_bug1254766.html4
-rw-r--r--toolkit/components/url-classifier/tests/mochitest/test_cachemiss.html4
-rw-r--r--toolkit/components/url-classifier/tests/mochitest/test_classified_annotations.html2
-rw-r--r--toolkit/components/url-classifier/tests/mochitest/test_classifier_changetablepref.html6
-rw-r--r--toolkit/components/url-classifier/tests/mochitest/test_classify_by_default.html2
-rw-r--r--toolkit/components/url-classifier/tests/mochitest/test_classify_ping.html6
-rw-r--r--toolkit/components/url-classifier/tests/mochitest/test_classify_top_sandboxed.html2
-rw-r--r--toolkit/components/url-classifier/tests/mochitest/test_classify_track.html6
-rw-r--r--toolkit/components/url-classifier/tests/mochitest/test_donottrack.html2
-rw-r--r--toolkit/components/url-classifier/tests/mochitest/test_gethash.html2
-rw-r--r--toolkit/components/url-classifier/tests/mochitest/test_privatebrowsing_trackingprotection.html2
-rw-r--r--toolkit/components/url-classifier/tests/mochitest/test_reporturl.html4
-rw-r--r--toolkit/components/url-classifier/tests/mochitest/test_threathit_report.html4
-rw-r--r--toolkit/components/url-classifier/tests/mochitest/test_trackingprotection_bug1312515.html8
-rw-r--r--toolkit/components/url-classifier/tests/mochitest/test_trackingprotection_bug1580416.html2
-rw-r--r--toolkit/components/url-classifier/tests/mochitest/test_trackingprotection_whitelist.html2
-rw-r--r--toolkit/components/url-classifier/tests/mochitest/workerFrame.html10
-rw-r--r--toolkit/components/url-classifier/tests/unit/head_urlclassifier.js10
-rw-r--r--toolkit/components/url-classifier/tests/unit/test_channelClassifierService.js20
-rw-r--r--toolkit/components/url-classifier/tests/unit/test_dbservice.js2
-rw-r--r--toolkit/components/url-classifier/tests/unit/test_hashcompleter.js4
-rw-r--r--toolkit/components/url-classifier/tests/unit/test_partial.js2
-rw-r--r--toolkit/components/url-classifier/tests/unit/test_rsListService.js24
-rw-r--r--toolkit/components/url-classifier/tests/unit/test_shouldclassify.js6
-rw-r--r--toolkit/components/utils/ClientEnvironment.sys.mjs2
-rw-r--r--toolkit/components/utils/FilterExpressions.sys.mjs6
-rw-r--r--toolkit/components/utils/JsonSchemaValidator.sys.mjs2
-rw-r--r--toolkit/components/utils/moz.build2
-rw-r--r--toolkit/components/utils/mozjexl.js1
-rw-r--r--toolkit/components/utils/mozjexl.sys.mjs1
-rw-r--r--toolkit/components/windowcreator/nsIWindowCreator.idl6
-rw-r--r--toolkit/components/windowwatcher/moz.build1
-rw-r--r--toolkit/components/windowwatcher/nsIPromptService.idl2
-rw-r--r--toolkit/components/windowwatcher/nsPIPromptService.idl30
-rw-r--r--toolkit/content/aboutLogging.js6
-rw-r--r--toolkit/content/aboutNetError.mjs12
-rw-r--r--toolkit/content/aboutSupport.js2
-rw-r--r--toolkit/content/aboutSupport.xhtml4
-rw-r--r--toolkit/content/aboutwebrtc/aboutWebrtc.mjs2
-rw-r--r--toolkit/content/contentAreaUtils.js3
-rw-r--r--toolkit/content/customElements.js2
-rw-r--r--toolkit/content/jar.mn5
-rw-r--r--toolkit/content/license.html83
-rw-r--r--toolkit/content/tests/browser/browser_about_logging.js22
-rw-r--r--toolkit/content/tests/browser/browser_default_audio_filename.js2
-rw-r--r--toolkit/content/tests/browser/browser_default_image_filename.js2
-rw-r--r--toolkit/content/tests/browser/browser_default_image_filename_redirect.js2
-rw-r--r--toolkit/content/tests/browser/browser_saveImageURL.js2
-rw-r--r--toolkit/content/tests/browser/browser_save_folder_standalone_image.js2
-rw-r--r--toolkit/content/tests/browser/browser_save_resend_postdata.js2
-rw-r--r--toolkit/content/tests/browser/datetime/browser.toml5
-rw-r--r--toolkit/content/tests/browser/datetime/browser_datetime_change_event.js46
-rw-r--r--toolkit/content/tests/browser/datetime/head.js12
-rw-r--r--toolkit/content/tests/chrome/chrome.toml2
-rw-r--r--toolkit/content/tests/chrome/test_autocomplete_mac_caret.xhtml4
-rw-r--r--toolkit/content/tests/chrome/test_menulist_initial_selection.xhtml55
-rw-r--r--toolkit/content/tests/chrome/window_tooltip.xhtml6
-rw-r--r--toolkit/content/tests/mochitest/test_autocomplete_change_after_focus.html2
-rw-r--r--toolkit/content/tests/widgets/chrome.toml5
-rw-r--r--toolkit/content/tests/widgets/test_moz_button.html158
-rw-r--r--toolkit/content/tests/widgets/test_moz_page_nav.html306
-rw-r--r--toolkit/content/tests/widgets/test_videocontrols.html2
-rw-r--r--toolkit/content/widgets/arrowscrollbox.js15
-rw-r--r--toolkit/content/widgets/autocomplete-input.js10
-rw-r--r--toolkit/content/widgets/autocomplete-popup.js6
-rw-r--r--toolkit/content/widgets/autocomplete-richlistitem.js4
-rw-r--r--toolkit/content/widgets/browser-custom-element.js4
-rw-r--r--toolkit/content/widgets/datetimebox.js2
-rw-r--r--toolkit/content/widgets/dialog.js4
-rw-r--r--toolkit/content/widgets/editor.js6
-rw-r--r--toolkit/content/widgets/findbar.js12
-rw-r--r--toolkit/content/widgets/menu.js2
-rw-r--r--toolkit/content/widgets/menulist.js3
-rw-r--r--toolkit/content/widgets/menupopup.js23
-rw-r--r--toolkit/content/widgets/moz-button-group/moz-button-group.mjs2
-rw-r--r--toolkit/content/widgets/moz-button/moz-button.css142
-rw-r--r--toolkit/content/widgets/moz-button/moz-button.mjs90
-rw-r--r--toolkit/content/widgets/moz-button/moz-button.stories.mjs100
-rw-r--r--toolkit/content/widgets/moz-input-box.js2
-rw-r--r--toolkit/content/widgets/moz-label/README.stories.md4
-rw-r--r--toolkit/content/widgets/moz-label/moz-label.mjs2
-rw-r--r--toolkit/content/widgets/moz-message-bar/moz-message-bar.mjs2
-rw-r--r--toolkit/content/widgets/moz-message-bar/moz-message-bar.stories.mjs6
-rw-r--r--toolkit/content/widgets/moz-page-nav/moz-page-nav-button.css123
-rw-r--r--toolkit/content/widgets/moz-page-nav/moz-page-nav.css76
-rw-r--r--toolkit/content/widgets/moz-page-nav/moz-page-nav.mjs170
-rw-r--r--toolkit/content/widgets/moz-page-nav/moz-page-nav.stories.mjs77
-rw-r--r--toolkit/content/widgets/moz-support-link/moz-support-link.mjs2
-rw-r--r--toolkit/content/widgets/moz-toggle/moz-toggle.css39
-rw-r--r--toolkit/content/widgets/named-deck.js2
-rw-r--r--toolkit/content/widgets/notificationbox.js2
-rw-r--r--toolkit/content/widgets/panel-list/README.stories.md22
-rw-r--r--toolkit/content/widgets/panel-list/panel-list.css4
-rw-r--r--toolkit/content/widgets/panel-list/panel-list.js59
-rw-r--r--toolkit/content/widgets/panel-list/panel-list.stories.mjs45
-rw-r--r--toolkit/content/widgets/radio.js6
-rw-r--r--toolkit/content/widgets/richlistbox.js2
-rw-r--r--toolkit/content/widgets/search-textbox.js2
-rw-r--r--toolkit/content/widgets/tabbox.js8
-rw-r--r--toolkit/content/widgets/text.js2
-rw-r--r--toolkit/content/widgets/textrecognition.js2
-rw-r--r--toolkit/content/widgets/tree.js8
-rw-r--r--toolkit/content/widgets/videocontrols.js80
-rw-r--r--toolkit/content/widgets/wizard.js2
-rw-r--r--toolkit/content/xul.css89
-rw-r--r--toolkit/crashreporter/CrashAnnotations.h.in18
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/microdump_writer/microdump_writer.cc4
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_dumper.cc12
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_dumper.h9
-rw-r--r--toolkit/crashreporter/breakpad-client/linux/minidump_writer/minidump_writer.cc12
-rw-r--r--toolkit/crashreporter/breakpad-client/mac/handler/minidump_generator.cc15
-rw-r--r--toolkit/crashreporter/breakpad-client/mac/handler/minidump_generator.h3
-rw-r--r--toolkit/crashreporter/docs/index.rst5
-rw-r--r--toolkit/crashreporter/linux_utils.cc59
-rw-r--r--toolkit/crashreporter/linux_utils.h16
-rw-r--r--toolkit/crashreporter/moz.build8
-rw-r--r--toolkit/crashreporter/mozannotation_client/src/lib.rs257
-rw-r--r--toolkit/crashreporter/mozannotation_server/Cargo.toml1
-rw-r--r--toolkit/crashreporter/mozannotation_server/src/lib.rs66
-rw-r--r--toolkit/crashreporter/mozannotation_server/src/process_reader/linux.rs4
-rw-r--r--toolkit/crashreporter/nsDummyExceptionHandler.cpp71
-rw-r--r--toolkit/crashreporter/nsExceptionHandler.cpp678
-rw-r--r--toolkit/crashreporter/nsExceptionHandler.h47
-rw-r--r--toolkit/crashreporter/test/gtest/TestElfSoVersion.cpp143
-rw-r--r--toolkit/crashreporter/test/gtest/moz.build16
-rw-r--r--toolkit/crashreporter/test/moz.build4
-rw-r--r--toolkit/crashreporter/test/unit/head_crashreporter.js3
-rw-r--r--toolkit/crashreporter/test/unit/test_crash_modules_linux.js33
-rw-r--r--toolkit/crashreporter/test/unit/xpcshell-phc.toml4
-rw-r--r--toolkit/crashreporter/test/unit/xpcshell.toml40
-rwxr-xr-xtoolkit/crashreporter/tools/symbolstore.py2
-rw-r--r--toolkit/library/moz.build1
-rw-r--r--toolkit/library/rust/moz.build3
-rw-r--r--toolkit/library/rust/shared/Cargo.toml2
-rw-r--r--toolkit/locales/en-US/chrome/global/narrate.properties6
-rw-r--r--toolkit/locales/en-US/toolkit/contentanalysis/contentanalysis.ftl20
-rw-r--r--toolkit/locales/en-US/toolkit/formautofill/formAutofill.ftl18
-rw-r--r--toolkit/locales/en-US/toolkit/global/arrowscrollbox.ftl20
-rw-r--r--toolkit/locales/en-US/toolkit/global/processTypes.ftl7
-rw-r--r--toolkit/locales/en-US/toolkit/global/textActions.ftl3
-rw-r--r--toolkit/locales/en-US/toolkit/pdfviewer/viewer.ftl17
-rw-r--r--toolkit/modules/ActorManagerParent.sys.mjs29
-rw-r--r--toolkit/modules/AppConstants.sys.mjs23
-rw-r--r--toolkit/modules/BrowserUtils.sys.mjs1
-rw-r--r--toolkit/modules/ClipboardContextMenu.sys.mjs10
-rw-r--r--toolkit/modules/Console.sys.mjs2
-rw-r--r--toolkit/modules/DateTimePickerPanel.sys.mjs2
-rw-r--r--toolkit/modules/Deprecated.sys.mjs81
-rw-r--r--toolkit/modules/FindBarContent.sys.mjs9
-rw-r--r--toolkit/modules/FirstStartup.sys.mjs26
-rw-r--r--toolkit/modules/ObjectUtils.sys.mjs16
-rw-r--r--toolkit/modules/ProcessType.sys.mjs11
-rw-r--r--toolkit/modules/RemotePageAccessManager.sys.mjs5
-rw-r--r--toolkit/modules/Sqlite.sys.mjs158
-rw-r--r--toolkit/modules/Troubleshoot.sys.mjs20
-rw-r--r--toolkit/modules/docs/AsyncShutdown.rst8
-rw-r--r--toolkit/modules/metrics.yaml20
-rw-r--r--toolkit/modules/moz.build2
-rw-r--r--toolkit/modules/subprocess/.eslintrc.js4
-rw-r--r--toolkit/modules/tests/browser/browser.toml2
-rw-r--r--toolkit/modules/tests/browser/browser_Deprecated.js140
-rw-r--r--toolkit/modules/tests/marionette/manifest.toml4
-rw-r--r--toolkit/modules/tests/marionette/test_first_startup.py54
-rw-r--r--toolkit/modules/tests/xpcshell/test_GMPInstallManager.js2
-rw-r--r--toolkit/modules/tests/xpcshell/test_Services.js1
-rw-r--r--toolkit/modules/tests/xpcshell/test_firstStartup.js25
-rw-r--r--toolkit/modules/tests/xpcshell/test_sqlite.js5
-rw-r--r--toolkit/modules/third_party/fathom/README2
-rw-r--r--toolkit/modules/third_party/fathom/fathom.mjs2
-rw-r--r--toolkit/moz.configure199
-rw-r--r--toolkit/mozapps/defaultagent/BackgroundTask_defaultagent.sys.mjs45
-rw-r--r--toolkit/mozapps/defaultagent/DefaultAgent.cpp59
-rw-r--r--toolkit/mozapps/defaultagent/DefaultBrowser.cpp11
-rw-r--r--toolkit/mozapps/defaultagent/DefaultBrowser.h4
-rw-r--r--toolkit/mozapps/defaultagent/Notification.cpp602
-rw-r--r--toolkit/mozapps/defaultagent/Notification.h7
-rw-r--r--toolkit/mozapps/defaultagent/ScheduledTask.cpp3
-rw-r--r--toolkit/mozapps/defaultagent/SetDefaultBrowser.cpp393
-rw-r--r--toolkit/mozapps/defaultagent/defaultagent.ini9
-rw-r--r--toolkit/mozapps/defaultagent/moz.build20
-rw-r--r--toolkit/mozapps/defaultagent/nsIDefaultAgent.idl14
-rw-r--r--toolkit/mozapps/defaultagent/proxy/main.cpp2
-rw-r--r--toolkit/mozapps/defaultagent/proxy/moz.build1
-rw-r--r--toolkit/mozapps/downloads/DownloadLastDir.sys.mjs2
-rw-r--r--toolkit/mozapps/downloads/DownloadUtils.sys.mjs2
-rw-r--r--toolkit/mozapps/downloads/HelperAppDlg.sys.mjs32
-rw-r--r--toolkit/mozapps/downloads/tests/browser/browser_save_wrongextension.js2
-rw-r--r--toolkit/mozapps/downloads/tests/browser/browser_unknownContentType_delayedbutton.js6
-rw-r--r--toolkit/mozapps/downloads/tests/browser/browser_unknownContentType_dialog_layout.js4
-rw-r--r--toolkit/mozapps/downloads/tests/unit/test_lowMinutes.js2
-rw-r--r--toolkit/mozapps/extensions/AddonManager.sys.mjs4
-rw-r--r--toolkit/mozapps/extensions/content/aboutaddons.css12
-rw-r--r--toolkit/mozapps/extensions/content/aboutaddons.js27
-rw-r--r--toolkit/mozapps/extensions/content/aboutaddonsCommon.js6
-rw-r--r--toolkit/mozapps/extensions/internal/AddonRepository.sys.mjs2
-rw-r--r--toolkit/mozapps/extensions/internal/AddonTestUtils.sys.mjs31
-rw-r--r--toolkit/mozapps/extensions/internal/AddonUpdateChecker.sys.mjs2
-rw-r--r--toolkit/mozapps/extensions/internal/SitePermsAddonProvider.sys.mjs23
-rw-r--r--toolkit/mozapps/extensions/internal/XPIDatabase.sys.mjs2
-rw-r--r--toolkit/mozapps/extensions/internal/XPIInstall.sys.mjs33
-rw-r--r--toolkit/mozapps/extensions/internal/XPIProvider.sys.mjs2
-rw-r--r--toolkit/mozapps/extensions/test/browser/browser.toml2
-rw-r--r--toolkit/mozapps/extensions/test/browser/browser_html_options_ui.js11
-rw-r--r--toolkit/mozapps/extensions/test/browser/browser_page_options_install_addon.js2
-rw-r--r--toolkit/mozapps/extensions/test/browser/browser_sidebar_preferences_button.js73
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/test_corrupt.js4
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/test_system_update_enterprisepolicy.js26
-rw-r--r--toolkit/mozapps/handling/content/appChooser.js2
-rw-r--r--toolkit/mozapps/installer/packager.py2
-rw-r--r--toolkit/mozapps/update/AppUpdater.sys.mjs14
-rw-r--r--toolkit/mozapps/update/BackgroundUpdate.sys.mjs2
-rw-r--r--toolkit/mozapps/update/UpdateService.sys.mjs28
-rw-r--r--toolkit/mozapps/update/UpdateTelemetry.sys.mjs2
-rw-r--r--toolkit/mozapps/update/common/updatererrors.h18
-rw-r--r--toolkit/mozapps/update/docs/BackgroundUpdates.rst6
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_elevationDialog.js4
-rw-r--r--toolkit/mozapps/update/tests/browser/head.js2
-rw-r--r--toolkit/mozapps/update/tests/data/app_update.sjs4
-rw-r--r--toolkit/mozapps/update/tests/data/shared.js2
-rw-r--r--toolkit/mozapps/update/tests/data/xpcshellUtilsAUS.js14
-rw-r--r--toolkit/mozapps/update/tests/marionette/test_no_window_update_restart.py4
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/languagePackUpdates.js6
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/multiUpdate.js10
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/updateSyncManager.js4
-rw-r--r--toolkit/mozapps/update/tests/unit_background_update/test_backgroundupdate_glean.js6
-rw-r--r--toolkit/mozapps/update/updater/updater.cpp2
-rw-r--r--toolkit/profile/content/createProfileWizard.js2
-rw-r--r--toolkit/system/androidproxy/nsAndroidSystemProxySettings.cpp6
-rw-r--r--toolkit/system/osxproxy/nsOSXSystemProxySettings.mm11
-rw-r--r--toolkit/system/unixproxy/nsUnixSystemProxySettings.cpp6
-rw-r--r--toolkit/system/windowsproxy/nsWindowsSystemProxySettings.cpp13
-rw-r--r--toolkit/themes/linux/global/global.css4
-rw-r--r--toolkit/themes/osx/global/global.css6
-rw-r--r--toolkit/themes/shared/design-system/README.design-tokens.stories.md44
-rw-r--r--toolkit/themes/shared/design-system/tokens-brand.css55
-rw-r--r--toolkit/themes/shared/design-system/tokens-platform.css13
-rw-r--r--toolkit/themes/shared/design-system/tokens-shared.css392
-rw-r--r--toolkit/themes/shared/desktop-jar.inc.mn1
-rw-r--r--toolkit/themes/shared/global-shared.css66
-rw-r--r--toolkit/themes/shared/icons/plus-20.svg4
-rw-r--r--toolkit/themes/shared/in-content/common-shared.css7
-rw-r--r--toolkit/themes/shared/media/pause-fill.svg5
-rw-r--r--toolkit/themes/shared/media/videocontrols.css2
-rw-r--r--toolkit/themes/shared/popup.css89
-rw-r--r--toolkit/themes/windows/global/global.css6
-rw-r--r--toolkit/xre/dllservices/mozglue/WindowsDllBlocklist.cpp43
-rw-r--r--toolkit/xre/dllservices/mozglue/WindowsDllBlocklist.h9
-rw-r--r--toolkit/xre/nsAppRunner.cpp199
-rw-r--r--toolkit/xre/nsAppRunner.h2
-rw-r--r--toolkit/xre/nsEmbedFunctions.cpp14
-rw-r--r--toolkit/xre/nsWindowsWMain.cpp23
1101 files changed, 20597 insertions, 8420 deletions
diff --git a/toolkit/actors/AutoCompleteParent.sys.mjs b/toolkit/actors/AutoCompleteParent.sys.mjs
index 27f3dcbe07..611677a84f 100644
--- a/toolkit/actors/AutoCompleteParent.sys.mjs
+++ b/toolkit/actors/AutoCompleteParent.sys.mjs
@@ -503,7 +503,8 @@ export class AutoCompleteParent extends JSWindowActorParent {
* that the open popup should be focused.
*/
requestFocus() {
- // Bug 1582722 - See the response in AutoCompleteChild.jsm for why this disabled.
+ // Bug 1582722 - See the response in AutoCompleteChild.sys.mjs for why this
+ // disabled.
/*
if (this.openedPopup) {
this.sendAsyncMessage("FormAutoComplete:Focus");
diff --git a/toolkit/actors/AutoScrollChild.sys.mjs b/toolkit/actors/AutoScrollChild.sys.mjs
index 25e2ae77a5..11324b4eaa 100644
--- a/toolkit/actors/AutoScrollChild.sys.mjs
+++ b/toolkit/actors/AutoScrollChild.sys.mjs
@@ -215,8 +215,14 @@ export class AutoScrollChild extends JSWindowActorChild {
return;
}
- Services.els.addSystemEventListener(this.document, "mousemove", this, true);
- Services.els.addSystemEventListener(this.document, "mouseup", this, true);
+ 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;
@@ -254,18 +260,14 @@ export class AutoScrollChild extends JSWindowActorChild {
this._scrollable.mozScrollSnap();
this._scrollable = null;
- Services.els.removeSystemEventListener(
- this.document,
- "mousemove",
- this,
- true
- );
- Services.els.removeSystemEventListener(
- this.document,
- "mouseup",
- this,
- true
- );
+ 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(
diff --git a/toolkit/actors/FindBarChild.sys.mjs b/toolkit/actors/FindBarChild.sys.mjs
index 645456ad1f..8aed35a769 100644
--- a/toolkit/actors/FindBarChild.sys.mjs
+++ b/toolkit/actors/FindBarChild.sys.mjs
@@ -141,11 +141,8 @@ export class FindBarChild extends JSWindowActorChild {
return false;
}
- if (
- (win.HTMLIFrameElement.isInstance(elt) && elt.mozbrowser) ||
- win.XULFrameElement.isInstance(elt)
- ) {
- // If we're targeting a mozbrowser iframe or an embedded XULFrameElement
+ 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;
diff --git a/toolkit/actors/PictureInPictureChild.sys.mjs b/toolkit/actors/PictureInPictureChild.sys.mjs
index 62dc6cdfff..2a415df32d 100644
--- a/toolkit/actors/PictureInPictureChild.sys.mjs
+++ b/toolkit/actors/PictureInPictureChild.sys.mjs
@@ -1225,11 +1225,11 @@ export class PictureInPictureToggleChild extends JSWindowActorChild {
let shadowRoot = video.openOrClosedShadowRoot;
if (shadowRoot.firstChild && video != oldOverVideo) {
- if (video.getTransformToViewport().a == -1) {
- shadowRoot.firstChild.setAttribute("flipped", true);
- } else {
- shadowRoot.firstChild.removeAttribute("flipped");
- }
+ // 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
@@ -1812,7 +1812,7 @@ export class PictureInPictureChild extends JSWindowActorChild {
* 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.jsm currently sets snapToLines to false if line is a percentage value, but
+ * 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
diff --git a/toolkit/actors/SelectChild.sys.mjs b/toolkit/actors/SelectChild.sys.mjs
index a6d96d1b79..1bc80001ae 100644
--- a/toolkit/actors/SelectChild.sys.mjs
+++ b/toolkit/actors/SelectChild.sys.mjs
@@ -15,7 +15,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
const kStateActive = 0x00000001; // ElementState::ACTIVE
const kStateHover = 0x00000004; // ElementState::HOVER
-// Duplicated in SelectParent.jsm
+// Duplicated in SelectParent.sys.mjs
// Please keep these lists in sync.
const SUPPORTED_OPTION_OPTGROUP_PROPERTIES = [
"direction",
@@ -277,7 +277,7 @@ SelectContentHelper.prototype = {
InspectorUtils.removeContentState(this.element, kStateHover);
break;
- case "Forms:MouseUp":
+ case "Forms:MouseUp": {
let win = this.element.ownerGlobal;
if (message.data.onAnchor) {
this.dispatchMouseEvent(win, this.element, "mouseup");
@@ -287,6 +287,7 @@ SelectContentHelper.prototype = {
this.dispatchMouseEvent(win, this.element, "click");
}
break;
+ }
case "Forms:SearchFocused":
this._closeAfterBlur = false;
@@ -346,6 +347,13 @@ function getComputedStyles(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;
diff --git a/toolkit/actors/SelectParent.sys.mjs b/toolkit/actors/SelectParent.sys.mjs
index 6d14807e44..5382b35ab3 100644
--- a/toolkit/actors/SelectParent.sys.mjs
+++ b/toolkit/actors/SelectParent.sys.mjs
@@ -32,7 +32,7 @@ const PROPERTIES_RESET_WHEN_ACTIVE = [
"text-shadow",
];
-// Duplicated in SelectChild.jsm
+// Duplicated in SelectChild.sys.mjs
// Please keep these lists in sync.
const SUPPORTED_OPTION_OPTGROUP_PROPERTIES = [
"direction",
diff --git a/toolkit/components/aboutconfig/content/aboutconfig.js b/toolkit/components/aboutconfig/content/aboutconfig.js
index e11021ed64..5e67b764b5 100644
--- a/toolkit/components/aboutconfig/content/aboutconfig.js
+++ b/toolkit/components/aboutconfig/content/aboutconfig.js
@@ -255,6 +255,7 @@ class PrefRow {
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;
diff --git a/toolkit/components/aboutconfig/test/browser/browser.toml b/toolkit/components/aboutconfig/test/browser/browser.toml
index acccf1f8f0..a0cce686b8 100644
--- a/toolkit/components/aboutconfig/test/browser/browser.toml
+++ b/toolkit/components/aboutconfig/test/browser/browser.toml
@@ -11,10 +11,9 @@ support-files = ["head.js"]
["browser_basic.js"]
["browser_clipboard.js"]
-fail-if = ["a11y_checks"] # Bug 1854447 th, td.cell-value, #prefs may not be focusable; #show-all may be unlabeled
["browser_edit.js"]
-fail-if = ["a11y_checks"] # Bug 1854447 span, th, td.cell-value may not be focusable; #show-all, input, button-add/delete/reset ghost-buttons may not be labeled
+fail-if = ["a11y_checks"] # Bugs 1854447 and 1882380 span may not be focusable
skip-if = ["os == 'linux' && ccov"] # Bug 1613515, the test consistently times out on Linux coverage builds.
["browser_locked.js"]
diff --git a/toolkit/components/aboutconfig/test/browser/browser_clipboard.js b/toolkit/components/aboutconfig/test/browser/browser_clipboard.js
index bcaa2c0328..a8fca568c7 100644
--- a/toolkit/components/aboutconfig/test/browser/browser_clipboard.js
+++ b/toolkit/components/aboutconfig/test/browser/browser_clipboard.js
@@ -33,6 +33,14 @@ add_task(async function test_copy() {
let selectText = async target => {
let { width, height } = target.getBoundingClientRect();
+
+ // We intentionally turn off this a11y check, because the following
+ // series of mouse events is purposefully targeting a non-interactive
+ // text content. This action does not require the element to have an
+ // interactive accessible to be done by assistive technology with caret
+ // browsing (when/if supported), this rule check shall be ignored by
+ // a11y_checks suite.
+ AccessibilityUtils.setEnv({ mustHaveAccessibleRule: false });
EventUtils.synthesizeMouse(
target,
1,
@@ -54,6 +62,7 @@ add_task(async function test_copy() {
{ type: "mouseup" },
this.browser.contentWindow
);
+ AccessibilityUtils.resetEnv();
};
// Drag across the name cell.
@@ -107,6 +116,13 @@ add_task(async function test_copy_multiple() {
let { width, height } = endRow.valueCell.getBoundingClientRect();
// Drag from the top left of the first row to the bottom right of the last.
+ // We intentionally turn off this a11y check, because the following
+ // series of mouse events is purposefully targeting a non-interactive
+ // text content. This action does not require the element to have an
+ // interactive accessible to be done by assistive technology with caret
+ // browsing (when/if supported), this rule check shall be ignored by
+ // a11y_checks suite.
+ AccessibilityUtils.setEnv({ mustHaveAccessibleRule: false });
EventUtils.synthesizeMouse(
startRow.nameCell,
1,
@@ -129,6 +145,7 @@ add_task(async function test_copy_multiple() {
{ type: "mouseup" },
this.browser.contentWindow
);
+ AccessibilityUtils.resetEnv();
await SimpleTest.promiseClipboardChange(expectedString, async () => {
await BrowserTestUtils.synthesizeKey(
diff --git a/toolkit/components/aboutconfig/test/browser/browser_edit.js b/toolkit/components/aboutconfig/test/browser/browser_edit.js
index 9d10fb1e75..24fb168e76 100644
--- a/toolkit/components/aboutconfig/test/browser/browser_edit.js
+++ b/toolkit/components/aboutconfig/test/browser/browser_edit.js
@@ -282,6 +282,11 @@ add_task(async function test_edit_field_selected() {
Assert.equal(row.value, startValue);
row.editColumnButton.click();
Assert.equal(row.valueInput.value, startValue);
+ Assert.equal(
+ row.valueInput.getAttribute("aria-label"),
+ prefName,
+ "The input field is labeled from the pref name"
+ );
EventUtils.sendString(endValue, this.window);
@@ -334,15 +339,31 @@ add_task(async function test_double_click_modify() {
let click = (target, opts) =>
EventUtils.synthesizeMouseAtCenter(target, opts, this.window);
let doubleClick = target => {
+ // We intentionally turn off this a11y check, because the following series
+ // of clicks (in these test cases) is either performing an activation of
+ // the edit mode for prefs or selecting a text in focused inputs. The
+ // edit mode can be activated with a separate "Edit" or "Toggle" button
+ // provided for each pref, and the text selection can be performed with
+ // caret browsing (when supported). Thus, this rule check can be ignored
+ // by a11y_checks suite.
+ AccessibilityUtils.setEnv({ mustHaveAccessibleRule: false });
// Trigger two mouse events to simulate the first then second click.
click(target, { clickCount: 1 });
click(target, { clickCount: 2 });
+ AccessibilityUtils.resetEnv();
};
let tripleClick = target => {
+ // We intentionally turn off this a11y check, because the following series
+ // of clicks is purposefully targeting a non - interactive text content.
+ // This action does not require the element to have an interactive
+ // accessible to be done by assistive technology with caret browsing
+ // (when supported), this rule check shall be ignored by a11y_checks suite.
+ AccessibilityUtils.setEnv({ mustHaveAccessibleRule: false });
// Trigger all 3 mouse events to simulate the three mouse events we'd see.
click(target, { clickCount: 1 });
click(target, { clickCount: 2 });
click(target, { clickCount: 3 });
+ AccessibilityUtils.resetEnv();
};
// Check double-click to edit a boolean.
diff --git a/toolkit/components/aboutmemory/content/aboutMemory.js b/toolkit/components/aboutmemory/content/aboutMemory.js
index da2fd560d2..049818263f 100644
--- a/toolkit/components/aboutmemory/content/aboutMemory.js
+++ b/toolkit/components/aboutmemory/content/aboutMemory.js
@@ -2469,7 +2469,11 @@ function saveReportsToFile() {
};
try {
- fp.init(window, "Save Memory Reports", Ci.nsIFilePicker.modeSave);
+ 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.
diff --git a/toolkit/components/antitracking/ContentBlockingAllowList.cpp b/toolkit/components/antitracking/ContentBlockingAllowList.cpp
index 25806cd1e7..660906a49c 100644
--- a/toolkit/components/antitracking/ContentBlockingAllowList.cpp
+++ b/toolkit/components/antitracking/ContentBlockingAllowList.cpp
@@ -17,7 +17,9 @@
#include "nsICookieJarSettings.h"
#include "nsIHttpChannel.h"
#include "nsIHttpChannelInternal.h"
+#include "nsIPermission.h"
#include "nsNetUtil.h"
+#include "nsString.h"
using namespace mozilla;
@@ -255,3 +257,70 @@ nsresult ContentBlockingAllowList::Check(
returnInputArgument.release();
principal.forget(aPrincipal);
}
+
+// ContentBlockingAllowListCache
+
+nsresult ContentBlockingAllowListCache::CheckForBaseDomain(
+ const nsACString& aBaseDomain, const OriginAttributes& aOriginAttributes,
+ bool& aIsAllowListed) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ NS_ENSURE_TRUE(!aBaseDomain.IsEmpty(), NS_ERROR_INVALID_ARG);
+ aIsAllowListed = false;
+
+ // Ensure we have the permission list.
+ nsresult rv = EnsureInit();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aOriginAttributes.mPrivateBrowsingId > 0) {
+ aIsAllowListed = mEntriesPrivateBrowsing.Contains(aBaseDomain);
+ } else {
+ aIsAllowListed = mEntries.Contains(aBaseDomain);
+ }
+
+ return NS_OK;
+}
+
+nsresult ContentBlockingAllowListCache::EnsureInit() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ if (mIsInitialized) {
+ return NS_OK;
+ }
+ mIsInitialized = true;
+
+ // 1. Get all permissions representing allow-list entries.
+ PermissionManager* permManager = PermissionManager::GetInstance();
+ NS_ENSURE_TRUE(permManager, NS_ERROR_FAILURE);
+
+ nsTArray<nsCString> types;
+ types.AppendElement("trackingprotection");
+ types.AppendElement("trackingprotection-pb");
+
+ nsTArray<RefPtr<nsIPermission>> permissions;
+ nsresult rv = permManager->GetAllByTypes(types, permissions);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // 2. Populate mEntries and mEntriesPrivateBrowsing from permission list for
+ // faster lookup.
+ for (auto& permission : permissions) {
+ MOZ_ASSERT(permission);
+
+ nsCOMPtr<nsIPrincipal> principal;
+ rv = permission->GetPrincipal(getter_AddRefs(principal));
+ NS_ENSURE_SUCCESS(rv, rv);
+ MOZ_ASSERT(principal);
+
+ nsAutoCString baseDomain;
+ rv = principal->GetBaseDomain(baseDomain);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Sort base domains into sets for normal / private browsing.
+ if (principal->OriginAttributesRef().mPrivateBrowsingId > 0) {
+ mEntriesPrivateBrowsing.Insert(baseDomain);
+ } else {
+ mEntries.Insert(baseDomain);
+ }
+ }
+
+ return NS_OK;
+}
diff --git a/toolkit/components/antitracking/ContentBlockingAllowList.h b/toolkit/components/antitracking/ContentBlockingAllowList.h
index 2601173db7..1db29f3e35 100644
--- a/toolkit/components/antitracking/ContentBlockingAllowList.h
+++ b/toolkit/components/antitracking/ContentBlockingAllowList.h
@@ -9,6 +9,8 @@
#include "mozilla/dom/BrowsingContext.h"
#include "nsIContentBlockingAllowList.h"
+#include "nsIPermission.h"
+#include "nsTHashSet.h"
class nsICookieJarSettings;
class nsIHttpChannel;
@@ -19,7 +21,59 @@ class nsPIDOMWindowInner;
namespace mozilla {
class OriginAttributes;
-struct ContentBlockingAllowListCache;
+
+/**
+ * @class ContentBlockingAllowListCache
+ *
+ * @brief This class represents a cache for the content blocking allow list. It
+ * is used for repeated lookups of the allow list for a specific base
+ * domain. Only use it if you need base domain lookups. In most cases
+ * this is not what you want. For regular allow-list checks by principal
+ * please use ContentBlockingAllowList.
+ */
+class ContentBlockingAllowListCache final {
+ public:
+ /**
+ * @brief Checks if a given base domain is allow-listed. This method considers
+ * the domain to be allow list if either the base domain or any of its
+ * subdomains are allow-listed.
+ * This is different from regular allow-list checks,
+ * @see{ContentBlockingAllowList::Check} where allow-listed state is only
+ * inherited to subdomains if the base domain is allow-listed.
+ *
+ * Example:
+ * If "example.com" is allow-listed, then "www.example.com" is also
+ * considered allow-listed.
+ * If foobar.example.org is allow-listed, then "example.org" is not
+ * considered allow-listed.
+ *
+ * @param aBaseDomain The base domain to check.
+ * @param aOriginAttributes The origin attributes associated with the base
+ * domain.
+ * @param aIsAllowListed [out] Set to true if the base domain is allow-listed,
+ * false otherwise.
+ *
+ * @return NS_OK if the check is successful, or an error code otherwise.
+ */
+ nsresult CheckForBaseDomain(const nsACString& aBaseDomain,
+ const OriginAttributes& aOriginAttributes,
+ bool& aIsAllowListed);
+
+ private:
+ bool mIsInitialized = false;
+
+ // The cache is a hash set of base domains. If a base domain is in the set, it
+ // is allow-listed for that context (normal browsing, private browsing.)
+ nsTHashSet<nsCString> mEntries;
+ nsTHashSet<nsCString> mEntriesPrivateBrowsing;
+
+ /**
+ * @brief Initializes the content blocking allow list cache if needed.
+ *
+ * @return NS_OK if initialization is successful, or an error code otherwise.
+ */
+ nsresult EnsureInit();
+};
class ContentBlockingAllowList final : public nsIContentBlockingAllowList {
public:
diff --git a/toolkit/components/antitracking/ContentBlockingAllowList.sys.mjs b/toolkit/components/antitracking/ContentBlockingAllowList.sys.mjs
index af1028083c..7efb0e7810 100644
--- a/toolkit/components/antitracking/ContentBlockingAllowList.sys.mjs
+++ b/toolkit/components/antitracking/ContentBlockingAllowList.sys.mjs
@@ -26,7 +26,7 @@ export const ContentBlockingAllowList = {
"nsISupportsWeakReference",
]),
- observe(subject, topic, data) {
+ observe(subject, topic) {
if (topic == "last-pb-context-exited") {
Services.perms.removeByType("trackingprotection-pb");
}
diff --git a/toolkit/components/antitracking/PurgeTrackerService.sys.mjs b/toolkit/components/antitracking/PurgeTrackerService.sys.mjs
index 08b5612287..458a35fef3 100644
--- a/toolkit/components/antitracking/PurgeTrackerService.sys.mjs
+++ b/toolkit/components/antitracking/PurgeTrackerService.sys.mjs
@@ -52,7 +52,7 @@ PurgeTrackerService.prototype = {
// protection list, so we cache the result for faster future lookups.
_trackingState: new Map(),
- observe(aSubject, aTopic, aData) {
+ observe(aSubject, aTopic) {
switch (aTopic) {
case "idle-daily":
// only allow one idle-daily listener to trigger until the list has been fully parsed.
diff --git a/toolkit/components/antitracking/URLDecorationAnnotationsService.sys.mjs b/toolkit/components/antitracking/URLDecorationAnnotationsService.sys.mjs
index ca285e972d..e445c8ec33 100644
--- a/toolkit/components/antitracking/URLDecorationAnnotationsService.sys.mjs
+++ b/toolkit/components/antitracking/URLDecorationAnnotationsService.sys.mjs
@@ -40,7 +40,7 @@ URLDecorationAnnotationsService.prototype = {
branch.lockPref(PREF_NAME);
},
- observe(aSubject, aTopic, aData) {
+ observe(aSubject, aTopic) {
if (aTopic == "profile-after-change") {
this.ensureUpdated();
}
diff --git a/toolkit/components/antitracking/URLQueryStringStripper.cpp b/toolkit/components/antitracking/URLQueryStringStripper.cpp
index 3b46738280..2e154b9103 100644
--- a/toolkit/components/antitracking/URLQueryStringStripper.cpp
+++ b/toolkit/components/antitracking/URLQueryStringStripper.cpp
@@ -98,7 +98,7 @@ URLQueryStringStripper::StripForCopyOrShare(nsIURI* aURI,
URLParams params;
- URLParams::Parse(query, [&](nsString&& name, nsString&& value) {
+ URLParams::Parse(query, true, [&](nsString&& name, nsString&& value) {
nsAutoString lowerCaseName;
ToLowerCase(name, lowerCaseName);
// Look through the global rules.
@@ -308,7 +308,7 @@ nsresult URLQueryStringStripper::StripQueryString(nsIURI* aURI,
URLParams params;
- URLParams::Parse(query, [&](nsString&& name, nsString&& value) {
+ URLParams::Parse(query, false, [&](nsString&& name, nsString&& value) {
nsAutoString lowerCaseName;
ToLowerCase(name, lowerCaseName);
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingProtection.cpp b/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingProtection.cpp
index e5d9ccfea9..2b0577d5c6 100644
--- a/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingProtection.cpp
+++ b/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingProtection.cpp
@@ -12,6 +12,7 @@
#include "ErrorList.h"
#include "mozilla/AlreadyAddRefed.h"
#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/ContentBlockingAllowList.h"
#include "mozilla/Logging.h"
#include "mozilla/Services.h"
#include "mozilla/StaticPrefs_privacy.h"
@@ -122,7 +123,7 @@ nsresult BounceTrackingProtection::RecordStatefulBounces(
aBounceTrackingState->Describe().get()));
// Assert: navigable’s bounce tracking record is not null.
- BounceTrackingRecord* record =
+ const Maybe<BounceTrackingRecord>& record =
aBounceTrackingState->GetBounceTrackingRecord();
NS_ENSURE_TRUE(record, NS_ERROR_FAILURE);
@@ -369,6 +370,11 @@ BounceTrackingProtection::TestRunPurgeBounceTrackers(
}
NS_IMETHODIMP
+BounceTrackingProtection::TestClearExpiredUserActivations() {
+ return ClearExpiredUserInteractions();
+}
+
+NS_IMETHODIMP
BounceTrackingProtection::TestAddBounceTrackerCandidate(
JS::Handle<JS::Value> aOriginAttributes, const nsACString& aHost,
const PRTime aBounceTime, JSContext* aCx) {
@@ -415,6 +421,22 @@ BounceTrackingProtection::TestAddUserActivation(
RefPtr<BounceTrackingProtection::PurgeBounceTrackersMozPromise>
BounceTrackingProtection::PurgeBounceTrackers() {
+ // Prevent multiple purge operations from running at the same time.
+ if (mPurgeInProgress) {
+ MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
+ ("%s: Skip: Purge already in progress.", __FUNCTION__));
+ return PurgeBounceTrackersMozPromise::CreateAndReject(
+ nsresult::NS_ERROR_NOT_AVAILABLE, __func__);
+ }
+ mPurgeInProgress = true;
+
+ // Obtain a cache of ContentBlockingAllowList permissions so we only need to
+ // fetch permissions once even when we do multiple base domain lookups.
+ ContentBlockingAllowListCache contentBlockingAllowListCache;
+
+ // Collect promises for all clearing operations to later await on.
+ nsTArray<RefPtr<ClearDataMozPromise>> clearPromises;
+
// Run the purging algorithm for all global state objects.
for (const auto& entry : mStorage->StateGlobalMapRef()) {
const OriginAttributes& originAttributes = entry.GetKey();
@@ -429,13 +451,17 @@ BounceTrackingProtection::PurgeBounceTrackers() {
oaSuffix.get()));
}
- PurgeBounceTrackersForStateGlobal(stateGlobal, originAttributes);
+ nsresult rv = PurgeBounceTrackersForStateGlobal(
+ stateGlobal, contentBlockingAllowListCache, clearPromises);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return PurgeBounceTrackersMozPromise::CreateAndReject(rv, __func__);
+ }
}
// Wait for all data clearing operations to complete. mClearPromises contains
// one promise per host / clear task.
return ClearDataMozPromise::AllSettled(GetCurrentSerialEventTarget(),
- mClearPromises)
+ clearPromises)
->Then(
GetCurrentSerialEventTarget(), __func__,
[&](ClearDataMozPromise::AllSettledPromiseType::ResolveOrRejectValue&&
@@ -450,7 +476,7 @@ BounceTrackingProtection::PurgeBounceTrackers() {
// If any clear call failed reject.
for (auto& result : aResults.ResolveValue()) {
if (result.IsReject()) {
- mClearPromises.Clear();
+ mPurgeInProgress = false;
return PurgeBounceTrackersMozPromise::CreateAndReject(
NS_ERROR_FAILURE, __func__);
}
@@ -458,7 +484,8 @@ BounceTrackingProtection::PurgeBounceTrackers() {
}
// No clearing errors, resolve.
- mClearPromises.Clear();
+
+ mPurgeInProgress = false;
return PurgeBounceTrackersMozPromise::CreateAndResolve(
std::move(purgedSiteHosts), __func__);
});
@@ -466,34 +493,17 @@ BounceTrackingProtection::PurgeBounceTrackers() {
nsresult BounceTrackingProtection::PurgeBounceTrackersForStateGlobal(
BounceTrackingStateGlobal* aStateGlobal,
- const OriginAttributes& aOriginAttributes) {
+ ContentBlockingAllowListCache& aContentBlockingAllowList,
+ nsTArray<RefPtr<ClearDataMozPromise>>& aClearPromises) {
MOZ_ASSERT(aStateGlobal);
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
- ("%s: #mUserActivation: %d, #mBounceTrackers: %d", __FUNCTION__,
- aStateGlobal->UserActivationMapRef().Count(),
- aStateGlobal->BounceTrackersMapRef().Count()));
-
- // Purge already in progress.
- if (!mClearPromises.IsEmpty()) {
- MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
- ("%s: Skip: Purge already in progress.", __FUNCTION__));
- return NS_ERROR_NOT_AVAILABLE;
- }
+ ("%s: %s", __FUNCTION__, aStateGlobal->Describe().get()));
const PRTime now = PR_Now();
- // Convert the user activation lifetime into microseconds for calculation with
- // PRTime values. The pref is a 32-bit value. Cast into 64-bit before
- // multiplying so we get the correct result.
- int64_t activationLifetimeUsec =
- static_cast<int64_t>(
- StaticPrefs::
- privacy_bounceTrackingProtection_bounceTrackingActivationLifetimeSec()) *
- PR_USEC_PER_SEC;
// 1. Remove hosts from the user activation map whose user activation flag has
// expired.
- nsresult rv =
- aStateGlobal->ClearUserActivationBefore(now - activationLifetimeUsec);
+ nsresult rv = ClearExpiredUserInteractions(aStateGlobal);
NS_ENSURE_SUCCESS(rv, rv);
// 2. Go over bounce tracker candidate map and purge state.
@@ -502,7 +512,6 @@ nsresult BounceTrackingProtection::PurgeBounceTrackersForStateGlobal(
do_GetService("@mozilla.org/clear-data-service;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
- mClearPromises.Clear();
nsTArray<nsCString> purgedSiteHosts;
// Collect hosts to remove from the bounce trackers map. We can not remove
@@ -545,6 +554,27 @@ nsresult BounceTrackingProtection::PurgeBounceTrackersForStateGlobal(
continue;
}
+ // Gecko specific: If the host is on the content blocking allow-list,
+ // continue.
+ bool isAllowListed = false;
+ rv = aContentBlockingAllowList.CheckForBaseDomain(
+ host, aStateGlobal->OriginAttributesRef(), isAllowListed);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+ if (isAllowListed) {
+ if (MOZ_LOG_TEST(gBounceTrackingProtectionLog, LogLevel::Debug)) {
+ nsAutoCString originAttributeSuffix;
+ aStateGlobal->OriginAttributesRef().CreateSuffix(originAttributeSuffix);
+ MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
+ ("%s: Skip host on the content blocking allow-list: host: %s, "
+ "originAttributes: %s",
+ __FUNCTION__, PromiseFlatCString(host).get(),
+ originAttributeSuffix.get()));
+ }
+ continue;
+ }
+
// No exception above applies, clear state for the given host.
RefPtr<ClearDataMozPromise::Private> clearPromise =
@@ -562,7 +592,7 @@ nsresult BounceTrackingProtection::PurgeBounceTrackersForStateGlobal(
clearPromise->Reject(0, __func__);
}
- mClearPromises.AppendElement(clearPromise);
+ aClearPromises.AppendElement(clearPromise);
// Remove it from the bounce trackers map, it's about to be purged. If the
// clear call fails still remove it. We want to avoid an ever growing list
@@ -575,6 +605,43 @@ nsresult BounceTrackingProtection::PurgeBounceTrackersForStateGlobal(
return aStateGlobal->RemoveBounceTrackers(bounceTrackerCandidatesToRemove);
}
+nsresult BounceTrackingProtection::ClearExpiredUserInteractions(
+ BounceTrackingStateGlobal* aStateGlobal) {
+ if (!aStateGlobal && mStorage->StateGlobalMapRef().IsEmpty()) {
+ // Nothing to clear.
+ return NS_OK;
+ }
+
+ const PRTime now = PR_Now();
+
+ // Convert the user activation lifetime into microseconds for calculation with
+ // PRTime values. The pref is a 32-bit value. Cast into 64-bit before
+ // multiplying so we get the correct result.
+ int64_t activationLifetimeUsec =
+ static_cast<int64_t>(
+ StaticPrefs::
+ privacy_bounceTrackingProtection_bounceTrackingActivationLifetimeSec()) *
+ PR_USEC_PER_SEC;
+
+ // Clear user activation for the given state global.
+ if (aStateGlobal) {
+ return aStateGlobal->ClearUserActivationBefore(now -
+ activationLifetimeUsec);
+ }
+
+ // aStateGlobal not passed, clear user activation for all state globals.
+ for (const auto& entry : mStorage->StateGlobalMapRef()) {
+ const RefPtr<BounceTrackingStateGlobal>& stateGlobal = entry.GetData();
+ MOZ_ASSERT(stateGlobal);
+
+ nsresult rv =
+ stateGlobal->ClearUserActivationBefore(now - activationLifetimeUsec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
// ClearDataCallback
NS_IMPL_ISUPPORTS(BounceTrackingProtection::ClearDataCallback,
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingProtection.h b/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingProtection.h
index 98c61504c0..e99cf895be 100644
--- a/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingProtection.h
+++ b/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingProtection.h
@@ -17,6 +17,7 @@ namespace mozilla {
class BounceTrackingState;
class BounceTrackingStateGlobal;
class BounceTrackingProtectionStorage;
+class ContentBlockingAllowListCache;
class OriginAttributes;
extern LazyLogModule gBounceTrackingProtectionLog;
@@ -33,10 +34,17 @@ class BounceTrackingProtection final : public nsIBounceTrackingProtection {
// navigation start for bounce tracking, or if the client bounce detection
// timer expires after process response received for bounce tracking without
// observing a client redirect.
- nsresult RecordStatefulBounces(BounceTrackingState* aBounceTrackingState);
+ [[nodiscard]] nsresult RecordStatefulBounces(
+ BounceTrackingState* aBounceTrackingState);
// Stores a user activation flag with a timestamp for the given principal.
- nsresult RecordUserActivation(nsIPrincipal* aPrincipal);
+ [[nodiscard]] nsresult RecordUserActivation(nsIPrincipal* aPrincipal);
+
+ // Clears expired user interaction flags for the given state global. If
+ // aStateGlobal == nullptr, clears expired user interaction flags for all
+ // state globals.
+ [[nodiscard]] nsresult ClearExpiredUserInteractions(
+ BounceTrackingStateGlobal* aStateGlobal = nullptr);
private:
BounceTrackingProtection();
@@ -53,13 +61,19 @@ class BounceTrackingProtection final : public nsIBounceTrackingProtection {
MozPromise<nsTArray<nsCString>, nsresult, true>;
RefPtr<PurgeBounceTrackersMozPromise> PurgeBounceTrackers();
- nsresult PurgeBounceTrackersForStateGlobal(
- BounceTrackingStateGlobal* aStateGlobal,
- const OriginAttributes& aOriginAttributes);
-
// Pending clear operations are stored as ClearDataMozPromise, one per host.
using ClearDataMozPromise = MozPromise<nsCString, uint32_t, true>;
- nsTArray<RefPtr<ClearDataMozPromise>> mClearPromises;
+
+ // Clear state for classified bounce trackers for a specific state global.
+ // aClearPromises is populated with promises for each host that is cleared.
+ [[nodiscard]] nsresult PurgeBounceTrackersForStateGlobal(
+ BounceTrackingStateGlobal* aStateGlobal,
+ ContentBlockingAllowListCache& aContentBlockingAllowList,
+ nsTArray<RefPtr<ClearDataMozPromise>>& aClearPromises);
+
+ // Whether a purge operation is currently in progress. This avoids running
+ // multiple purge operations at the same time.
+ bool mPurgeInProgress = false;
// Wraps nsIClearDataCallback in MozPromise.
class ClearDataCallback final : public nsIClearDataCallback {
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingRecord.cpp b/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingRecord.cpp
index 14ee178ae2..d4b33f9edb 100644
--- a/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingRecord.cpp
+++ b/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingRecord.cpp
@@ -12,13 +12,11 @@ namespace mozilla {
extern LazyLogModule gBounceTrackingProtectionLog;
-NS_IMPL_CYCLE_COLLECTION(BounceTrackingRecord);
-
void BounceTrackingRecord::SetInitialHost(const nsACString& aHost) {
mInitialHost = aHost;
}
-const nsACString& BounceTrackingRecord::GetInitialHost() {
+const nsACString& BounceTrackingRecord::GetInitialHost() const {
return mInitialHost;
}
@@ -26,7 +24,9 @@ void BounceTrackingRecord::SetFinalHost(const nsACString& aHost) {
mFinalHost = aHost;
}
-const nsACString& BounceTrackingRecord::GetFinalHost() { return mFinalHost; }
+const nsACString& BounceTrackingRecord::GetFinalHost() const {
+ return mFinalHost;
+}
void BounceTrackingRecord::AddBounceHost(const nsACString& aHost) {
mBounceHosts.Insert(aHost);
@@ -57,11 +57,12 @@ void BounceTrackingRecord::AddStorageAccessHost(const nsACString& aHost) {
mStorageAccessHosts.Insert(aHost);
}
-const nsTHashSet<nsCString>& BounceTrackingRecord::GetBounceHosts() {
+const nsTHashSet<nsCString>& BounceTrackingRecord::GetBounceHosts() const {
return mBounceHosts;
}
-const nsTHashSet<nsCString>& BounceTrackingRecord::GetStorageAccessHosts() {
+const nsTHashSet<nsCString>& BounceTrackingRecord::GetStorageAccessHosts()
+ const {
return mStorageAccessHosts;
}
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingRecord.h b/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingRecord.h
index d3e980d00b..73985a00a4 100644
--- a/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingRecord.h
+++ b/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingRecord.h
@@ -7,9 +7,7 @@
#ifndef mozilla_BounceTrackingRecord_h
#define mozilla_BounceTrackingRecord_h
-#include "nsISupports.h"
#include "nsStringFwd.h"
-#include "nsCycleCollectionParticipant.h"
#include "nsTHashSet.h"
namespace mozilla {
@@ -22,31 +20,26 @@ class CanonicalBrowsingContext;
// navigation.
class BounceTrackingRecord final {
public:
- NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(BounceTrackingRecord);
- NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(BounceTrackingRecord);
-
void SetInitialHost(const nsACString& aHost);
- const nsACString& GetInitialHost();
+ const nsACString& GetInitialHost() const;
void SetFinalHost(const nsACString& aHost);
- const nsACString& GetFinalHost();
+ const nsACString& GetFinalHost() const;
void AddBounceHost(const nsACString& aHost);
void AddStorageAccessHost(const nsACString& aHost);
- const nsTHashSet<nsCString>& GetBounceHosts();
+ const nsTHashSet<nsCString>& GetBounceHosts() const;
- const nsTHashSet<nsCString>& GetStorageAccessHosts();
+ const nsTHashSet<nsCString>& GetStorageAccessHosts() const;
// Create a string that describes this record. Used for logging.
nsCString Describe();
private:
- ~BounceTrackingRecord() = default;
-
// A site's host. The initiator site of the current extended navigation.
nsAutoCString mInitialHost;
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingState.cpp b/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingState.cpp
index c5abb8b8d7..b4af3daa07 100644
--- a/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingState.cpp
+++ b/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingState.cpp
@@ -143,10 +143,11 @@ nsresult BounceTrackingState::Init(
}
void BounceTrackingState::ResetBounceTrackingRecord() {
- mBounceTrackingRecord = nullptr;
+ mBounceTrackingRecord = Nothing();
}
-BounceTrackingRecord* BounceTrackingState::GetBounceTrackingRecord() {
+const Maybe<BounceTrackingRecord>&
+BounceTrackingState::GetBounceTrackingRecord() {
return mBounceTrackingRecord;
}
@@ -452,7 +453,7 @@ nsresult BounceTrackingState::OnStartNavigation(
// tracking record to a new bounce tracking record with initial host set to
// initialHost.
if (!mBounceTrackingRecord) {
- mBounceTrackingRecord = new BounceTrackingRecord();
+ mBounceTrackingRecord = Some(BounceTrackingRecord());
mBounceTrackingRecord->SetInitialHost(siteHost);
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
@@ -477,7 +478,7 @@ nsresult BounceTrackingState::OnStartNavigation(
NS_ENSURE_SUCCESS(rv, rv);
MOZ_ASSERT(!mBounceTrackingRecord);
- mBounceTrackingRecord = new BounceTrackingRecord();
+ mBounceTrackingRecord = Some(BounceTrackingRecord());
mBounceTrackingRecord->SetInitialHost(siteHost);
return NS_OK;
@@ -539,8 +540,12 @@ nsresult BounceTrackingState::OnResponseReceived(
("%s: Calling RecordStatefulBounces after timeout.", __FUNCTION__));
BounceTrackingState* bounceTrackingState = thisWeak;
- bounceTrackingState->mBounceTrackingProtection->RecordStatefulBounces(
- bounceTrackingState);
+ DebugOnly<nsresult> rv =
+ bounceTrackingState->mBounceTrackingProtection
+ ->RecordStatefulBounces(bounceTrackingState);
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "Running RecordStatefulBounces after a timeout failed.");
bounceTrackingState->mClientBounceDetectionTimeout = nullptr;
},
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingState.h b/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingState.h
index 70deee5abe..17d324bda9 100644
--- a/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingState.h
+++ b/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingState.h
@@ -7,6 +7,7 @@
#ifndef mozilla_BounceTrackingState_h
#define mozilla_BounceTrackingState_h
+#include "BounceTrackingRecord.h"
#include "mozilla/WeakPtr.h"
#include "mozilla/OriginAttributes.h"
#include "nsIPrincipal.h"
@@ -22,7 +23,6 @@ class nsIPrincipal;
namespace mozilla {
class BounceTrackingProtection;
-class BounceTrackingRecord;
namespace dom {
class CanonicalBrowsingContext;
@@ -55,7 +55,7 @@ class BounceTrackingState : public nsIWebProgressListener,
static void ResetAllForOriginAttributesPattern(
const OriginAttributesPattern& aPattern);
- BounceTrackingRecord* GetBounceTrackingRecord();
+ const Maybe<BounceTrackingRecord>& GetBounceTrackingRecord();
void ResetBounceTrackingRecord();
@@ -113,7 +113,7 @@ class BounceTrackingState : public nsIWebProgressListener,
// Record to keep track of extended navigation data. Reset on extended
// navigation end.
- RefPtr<BounceTrackingRecord> mBounceTrackingRecord;
+ Maybe<BounceTrackingRecord> mBounceTrackingRecord;
// Timer to wait to wait for a client redirect after a navigation ends.
RefPtr<nsITimer> mClientBounceDetectionTimeout;
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingStateGlobal.cpp b/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingStateGlobal.cpp
index 3481753431..b94c887c90 100644
--- a/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingStateGlobal.cpp
+++ b/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingStateGlobal.cpp
@@ -106,6 +106,11 @@ nsresult BounceTrackingStateGlobal::ClearByTimeRange(
NS_ENSURE_ARG_MIN(aFrom, 0);
NS_ENSURE_TRUE(!aTo || aTo.value() > aFrom, NS_ERROR_INVALID_ARG);
+ MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
+ ("%s: Clearing user activations by time range from %" PRIu64
+ " to %" PRIu64 " %s",
+ __FUNCTION__, aFrom, aTo.valueOr(0), Describe().get()));
+
// Clear in memory user activation data.
if (aEntryType.isNothing() ||
aEntryType.value() ==
@@ -113,10 +118,10 @@ nsresult BounceTrackingStateGlobal::ClearByTimeRange(
for (auto iter = mUserActivation.Iter(); !iter.Done(); iter.Next()) {
if (iter.Data() >= aFrom &&
(aTo.isNothing() || iter.Data() <= aTo.value())) {
- iter.Remove();
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
("%s: Remove user activation for %s", __FUNCTION__,
PromiseFlatCString(iter.Key()).get()));
+ iter.Remove();
}
}
}
@@ -128,10 +133,10 @@ nsresult BounceTrackingStateGlobal::ClearByTimeRange(
for (auto iter = mBounceTrackers.Iter(); !iter.Done(); iter.Next()) {
if (iter.Data() >= aFrom &&
(aTo.isNothing() || iter.Data() <= aTo.value())) {
- iter.Remove();
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
("%s: Remove bouncer tracker for %s", __FUNCTION__,
PromiseFlatCString(iter.Key()).get()));
+ iter.Remove();
}
}
}
@@ -187,4 +192,27 @@ nsresult BounceTrackingStateGlobal::RemoveBounceTrackers(
return NS_OK;
}
+// static
+nsCString BounceTrackingStateGlobal::DescribeMap(
+ const nsTHashMap<nsCStringHashKey, PRTime>& aMap) {
+ nsAutoCString mapStr;
+
+ for (auto iter = aMap.ConstIter(); !iter.Done(); iter.Next()) {
+ mapStr.Append(nsPrintfCString("{ %s: %" PRIu64 " }, ",
+ PromiseFlatCString(iter.Key()).get(),
+ iter.Data()));
+ }
+
+ return std::move(mapStr);
+}
+
+nsCString BounceTrackingStateGlobal::Describe() {
+ nsAutoCString originAttributeSuffix;
+ mOriginAttributes.CreateSuffix(originAttributeSuffix);
+ return nsPrintfCString(
+ "{ mOriginAttributes: %s, mUserActivation: %s, mBounceTrackers: %s }",
+ originAttributeSuffix.get(), DescribeMap(mUserActivation).get(),
+ DescribeMap(mBounceTrackers).get());
+}
+
} // namespace mozilla
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingStateGlobal.h b/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingStateGlobal.h
index 6680ceae6f..c8ed72c11f 100644
--- a/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingStateGlobal.h
+++ b/toolkit/components/antitracking/bouncetrackingprotection/BounceTrackingStateGlobal.h
@@ -37,6 +37,10 @@ class BounceTrackingStateGlobal final {
bool ShouldPersistToDisk() const { return !IsPrivateBrowsing(); }
+ const OriginAttributes& OriginAttributesRef() const {
+ return mOriginAttributes;
+ };
+
bool HasUserActivation(const nsACString& aSiteHost) const;
// Store a user interaction flag for the given host. This will remove the
@@ -79,6 +83,9 @@ class BounceTrackingStateGlobal final {
return mBounceTrackers;
}
+ // Create a string that describes this object. Used for logging.
+ nsCString Describe();
+
private:
~BounceTrackingStateGlobal() = default;
@@ -103,6 +110,10 @@ class BounceTrackingStateGlobal final {
// on the given site host performed an action that could indicate stateful
// bounce tracking took place.
nsTHashMap<nsCStringHashKey, PRTime> mBounceTrackers{};
+
+ // Helper to create a string representation of a siteHost -> timestamp map.
+ static nsCString DescribeMap(
+ const nsTHashMap<nsCStringHashKey, PRTime>& aMap);
};
} // namespace mozilla
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/nsIBounceTrackingProtection.idl b/toolkit/components/antitracking/bouncetrackingprotection/nsIBounceTrackingProtection.idl
index 9ade9cb0ea..1163492333 100644
--- a/toolkit/components/antitracking/bouncetrackingprotection/nsIBounceTrackingProtection.idl
+++ b/toolkit/components/antitracking/bouncetrackingprotection/nsIBounceTrackingProtection.idl
@@ -28,6 +28,10 @@ interface nsIBounceTrackingProtection : nsISupports {
[implicit_jscontext]
Promise testRunPurgeBounceTrackers();
+ // Clear expired user activation flags. Expiry is set via pref
+ // "privacy.bounceTrackingProtection.bounceTrackingActivationLifetimeSec".
+ void testClearExpiredUserActivations();
+
// Getters and setters for user activation and bounce tracker state.
// These are used for testing purposes only.
// State is keyed by OriginAttributes.
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_purge.js b/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_purge.js
index a8e98b80f0..eedd374197 100644
--- a/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_purge.js
+++ b/toolkit/components/antitracking/bouncetrackingprotection/test/browser/browser_bouncetracking_purge.js
@@ -119,3 +119,69 @@ add_task(async function test_purging_skip_open_tab_extra_window() {
bounceTrackingProtection.clearAll();
});
+
+add_task(async function test_purging_skip_content_blocking_allow_list() {
+ initBounceTrackerState();
+
+ await BrowserTestUtils.withNewTab("https://example.com", async browser => {
+ window.ContentBlockingAllowList.add(browser);
+ });
+
+ Assert.deepEqual(
+ await bounceTrackingProtection.testRunPurgeBounceTrackers(),
+ ["example.net"],
+ "Should only purge example.net. example.org is within the grace period, example.com is allow-listed."
+ );
+
+ info(
+ "Remove the allow-list entry for example.com and test that it gets purged now."
+ );
+
+ await BrowserTestUtils.withNewTab("https://example.com", async browser => {
+ window.ContentBlockingAllowList.remove(browser);
+ });
+ Assert.deepEqual(
+ await bounceTrackingProtection.testRunPurgeBounceTrackers(),
+ ["example.com"],
+ "example.com should have been purged now that it is no longer allow-listed."
+ );
+
+ bounceTrackingProtection.clearAll();
+});
+
+add_task(
+ async function test_purging_skip_content_blocking_allow_list_subdomain() {
+ initBounceTrackerState();
+
+ await BrowserTestUtils.withNewTab(
+ "https://test1.example.com",
+ async browser => {
+ window.ContentBlockingAllowList.add(browser);
+ }
+ );
+
+ Assert.deepEqual(
+ await bounceTrackingProtection.testRunPurgeBounceTrackers(),
+ ["example.net"],
+ "Should only purge example.net. example.org is within the grace period, example.com is allow-listed via test1.example.com."
+ );
+
+ info(
+ "Remove the allow-list entry for test1.example.com and test that it gets purged now."
+ );
+
+ await BrowserTestUtils.withNewTab(
+ "https://test1.example.com",
+ async browser => {
+ window.ContentBlockingAllowList.remove(browser);
+ }
+ );
+ Assert.deepEqual(
+ await bounceTrackingProtection.testRunPurgeBounceTrackers(),
+ ["example.com"],
+ "example.com should have been purged now that test1.example.com it is no longer allow-listed."
+ );
+
+ bounceTrackingProtection.clearAll();
+ }
+);
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/test/xpcshell/test_bouncetracking_clearExpiredUserActivation.js b/toolkit/components/antitracking/bouncetrackingprotection/test/xpcshell/test_bouncetracking_clearExpiredUserActivation.js
new file mode 100644
index 0000000000..28a1350b3e
--- /dev/null
+++ b/toolkit/components/antitracking/bouncetrackingprotection/test/xpcshell/test_bouncetracking_clearExpiredUserActivation.js
@@ -0,0 +1,85 @@
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Test that expired user activations are cleared by the the helper method
+ * testClearExpiredUserActivations.
+ */
+add_task(async function test() {
+ // Need a profile to data clearing calls.
+ do_get_profile();
+
+ let btp = Cc["@mozilla.org/bounce-tracking-protection;1"].getService(
+ Ci.nsIBounceTrackingProtection
+ );
+
+ // Reset global bounce tracking state.
+ btp.clearAll();
+
+ // Assert initial test state.
+ Assert.deepEqual(
+ btp.testGetBounceTrackerCandidateHosts({}),
+ [],
+ "No tracker candidates initially."
+ );
+ Assert.deepEqual(
+ btp.testGetUserActivationHosts({}),
+ [],
+ "No user activation hosts initially."
+ );
+
+ // Get the bounce tracking activation lifetime. The pref is in seconds, we
+ // need to convert it to microseconds, as the user activation timestamps are
+ // in microseconds (PRTime).
+ let bounceTrackingActivationLifetimeUSec =
+ 1000 *
+ 1000 *
+ Services.prefs.getIntPref(
+ "privacy.bounceTrackingProtection.bounceTrackingActivationLifetimeSec"
+ );
+
+ // Add some test data for user activation.
+ btp.testAddUserActivation({}, "not-expired1.com", Date.now() * 1000);
+ btp.testAddUserActivation(
+ {},
+ "not-expired2.com",
+ Date.now() * 1000 - bounceTrackingActivationLifetimeUSec / 2
+ );
+ btp.testAddUserActivation(
+ { privateBrowsingId: 1 },
+ "pbm-not-expired.com",
+ Date.now() * 1000
+ );
+ btp.testAddUserActivation(
+ {},
+ "expired1.com",
+ Date.now() * 1000 - bounceTrackingActivationLifetimeUSec * 2
+ );
+ btp.testAddUserActivation(
+ {},
+ "expired2.com",
+ Date.now() * 1000 - (bounceTrackingActivationLifetimeUSec + 1000 * 1000)
+ );
+ btp.testAddUserActivation({ privateBrowsingId: 1 }, "pbm-expired.com", 1);
+
+ // Clear expired user activations.
+ btp.testClearExpiredUserActivations();
+
+ // Assert that expired user activations have been cleared.
+ Assert.deepEqual(
+ btp.testGetUserActivationHosts({}).sort(),
+ ["not-expired1.com", "not-expired2.com"],
+ "Expired user activation flags have been cleared for normal browsing."
+ );
+
+ Assert.deepEqual(
+ btp.testGetUserActivationHosts({ privateBrowsingId: 1 }).sort(),
+ ["pbm-not-expired.com"],
+ "Expired user activation flags have been cleared for private browsing."
+ );
+
+ // Reset global bounce tracking state.
+ btp.clearAll();
+});
diff --git a/toolkit/components/antitracking/bouncetrackingprotection/test/xpcshell/xpcshell.toml b/toolkit/components/antitracking/bouncetrackingprotection/test/xpcshell/xpcshell.toml
index 16e270b85c..c3aeee502f 100644
--- a/toolkit/components/antitracking/bouncetrackingprotection/test/xpcshell/xpcshell.toml
+++ b/toolkit/components/antitracking/bouncetrackingprotection/test/xpcshell/xpcshell.toml
@@ -5,4 +5,6 @@ prefs = [
"privacy.bounceTrackingProtection.bounceTrackingPurgeTimerPeriodSec=0",
]
+["test_bouncetracking_clearExpiredUserActivation.js"]
+
["test_bouncetracking_purge.js"]
diff --git a/toolkit/components/antitracking/test/browser/3rdPartySVG.html b/toolkit/components/antitracking/test/browser/3rdPartySVG.html
index df791f355f..9ce6fc8ba1 100644
--- a/toolkit/components/antitracking/test/browser/3rdPartySVG.html
+++ b/toolkit/components/antitracking/test/browser/3rdPartySVG.html
@@ -11,7 +11,7 @@
<h1>3rd party content with an SVG image background</h1>
<script>
-onload = function(e) {
+onload = function() {
parent.postMessage({ type: "finish" }, "*");
};
diff --git a/toolkit/components/antitracking/test/browser/3rdPartyWorker.html b/toolkit/components/antitracking/test/browser/3rdPartyWorker.html
index e79a992660..9544bfed2b 100644
--- a/toolkit/components/antitracking/test/browser/3rdPartyWorker.html
+++ b/toolkit/components/antitracking/test/browser/3rdPartyWorker.html
@@ -20,7 +20,7 @@ function is(a, b, msg) {
}
function workerCode() {
- onmessage = e => {
+ onmessage = () => {
try {
indexedDB.open("test", "1");
postMessage(true);
diff --git a/toolkit/components/antitracking/test/browser/antitracking_head.js b/toolkit/components/antitracking/test/browser/antitracking_head.js
index 52871b60c2..fedad72e9b 100644
--- a/toolkit/components/antitracking/test/browser/antitracking_head.js
+++ b/toolkit/components/antitracking/test/browser/antitracking_head.js
@@ -356,7 +356,7 @@ this.AntiTracking = {
let cnt = 0;
return new Promise(resolve => {
- Services.obs.addObserver(function observer(subject, topic, data) {
+ Services.obs.addObserver(function observer(subject, topic) {
if (topic != targetTopic) {
return;
}
@@ -1174,8 +1174,7 @@ this.AntiTracking = {
let windowClosed = new content.Promise(resolve => {
Services.ww.registerNotification(function notification(
aSubject,
- aTopic,
- aData
+ aTopic
) {
if (aTopic == "domwindowclosed") {
Services.ww.unregisterNotification(notification);
@@ -1279,8 +1278,7 @@ this.AntiTracking = {
let windowClosed = new content.Promise(resolve => {
Services.ww.registerNotification(function notification(
aSubject,
- aTopic,
- aData
+ aTopic
) {
if (aTopic == "domwindowclosed") {
Services.ww.unregisterNotification(notification);
@@ -1366,12 +1364,12 @@ this.AntiTracking = {
function Listener() {}
Listener.prototype = {
- onStartRequest(request) {},
+ onStartRequest() {},
onDataAvailable(request, stream, off, cnt) {
// Consume the data to prevent hitting the assertion.
NetUtil.readInputStreamToString(stream, cnt);
},
- onStopRequest(request, st) {
+ onStopRequest(request) {
let status = request.QueryInterface(Ci.nsIHttpChannel).responseStatus;
if (status == 200) {
resolve();
diff --git a/toolkit/components/antitracking/test/browser/blobPartitionPage.html b/toolkit/components/antitracking/test/browser/blobPartitionPage.html
index d0dd156bc5..d066bb3603 100644
--- a/toolkit/components/antitracking/test/browser/blobPartitionPage.html
+++ b/toolkit/components/antitracking/test/browser/blobPartitionPage.html
@@ -17,7 +17,7 @@
.then(text => {
parent.postMessage(text, "*");
})
- .catch(error => {
+ .catch(() => {
parent.postMessage("error", "*");
});
};
diff --git a/toolkit/components/antitracking/test/browser/browser-blocking.toml b/toolkit/components/antitracking/test/browser/browser-blocking.toml
index 4a0aa8f44d..573b8b4d15 100644
--- a/toolkit/components/antitracking/test/browser/browser-blocking.toml
+++ b/toolkit/components/antitracking/test/browser/browser-blocking.toml
@@ -1,5 +1,9 @@
[DEFAULT]
-skip-if = ["os == 'linux' && (asan || tsan)"] # bug 1662229 - task exception
+skip-if = [
+ "os == 'linux' && os_version == '18.04' && asan", # bug 1662229 - task exception
+ "os == 'linux' && os_version == '18.04' && tsan", # bug 1662229 - task exception
+ "debug", # bug 1884982 - takes 20+ minutes to run on debug
+]
prefs = [
# Disable the Storage Access API prompts for all of the tests in this directory
"dom.storage_access.prompt.testing=true",
diff --git a/toolkit/components/antitracking/test/browser/browser_aboutblank.js b/toolkit/components/antitracking/test/browser/browser_aboutblank.js
index f80a948771..0fc86cc3d2 100644
--- a/toolkit/components/antitracking/test/browser/browser_aboutblank.js
+++ b/toolkit/components/antitracking/test/browser/browser_aboutblank.js
@@ -23,7 +23,7 @@ add_task(async function test_aboutblankInIframe() {
);
let browser = tab.linkedBrowser;
- await SpecialPowers.spawn(browser, [], async function (obj) {
+ await SpecialPowers.spawn(browser, [], async function () {
let ifr = content.document.createElement("iframe");
let loading = new content.Promise(resolve => {
ifr.onload = resolve;
@@ -32,7 +32,7 @@ add_task(async function test_aboutblankInIframe() {
content.document.body.appendChild(ifr);
await loading;
- await SpecialPowers.spawn(ifr, [], async function (obj) {
+ await SpecialPowers.spawn(ifr, [], async function () {
ok(
content.navigator.cookieEnabled,
"Cookie should be enabled in about blank"
diff --git a/toolkit/components/antitracking/test/browser/browser_addonHostPermissionIgnoredInTP.js b/toolkit/components/antitracking/test/browser/browser_addonHostPermissionIgnoredInTP.js
index 44664e239a..08613ca415 100644
--- a/toolkit/components/antitracking/test/browser/browser_addonHostPermissionIgnoredInTP.js
+++ b/toolkit/components/antitracking/test/browser/browser_addonHostPermissionIgnoredInTP.js
@@ -28,7 +28,7 @@ add_task(async function () {
let browser = tab.linkedBrowser;
info("Verify the number of script nodes found");
- await ContentTask.spawn(browser, [], async function (obj) {
+ await ContentTask.spawn(browser, [], async function () {
// Need to wait a bit for cross-process postMessage...
await ContentTaskUtils.waitForCondition(
() => content.document.documentElement.getAttribute("count") !== null,
diff --git a/toolkit/components/antitracking/test/browser/browser_allowListNotifications.js b/toolkit/components/antitracking/test/browser/browser_allowListNotifications.js
index 999c6f93a0..4bc2e3b1ae 100644
--- a/toolkit/components/antitracking/test/browser/browser_allowListNotifications.js
+++ b/toolkit/components/antitracking/test/browser/browser_allowListNotifications.js
@@ -134,7 +134,7 @@ add_task(async function () {
add_task(async function () {
info("Cleaning up.");
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_allowListNotifications_alwaysPartition.js b/toolkit/components/antitracking/test/browser/browser_allowListNotifications_alwaysPartition.js
index 0fd677e27f..62e7aa04e1 100644
--- a/toolkit/components/antitracking/test/browser/browser_allowListNotifications_alwaysPartition.js
+++ b/toolkit/components/antitracking/test/browser/browser_allowListNotifications_alwaysPartition.js
@@ -135,7 +135,7 @@ add_task(async function () {
add_task(async function () {
info("Cleaning up.");
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_allowListSeparationInPrivateAndNormalWindows.js b/toolkit/components/antitracking/test/browser/browser_allowListSeparationInPrivateAndNormalWindows.js
index 38eb9fc090..64eb39795a 100644
--- a/toolkit/components/antitracking/test/browser/browser_allowListSeparationInPrivateAndNormalWindows.js
+++ b/toolkit/components/antitracking/test/browser/browser_allowListSeparationInPrivateAndNormalWindows.js
@@ -39,7 +39,7 @@ AntiTracking.runTest(
// Cleanup callback
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_allowPermissionForTracker.js b/toolkit/components/antitracking/test/browser/browser_allowPermissionForTracker.js
index e628199ead..bec8da9ba5 100644
--- a/toolkit/components/antitracking/test/browser/browser_allowPermissionForTracker.js
+++ b/toolkit/components/antitracking/test/browser/browser_allowPermissionForTracker.js
@@ -32,6 +32,8 @@ AntiTracking._createTask({
allowList: false,
callback: async _ => {
document.cookie = "name=value";
+ // Assert isn't available in the webpage.
+ // eslint-disable-next-line mozilla/no-comparison-or-assignment-inside-ok
ok(document.cookie != "", "Nothing is blocked");
// requestStorageAccess should resolve
@@ -57,7 +59,7 @@ AntiTracking._createTask({
add_task(async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_backgroundImageAssertion.js b/toolkit/components/antitracking/test/browser/browser_backgroundImageAssertion.js
index 16eec0da9e..27fbdfe971 100644
--- a/toolkit/components/antitracking/test/browser/browser_backgroundImageAssertion.js
+++ b/toolkit/components/antitracking/test/browser/browser_backgroundImageAssertion.js
@@ -62,7 +62,7 @@ add_task(async function () {
add_task(async function () {
info("Cleaning up.");
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_blockingCookies.js b/toolkit/components/antitracking/test/browser/browser_blockingCookies.js
index dc22ad77b9..bedbccea83 100644
--- a/toolkit/components/antitracking/test/browser/browser_blockingCookies.js
+++ b/toolkit/components/antitracking/test/browser/browser_blockingCookies.js
@@ -66,7 +66,7 @@ AntiTracking.runTestInNormalAndPrivateMode(
// Cleanup callback
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
@@ -172,7 +172,7 @@ AntiTracking.runTestInNormalAndPrivateMode(
// Cleanup callback
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_blockingDOMCache.js b/toolkit/components/antitracking/test/browser/browser_blockingDOMCache.js
index a8353cc8f1..9bc418ac8f 100644
--- a/toolkit/components/antitracking/test/browser/browser_blockingDOMCache.js
+++ b/toolkit/components/antitracking/test/browser/browser_blockingDOMCache.js
@@ -24,7 +24,7 @@ AntiTracking.runTest(
},
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_blockingDOMCacheAlwaysPartition.js b/toolkit/components/antitracking/test/browser/browser_blockingDOMCacheAlwaysPartition.js
index 4cb90af8ad..dc50cb387e 100644
--- a/toolkit/components/antitracking/test/browser/browser_blockingDOMCacheAlwaysPartition.js
+++ b/toolkit/components/antitracking/test/browser/browser_blockingDOMCacheAlwaysPartition.js
@@ -42,7 +42,7 @@ AntiTracking.runTest(
},
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_blockingDOMCacheAlwaysPartitionSAA.js b/toolkit/components/antitracking/test/browser/browser_blockingDOMCacheAlwaysPartitionSAA.js
index bef53f2936..09321a3e67 100644
--- a/toolkit/components/antitracking/test/browser/browser_blockingDOMCacheAlwaysPartitionSAA.js
+++ b/toolkit/components/antitracking/test/browser/browser_blockingDOMCacheAlwaysPartitionSAA.js
@@ -66,7 +66,7 @@ AntiTracking.runTest(
},
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_blockingDOMCacheSAA.js b/toolkit/components/antitracking/test/browser/browser_blockingDOMCacheSAA.js
index 06526972dd..c689110ee3 100644
--- a/toolkit/components/antitracking/test/browser/browser_blockingDOMCacheSAA.js
+++ b/toolkit/components/antitracking/test/browser/browser_blockingDOMCacheSAA.js
@@ -68,7 +68,7 @@ AntiTracking.runTest(
},
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_blockingIndexedDb.js b/toolkit/components/antitracking/test/browser/browser_blockingIndexedDb.js
index 7fde2365bb..5879eac590 100644
--- a/toolkit/components/antitracking/test/browser/browser_blockingIndexedDb.js
+++ b/toolkit/components/antitracking/test/browser/browser_blockingIndexedDb.js
@@ -20,7 +20,7 @@ AntiTracking.runTestInNormalAndPrivateMode(
// Cleanup callback
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
@@ -91,7 +91,7 @@ AntiTracking.runTestInNormalAndPrivateMode(
// Cleanup callback
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_blockingIndexedDbInWorkers.js b/toolkit/components/antitracking/test/browser/browser_blockingIndexedDbInWorkers.js
index 55db3cc05b..7efb26d5d1 100644
--- a/toolkit/components/antitracking/test/browser/browser_blockingIndexedDbInWorkers.js
+++ b/toolkit/components/antitracking/test/browser/browser_blockingIndexedDbInWorkers.js
@@ -28,7 +28,7 @@ AntiTracking.runTestInNormalAndPrivateMode(
}
};
- worker.onerror = function (e) {
+ worker.onerror = function () {
reject();
};
});
@@ -57,14 +57,14 @@ AntiTracking.runTestInNormalAndPrivateMode(
}
};
- worker.onerror = function (e) {
+ worker.onerror = function () {
reject();
};
});
},
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_blockingIndexedDbInWorkers2.js b/toolkit/components/antitracking/test/browser/browser_blockingIndexedDbInWorkers2.js
index 02ce8dc588..10e6b8c248 100644
--- a/toolkit/components/antitracking/test/browser/browser_blockingIndexedDbInWorkers2.js
+++ b/toolkit/components/antitracking/test/browser/browser_blockingIndexedDbInWorkers2.js
@@ -37,7 +37,7 @@ AntiTracking.runTestInNormalAndPrivateMode(
}
};
- worker.onerror = function (e) {
+ worker.onerror = function () {
reject();
};
});
@@ -80,7 +80,7 @@ AntiTracking.runTestInNormalAndPrivateMode(
}
};
- worker.onerror = function (e) {
+ worker.onerror = function () {
reject();
};
});
@@ -112,7 +112,7 @@ AntiTracking.runTestInNormalAndPrivateMode(
}
};
- worker.onerror = function (e) {
+ worker.onerror = function () {
reject();
};
});
@@ -134,14 +134,14 @@ AntiTracking.runTestInNormalAndPrivateMode(
}
};
- worker.onerror = function (e) {
+ worker.onerror = function () {
reject();
};
});
},
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_blockingLocalStorage.js b/toolkit/components/antitracking/test/browser/browser_blockingLocalStorage.js
index 1362fc7519..c52b676642 100644
--- a/toolkit/components/antitracking/test/browser/browser_blockingLocalStorage.js
+++ b/toolkit/components/antitracking/test/browser/browser_blockingLocalStorage.js
@@ -17,7 +17,7 @@ AntiTracking.runTestInNormalAndPrivateMode(
},
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
@@ -83,7 +83,7 @@ AntiTracking.runTestInNormalAndPrivateMode(
},
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_blockingMessaging.js b/toolkit/components/antitracking/test/browser/browser_blockingMessaging.js
index 34af2b76ce..054616460f 100644
--- a/toolkit/components/antitracking/test/browser/browser_blockingMessaging.js
+++ b/toolkit/components/antitracking/test/browser/browser_blockingMessaging.js
@@ -21,7 +21,7 @@ AntiTracking.runTestInNormalAndPrivateMode(
},
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
@@ -58,7 +58,7 @@ AntiTracking.runTestInNormalAndPrivateMode(
}
};
- worker.onerror = function (e) {
+ worker.onerror = function () {
reject();
};
});
@@ -87,14 +87,14 @@ AntiTracking.runTestInNormalAndPrivateMode(
}
};
- worker.onerror = function (e) {
+ worker.onerror = function () {
reject();
};
});
},
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
@@ -159,7 +159,7 @@ AntiTracking.runTestInNormalAndPrivateMode(
},
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
@@ -206,7 +206,7 @@ AntiTracking.runTestInNormalAndPrivateMode(
}
};
- worker.onerror = function (e) {
+ worker.onerror = function () {
reject();
};
});
@@ -250,7 +250,7 @@ AntiTracking.runTestInNormalAndPrivateMode(
}
};
- worker.onerror = function (e) {
+ worker.onerror = function () {
reject();
};
});
@@ -282,7 +282,7 @@ AntiTracking.runTestInNormalAndPrivateMode(
}
};
- worker.onerror = function (e) {
+ worker.onerror = function () {
reject();
};
});
@@ -304,14 +304,14 @@ AntiTracking.runTestInNormalAndPrivateMode(
}
};
- worker.onerror = function (e) {
+ worker.onerror = function () {
reject();
};
});
},
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_blockingNoOpener.js b/toolkit/components/antitracking/test/browser/browser_blockingNoOpener.js
index ba75454e18..05bf0c8a26 100644
--- a/toolkit/components/antitracking/test/browser/browser_blockingNoOpener.js
+++ b/toolkit/components/antitracking/test/browser/browser_blockingNoOpener.js
@@ -30,7 +30,7 @@ AntiTracking.runTestInNormalAndPrivateMode(
},
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_blockingServiceWorkers.js b/toolkit/components/antitracking/test/browser/browser_blockingServiceWorkers.js
index c61f85d69f..e730f7df6f 100644
--- a/toolkit/components/antitracking/test/browser/browser_blockingServiceWorkers.js
+++ b/toolkit/components/antitracking/test/browser/browser_blockingServiceWorkers.js
@@ -16,7 +16,7 @@ AntiTracking.runTest(
null,
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_blockingServiceWorkersStorageAccessAPI.js b/toolkit/components/antitracking/test/browser/browser_blockingServiceWorkersStorageAccessAPI.js
index a7676bf939..f9d98b4c95 100644
--- a/toolkit/components/antitracking/test/browser/browser_blockingServiceWorkersStorageAccessAPI.js
+++ b/toolkit/components/antitracking/test/browser/browser_blockingServiceWorkersStorageAccessAPI.js
@@ -117,7 +117,7 @@ AntiTracking.runTest(
},
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_blockingSessionStorage.js b/toolkit/components/antitracking/test/browser/browser_blockingSessionStorage.js
index 25a926ed3f..1410b57f93 100644
--- a/toolkit/components/antitracking/test/browser/browser_blockingSessionStorage.js
+++ b/toolkit/components/antitracking/test/browser/browser_blockingSessionStorage.js
@@ -36,7 +36,7 @@ AntiTracking.runTestInNormalAndPrivateMode(
},
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
@@ -115,7 +115,7 @@ AntiTracking.runTestInNormalAndPrivateMode(
},
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_blockingSharedWorkers.js b/toolkit/components/antitracking/test/browser/browser_blockingSharedWorkers.js
index 0daffc4565..d88146e8ce 100644
--- a/toolkit/components/antitracking/test/browser/browser_blockingSharedWorkers.js
+++ b/toolkit/components/antitracking/test/browser/browser_blockingSharedWorkers.js
@@ -17,7 +17,7 @@ AntiTracking.runTestInNormalAndPrivateMode(
},
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
@@ -83,7 +83,7 @@ AntiTracking.runTestInNormalAndPrivateMode(
},
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_contentBlockingAllowListPrincipal.js b/toolkit/components/antitracking/test/browser/browser_contentBlockingAllowListPrincipal.js
index a9e2ac6fce..a94968b228 100644
--- a/toolkit/components/antitracking/test/browser/browser_contentBlockingAllowListPrincipal.js
+++ b/toolkit/components/antitracking/test/browser/browser_contentBlockingAllowListPrincipal.js
@@ -90,7 +90,7 @@ function createFrame(browser, src, id, sandboxAttr) {
);
}
-add_task(async setup => {
+add_task(async () => {
// Disable heuristics. We don't need them and if enabled the resulting
// telemetry can race with the telemetry in the next test.
// See Bug 1686836, Bug 1686894.
@@ -108,7 +108,7 @@ add_task(async setup => {
* Test that we get the correct allow list principal which matches the content
* principal for an https site.
*/
-add_task(async test_contentPrincipalHTTPS => {
+add_task(async () => {
await runTestInNormalAndPrivateMode("https://example.com", browser => {
checkAllowListPrincipal(browser, "content");
});
@@ -118,7 +118,7 @@ add_task(async test_contentPrincipalHTTPS => {
* Tests that the scheme of the allowlist principal is HTTPS, even though the
* site is loaded via HTTP.
*/
-add_task(async test_contentPrincipalHTTP => {
+add_task(async () => {
await runTestInNormalAndPrivateMode(
"http://example.net",
(browser, isPrivateBrowsing) => {
@@ -136,7 +136,7 @@ add_task(async test_contentPrincipalHTTP => {
* Tests that the allow list principal is a system principal for the preferences
* about site.
*/
-add_task(async test_systemPrincipal => {
+add_task(async () => {
await runTestInNormalAndPrivateMode("about:preferences", browser => {
checkAllowListPrincipal(browser, "system");
});
@@ -146,7 +146,7 @@ add_task(async test_systemPrincipal => {
* Tests that we get a valid content principal for top level sandboxed pages,
* and not the document principal which is a null principal.
*/
-add_task(async test_TopLevelSandbox => {
+add_task(async () => {
await runTestInNormalAndPrivateMode(
TEST_SANDBOX_URL,
(browser, isPrivateBrowsing) => {
@@ -168,7 +168,7 @@ add_task(async test_TopLevelSandbox => {
* Tests that we get a valid content principal for a new tab opened via
* window.open.
*/
-add_task(async test_windowOpen => {
+add_task(async () => {
await runTestInNormalAndPrivateMode("https://example.com", async browser => {
checkAllowListPrincipal(browser, "content");
@@ -195,7 +195,7 @@ add_task(async test_windowOpen => {
* Tests that we get a valid content principal for a new tab opened via
* window.open from a sandboxed iframe.
*/
-add_task(async test_windowOpenFromSandboxedFrame => {
+add_task(async () => {
await runTestInNormalAndPrivateMode(
"https://example.com",
async (browser, isPrivateBrowsing) => {
diff --git a/toolkit/components/antitracking/test/browser/browser_contentBlockingTelemetry.js b/toolkit/components/antitracking/test/browser/browser_contentBlockingTelemetry.js
index 1e006541c8..8e74c454f0 100644
--- a/toolkit/components/antitracking/test/browser/browser_contentBlockingTelemetry.js
+++ b/toolkit/components/antitracking/test/browser/browser_contentBlockingTelemetry.js
@@ -336,8 +336,7 @@ add_task(async function testTelemetryForUserInteractionHeuristic() {
let windowClosed = new content.Promise(resolve => {
Services.ww.registerNotification(function notification(
aSubject,
- aTopic,
- aData
+ aTopic
) {
// We need to check the document URI here as well for the same
// reason above.
diff --git a/toolkit/components/antitracking/test/browser/browser_cookieBetweenTabs.js b/toolkit/components/antitracking/test/browser/browser_cookieBetweenTabs.js
index 635f145d46..f6a9f7521d 100644
--- a/toolkit/components/antitracking/test/browser/browser_cookieBetweenTabs.js
+++ b/toolkit/components/antitracking/test/browser/browser_cookieBetweenTabs.js
@@ -52,7 +52,7 @@ add_task(async function () {
BrowserTestUtils.removeTab(tab2);
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_denyPermissionForTracker.js b/toolkit/components/antitracking/test/browser/browser_denyPermissionForTracker.js
index cd19fa4466..0c13bdf0fb 100644
--- a/toolkit/components/antitracking/test/browser/browser_denyPermissionForTracker.js
+++ b/toolkit/components/antitracking/test/browser/browser_denyPermissionForTracker.js
@@ -57,7 +57,7 @@ AntiTracking._createTask({
add_task(async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_doublyNestedTracker.js b/toolkit/components/antitracking/test/browser/browser_doublyNestedTracker.js
index c15e3abd5f..4f38b29a7d 100644
--- a/toolkit/components/antitracking/test/browser/browser_doublyNestedTracker.js
+++ b/toolkit/components/antitracking/test/browser/browser_doublyNestedTracker.js
@@ -123,7 +123,7 @@ add_task(async function () {
info("Cleaning up.");
SpecialPowers.clearUserPref("network.cookie.sameSite.laxByDefault");
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_existingCookiesForSubresources.js b/toolkit/components/antitracking/test/browser/browser_existingCookiesForSubresources.js
index 35e9dfb169..76b5602f7c 100644
--- a/toolkit/components/antitracking/test/browser/browser_existingCookiesForSubresources.js
+++ b/toolkit/components/antitracking/test/browser/browser_existingCookiesForSubresources.js
@@ -228,7 +228,7 @@ add_task(async function () {
add_task(async function () {
info("Cleaning up.");
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_firstPartyCookieRejectionHonoursAllowList.js b/toolkit/components/antitracking/test/browser/browser_firstPartyCookieRejectionHonoursAllowList.js
index d3d06d2950..6aaed74331 100644
--- a/toolkit/components/antitracking/test/browser/browser_firstPartyCookieRejectionHonoursAllowList.js
+++ b/toolkit/components/antitracking/test/browser/browser_firstPartyCookieRejectionHonoursAllowList.js
@@ -27,7 +27,7 @@ add_task(async function () {
// The previous function reloads the browser, so wait for it to load again!
await BrowserTestUtils.browserLoaded(browser);
- await SpecialPowers.spawn(browser, [], async function (obj) {
+ await SpecialPowers.spawn(browser, [], async function () {
await new content.Promise(async resolve => {
let document = content.document;
let window = document.defaultView;
@@ -70,7 +70,7 @@ add_task(async function () {
add_task(async function () {
info("Cleaning up.");
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_hasStorageAccess.js b/toolkit/components/antitracking/test/browser/browser_hasStorageAccess.js
index a2733f0a53..3d62306b7d 100644
--- a/toolkit/components/antitracking/test/browser/browser_hasStorageAccess.js
+++ b/toolkit/components/antitracking/test/browser/browser_hasStorageAccess.js
@@ -209,7 +209,7 @@ var testCases = [
add_task(async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_hasStorageAccess_alwaysPartition.js b/toolkit/components/antitracking/test/browser/browser_hasStorageAccess_alwaysPartition.js
index ea52ff2fad..aca766f673 100644
--- a/toolkit/components/antitracking/test/browser/browser_hasStorageAccess_alwaysPartition.js
+++ b/toolkit/components/antitracking/test/browser/browser_hasStorageAccess_alwaysPartition.js
@@ -218,7 +218,7 @@ var testCases = [
add_task(async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_localStorageEvents.js b/toolkit/components/antitracking/test/browser/browser_localStorageEvents.js
index b46cc60b91..b7abad2d94 100644
--- a/toolkit/components/antitracking/test/browser/browser_localStorageEvents.js
+++ b/toolkit/components/antitracking/test/browser/browser_localStorageEvents.js
@@ -92,7 +92,7 @@ add_task(async function testLocalStorageEventPropagation() {
info("Cleaning up.");
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
@@ -179,7 +179,7 @@ add_task(async function testBlockedLocalStorageEventPropagation() {
info("Cleaning up.");
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_onModifyRequestNotificationForTrackingResources.js b/toolkit/components/antitracking/test/browser/browser_onModifyRequestNotificationForTrackingResources.js
index 0674b87136..a05f1219ba 100644
--- a/toolkit/components/antitracking/test/browser/browser_onModifyRequestNotificationForTrackingResources.js
+++ b/toolkit/components/antitracking/test/browser/browser_onModifyRequestNotificationForTrackingResources.js
@@ -10,8 +10,8 @@
let gExpectedResourcesSeen = 0;
async function onModifyRequest() {
- return new Promise((resolve, reject) => {
- Services.obs.addObserver(function observer(subject, topic, data) {
+ return new Promise(resolve => {
+ Services.obs.addObserver(function observer(subject) {
let httpChannel = subject.QueryInterface(Ci.nsIHttpChannel);
let spec = httpChannel.URI.spec;
info("Observed channel for " + spec);
diff --git a/toolkit/components/antitracking/test/browser/browser_partitionedConsoleMessage.js b/toolkit/components/antitracking/test/browser/browser_partitionedConsoleMessage.js
index ce74076825..bb8cdce3da 100644
--- a/toolkit/components/antitracking/test/browser/browser_partitionedConsoleMessage.js
+++ b/toolkit/components/antitracking/test/browser/browser_partitionedConsoleMessage.js
@@ -73,7 +73,7 @@ add_task(async function runTest() {
info("Clean up");
BrowserTestUtils.removeTab(tab);
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_partitionedCookies.js b/toolkit/components/antitracking/test/browser/browser_partitionedCookies.js
index d2d1e87dd4..d2e88795b1 100644
--- a/toolkit/components/antitracking/test/browser/browser_partitionedCookies.js
+++ b/toolkit/components/antitracking/test/browser/browser_partitionedCookies.js
@@ -1,7 +1,9 @@
PartitionedStorageHelper.runTestInNormalAndPrivateMode(
"HTTP Cookies",
async (win3rdParty, win1stParty, allowed) => {
- await win3rdParty.fetch("cookies.sjs?3rd").then(r => r.text());
+ await win3rdParty
+ .fetch("cookies.sjs?3rd;Partitioned;Secure")
+ .then(r => r.text());
await win3rdParty
.fetch("cookies.sjs")
.then(r => r.text())
@@ -39,7 +41,7 @@ PartitionedStorageHelper.runTestInNormalAndPrivateMode(
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
@@ -49,7 +51,7 @@ PartitionedStorageHelper.runTestInNormalAndPrivateMode(
PartitionedStorageHelper.runTestInNormalAndPrivateMode(
"DOM Cookies",
async (win3rdParty, win1stParty, allowed) => {
- win3rdParty.document.cookie = "foo=3rd";
+ win3rdParty.document.cookie = "foo=3rd;Partitioned;Secure";
is(win3rdParty.document.cookie, "foo=3rd", "3rd party cookie set");
win1stParty.document.cookie = "foo=first";
@@ -72,7 +74,7 @@ PartitionedStorageHelper.runTestInNormalAndPrivateMode(
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
@@ -90,14 +92,14 @@ PartitionedStorageHelper.runPartitioningTestInNormalAndPrivateMode(
// addDataCallback
async (win, value) => {
- win.document.cookie = value;
+ win.document.cookie = value + ";Partitioned;Secure";
return true;
},
// cleanup
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
@@ -121,14 +123,16 @@ PartitionedStorageHelper.runPartitioningTestInNormalAndPrivateMode(
// addDataCallback
async (win, value) => {
- await win.fetch("cookies.sjs?" + value).then(r => r.text());
+ await win
+ .fetch("cookies.sjs?" + value + ";Partitioned;Secure")
+ .then(r => r.text());
return true;
},
// cleanup
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_partitionedDOMCache.js b/toolkit/components/antitracking/test/browser/browser_partitionedDOMCache.js
index 8f901e2977..920f0a1849 100644
--- a/toolkit/components/antitracking/test/browser/browser_partitionedDOMCache.js
+++ b/toolkit/components/antitracking/test/browser/browser_partitionedDOMCache.js
@@ -25,7 +25,7 @@ PartitionedStorageHelper.runTest(
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
@@ -38,7 +38,7 @@ PartitionedStorageHelper.runTest(
PartitionedStorageHelper.runTest(
"DOMCache",
- async (win3rdParty, win1stParty, allowed) => {
+ async (win3rdParty, win1stParty) => {
await win1stParty.caches.open("wow").then(
async cache => {
ok(true, "DOM Cache should be available");
@@ -62,7 +62,7 @@ PartitionedStorageHelper.runTest(
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
@@ -76,9 +76,9 @@ PartitionedStorageHelper.runTest(
// Test that DOM cache is also available in PBM.
PartitionedStorageHelper.runTest(
"DOMCache",
- async (win3rdParty, win1stParty, allowed) => {
+ async (win3rdParty, win1stParty) => {
await win1stParty.caches.open("wow").then(
- async cache => {
+ async () => {
ok(true, "DOM Cache should be available in PBM");
},
_ => {
@@ -87,7 +87,7 @@ PartitionedStorageHelper.runTest(
);
await win3rdParty.caches.open("wow").then(
- async cache => {
+ async () => {
ok(true, "DOM Cache should be available in PBM");
},
_ => {
@@ -98,7 +98,7 @@ PartitionedStorageHelper.runTest(
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_partitionedIndexedDB.js b/toolkit/components/antitracking/test/browser/browser_partitionedIndexedDB.js
index 70dd0b03db..683f90e835 100644
--- a/toolkit/components/antitracking/test/browser/browser_partitionedIndexedDB.js
+++ b/toolkit/components/antitracking/test/browser/browser_partitionedIndexedDB.js
@@ -1,6 +1,6 @@
PartitionedStorageHelper.runTest(
"IndexedDB",
- async (win3rdParty, win1stParty, allowed) => {
+ async (win3rdParty, win1stParty) => {
await new Promise(resolve => {
let a = win1stParty.indexedDB.open("test", 1);
ok(!!a, "IDB should not be blocked in 1st party contexts");
@@ -34,7 +34,7 @@ PartitionedStorageHelper.runTest(
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
@@ -87,7 +87,7 @@ PartitionedStorageHelper.runPartitioningTest(
// cleanup
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_partitionedLocalStorage.js b/toolkit/components/antitracking/test/browser/browser_partitionedLocalStorage.js
index fe45970132..583f4bacdc 100644
--- a/toolkit/components/antitracking/test/browser/browser_partitionedLocalStorage.js
+++ b/toolkit/components/antitracking/test/browser/browser_partitionedLocalStorage.js
@@ -74,7 +74,7 @@ AntiTracking.runTestInNormalAndPrivateMode(
},
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
@@ -107,7 +107,7 @@ PartitionedStorageHelper.runPartitioningTestInNormalAndPrivateMode(
// cleanup
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_partitionedLocalStorage_events.js b/toolkit/components/antitracking/test/browser/browser_partitionedLocalStorage_events.js
index 14ca62d062..afbcc79022 100644
--- a/toolkit/components/antitracking/test/browser/browser_partitionedLocalStorage_events.js
+++ b/toolkit/components/antitracking/test/browser/browser_partitionedLocalStorage_events.js
@@ -999,7 +999,7 @@ function runAllTests(prefValue) {
// Cleanup data.
add_task(async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_partitionedLockManager.js b/toolkit/components/antitracking/test/browser/browser_partitionedLockManager.js
index 21c3c9637d..342a68bf31 100644
--- a/toolkit/components/antitracking/test/browser/browser_partitionedLockManager.js
+++ b/toolkit/components/antitracking/test/browser/browser_partitionedLockManager.js
@@ -2,7 +2,7 @@
PartitionedStorageHelper.runTest(
"LockManager works in both first and third party contexts",
- async (win3rdParty, win1stParty, allowed) => {
+ async (win3rdParty, win1stParty) => {
let locks = [];
ok(win1stParty.isSecureContext, "1st party is in a secure context");
ok(win3rdParty.isSecureContext, "3rd party is in a secure context");
@@ -20,7 +20,7 @@ PartitionedStorageHelper.runTest(
},
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_partitionedMessaging.js b/toolkit/components/antitracking/test/browser/browser_partitionedMessaging.js
index 683b1cc874..d08c1f8b7a 100644
--- a/toolkit/components/antitracking/test/browser/browser_partitionedMessaging.js
+++ b/toolkit/components/antitracking/test/browser/browser_partitionedMessaging.js
@@ -1,6 +1,6 @@
PartitionedStorageHelper.runTestInNormalAndPrivateMode(
"BroadcastChannel",
- async (win3rdParty, win1stParty, allowed) => {
+ async (win3rdParty, win1stParty) => {
let a = new win3rdParty.BroadcastChannel("hello");
ok(!!a, "BroadcastChannel should be created by 3rd party iframe");
@@ -12,7 +12,7 @@ PartitionedStorageHelper.runTestInNormalAndPrivateMode(
},
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_partitionedServiceWorkers.js b/toolkit/components/antitracking/test/browser/browser_partitionedServiceWorkers.js
index 5fca844dfe..bb9baa460f 100644
--- a/toolkit/components/antitracking/test/browser/browser_partitionedServiceWorkers.js
+++ b/toolkit/components/antitracking/test/browser/browser_partitionedServiceWorkers.js
@@ -25,7 +25,7 @@ PartitionedStorageHelper.runTest(
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
@@ -42,7 +42,7 @@ PartitionedStorageHelper.runTest(
PartitionedStorageHelper.runTest(
"ServiceWorkers - enable partitioning",
- async (win3rdParty, win1stParty, allowed) => {
+ async (win3rdParty, win1stParty) => {
// Partitioned serviceWorkers are enabled in third-party context.
await win3rdParty.navigator.serviceWorker.register("empty.js").then(
_ => {
@@ -71,7 +71,7 @@ PartitionedStorageHelper.runTest(
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
@@ -88,7 +88,7 @@ PartitionedStorageHelper.runTest(
PartitionedStorageHelper.runTest(
"ServiceWorkers - MatchAll",
- async (win3rdParty, win1stParty, allowed) => {
+ async (win3rdParty, win1stParty) => {
if (!win1stParty.sw) {
win1stParty.sw = await registerServiceWorker(win1stParty, "matchAll.js");
}
@@ -113,7 +113,7 @@ PartitionedStorageHelper.runTest(
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
@@ -130,7 +130,7 @@ PartitionedStorageHelper.runTest(
PartitionedStorageHelper.runTest(
"ServiceWorkers - Partition ScriptContext",
- async (win3rdParty, win1stParty, allowed) => {
+ async (win3rdParty, win1stParty) => {
// Register service worker for the first-party window.
if (!win1stParty.sw) {
win1stParty.sw = await registerServiceWorker(
@@ -197,7 +197,7 @@ PartitionedStorageHelper.runTest(
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
@@ -214,7 +214,7 @@ PartitionedStorageHelper.runTest(
PartitionedStorageHelper.runTest(
"ServiceWorkers - Partition DOM Cache",
- async (win3rdParty, win1stParty, allowed) => {
+ async (win3rdParty, win1stParty) => {
// Register service worker for the first-party window.
if (!win1stParty.sw) {
win1stParty.sw = await registerServiceWorker(
@@ -299,7 +299,7 @@ PartitionedStorageHelper.runTest(
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
@@ -316,7 +316,7 @@ PartitionedStorageHelper.runTest(
PartitionedStorageHelper.runTest(
"ServiceWorkers - Partition IndexedDB",
- async (win3rdParty, win1stParty, allowed) => {
+ async (win3rdParty, win1stParty) => {
// Register service worker for the first-party window.
if (!win1stParty.sw) {
win1stParty.sw = await registerServiceWorker(
@@ -383,7 +383,7 @@ PartitionedStorageHelper.runTest(
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
@@ -400,7 +400,7 @@ PartitionedStorageHelper.runTest(
PartitionedStorageHelper.runTest(
"ServiceWorkers - Partition Intercept",
- async (win3rdParty, win1stParty, allowed) => {
+ async (win3rdParty, win1stParty) => {
// Register service worker for the first-party window.
if (!win1stParty.sw) {
win1stParty.sw = await registerServiceWorker(
@@ -470,7 +470,7 @@ PartitionedStorageHelper.runTest(
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
@@ -547,7 +547,7 @@ PartitionedStorageHelper.runTest(
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
@@ -594,7 +594,7 @@ PartitionedStorageHelper.runTest(
// Post a message to the dedicated worker and wait until the message circles
// back.
await new Promise(resolve => {
- thirdPartyWorker.port.onmessage = msg => {
+ thirdPartyWorker.port.onmessage = () => {
resolve();
};
thirdPartyWorker.onerror = _ => {
@@ -609,7 +609,7 @@ PartitionedStorageHelper.runTest(
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
@@ -626,7 +626,7 @@ PartitionedStorageHelper.runTest(
PartitionedStorageHelper.runTest(
"ServiceWorkers - Private Browsing with partitioning disabled",
- async (win3rdParty, win1stParty, allowed) => {
+ async (win3rdParty, win1stParty) => {
// Partitioned serviceWorkers are disabled in third-party context.
ok(
!win3rdParty.navigator.serviceWorker,
@@ -640,7 +640,7 @@ PartitionedStorageHelper.runTest(
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
@@ -661,7 +661,7 @@ PartitionedStorageHelper.runTest(
PartitionedStorageHelper.runTest(
"ServiceWorkers - Private Browsing with partitioning enabled",
- async (win3rdParty, win1stParty, allowed) => {
+ async (win3rdParty, win1stParty) => {
// Partitioned serviceWorkers are disabled in third-party context.
ok(
!win3rdParty.navigator.serviceWorker,
@@ -675,7 +675,7 @@ PartitionedStorageHelper.runTest(
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_partitionedSharedWorkers.js b/toolkit/components/antitracking/test/browser/browser_partitionedSharedWorkers.js
index 97c58c5217..10f69a51df 100644
--- a/toolkit/components/antitracking/test/browser/browser_partitionedSharedWorkers.js
+++ b/toolkit/components/antitracking/test/browser/browser_partitionedSharedWorkers.js
@@ -1,6 +1,6 @@
PartitionedStorageHelper.runTestInNormalAndPrivateMode(
"SharedWorkers",
- async (win3rdParty, win1stParty, allowed) => {
+ async (win3rdParty, win1stParty) => {
// This test fails if run with an HTTPS 3rd-party URL because the shared worker
// which would start from the window opened from 3rdPartyStorage.html will become
// secure context and per step 11.4.3 of
@@ -40,7 +40,7 @@ PartitionedStorageHelper.runTestInNormalAndPrivateMode(
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_permissionInNormalWindows.js b/toolkit/components/antitracking/test/browser/browser_permissionInNormalWindows.js
index 69c5902a91..c8b4ac5dcb 100644
--- a/toolkit/components/antitracking/test/browser/browser_permissionInNormalWindows.js
+++ b/toolkit/components/antitracking/test/browser/browser_permissionInNormalWindows.js
@@ -1,3 +1,6 @@
+// We're using custom message passing so don't have access to Assert.foo
+// everywhere. Disable the linter:
+/* eslint-disable mozilla/no-comparison-or-assignment-inside-ok */
AntiTracking.runTest(
"Test whether we receive any persistent permissions in normal windows",
// Blocking callback
@@ -94,7 +97,7 @@ AntiTracking.runTest(
// Cleanup callback
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_permissionInNormalWindows_alwaysPartition.js b/toolkit/components/antitracking/test/browser/browser_permissionInNormalWindows_alwaysPartition.js
index 99c40bb0de..e2eaa6831a 100644
--- a/toolkit/components/antitracking/test/browser/browser_permissionInNormalWindows_alwaysPartition.js
+++ b/toolkit/components/antitracking/test/browser/browser_permissionInNormalWindows_alwaysPartition.js
@@ -1,3 +1,7 @@
+// We're using custom message passing so don't have access to Assert.foo
+// everywhere. Disable the linter:
+/* eslint-disable mozilla/no-comparison-or-assignment-inside-ok */
+
AntiTracking.runTest(
"Test whether we receive any persistent permissions in normal windows",
// Blocking callback
@@ -94,7 +98,7 @@ AntiTracking.runTest(
// Cleanup callback
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_permissionInPrivateWindows.js b/toolkit/components/antitracking/test/browser/browser_permissionInPrivateWindows.js
index 84f8d82420..3786634cc2 100644
--- a/toolkit/components/antitracking/test/browser/browser_permissionInPrivateWindows.js
+++ b/toolkit/components/antitracking/test/browser/browser_permissionInPrivateWindows.js
@@ -35,7 +35,7 @@ AntiTracking.runTest(
// Cleanup callback
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_permissionInPrivateWindows_alwaysPartition.js b/toolkit/components/antitracking/test/browser/browser_permissionInPrivateWindows_alwaysPartition.js
index 6ebcfae5a2..352dbae11d 100644
--- a/toolkit/components/antitracking/test/browser/browser_permissionInPrivateWindows_alwaysPartition.js
+++ b/toolkit/components/antitracking/test/browser/browser_permissionInPrivateWindows_alwaysPartition.js
@@ -35,7 +35,7 @@ AntiTracking.runTest(
// Cleanup callback
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_permissionPropagation.js b/toolkit/components/antitracking/test/browser/browser_permissionPropagation.js
index 73dc9e7145..fb869087cd 100644
--- a/toolkit/components/antitracking/test/browser/browser_permissionPropagation.js
+++ b/toolkit/components/antitracking/test/browser/browser_permissionPropagation.js
@@ -229,7 +229,7 @@ add_task(async function testPermissionGrantedOn3rdParty() {
let browser4 = gBrowser.getBrowserForTab(tab4);
info("Grant storage permission to the first iframe in the first tab");
- await SpecialPowers.spawn(browser1, [page, msg], async function (page, msg) {
+ await SpecialPowers.spawn(browser1, [page, msg], async function () {
await new content.Promise(resolve => {
content.addEventListener("message", function msg(event) {
if (event.data.type == "finish") {
@@ -289,7 +289,7 @@ add_task(async function testPermissionGrantedOn3rdParty() {
add_task(async function () {
info("Cleaning up.");
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
@@ -419,7 +419,7 @@ add_task(async function testPermissionGrantedOnFirstParty() {
add_task(async function () {
info("Cleaning up.");
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_referrerDefaultPolicy.js b/toolkit/components/antitracking/test/browser/browser_referrerDefaultPolicy.js
index e9030b98e4..7e5bba37bb 100644
--- a/toolkit/components/antitracking/test/browser/browser_referrerDefaultPolicy.js
+++ b/toolkit/components/antitracking/test/browser/browser_referrerDefaultPolicy.js
@@ -627,7 +627,7 @@ add_task(async function () {
add_task(async function () {
info("Cleaning up.");
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_script.js b/toolkit/components/antitracking/test/browser/browser_script.js
index ef6c7f67c6..952e26c1d4 100644
--- a/toolkit/components/antitracking/test/browser/browser_script.js
+++ b/toolkit/components/antitracking/test/browser/browser_script.js
@@ -217,7 +217,7 @@ add_task(async function () {
add_task(async function () {
info("Cleaning up.");
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_siteSpecificWorkArounds.js b/toolkit/components/antitracking/test/browser/browser_siteSpecificWorkArounds.js
index b35fbea8c7..89eeeb089e 100644
--- a/toolkit/components/antitracking/test/browser/browser_siteSpecificWorkArounds.js
+++ b/toolkit/components/antitracking/test/browser/browser_siteSpecificWorkArounds.js
@@ -30,7 +30,7 @@ AntiTracking.runTest(
},
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
@@ -74,7 +74,7 @@ AntiTracking.runTest(
},
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
@@ -108,7 +108,7 @@ AntiTracking.runTest(
},
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
@@ -137,7 +137,7 @@ AntiTracking.runTest(
},
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_socialtracking_save_image.js b/toolkit/components/antitracking/test/browser/browser_socialtracking_save_image.js
index e7389b1456..1c74adc0a3 100644
--- a/toolkit/components/antitracking/test/browser/browser_socialtracking_save_image.js
+++ b/toolkit/components/antitracking/test/browser/browser_socialtracking_save_image.js
@@ -13,7 +13,7 @@ const TEST_IMAGE_URL =
"http://social-tracking.example.org/browser/toolkit/components/antitracking/test/browser/raptor.jpg";
let MockFilePicker = SpecialPowers.MockFilePicker;
-MockFilePicker.init(window);
+MockFilePicker.init(window.browsingContext);
const tempDir = createTemporarySaveDirectory();
MockFilePicker.displayDirectory = tempDir;
diff --git a/toolkit/components/antitracking/test/browser/browser_staticPartition_cache.js b/toolkit/components/antitracking/test/browser/browser_staticPartition_cache.js
index 4d3a72d7a7..9afe3180fb 100644
--- a/toolkit/components/antitracking/test/browser/browser_staticPartition_cache.js
+++ b/toolkit/components/antitracking/test/browser/browser_staticPartition_cache.js
@@ -21,7 +21,7 @@ async function checkCache(suffixes, originAttributes) {
const data = await new Promise(resolve => {
let cacheEntries = [];
let cacheVisitor = {
- onCacheStorageInfo(num, consumption) {},
+ onCacheStorageInfo() {},
onCacheEntryInfo(uri, idEnhance) {
cacheEntries.push({ uri, idEnhance });
},
diff --git a/toolkit/components/antitracking/test/browser/browser_staticPartition_network.js b/toolkit/components/antitracking/test/browser/browser_staticPartition_network.js
index a28f7d5adc..53e410bb03 100644
--- a/toolkit/components/antitracking/test/browser/browser_staticPartition_network.js
+++ b/toolkit/components/antitracking/test/browser/browser_staticPartition_network.js
@@ -109,7 +109,7 @@ add_task(async function () {
add_task(async function () {
info("Cleaning up.");
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_staticPartition_saveAs.js b/toolkit/components/antitracking/test/browser/browser_staticPartition_saveAs.js
index 22db484e16..daf3234858 100644
--- a/toolkit/components/antitracking/test/browser/browser_staticPartition_saveAs.js
+++ b/toolkit/components/antitracking/test/browser/browser_staticPartition_saveAs.js
@@ -16,7 +16,7 @@ const TEST_VIDEO_URL = TEST_DOMAIN + TEST_PATH + "file_saveAsVideo.sjs";
const TEST_PAGEINFO_URL = TEST_DOMAIN + TEST_PATH + "file_saveAsPageInfo.html";
let MockFilePicker = SpecialPowers.MockFilePicker;
-MockFilePicker.init(window);
+MockFilePicker.init(window.browsingContext);
const tempDir = createTemporarySaveDirectory();
MockFilePicker.displayDirectory = tempDir;
@@ -102,6 +102,7 @@ add_task(async function testContextMenuSaveImage() {
set: [
["privacy.partition.network_state", networkIsolation],
["privacy.dynamic_firstparty.use_site", partitionPerSite],
+ ["dom.block_download_insecure", false],
],
});
@@ -197,6 +198,7 @@ add_task(async function testContextMenuSaveVideo() {
set: [
["privacy.partition.network_state", networkIsolation],
["privacy.dynamic_firstparty.use_site", partitionPerSite],
+ ["dom.block_download_insecure", false],
],
});
@@ -370,7 +372,7 @@ add_task(async function testSavePageInOfflineMode() {
// Clean up the cache count on the server side.
await fetch(`${TEST_IMAGE_URL}?result`);
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_staticPartition_tls_session.js b/toolkit/components/antitracking/test/browser/browser_staticPartition_tls_session.js
index e131f71169..b15c9d42e0 100644
--- a/toolkit/components/antitracking/test/browser/browser_staticPartition_tls_session.js
+++ b/toolkit/components/antitracking/test/browser/browser_staticPartition_tls_session.js
@@ -29,7 +29,7 @@ async function waitForLoad(url) {
return new Promise(resolve => {
const TOPIC = "http-on-examine-response";
- function observer(subject, topic, data) {
+ function observer(subject, topic) {
if (topic != TOPIC) {
return;
}
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccessDoorHanger.js b/toolkit/components/antitracking/test/browser/browser_storageAccessDoorHanger.js
index c922234ed2..715064aa43 100644
--- a/toolkit/components/antitracking/test/browser/browser_storageAccessDoorHanger.js
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessDoorHanger.js
@@ -305,7 +305,7 @@ async function cleanUp() {
info("Cleaning up.");
SpecialPowers.clearUserPref("network.cookie.sameSite.laxByDefault");
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccessPrivilegeAPI.js b/toolkit/components/antitracking/test/browser/browser_storageAccessPrivilegeAPI.js
index 9594fdf270..df3602710a 100644
--- a/toolkit/components/antitracking/test/browser/browser_storageAccessPrivilegeAPI.js
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessPrivilegeAPI.js
@@ -62,7 +62,7 @@ function runScriptInSubFrame(browser, id, script) {
}
function waitStoragePermission(trackingOrigin) {
- return TestUtils.topicObserved("perm-changed", (aSubject, aData) => {
+ return TestUtils.topicObserved("perm-changed", aSubject => {
let permission = aSubject.QueryInterface(Ci.nsIPermission);
let uri = Services.io.newURI(TEST_DOMAIN);
return (
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccessPromiseRejectHandlerUserInteraction.js b/toolkit/components/antitracking/test/browser/browser_storageAccessPromiseRejectHandlerUserInteraction.js
index 835c02e262..f95d760be2 100644
--- a/toolkit/components/antitracking/test/browser/browser_storageAccessPromiseRejectHandlerUserInteraction.js
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessPromiseRejectHandlerUserInteraction.js
@@ -17,7 +17,7 @@ AntiTracking.runTest(
// cleanup function
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccessPromiseRejectHandlerUserInteraction_alwaysPartition.js b/toolkit/components/antitracking/test/browser/browser_storageAccessPromiseRejectHandlerUserInteraction_alwaysPartition.js
index 21bf8b7639..d1006d2328 100644
--- a/toolkit/components/antitracking/test/browser/browser_storageAccessPromiseRejectHandlerUserInteraction_alwaysPartition.js
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessPromiseRejectHandlerUserInteraction_alwaysPartition.js
@@ -17,7 +17,7 @@ AntiTracking.runTest(
// cleanup function
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccessPromiseResolveHandlerUserInteraction.js b/toolkit/components/antitracking/test/browser/browser_storageAccessPromiseResolveHandlerUserInteraction.js
index 6db7c4c241..3f44dd9868 100644
--- a/toolkit/components/antitracking/test/browser/browser_storageAccessPromiseResolveHandlerUserInteraction.js
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessPromiseResolveHandlerUserInteraction.js
@@ -26,7 +26,7 @@ AntiTracking.runTest(
// cleanup function
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccessRemovalNavigateSubframe.js b/toolkit/components/antitracking/test/browser/browser_storageAccessRemovalNavigateSubframe.js
index f658dde790..c3f8b68ab1 100644
--- a/toolkit/components/antitracking/test/browser/browser_storageAccessRemovalNavigateSubframe.js
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessRemovalNavigateSubframe.js
@@ -19,7 +19,7 @@ AntiTracking.runTest(
// cleanup function
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccessRemovalNavigateSubframe_alwaysPartition.js b/toolkit/components/antitracking/test/browser/browser_storageAccessRemovalNavigateSubframe_alwaysPartition.js
index b4355f2a24..1157865cd4 100644
--- a/toolkit/components/antitracking/test/browser/browser_storageAccessRemovalNavigateSubframe_alwaysPartition.js
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessRemovalNavigateSubframe_alwaysPartition.js
@@ -19,7 +19,7 @@ AntiTracking.runTest(
// cleanup function
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccessRemovalNavigateTopframe.js b/toolkit/components/antitracking/test/browser/browser_storageAccessRemovalNavigateTopframe.js
index fe79a8d935..06faf41f26 100644
--- a/toolkit/components/antitracking/test/browser/browser_storageAccessRemovalNavigateTopframe.js
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessRemovalNavigateTopframe.js
@@ -19,7 +19,7 @@ AntiTracking.runTest(
// cleanup function
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccessRemovalNavigateTopframe_alwaysPartition.js b/toolkit/components/antitracking/test/browser/browser_storageAccessRemovalNavigateTopframe_alwaysPartition.js
index 63a0480e83..1cde2c8df3 100644
--- a/toolkit/components/antitracking/test/browser/browser_storageAccessRemovalNavigateTopframe_alwaysPartition.js
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessRemovalNavigateTopframe_alwaysPartition.js
@@ -19,7 +19,7 @@ AntiTracking.runTest(
// cleanup function
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccessSandboxed.js b/toolkit/components/antitracking/test/browser/browser_storageAccessSandboxed.js
index 85c0d60955..b2aa623d96 100644
--- a/toolkit/components/antitracking/test/browser/browser_storageAccessSandboxed.js
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessSandboxed.js
@@ -22,7 +22,7 @@ AntiTracking.runTest(
Services.io.newURI(TEST_3RD_PARTY_DOMAIN).host,
true,
Ci.nsIClearDataService.CLEAR_PERMISSIONS,
- value => resolve()
+ () => resolve()
);
});
},
@@ -206,7 +206,7 @@ AntiTracking.runTest(
// cleanup function
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
@@ -239,7 +239,7 @@ AntiTracking.runTest(
Services.io.newURI(TEST_3RD_PARTY_DOMAIN).host,
true,
Ci.nsIClearDataService.CLEAR_PERMISSIONS,
- value => resolve()
+ () => resolve()
);
});
},
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccessSandboxed_alwaysPartition.js b/toolkit/components/antitracking/test/browser/browser_storageAccessSandboxed_alwaysPartition.js
index 1b7f2cdf37..97c4851128 100644
--- a/toolkit/components/antitracking/test/browser/browser_storageAccessSandboxed_alwaysPartition.js
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessSandboxed_alwaysPartition.js
@@ -22,7 +22,7 @@ AntiTracking.runTest(
Services.io.newURI(TEST_3RD_PARTY_DOMAIN).host,
true,
Ci.nsIClearDataService.CLEAR_PERMISSIONS,
- value => resolve()
+ () => resolve()
);
});
},
@@ -205,7 +205,7 @@ AntiTracking.runTest(
// cleanup function
async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
@@ -238,7 +238,7 @@ AntiTracking.runTest(
Services.io.newURI(TEST_3RD_PARTY_DOMAIN).host,
true,
Ci.nsIClearDataService.CLEAR_PERMISSIONS,
- value => resolve()
+ () => resolve()
);
});
},
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccessThirdPartyChecks.js b/toolkit/components/antitracking/test/browser/browser_storageAccessThirdPartyChecks.js
index 8fd60eb2dc..4793ed9325 100644
--- a/toolkit/components/antitracking/test/browser/browser_storageAccessThirdPartyChecks.js
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessThirdPartyChecks.js
@@ -54,7 +54,7 @@ AntiTracking._createTask({
add_task(async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
@@ -85,7 +85,7 @@ AntiTracking._createTask({
add_task(async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
@@ -116,7 +116,7 @@ AntiTracking._createTask({
add_task(async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccessThirdPartyChecks_alwaysPartition.js b/toolkit/components/antitracking/test/browser/browser_storageAccessThirdPartyChecks_alwaysPartition.js
index bf66b515c4..f31c7893d0 100644
--- a/toolkit/components/antitracking/test/browser/browser_storageAccessThirdPartyChecks_alwaysPartition.js
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessThirdPartyChecks_alwaysPartition.js
@@ -59,7 +59,7 @@ AntiTracking._createTask({
add_task(async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
@@ -87,7 +87,7 @@ AntiTracking._createTask({
add_task(async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
@@ -115,7 +115,7 @@ AntiTracking._createTask({
add_task(async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccessWithDynamicFpi.js b/toolkit/components/antitracking/test/browser/browser_storageAccessWithDynamicFpi.js
index b5e950cc5f..440d6bb0b0 100644
--- a/toolkit/components/antitracking/test/browser/browser_storageAccessWithDynamicFpi.js
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessWithDynamicFpi.js
@@ -32,7 +32,7 @@ const EXCEPTION_LIST_PREF_NAME = "privacy.restrict3rdpartystorage.skip_list";
async function cleanup() {
Services.prefs.clearUserPref(EXCEPTION_LIST_PREF_NAME);
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccessWithHeuristics.js b/toolkit/components/antitracking/test/browser/browser_storageAccessWithHeuristics.js
index 5b68975d03..134fe045e8 100644
--- a/toolkit/components/antitracking/test/browser/browser_storageAccessWithHeuristics.js
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccessWithHeuristics.js
@@ -131,7 +131,7 @@ async function runTestWindowOpenHeuristic(disableHeuristics) {
info("Cleaning up.");
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
@@ -216,7 +216,7 @@ add_task(async function testDoublyNestedWindowOpenHeuristic() {
add_task(async function () {
info("Cleaning up.");
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
@@ -296,8 +296,7 @@ async function runTestUserInteractionHeuristic(disableHeuristics) {
let windowClosed = new content.Promise(resolve => {
Services.ww.registerNotification(function notification(
aSubject,
- aTopic,
- aData
+ aTopic
) {
// We need to check the document URI for Fission. It's because the
// 'domwindowclosed' would be triggered twice, one for the
@@ -418,8 +417,7 @@ async function runTestUserInteractionHeuristic(disableHeuristics) {
let windowClosed = new content.Promise(resolve => {
Services.ww.registerNotification(function notification(
aSubject,
- aTopic,
- aData
+ aTopic
) {
// We need to check the document URI here as well for the same
// reason above.
@@ -479,7 +477,7 @@ async function runTestUserInteractionHeuristic(disableHeuristics) {
info("Cleaning up.");
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
@@ -564,8 +562,7 @@ add_task(async function testDoublyNestedUserInteractionHeuristic() {
let windowClosed = new content.Promise(resolve => {
Services.ww.registerNotification(function notification(
aSubject,
- aTopic,
- aData
+ aTopic
) {
if (aTopic == "domwindowclosed") {
Services.ww.unregisterNotification(notification);
@@ -674,8 +671,7 @@ add_task(async function testDoublyNestedUserInteractionHeuristic() {
let windowClosed = new content.Promise(resolve => {
Services.ww.registerNotification(function notification(
aSubject,
- aTopic,
- aData
+ aTopic
) {
if (aTopic == "domwindowclosed") {
Services.ww.unregisterNotification(notification);
@@ -732,7 +728,7 @@ add_task(async function () {
info("Cleaning up.");
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
@@ -893,7 +889,7 @@ async function runTestFirstPartyWindowOpenHeuristic(disableHeuristics) {
info("Cleaning up.");
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_Arguments.js b/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_Arguments.js
index a88fc8bcb3..3356ef00dd 100644
--- a/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_Arguments.js
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_Arguments.js
@@ -110,7 +110,7 @@ add_task(async function testArgumentInCompleteStorageAccessRequest() {
add_task(async () => {
Services.perms.removeAll();
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_CookieBehavior.js b/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_CookieBehavior.js
index ea44a34f24..17b1673422 100644
--- a/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_CookieBehavior.js
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_CookieBehavior.js
@@ -405,7 +405,7 @@ add_task(
add_task(async () => {
Services.perms.removeAll();
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_CookiePermission.js b/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_CookiePermission.js
index 64ead20020..3debdc4580 100644
--- a/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_CookiePermission.js
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_CookiePermission.js
@@ -167,7 +167,7 @@ add_task(
add_task(async () => {
Services.perms.removeAll();
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_CrossOriginSameSite.js b/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_CrossOriginSameSite.js
index ca3e47d8e7..933b42a106 100644
--- a/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_CrossOriginSameSite.js
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_CrossOriginSameSite.js
@@ -155,7 +155,7 @@ add_task(async function testIntermediatePreferenceWriteCrossOrigin() {
add_task(async () => {
Services.perms.removeAll();
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_Doorhanger.js b/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_Doorhanger.js
index e8d6ce9bb1..b8eec5f466 100644
--- a/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_Doorhanger.js
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_Doorhanger.js
@@ -6,7 +6,7 @@ Services.scriptloader.loadSubScript(
async function cleanUp() {
Services.perms.removeAll();
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_Embed.js b/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_Embed.js
index 86f584763c..fc93d41666 100644
--- a/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_Embed.js
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_Embed.js
@@ -148,7 +148,7 @@ add_task(async function cSAR_crossSiteIframe() {
add_task(async () => {
Services.perms.removeAll();
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_Enable.js b/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_Enable.js
index 6ee61cc378..e01c52388e 100644
--- a/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_Enable.js
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_Enable.js
@@ -79,7 +79,7 @@ add_task(async function testExplicitlyEnabled() {
add_task(async () => {
Services.perms.removeAll();
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_RequireIntermediatePermission.js b/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_RequireIntermediatePermission.js
index a5cc6f10b5..94a2522f7e 100644
--- a/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_RequireIntermediatePermission.js
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_RequireIntermediatePermission.js
@@ -54,7 +54,7 @@ add_task(async function testIntermediatePermissionRequired() {
add_task(async () => {
Services.perms.removeAll();
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_StorageAccessPermission.js b/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_StorageAccessPermission.js
index 0b4f3e7273..ae24234d1f 100644
--- a/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_StorageAccessPermission.js
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_StorageAccessPermission.js
@@ -87,7 +87,7 @@ add_task(
add_task(async () => {
Services.perms.removeAll();
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_UserActivation.js b/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_UserActivation.js
index 49dd73fbf7..2e1f246f2e 100644
--- a/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_UserActivation.js
+++ b/toolkit/components/antitracking/test/browser/browser_storageAccess_TopLevel_UserActivation.js
@@ -56,7 +56,7 @@ add_task(async function testUserActivations() {
add_task(async () => {
Services.perms.removeAll();
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_subResources.js b/toolkit/components/antitracking/test/browser/browser_subResources.js
index 4841527d19..1b85aabb24 100644
--- a/toolkit/components/antitracking/test/browser/browser_subResources.js
+++ b/toolkit/components/antitracking/test/browser/browser_subResources.js
@@ -270,7 +270,7 @@ add_task(async function () {
info("Cleaning up.");
SpecialPowers.clearUserPref("network.cookie.sameSite.laxByDefault");
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_subResourcesPartitioned.js b/toolkit/components/antitracking/test/browser/browser_subResourcesPartitioned.js
index b2de150075..ad76be6e54 100644
--- a/toolkit/components/antitracking/test/browser/browser_subResourcesPartitioned.js
+++ b/toolkit/components/antitracking/test/browser/browser_subResourcesPartitioned.js
@@ -301,7 +301,7 @@ add_task(async function () {
add_task(async function () {
info("Cleaning up.");
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_subResourcesPartitioned_alwaysPartition.js b/toolkit/components/antitracking/test/browser/browser_subResourcesPartitioned_alwaysPartition.js
index 53a90854b3..9e1a573418 100644
--- a/toolkit/components/antitracking/test/browser/browser_subResourcesPartitioned_alwaysPartition.js
+++ b/toolkit/components/antitracking/test/browser/browser_subResourcesPartitioned_alwaysPartition.js
@@ -306,7 +306,7 @@ add_task(async function () {
add_task(async function () {
info("Cleaning up.");
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_thirdPartyStorageRejectionForCORS.js b/toolkit/components/antitracking/test/browser/browser_thirdPartyStorageRejectionForCORS.js
index f609c2d5b0..47f102d054 100644
--- a/toolkit/components/antitracking/test/browser/browser_thirdPartyStorageRejectionForCORS.js
+++ b/toolkit/components/antitracking/test/browser/browser_thirdPartyStorageRejectionForCORS.js
@@ -48,7 +48,7 @@ AntiTracking._createTask({
add_task(async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_urlDecorationStripping.js b/toolkit/components/antitracking/test/browser/browser_urlDecorationStripping.js
index 642b5d2cbd..f00ddcd4de 100644
--- a/toolkit/components/antitracking/test/browser/browser_urlDecorationStripping.js
+++ b/toolkit/components/antitracking/test/browser/browser_urlDecorationStripping.js
@@ -246,7 +246,7 @@ AntiTracking._createTask({
add_task(async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_urlDecorationStripping_alwaysPartition.js b/toolkit/components/antitracking/test/browser/browser_urlDecorationStripping_alwaysPartition.js
index 2fbac9811b..d66a84d1ad 100644
--- a/toolkit/components/antitracking/test/browser/browser_urlDecorationStripping_alwaysPartition.js
+++ b/toolkit/components/antitracking/test/browser/browser_urlDecorationStripping_alwaysPartition.js
@@ -248,7 +248,7 @@ AntiTracking._createTask({
add_task(async _ => {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_urlQueryStringStripping.js b/toolkit/components/antitracking/test/browser/browser_urlQueryStringStripping.js
index 6395110f41..567158dadc 100644
--- a/toolkit/components/antitracking/test/browser/browser_urlQueryStringStripping.js
+++ b/toolkit/components/antitracking/test/browser/browser_urlQueryStringStripping.js
@@ -43,7 +43,7 @@ const TEST_CASES = [
let listService;
function observeChannel(uri, expected) {
- return TestUtils.topicObserved("http-on-modify-request", (subject, data) => {
+ return TestUtils.topicObserved("http-on-modify-request", subject => {
let channel = subject.QueryInterface(Ci.nsIHttpChannel);
let channelURI = channel.URI;
diff --git a/toolkit/components/antitracking/test/browser/browser_urlQueryStringStripping_allowList.js b/toolkit/components/antitracking/test/browser/browser_urlQueryStringStripping_allowList.js
index 6dee6cede0..839d6980d1 100644
--- a/toolkit/components/antitracking/test/browser/browser_urlQueryStringStripping_allowList.js
+++ b/toolkit/components/antitracking/test/browser/browser_urlQueryStringStripping_allowList.js
@@ -15,7 +15,7 @@ const TEST_REDIRECT_URI = TEST_DOMAIN + TEST_PATH + "redirect.sjs";
const TEST_QUERY_STRING = "paramToStrip=1";
function observeChannel(uri, expected) {
- return TestUtils.topicObserved("http-on-before-connect", (subject, data) => {
+ return TestUtils.topicObserved("http-on-before-connect", subject => {
let channel = subject.QueryInterface(Ci.nsIHttpChannel);
let channelURI = channel.URI;
diff --git a/toolkit/components/antitracking/test/browser/browser_userInteraction.js b/toolkit/components/antitracking/test/browser/browser_userInteraction.js
index d343a56731..c024f4d9f7 100644
--- a/toolkit/components/antitracking/test/browser/browser_userInteraction.js
+++ b/toolkit/components/antitracking/test/browser/browser_userInteraction.js
@@ -40,7 +40,7 @@ add_task(async function () {
"Before user-interaction we don't have a permission"
);
- let promise = TestUtils.topicObserved("perm-changed", (aSubject, aData) => {
+ let promise = TestUtils.topicObserved("perm-changed", aSubject => {
let permission = aSubject.QueryInterface(Ci.nsIPermission);
return (
permission.type == "storageAccessAPI" &&
@@ -59,7 +59,7 @@ add_task(async function () {
// Let's see if the document is able to update the permission correctly.
for (var i = 0; i < 3; ++i) {
// Another perm-changed event should be triggered by the timer.
- promise = TestUtils.topicObserved("perm-changed", (aSubject, aData) => {
+ promise = TestUtils.topicObserved("perm-changed", aSubject => {
let permission = aSubject.QueryInterface(Ci.nsIPermission);
return (
permission.type == "storageAccessAPI" &&
@@ -84,7 +84,7 @@ add_task(async function () {
promise = new Promise(resolve => {
let id;
- function observer(subject, topic, data) {
+ function observer() {
ok(false, "Notification received!");
Services.obs.removeObserver(observer, "perm-changed");
clearTimeout(id);
@@ -117,7 +117,7 @@ add_task(async function () {
add_task(async function () {
info("Cleaning up.");
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/browser_workerPropagation.js b/toolkit/components/antitracking/test/browser/browser_workerPropagation.js
index 54ec0c1bf6..4f92a0450b 100644
--- a/toolkit/components/antitracking/test/browser/browser_workerPropagation.js
+++ b/toolkit/components/antitracking/test/browser/browser_workerPropagation.js
@@ -80,7 +80,7 @@ add_task(async function () {
add_task(async function () {
info("Cleaning up.");
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/file_saveAsPageInfo.html b/toolkit/components/antitracking/test/browser/file_saveAsPageInfo.html
index aa3de2a555..8f22e911e7 100644
--- a/toolkit/components/antitracking/test/browser/file_saveAsPageInfo.html
+++ b/toolkit/components/antitracking/test/browser/file_saveAsPageInfo.html
@@ -1,6 +1,6 @@
<html>
<body>
- <img src="http://example.net/browser/toolkit/components/antitracking/test/browser/raptor.jpg" id="image1">
- <video src="http://example.net/browser/toolkit/components/antitracking/test/browser/file_video.ogv" id="video1"> </video>
+ <img src="https://example.net/browser/toolkit/components/antitracking/test/browser/raptor.jpg" id="image1">
+ <video src="https://example.net/browser/toolkit/components/antitracking/test/browser/file_video.ogv" id="video1"> </video>
</body>
</html>
diff --git a/toolkit/components/antitracking/test/browser/head.js b/toolkit/components/antitracking/test/browser/head.js
index d7721b7cac..415da8177f 100644
--- a/toolkit/components/antitracking/test/browser/head.js
+++ b/toolkit/components/antitracking/test/browser/head.js
@@ -14,6 +14,7 @@ const TEST_DOMAIN_5 = "http://test/";
const TEST_DOMAIN_6 = "http://mochi.test:8888/";
const TEST_DOMAIN_7 = "http://example.com/";
const TEST_DOMAIN_8 = "http://www.example.com/";
+const TEST_DOMAIN_9 = "https://example.org:443/";
const TEST_3RD_PARTY_DOMAIN = "https://tracking.example.org/";
const TEST_3RD_PARTY_DOMAIN_HTTP = "http://tracking.example.org/";
const TEST_3RD_PARTY_DOMAIN_TP = "https://tracking.example.com/";
@@ -40,6 +41,7 @@ const TEST_TOP_PAGE_5 = TEST_DOMAIN_5 + TEST_PATH + "page.html";
const TEST_TOP_PAGE_6 = TEST_DOMAIN_6 + TEST_PATH + "page.html";
const TEST_TOP_PAGE_7 = TEST_DOMAIN_7 + TEST_PATH + "page.html";
const TEST_TOP_PAGE_8 = TEST_DOMAIN_8 + TEST_PATH + "page.html";
+const TEST_TOP_PAGE_9 = TEST_DOMAIN_9 + TEST_PATH + "page.html";
const TEST_EMBEDDER_PAGE = TEST_DOMAIN + TEST_PATH + "embedder.html";
const TEST_POPUP_PAGE = TEST_DOMAIN + TEST_PATH + "popup.html";
const TEST_IFRAME_PAGE = TEST_DOMAIN + TEST_PATH + "iframe.html";
diff --git a/toolkit/components/antitracking/test/browser/imageCacheWorker.js b/toolkit/components/antitracking/test/browser/imageCacheWorker.js
index d11221112c..6b2b57d908 100644
--- a/toolkit/components/antitracking/test/browser/imageCacheWorker.js
+++ b/toolkit/components/antitracking/test/browser/imageCacheWorker.js
@@ -71,7 +71,7 @@ add_task(async _ => {
});
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/localStorage.html b/toolkit/components/antitracking/test/browser/localStorage.html
index e08c25f2c4..6ba733a31d 100644
--- a/toolkit/components/antitracking/test/browser/localStorage.html
+++ b/toolkit/components/antitracking/test/browser/localStorage.html
@@ -50,7 +50,7 @@ if (parent) {
}
};
- window.addEventListener("storage", e => {
+ window.addEventListener("storage", () => {
let fromOpener = localStorage.foo.startsWith("opener");
let status;
diff --git a/toolkit/components/antitracking/test/browser/partitionedstorage_head.js b/toolkit/components/antitracking/test/browser/partitionedstorage_head.js
index d37b8177c4..15ba20fd68 100644
--- a/toolkit/components/antitracking/test/browser/partitionedstorage_head.js
+++ b/toolkit/components/antitracking/test/browser/partitionedstorage_head.js
@@ -141,6 +141,7 @@ this.PartitionedStorageHelper = {
await SpecialPowers.pushPrefEnv({
set: [
["dom.storage_access.enabled", true],
+ ["network.cookie.cookieBehavior.optInPartitioning", true],
[
"privacy.partition.always_partition_third_party_non_cookie_storage",
true,
@@ -164,14 +165,14 @@ this.PartitionedStorageHelper = {
}
info("Creating the first tab");
- let tab1 = BrowserTestUtils.addTab(win.gBrowser, TEST_TOP_PAGE);
+ let tab1 = BrowserTestUtils.addTab(win.gBrowser, TEST_TOP_PAGE_HTTPS);
win.gBrowser.selectedTab = tab1;
let browser1 = win.gBrowser.getBrowserForTab(tab1);
await BrowserTestUtils.browserLoaded(browser1);
info("Creating the second tab");
- let tab2 = BrowserTestUtils.addTab(win.gBrowser, TEST_TOP_PAGE_6);
+ let tab2 = BrowserTestUtils.addTab(win.gBrowser, TEST_TOP_PAGE_9);
win.gBrowser.selectedTab = tab2;
let browser2 = win.gBrowser.getBrowserForTab(tab2);
@@ -180,7 +181,7 @@ this.PartitionedStorageHelper = {
info("Creating the third tab");
let tab3 = BrowserTestUtils.addTab(
win.gBrowser,
- TEST_4TH_PARTY_PARTITIONED_PAGE
+ TEST_4TH_PARTY_PARTITIONED_PAGE_HTTPS
);
win.gBrowser.selectedTab = tab3;
@@ -189,7 +190,7 @@ this.PartitionedStorageHelper = {
// Use the same URL as first tab to check partitioned data
info("Creating the forth tab");
- let tab4 = BrowserTestUtils.addTab(win.gBrowser, TEST_TOP_PAGE);
+ let tab4 = BrowserTestUtils.addTab(win.gBrowser, TEST_TOP_PAGE_HTTPS);
win.gBrowser.selectedTab = tab4;
let browser4 = win.gBrowser.getBrowserForTab(tab4);
@@ -207,7 +208,8 @@ this.PartitionedStorageHelper = {
browser,
[
{
- page: TEST_4TH_PARTY_PARTITIONED_PAGE + "?variant=" + variant,
+ page:
+ TEST_4TH_PARTY_PARTITIONED_PAGE_HTTPS + "?variant=" + variant,
getDataCallback: getDataCallback.toString(),
result,
},
@@ -289,7 +291,8 @@ this.PartitionedStorageHelper = {
browser,
[
{
- page: TEST_4TH_PARTY_PARTITIONED_PAGE + "?variant=" + variant,
+ page:
+ TEST_4TH_PARTY_PARTITIONED_PAGE_HTTPS + "?variant=" + variant,
addDataCallback: addDataCallback.toString(),
value,
},
@@ -382,7 +385,7 @@ this.PartitionedStorageHelper = {
async function setStorageAccessForThirdParty(browser) {
info(`Setting permission for ${browser.currentURI.spec}`);
- let type = "3rdPartyStorage^http://not-tracking.example.com";
+ let type = "3rdPartyStorage^https://not-tracking.example.com";
let permission = Services.perms.ALLOW_ACTION;
let expireType = Services.perms.EXPIRE_SESSION;
Services.perms.addFromPrincipal(
diff --git a/toolkit/components/antitracking/test/browser/storage_access_head.js b/toolkit/components/antitracking/test/browser/storage_access_head.js
index ea4f67b4fe..e43f680042 100644
--- a/toolkit/components/antitracking/test/browser/storage_access_head.js
+++ b/toolkit/components/antitracking/test/browser/storage_access_head.js
@@ -246,7 +246,7 @@ async function requestStorageAccessAndExpectFailure() {
async function cleanUpData() {
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
diff --git a/toolkit/components/antitracking/test/browser/tracker.js b/toolkit/components/antitracking/test/browser/tracker.js
index 85e943f7c4..eeed80a71b 100644
--- a/toolkit/components/antitracking/test/browser/tracker.js
+++ b/toolkit/components/antitracking/test/browser/tracker.js
@@ -1,4 +1,4 @@
-window.addEventListener("message", e => {
+window.addEventListener("message", () => {
let bc = new BroadcastChannel("a");
bc.postMessage("ready!");
});
diff --git a/toolkit/components/antitracking/test/browser/workerIframe.html b/toolkit/components/antitracking/test/browser/workerIframe.html
index 37aa5d7c0d..c31d9ff854 100644
--- a/toolkit/components/antitracking/test/browser/workerIframe.html
+++ b/toolkit/components/antitracking/test/browser/workerIframe.html
@@ -21,7 +21,7 @@ function is(a, b, msg) {
async function runTest() {
function workerCode() {
- onmessage = e => {
+ onmessage = () => {
try {
indexedDB.open("test", "1");
postMessage(true);
diff --git a/toolkit/components/antitracking/test/xpcshell/test_staticPartition_authhttp.js b/toolkit/components/antitracking/test/xpcshell/test_staticPartition_authhttp.js
index 0492f5ff2a..ddc942f8d8 100644
--- a/toolkit/components/antitracking/test/xpcshell/test_staticPartition_authhttp.js
+++ b/toolkit/components/antitracking/test/xpcshell/test_staticPartition_authhttp.js
@@ -38,7 +38,7 @@ Requestor.prototype = {
return true;
},
- asyncPromptAuth(chan, cb, ctx, lvl, info) {
+ asyncPromptAuth() {
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
},
};
diff --git a/toolkit/components/antitracking/test/xpcshell/test_staticPartition_prefetch.js b/toolkit/components/antitracking/test/xpcshell/test_staticPartition_prefetch.js
index f7ec4cc8e3..c9253d33ea 100644
--- a/toolkit/components/antitracking/test/xpcshell/test_staticPartition_prefetch.js
+++ b/toolkit/components/antitracking/test/xpcshell/test_staticPartition_prefetch.js
@@ -34,7 +34,7 @@ async function checkCache(originAttributes) {
const data = await new Promise(resolve => {
let cacheEntries = [];
let cacheVisitor = {
- onCacheStorageInfo(num, consumption) {},
+ onCacheStorageInfo() {},
onCacheEntryInfo(uri, idEnhance) {
cacheEntries.push({ uri, idEnhance });
},
diff --git a/toolkit/components/antitracking/test/xpcshell/test_staticPartition_preload.js b/toolkit/components/antitracking/test/xpcshell/test_staticPartition_preload.js
index 20158f2f7a..0bab966308 100644
--- a/toolkit/components/antitracking/test/xpcshell/test_staticPartition_preload.js
+++ b/toolkit/components/antitracking/test/xpcshell/test_staticPartition_preload.js
@@ -28,7 +28,7 @@ async function checkCache(originAttributes) {
const data = await new Promise(resolve => {
let cacheEntries = [];
let cacheVisitor = {
- onCacheStorageInfo(num, consumption) {},
+ onCacheStorageInfo() {},
onCacheEntryInfo(uri, idEnhance) {
cacheEntries.push({ uri, idEnhance });
},
diff --git a/toolkit/components/antitracking/test/xpcshell/xpcshell.toml b/toolkit/components/antitracking/test/xpcshell/xpcshell.toml
index 86f524ab89..97fbf8fad1 100644
--- a/toolkit/components/antitracking/test/xpcshell/xpcshell.toml
+++ b/toolkit/components/antitracking/test/xpcshell/xpcshell.toml
@@ -26,10 +26,11 @@ skip-if = ["socketprocess_networking"] # Bug 1759035
["test_staticPartition_font.js"]
support-files = ["data/font.woff"]
skip-if = [
- "os == 'linux' && !debug", # Bug 1760086
+ "os == 'linux' && os_version == '18.04' && !debug", # Bug 1760086
"apple_silicon", # bug 1729551
- "os == 'mac' && bits == 64 && !debug", # Bug 1652119
- "os == 'win' && bits == 64 && !debug", # Bug 1652119
+ "apple_catalina && !debug", # Bug 1652119
+ "win10_2009 && bits == 64 && !debug", # Bug 1652119
+ "win11_2009 && bits == 64 && !debug", # Bug 1652119
"socketprocess_networking", # Bug 1759035
]
run-sequentially = "very high failure rate in parallel"
diff --git a/toolkit/components/apppicker/content/appPicker.js b/toolkit/components/apppicker/content/appPicker.js
index dc2a97af8c..dd1163d1b4 100644
--- a/toolkit/components/apppicker/content/appPicker.js
+++ b/toolkit/components/apppicker/content/appPicker.js
@@ -195,7 +195,11 @@ AppPicker.prototype = {
var nsIFilePicker = Ci.nsIFilePicker;
var fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
- fp.init(window, this._incomingParams.title, nsIFilePicker.modeOpen);
+ fp.init(
+ window.browsingContext,
+ this._incomingParams.title,
+ nsIFilePicker.modeOpen
+ );
fp.appendFilters(nsIFilePicker.filterApps);
var startLocation;
diff --git a/toolkit/components/asyncshutdown/AsyncShutdown.sys.mjs b/toolkit/components/asyncshutdown/AsyncShutdown.sys.mjs
index 4f0adc092a..4577fa49a4 100644
--- a/toolkit/components/asyncshutdown/AsyncShutdown.sys.mjs
+++ b/toolkit/components/asyncshutdown/AsyncShutdown.sys.mjs
@@ -335,8 +335,7 @@ function getOrigin(topFrame, filename = null, lineNumber = null, stack = null) {
lineNumber = frame ? frame.lineNumber : 0;
}
if (stack == null) {
- // Now build the rest of the stack as a string, using Task.jsm's rewriting
- // to ensure that we do not lose information at each call to `Task.spawn`.
+ // Now build the rest of the stack as a string.
stack = [];
while (frame != null) {
stack.push(frame.filename + ":" + frame.name + ":" + frame.lineNumber);
diff --git a/toolkit/components/asyncshutdown/nsIAsyncShutdown.idl b/toolkit/components/asyncshutdown/nsIAsyncShutdown.idl
index 945772bd51..a40d343836 100644
--- a/toolkit/components/asyncshutdown/nsIAsyncShutdown.idl
+++ b/toolkit/components/asyncshutdown/nsIAsyncShutdown.idl
@@ -223,7 +223,3 @@ interface nsIAsyncShutdownService: nsISupports {
// makes it easier to cause shutdown hangs.
};
-
-%{C++
-#define NS_ASYNCSHUTDOWNSERVICE_CONTRACTID "@mozilla.org/async-shutdown-service;1"
-%}
diff --git a/toolkit/components/backgroundhangmonitor/ThreadStackHelper.cpp b/toolkit/components/backgroundhangmonitor/ThreadStackHelper.cpp
index c43a322fd4..889f7c96f1 100644
--- a/toolkit/components/backgroundhangmonitor/ThreadStackHelper.cpp
+++ b/toolkit/components/backgroundhangmonitor/ThreadStackHelper.cpp
@@ -205,7 +205,8 @@ void ThreadStackHelper::CollectJitReturnAddr(void* aAddr) {
TryAppendFrame(HangEntryJit());
}
-void ThreadStackHelper::CollectWasmFrame(const char* aLabel) {
+void ThreadStackHelper::CollectWasmFrame(JS::ProfilingCategoryPair aCategory,
+ const char* aLabel) {
MOZ_RELEASE_ASSERT(mStackToFill);
// We don't want to collect WASM frames, as they are probably for content, so
// we just add a "(content wasm)" frame.
diff --git a/toolkit/components/backgroundhangmonitor/ThreadStackHelper.h b/toolkit/components/backgroundhangmonitor/ThreadStackHelper.h
index e54078d2dd..e55ba2cab9 100644
--- a/toolkit/components/backgroundhangmonitor/ThreadStackHelper.h
+++ b/toolkit/components/backgroundhangmonitor/ThreadStackHelper.h
@@ -92,7 +92,8 @@ class ThreadStackHelper : public ProfilerStackCollector {
virtual void SetIsMainThread() override;
virtual void CollectNativeLeafAddr(void* aAddr) override;
virtual void CollectJitReturnAddr(void* aAddr) override;
- virtual void CollectWasmFrame(const char* aLabel) override;
+ virtual void CollectWasmFrame(JS::ProfilingCategoryPair aCategory,
+ const char* aLabel) override;
virtual void CollectProfilingStackFrame(
const js::ProfilingStackFrame& aEntry) override;
diff --git a/toolkit/components/bitsdownload/nsIBits.idl b/toolkit/components/bitsdownload/nsIBits.idl
index 993c10243c..5d7203f605 100644
--- a/toolkit/components/bitsdownload/nsIBits.idl
+++ b/toolkit/components/bitsdownload/nsIBits.idl
@@ -418,10 +418,3 @@ interface nsIBitsCallback : nsISupports
in nsBitsErrorStage errorStage,
in AUTF8String errorMessage);
};
-
-%{C++
-#define NS_BITS_CID \
- { 0xa334de05, 0xb9de, 0x46a1, \
- { 0x98, 0xa9, 0x3f, 0x5c, 0xed, 0x82, 0x1e, 0x68 } }
-#define NS_BITS_CONTRACTID "@mozilla.org/bits;1"
-%}
diff --git a/toolkit/components/cleardata/PrincipalsCollector.sys.mjs b/toolkit/components/cleardata/PrincipalsCollector.sys.mjs
index 2b5917c6ce..e4f5e827f6 100644
--- a/toolkit/components/cleardata/PrincipalsCollector.sys.mjs
+++ b/toolkit/components/cleardata/PrincipalsCollector.sys.mjs
@@ -17,7 +17,7 @@ let logConsole;
function log(msg) {
if (!logConsole) {
logConsole = console.createInstance({
- prefix: "** PrincipalsCollector.jsm",
+ prefix: "PrincipalsCollector",
maxLogLevelPref: "browser.sanitizer.loglevel",
});
}
diff --git a/toolkit/components/cleardata/ServiceWorkerCleanUp.sys.mjs b/toolkit/components/cleardata/ServiceWorkerCleanUp.sys.mjs
index 1c49b2be08..b513e47aba 100644
--- a/toolkit/components/cleardata/ServiceWorkerCleanUp.sys.mjs
+++ b/toolkit/components/cleardata/ServiceWorkerCleanUp.sys.mjs
@@ -15,7 +15,7 @@ XPCOMUtils.defineLazyServiceGetter(
if (Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_CONTENT) {
throw new Error(
- "ServiceWorkerCleanUp.jsm can only be used in the parent process"
+ "ServiceWorkerCleanUp.sys.mjs can only be used in the parent process"
);
}
diff --git a/toolkit/components/cleardata/nsIClearDataService.idl b/toolkit/components/cleardata/nsIClearDataService.idl
index 0dff281dbe..ea17059bbe 100644
--- a/toolkit/components/cleardata/nsIClearDataService.idl
+++ b/toolkit/components/cleardata/nsIClearDataService.idl
@@ -140,7 +140,7 @@ interface nsIClearDataService : nsISupports
* whose base domain does not have any storage associated with it.
*
* The principals to be considered will need to be passed by the API consumer.
- * It is recommended to use PrincipalsCollector.jsm for that.
+ * It is recommended to use PrincipalsCollector.sys.mjs for that.
*
* @param aPrincipalsWithStorage principals to be excluded from clearing
* @param aFrom microseconds from the epoch
diff --git a/toolkit/components/cleardata/tests/marionette/test_moved_origin_directory_cleanup.py b/toolkit/components/cleardata/tests/marionette/test_moved_origin_directory_cleanup.py
index 876f86cd32..50f4c93f65 100644
--- a/toolkit/components/cleardata/tests/marionette/test_moved_origin_directory_cleanup.py
+++ b/toolkit/components/cleardata/tests/marionette/test_moved_origin_directory_cleanup.py
@@ -46,6 +46,11 @@ class MovedOriginDirectoryCleanupTestCase(MarionetteTestCase):
"""
)
+ def read_prefs_file(self):
+ pref_path = Path(self.marionette.profile_path) / "prefs.js"
+ with open(pref_path) as f:
+ return f.read()
+
def removeAllCookies(self):
with self.marionette.using_context("chrome"):
self.marionette.execute_script(
@@ -83,6 +88,12 @@ class MovedOriginDirectoryCleanupTestCase(MarionetteTestCase):
message="privacy.sanitize.pending must include offlineApps",
)
+ # Make sure the pref is written to the file
+ Wait(self.marionette).until(
+ lambda _: "offlineApps" in self.read_prefs_file(),
+ message="prefs.js must include offlineApps",
+ )
+
# Cleanup happens via Sanitizer.onStartup after restart
self.marionette.restart(in_app=False)
diff --git a/toolkit/components/cleardata/tests/unit/test_cookies.js b/toolkit/components/cleardata/tests/unit/test_cookies.js
index 4bcb6d725a..045f6c0b24 100644
--- a/toolkit/components/cleardata/tests/unit/test_cookies.js
+++ b/toolkit/components/cleardata/tests/unit/test_cookies.js
@@ -391,3 +391,74 @@ add_task(async function test_baseDomain_cookies_subdomain() {
// Cleanup
Services.cookies.removeAll();
});
+
+function addCookiesForHost(host) {
+ const expiry = Date.now() + 24 * 60 * 60;
+ Services.cookies.add(
+ host,
+ "path",
+ "name",
+ "value",
+ true /* secure */,
+ true /* http only */,
+ false /* session */,
+ expiry,
+ {},
+ Ci.nsICookie.SAMESITE_NONE,
+ Ci.nsICookie.SCHEME_HTTPS
+ );
+}
+
+function addIpv6Cookies() {
+ addCookiesForHost("[A:B:C:D:E:0:0:1]");
+ addCookiesForHost("[a:b:c:d:e:0:0:1]");
+ addCookiesForHost("[A:B:C:D:E::1]");
+ addCookiesForHost("[000A:000B:000C:000D:000E:0000:0000:0001]");
+ addCookiesForHost("A:B:C:D:E:0:0:1");
+ addCookiesForHost("a:b:c:d:e:0:0:1");
+ addCookiesForHost("A:B:C:D:E::1");
+
+ Assert.equal(Services.cookies.cookies.length, 7);
+}
+
+// This tests the intermediate fix for Bug 1860033.
+// When Bug 1882259 is resolved multiple cookies with same IPv6 host in
+// different representation will not be stored anymore and this test needs to
+// be removed.
+add_task(async function test_ipv6_cookies() {
+ // Add multiple cookies of same IPv6 address in different representations.
+ addIpv6Cookies();
+
+ // Delete cookies using cookie service
+ Services.cookies.removeCookiesFromExactHost(
+ "A:B:C:D:E::1",
+ JSON.stringify({})
+ );
+
+ // Assert that all cookies were removed.
+ Assert.equal(Services.cookies.cookies.length, 0);
+
+ // Add multiple cookies of same IPv6 address in different representations.
+ addIpv6Cookies();
+
+ // Delete cookies by principal from URI
+ let uri = Services.io.newURI("http://[A:B:C:D:E::1]");
+ let principal = Services.scriptSecurityManager.createContentPrincipal(
+ uri,
+ {}
+ );
+ await new Promise(aResolve => {
+ Services.clearData.deleteDataFromPrincipal(
+ principal,
+ true /* user request */,
+ Ci.nsIClearDataService.CLEAR_COOKIES,
+ value => {
+ Assert.equal(value, 0);
+ aResolve();
+ }
+ );
+ });
+
+ // Assert that all cookies were removed.
+ Assert.equal(Services.cookies.cookies.length, 0);
+});
diff --git a/toolkit/components/contentanalysis/ContentAnalysis.cpp b/toolkit/components/contentanalysis/ContentAnalysis.cpp
index e749fd0acd..1c71f5d986 100644
--- a/toolkit/components/contentanalysis/ContentAnalysis.cpp
+++ b/toolkit/components/contentanalysis/ContentAnalysis.cpp
@@ -12,9 +12,11 @@
#include "GMPUtils.h" // ToHexString
#include "mozilla/Components.h"
#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/WindowGlobalParent.h"
#include "mozilla/Logging.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/Services.h"
+#include "mozilla/StaticMutex.h"
#include "mozilla/StaticPrefs_browser.h"
#include "nsAppRunner.h"
#include "nsComponentManagerUtils.h"
@@ -27,11 +29,13 @@
#include <algorithm>
#include <sstream>
+#include <string>
#ifdef XP_WIN
# include <windows.h>
# define SECURITY_WIN32 1
# include <security.h>
+# include "mozilla/WinDllServices.h"
#endif // XP_WIN
namespace mozilla::contentanalysis {
@@ -53,8 +57,9 @@ const char* kIsDLPEnabledPref = "browser.contentanalysis.enabled";
const char* kIsPerUserPref = "browser.contentanalysis.is_per_user";
const char* kPipePathNamePref = "browser.contentanalysis.pipe_path_name";
const char* kDefaultAllowPref = "browser.contentanalysis.default_allow";
-
-static constexpr uint32_t kAnalysisTimeoutSecs = 30; // 30 sec
+const char* kClientSignature = "browser.contentanalysis.client_signature";
+const char* kAllowUrlPref = "browser.contentanalysis.allow_url_regex_list";
+const char* kDenyUrlPref = "browser.contentanalysis.deny_url_regex_list";
nsresult MakePromise(JSContext* aCx, RefPtr<mozilla::dom::Promise>* aPromise) {
nsIGlobalObject* go = xpc::CurrentNativeGlobal(aCx);
@@ -181,8 +186,9 @@ ContentAnalysisRequest::GetWindowGlobalParent(
return NS_OK;
}
-nsresult ContentAnalysis::CreateContentAnalysisClient(nsCString&& aPipePathName,
- bool aIsPerUser) {
+nsresult ContentAnalysis::CreateContentAnalysisClient(
+ nsCString&& aPipePathName, nsString&& aClientSignatureSetting,
+ bool aIsPerUser) {
MOZ_ASSERT(!NS_IsMainThread());
// This method should only be called once
MOZ_ASSERT(!mCaClientPromise->IsResolved());
@@ -191,6 +197,30 @@ nsresult ContentAnalysis::CreateContentAnalysisClient(nsCString&& aPipePathName,
content_analysis::sdk::Client::Create({aPipePathName.Data(), aIsPerUser})
.release());
LOGD("Content analysis is %s", client ? "connected" : "not available");
+#ifdef XP_WIN
+ if (client && !aClientSignatureSetting.IsEmpty()) {
+ std::string agentPath = client->GetAgentInfo().binary_path;
+ nsString agentWidePath = NS_ConvertUTF8toUTF16(agentPath);
+ UniquePtr<wchar_t[]> orgName =
+ mozilla::DllServices::Get()->GetBinaryOrgName(agentWidePath.Data());
+ bool signatureMatches = false;
+ if (orgName) {
+ auto dependentOrgName = nsDependentString(orgName.get());
+ LOGD("Content analysis client signed with organization name \"%S\"",
+ static_cast<const wchar_t*>(dependentOrgName.get()));
+ signatureMatches = aClientSignatureSetting.Equals(dependentOrgName);
+ } else {
+ LOGD("Content analysis client has no signature");
+ }
+ if (!signatureMatches) {
+ LOGE(
+ "Got mismatched content analysis client signature! All content "
+ "analysis operations will fail.");
+ mCaClientPromise->Reject(NS_ERROR_INVALID_SIGNATURE, __func__);
+ return NS_OK;
+ }
+ }
+#endif // XP_WIN
mCaClientPromise->Resolve(client, __func__);
return NS_OK;
@@ -258,6 +288,15 @@ nsresult ContentAnalysisRequest::GetFileDigest(const nsAString& aFilePath,
return NS_OK;
}
+// Generate an ID that will be shared by all DLP requests.
+// Used to cancel all requests on Firefox shutdown.
+void ContentAnalysis::GenerateUserActionId() {
+ nsID id = nsID::GenerateUUID();
+ mUserActionId = nsPrintfCString("Firefox %s", id.ToString().get());
+}
+
+nsCString ContentAnalysis::GetUserActionId() { return mUserActionId; }
+
static nsresult ConvertToProtobuf(
nsIClientDownloadResource* aIn,
content_analysis::sdk::ClientDownloadRequest_Resource* aOut) {
@@ -277,9 +316,11 @@ static nsresult ConvertToProtobuf(
}
static nsresult ConvertToProtobuf(
- nsIContentAnalysisRequest* aIn,
+ nsIContentAnalysisRequest* aIn, nsCString&& aUserActionId,
+ int64_t aRequestCount,
content_analysis::sdk::ContentAnalysisRequest* aOut) {
- aOut->set_expires_at(time(nullptr) + kAnalysisTimeoutSecs); // TODO:
+ uint32_t timeout = StaticPrefs::browser_contentanalysis_agent_timeout();
+ aOut->set_expires_at(time(nullptr) + timeout);
nsIContentAnalysisRequest::AnalysisType analysisType;
nsresult rv = aIn->GetAnalysisType(&analysisType);
@@ -293,6 +334,9 @@ static nsresult ConvertToProtobuf(
NS_ENSURE_SUCCESS(rv, rv);
aOut->set_request_token(requestToken.get(), requestToken.Length());
+ aOut->set_user_action_id(aUserActionId.get());
+ aOut->set_user_action_requests_count(aRequestCount);
+
const std::string tag = "dlp"; // TODO:
*aOut->add_tags() = tag;
@@ -440,8 +484,7 @@ static void LogRequest(
}
ContentAnalysisResponse::ContentAnalysisResponse(
- content_analysis::sdk::ContentAnalysisResponse&& aResponse)
- : mHasAcknowledged(false) {
+ content_analysis::sdk::ContentAnalysisResponse&& aResponse) {
mAction = Action::eUnspecified;
for (const auto& result : aResponse.results()) {
if (!result.has_status() ||
@@ -469,7 +512,7 @@ ContentAnalysisResponse::ContentAnalysisResponse(
ContentAnalysisResponse::ContentAnalysisResponse(
Action aAction, const nsACString& aRequestToken)
- : mAction(aAction), mRequestToken(aRequestToken), mHasAcknowledged(false) {}
+ : mAction(aAction), mRequestToken(aRequestToken) {}
/* static */
already_AddRefed<ContentAnalysisResponse> ContentAnalysisResponse::FromProtobuf(
@@ -658,6 +701,122 @@ NS_IMETHODIMP ContentAnalysisResult::GetShouldAllowContent(
return NS_OK;
}
+void ContentAnalysis::EnsureParsedUrlFilters() {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mParsedUrlLists) {
+ return;
+ }
+
+ mParsedUrlLists = true;
+ nsAutoCString allowList;
+ MOZ_ALWAYS_SUCCEEDS(Preferences::GetCString(kAllowUrlPref, allowList));
+ for (const nsACString& regexSubstr : allowList.Split(u' ')) {
+ if (!regexSubstr.IsEmpty()) {
+ auto flatStr = PromiseFlatCString(regexSubstr);
+ const char* regex = flatStr.get();
+ LOGD("CA will allow URLs that match %s", regex);
+ mAllowUrlList.push_back(std::regex(regex));
+ }
+ }
+
+ nsAutoCString denyList;
+ MOZ_ALWAYS_SUCCEEDS(Preferences::GetCString(kDenyUrlPref, denyList));
+ for (const nsACString& regexSubstr : denyList.Split(u' ')) {
+ if (!regexSubstr.IsEmpty()) {
+ auto flatStr = PromiseFlatCString(regexSubstr);
+ const char* regex = flatStr.get();
+ LOGD("CA will block URLs that match %s", regex);
+ mDenyUrlList.push_back(std::regex(regex));
+ }
+ }
+}
+
+ContentAnalysis::UrlFilterResult ContentAnalysis::FilterByUrlLists(
+ nsIContentAnalysisRequest* aRequest) {
+ EnsureParsedUrlFilters();
+
+ nsIURI* nsiUrl = nullptr;
+ MOZ_ALWAYS_SUCCEEDS(aRequest->GetUrl(&nsiUrl));
+ nsCString urlString;
+ nsresult rv = nsiUrl->GetSpec(urlString);
+ NS_ENSURE_SUCCESS(rv, UrlFilterResult::eDeny);
+ MOZ_ASSERT(!urlString.IsEmpty());
+ std::string url = urlString.BeginReading();
+ size_t count = 0;
+ for (const auto& denyFilter : mDenyUrlList) {
+ if (std::regex_search(url, denyFilter)) {
+ LOGD("Denying CA request : Deny filter %zu matched url %s", count,
+ url.c_str());
+ return UrlFilterResult::eDeny;
+ }
+ ++count;
+ }
+
+ count = 0;
+ UrlFilterResult result = UrlFilterResult::eCheck;
+ for (const auto& allowFilter : mAllowUrlList) {
+ if (std::regex_match(url, allowFilter)) {
+ LOGD("CA request : Allow filter %zu matched %s", count, url.c_str());
+ result = UrlFilterResult::eAllow;
+ break;
+ }
+ ++count;
+ }
+
+ // The rest only applies to download resources.
+ nsIContentAnalysisRequest::AnalysisType analysisType;
+ MOZ_ALWAYS_SUCCEEDS(aRequest->GetAnalysisType(&analysisType));
+ if (analysisType != ContentAnalysisRequest::AnalysisType::eFileDownloaded) {
+ MOZ_ASSERT(result == UrlFilterResult::eCheck ||
+ result == UrlFilterResult::eAllow);
+ LOGD("CA request filter result: %s",
+ result == UrlFilterResult::eCheck ? "check" : "allow");
+ return result;
+ }
+
+ nsTArray<RefPtr<nsIClientDownloadResource>> resources;
+ MOZ_ALWAYS_SUCCEEDS(aRequest->GetResources(resources));
+ for (size_t resourceIdx = 0; resourceIdx < resources.Length();
+ /* noop */) {
+ auto& resource = resources[resourceIdx];
+ nsAutoString nsUrl;
+ MOZ_ALWAYS_SUCCEEDS(resource->GetUrl(nsUrl));
+ std::string url = NS_ConvertUTF16toUTF8(nsUrl).get();
+ count = 0;
+ for (auto& denyFilter : mDenyUrlList) {
+ if (std::regex_search(url, denyFilter)) {
+ LOGD(
+ "Denying CA request : Deny filter %zu matched download resource "
+ "at url %s",
+ count, url.c_str());
+ return UrlFilterResult::eDeny;
+ }
+ ++count;
+ }
+
+ count = 0;
+ bool removed = false;
+ for (auto& allowFilter : mAllowUrlList) {
+ if (std::regex_search(url, allowFilter)) {
+ LOGD(
+ "CA request : Allow filter %zu matched download resource "
+ "at url %s",
+ count, url.c_str());
+ resources.RemoveElementAt(resourceIdx);
+ removed = true;
+ break;
+ }
+ ++count;
+ }
+ if (!removed) {
+ ++resourceIdx;
+ }
+ }
+
+ // Check unless all were allowed.
+ return resources.Length() ? UrlFilterResult::eCheck : UrlFilterResult::eAllow;
+}
+
NS_IMPL_CLASSINFO(ContentAnalysisRequest, nullptr, 0, {0});
NS_IMPL_ISUPPORTS_CI(ContentAnalysisRequest, nsIContentAnalysisRequest);
NS_IMPL_CLASSINFO(ContentAnalysisResponse, nullptr, 0, {0});
@@ -672,8 +831,11 @@ ContentAnalysis::ContentAnalysis()
: mCaClientPromise(
new ClientPromise::Private("ContentAnalysis::ContentAnalysis")),
mClientCreationAttempted(false),
+ mSetByEnterprise(false),
mCallbackMap("ContentAnalysis::mCallbackMap"),
- mWarnResponseDataMap("ContentAnalysis::mWarnResponseDataMap") {}
+ mWarnResponseDataMap("ContentAnalysis::mWarnResponseDataMap") {
+ GenerateUserActionId();
+}
ContentAnalysis::~ContentAnalysis() {
// Accessing mClientCreationAttempted so need to be on the main thread
@@ -689,12 +851,20 @@ ContentAnalysis::GetIsActive(bool* aIsActive) {
*aIsActive = false;
// Need to be on the main thread to read prefs
MOZ_ASSERT(NS_IsMainThread());
- // gAllowContentAnalysis is only set in the parent process
+ // gAllowContentAnalysisArgPresent is only set in the parent process
MOZ_ASSERT(XRE_IsParentProcess());
- if (!gAllowContentAnalysis || !Preferences::GetBool(kIsDLPEnabledPref)) {
+ if (!Preferences::GetBool(kIsDLPEnabledPref)) {
LOGD("Local DLP Content Analysis is not active");
return NS_OK;
}
+ if (!gAllowContentAnalysisArgPresent && !mSetByEnterprise) {
+ LOGE(
+ "The content analysis pref is enabled but not by an enterprise "
+ "policy and -allow-content-analysis was not present on the "
+ "command-line. Content Analysis will not be active.");
+ return NS_OK;
+ }
+
*aIsActive = true;
LOGD("Local DLP Content Analysis is active");
// mClientCreationAttempted is only accessed on the main thread,
@@ -710,12 +880,15 @@ ContentAnalysis::GetIsActive(bool* aIsActive) {
return rv;
}
bool isPerUser = Preferences::GetBool(kIsPerUserPref);
+ nsString clientSignature;
+ // It's OK if this fails, we will default to the empty string
+ Preferences::GetString(kClientSignature, clientSignature);
rv = NS_DispatchBackgroundTask(NS_NewCancelableRunnableFunction(
"ContentAnalysis::CreateContentAnalysisClient",
[owner = RefPtr{this}, pipePathName = std::move(pipePathName),
- isPerUser]() mutable {
- owner->CreateContentAnalysisClient(std::move(pipePathName),
- isPerUser);
+ clientSignature = std::move(clientSignature), isPerUser]() mutable {
+ owner->CreateContentAnalysisClient(
+ std::move(pipePathName), std::move(clientSignature), isPerUser);
}));
if (NS_WARN_IF(NS_FAILED(rv))) {
mCaClientPromise->Reject(rv, __func__);
@@ -736,10 +909,33 @@ ContentAnalysis::GetMightBeActive(bool* aMightBeActive) {
return NS_OK;
}
+NS_IMETHODIMP
+ContentAnalysis::GetIsSetByEnterprisePolicy(bool* aSetByEnterprise) {
+ *aSetByEnterprise = mSetByEnterprise;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ContentAnalysis::SetIsSetByEnterprisePolicy(bool aSetByEnterprise) {
+ mSetByEnterprise = aSetByEnterprise;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ContentAnalysis::TestOnlySetCACmdLineArg(bool aVal) {
+#ifdef ENABLE_TESTS
+ gAllowContentAnalysisArgPresent = aVal;
+ return NS_OK;
+#else
+ LOGE("ContentAnalysis::TestOnlySetCACmdLineArg is test-only");
+ return NS_ERROR_UNEXPECTED;
+#endif
+}
+
nsresult ContentAnalysis::CancelWithError(nsCString aRequestToken,
nsresult aResult) {
return NS_DispatchToMainThread(NS_NewCancelableRunnableFunction(
- "ContentAnalysis::RunAnalyzeRequestTask::HandleResponse",
+ "ContentAnalysis::CancelWithError",
[aResult, aRequestToken = std::move(aRequestToken)] {
RefPtr<ContentAnalysis> owner = GetContentAnalysisFromService();
if (!owner) {
@@ -787,7 +983,10 @@ RefPtr<ContentAnalysis> ContentAnalysis::GetContentAnalysisFromService() {
nsresult ContentAnalysis::RunAnalyzeRequestTask(
const RefPtr<nsIContentAnalysisRequest>& aRequest, bool aAutoAcknowledge,
+ int64_t aRequestCount,
const RefPtr<nsIContentAnalysisCallback>& aCallback) {
+ MOZ_ASSERT(NS_IsMainThread());
+
nsresult rv = NS_ERROR_FAILURE;
auto callbackCopy = aCallback;
auto se = MakeScopeExit([&] {
@@ -797,23 +996,52 @@ nsresult ContentAnalysis::RunAnalyzeRequestTask(
}
});
- content_analysis::sdk::ContentAnalysisRequest pbRequest;
- rv = ConvertToProtobuf(aRequest, &pbRequest);
+ nsCString requestToken;
+ rv = aRequest->GetRequestToken(requestToken);
NS_ENSURE_SUCCESS(rv, rv);
- nsCString requestToken;
nsMainThreadPtrHandle<nsIContentAnalysisCallback> callbackHolderCopy(
new nsMainThreadPtrHolder<nsIContentAnalysisCallback>(
"content analysis callback", aCallback));
CallbackData callbackData(std::move(callbackHolderCopy), aAutoAcknowledge);
- rv = aRequest->GetRequestToken(requestToken);
- NS_ENSURE_SUCCESS(rv, rv);
{
auto lock = mCallbackMap.Lock();
lock->InsertOrUpdate(requestToken, std::move(callbackData));
}
+ // Check URLs of requested info against
+ // browser.contentanalysis.allow_url_regex_list/deny_url_regex_list.
+ // Build the list once since creating regexs is slow.
+ // URLs that match the allow list are removed from the check. There is
+ // only one URL in all cases except downloads. If all contents are removed
+ // or the page URL is allowed (for downloads) then the operation is allowed.
+ // URLs that match the deny list block the entire operation.
+ // If the request is completely covered by this filter then flag it as
+ // not needing to send an Acknowledge.
+ auto filterResult = FilterByUrlLists(aRequest);
+ if (filterResult == ContentAnalysis::UrlFilterResult::eDeny) {
+ LOGD("Blocking request due to deny URL filter.");
+ auto response = ContentAnalysisResponse::FromAction(
+ nsIContentAnalysisResponse::Action::eBlock, requestToken);
+ response->DoNotAcknowledge();
+ IssueResponse(response);
+ return NS_OK;
+ }
+ if (filterResult == ContentAnalysis::UrlFilterResult::eAllow) {
+ LOGD("Allowing request -- all operations match allow URL filter.");
+ auto response = ContentAnalysisResponse::FromAction(
+ nsIContentAnalysisResponse::Action::eAllow, requestToken);
+ response->DoNotAcknowledge();
+ IssueResponse(response);
+ return NS_OK;
+ }
+
LOGD("Issuing ContentAnalysisRequest for token %s", requestToken.get());
+
+ content_analysis::sdk::ContentAnalysisRequest pbRequest;
+ rv =
+ ConvertToProtobuf(aRequest, GetUserActionId(), aRequestCount, &pbRequest);
+ NS_ENSURE_SUCCESS(rv, rv);
LogRequest(&pbRequest);
mCaClientPromise->Then(
@@ -915,75 +1143,75 @@ void ContentAnalysis::DoAnalyzeRequest(
LOGE("Content analysis got invalid response!");
return;
}
- nsCString responseRequestToken;
- nsresult requestRv = response->GetRequestToken(responseRequestToken);
- if (NS_FAILED(requestRv)) {
- LOGE(
- "Content analysis couldn't get request token "
- "from response!");
- return;
- }
- Maybe<CallbackData> maybeCallbackData;
- {
- auto callbackMap = owner->mCallbackMap.Lock();
- maybeCallbackData = callbackMap->Extract(responseRequestToken);
- }
- if (maybeCallbackData.isNothing()) {
- LOGD(
- "Content analysis did not find callback for "
- "token %s",
- responseRequestToken.get());
- return;
- }
- response->SetOwner(owner);
- if (maybeCallbackData->Canceled()) {
- // request has already been cancelled, so there's
- // nothing to do
- LOGD(
- "Content analysis got response but ignoring "
- "because it was already cancelled for token %s",
- responseRequestToken.get());
- // Note that we always acknowledge here, even if
- // autoAcknowledge isn't set, since we raise an exception
- // at the caller on cancellation.
- auto acknowledgement = MakeRefPtr<ContentAnalysisAcknowledgement>(
- nsIContentAnalysisAcknowledgement::Result::eTooLate,
- nsIContentAnalysisAcknowledgement::FinalAction::eBlock);
- response->Acknowledge(acknowledgement);
- return;
- }
+ owner->IssueResponse(response);
+ }));
+}
- LOGD(
- "Content analysis resolving response promise for "
- "token %s",
- responseRequestToken.get());
- nsIContentAnalysisResponse::Action action = response->GetAction();
- nsCOMPtr<nsIObserverService> obsServ =
- mozilla::services::GetObserverService();
- if (action == nsIContentAnalysisResponse::Action::eWarn) {
- {
- auto warnResponseDataMap = owner->mWarnResponseDataMap.Lock();
- warnResponseDataMap->InsertOrUpdate(
- responseRequestToken,
- WarnResponseData(std::move(*maybeCallbackData), response));
- }
- obsServ->NotifyObservers(response, "dlp-response", nullptr);
- return;
- }
+void ContentAnalysis::IssueResponse(RefPtr<ContentAnalysisResponse>& response) {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCString responseRequestToken;
+ nsresult requestRv = response->GetRequestToken(responseRequestToken);
+ if (NS_FAILED(requestRv)) {
+ LOGE("Content analysis couldn't get request token from response!");
+ return;
+ }
- obsServ->NotifyObservers(response, "dlp-response", nullptr);
- if (maybeCallbackData->AutoAcknowledge()) {
- auto acknowledgement = MakeRefPtr<ContentAnalysisAcknowledgement>(
- nsIContentAnalysisAcknowledgement::Result::eSuccess,
- ConvertResult(action));
- response->Acknowledge(acknowledgement);
- }
+ Maybe<CallbackData> maybeCallbackData;
+ {
+ auto callbackMap = mCallbackMap.Lock();
+ maybeCallbackData = callbackMap->Extract(responseRequestToken);
+ }
+ if (maybeCallbackData.isNothing()) {
+ LOGD("Content analysis did not find callback for token %s",
+ responseRequestToken.get());
+ return;
+ }
+ response->SetOwner(this);
+ if (maybeCallbackData->Canceled()) {
+ // request has already been cancelled, so there's
+ // nothing to do
+ LOGD(
+ "Content analysis got response but ignoring "
+ "because it was already cancelled for token %s",
+ responseRequestToken.get());
+ // Note that we always acknowledge here, even if
+ // autoAcknowledge isn't set, since we raise an exception
+ // at the caller on cancellation.
+ auto acknowledgement = MakeRefPtr<ContentAnalysisAcknowledgement>(
+ nsIContentAnalysisAcknowledgement::Result::eTooLate,
+ nsIContentAnalysisAcknowledgement::FinalAction::eBlock);
+ response->Acknowledge(acknowledgement);
+ return;
+ }
- nsMainThreadPtrHandle<nsIContentAnalysisCallback> callbackHolder =
- maybeCallbackData->TakeCallbackHolder();
- callbackHolder->ContentResult(response);
- }));
+ LOGD("Content analysis resolving response promise for token %s",
+ responseRequestToken.get());
+ nsIContentAnalysisResponse::Action action = response->GetAction();
+ nsCOMPtr<nsIObserverService> obsServ =
+ mozilla::services::GetObserverService();
+ if (action == nsIContentAnalysisResponse::Action::eWarn) {
+ {
+ auto warnResponseDataMap = mWarnResponseDataMap.Lock();
+ warnResponseDataMap->InsertOrUpdate(
+ responseRequestToken,
+ WarnResponseData(std::move(*maybeCallbackData), response));
+ }
+ obsServ->NotifyObservers(response, "dlp-response", nullptr);
+ return;
+ }
+
+ obsServ->NotifyObservers(response, "dlp-response", nullptr);
+ if (maybeCallbackData->AutoAcknowledge()) {
+ auto acknowledgement = MakeRefPtr<ContentAnalysisAcknowledgement>(
+ nsIContentAnalysisAcknowledgement::Result::eSuccess,
+ ConvertResult(action));
+ response->Acknowledge(acknowledgement);
+ }
+
+ nsMainThreadPtrHandle<nsIContentAnalysisCallback> callbackHolder =
+ maybeCallbackData->TakeCallbackHolder();
+ callbackHolder->ContentResult(response);
}
NS_IMETHODIMP
@@ -1018,7 +1246,11 @@ ContentAnalysis::AnalyzeContentRequestCallback(
mozilla::services::GetObserverService();
obsServ->NotifyObservers(aRequest, "dlp-request-made", nullptr);
- return RunAnalyzeRequestTask(aRequest, aAutoAcknowledge, aCallback);
+ MOZ_ASSERT(NS_IsMainThread());
+ // since we're on the main thread, don't need to synchronize this
+ int64_t requestCount = ++mRequestCount;
+ return RunAnalyzeRequestTask(aRequest, aAutoAcknowledge, requestCount,
+ aCallback);
}
NS_IMETHODIMP
@@ -1047,6 +1279,33 @@ ContentAnalysis::CancelContentAnalysisRequest(const nsACString& aRequestToken) {
}
NS_IMETHODIMP
+ContentAnalysis::CancelAllRequests() {
+ mCaClientPromise->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&](std::shared_ptr<content_analysis::sdk::Client> client) {
+ auto owner = GetContentAnalysisFromService();
+ if (!owner) {
+ // May be shutting down
+ return;
+ }
+ if (!client) {
+ LOGE("CancelAllRequests got a null client");
+ return;
+ }
+ content_analysis::sdk::ContentAnalysisCancelRequests requests;
+ requests.set_user_action_id(owner->GetUserActionId().get());
+ int err = client->CancelRequests(requests);
+ if (err != 0) {
+ LOGE("CancelAllRequests got error %d", err);
+ } else {
+ LOGD("CancelAllRequests did cancelling of requests");
+ }
+ },
+ [&](nsresult rv) { LOGE("CancelAllRequests failed to get the client"); });
+ return NS_OK;
+}
+
+NS_IMETHODIMP
ContentAnalysis::RespondToWarnDialog(const nsACString& aRequestToken,
bool aAllowContent) {
nsCString requestToken(aRequestToken);
@@ -1108,6 +1367,10 @@ ContentAnalysisResponse::Acknowledge(
return NS_ERROR_FAILURE;
}
mHasAcknowledged = true;
+
+ if (mDoNotAcknowledge) {
+ return NS_OK;
+ }
return mOwner->RunAcknowledgeTask(aAcknowledgement, mRequestToken);
};
diff --git a/toolkit/components/contentanalysis/ContentAnalysis.h b/toolkit/components/contentanalysis/ContentAnalysis.h
index 4579a7113d..17b6e3fc1b 100644
--- a/toolkit/components/contentanalysis/ContentAnalysis.h
+++ b/toolkit/components/contentanalysis/ContentAnalysis.h
@@ -7,15 +7,25 @@
#define mozilla_contentanalysis_h
#include "mozilla/DataMutex.h"
-#include "mozilla/dom/WindowGlobalParent.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/dom/Promise.h"
#include "nsIContentAnalysis.h"
#include "nsProxyRelease.h"
#include "nsString.h"
#include "nsTHashMap.h"
#include <atomic>
+#include <regex>
#include <string>
+class nsIPrincipal;
+class ContentAnalysisTest;
+
+namespace mozilla::dom {
+class DataTransfer;
+class WindowGlobalParent;
+} // namespace mozilla::dom
+
namespace content_analysis::sdk {
class Client;
class ContentAnalysisRequest;
@@ -75,6 +85,8 @@ class ContentAnalysisRequest final : public nsIContentAnalysisRequest {
nsString mOperationDisplayString;
RefPtr<dom::WindowGlobalParent> mWindowGlobalParent;
+
+ friend class ::ContentAnalysisTest;
};
#define CONTENTANALYSIS_IID \
@@ -92,6 +104,7 @@ class ContentAnalysis final : public nsIContentAnalysis {
NS_DECL_NSICONTENTANALYSIS
ContentAnalysis();
+ nsCString GetUserActionId();
private:
~ContentAnalysis();
@@ -99,27 +112,42 @@ class ContentAnalysis final : public nsIContentAnalysis {
ContentAnalysis(const ContentAnalysis&) = delete;
ContentAnalysis& operator=(ContentAnalysis&) = delete;
nsresult CreateContentAnalysisClient(nsCString&& aPipePathName,
+ nsString&& aClientSignatureSetting,
bool aIsPerUser);
nsresult RunAnalyzeRequestTask(
const RefPtr<nsIContentAnalysisRequest>& aRequest, bool aAutoAcknowledge,
+ int64_t aRequestCount,
const RefPtr<nsIContentAnalysisCallback>& aCallback);
nsresult RunAcknowledgeTask(
nsIContentAnalysisAcknowledgement* aAcknowledgement,
const nsACString& aRequestToken);
nsresult CancelWithError(nsCString aRequestToken, nsresult aResult);
+ void GenerateUserActionId();
static RefPtr<ContentAnalysis> GetContentAnalysisFromService();
static void DoAnalyzeRequest(
nsCString aRequestToken,
content_analysis::sdk::ContentAnalysisRequest&& aRequest,
const std::shared_ptr<content_analysis::sdk::Client>& aClient);
+ void IssueResponse(RefPtr<ContentAnalysisResponse>& response);
+
+ // Did the URL filter completely handle the request or do we need to check
+ // with the agent.
+ enum UrlFilterResult { eCheck, eDeny, eAllow };
+
+ UrlFilterResult FilterByUrlLists(nsIContentAnalysisRequest* aRequest);
+ void EnsureParsedUrlFilters();
using ClientPromise =
MozPromise<std::shared_ptr<content_analysis::sdk::Client>, nsresult,
false>;
+ nsCString mUserActionId;
+ int64_t mRequestCount = 0;
RefPtr<ClientPromise::Private> mCaClientPromise;
// Only accessed from the main thread
bool mClientCreationAttempted;
+ bool mSetByEnterprise;
+
class CallbackData final {
public:
CallbackData(
@@ -150,7 +178,12 @@ class ContentAnalysis final : public nsIContentAnalysis {
};
DataMutex<nsTHashMap<nsCString, WarnResponseData>> mWarnResponseDataMap;
+ std::vector<std::regex> mAllowUrlList;
+ std::vector<std::regex> mDenyUrlList;
+ bool mParsedUrlLists;
+
friend class ContentAnalysisResponse;
+ friend class ::ContentAnalysisTest;
};
NS_DEFINE_STATIC_IID_ACCESSOR(ContentAnalysis, CONTENTANALYSIS_IID)
@@ -164,6 +197,7 @@ class ContentAnalysisResponse final : public nsIContentAnalysisResponse {
Action aAction, const nsACString& aRequestToken);
void SetOwner(RefPtr<ContentAnalysis> aOwner);
+ void DoNotAcknowledge() { mDoNotAcknowledge = true; }
private:
~ContentAnalysisResponse() = default;
@@ -189,7 +223,11 @@ class ContentAnalysisResponse final : public nsIContentAnalysisResponse {
RefPtr<ContentAnalysis> mOwner;
// Whether the response has been acknowledged
- bool mHasAcknowledged;
+ bool mHasAcknowledged = false;
+
+ // If true, the request was completely handled by URL filter lists, so it
+ // was not sent to the agent and should not send an Acknowledge.
+ bool mDoNotAcknowledge = false;
friend class ContentAnalysis;
};
diff --git a/toolkit/components/contentanalysis/moz.build b/toolkit/components/contentanalysis/moz.build
index e602d30302..99b57711c6 100644
--- a/toolkit/components/contentanalysis/moz.build
+++ b/toolkit/components/contentanalysis/moz.build
@@ -1,62 +1,62 @@
-# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
-# vim: set filetype=python:
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-with Files("**"):
- BUG_COMPONENT = ("Toolkit", "General")
-
-UNIFIED_SOURCES += [
- "content_analysis/sdk/analysis.pb.cc",
- "ContentAnalysis.cpp",
-]
-
-UNIFIED_SOURCES += [
- "../../../third_party/content_analysis_sdk/browser/src/client_base.cc",
-]
-
-EXPORTS += ["ContentAnalysis.h"]
-
-EXPORTS.mozilla.contentanalysis += [
- "ContentAnalysisIPCTypes.h",
-]
-
-if CONFIG["OS_ARCH"] == "WINNT":
- UNIFIED_SOURCES += [
- "../../../third_party/content_analysis_sdk/browser/src/client_win.cc",
- "../../../third_party/content_analysis_sdk/common/utils_win.cc",
- ]
-elif CONFIG["OS_ARCH"] == "Darwin":
- UNIFIED_SOURCES += [
- "../../../third_party/content_analysis_sdk/browser/src/client_mac.cc",
- ]
-else:
- UNIFIED_SOURCES += [
- "../../../third_party/content_analysis_sdk/browser/src/client_posix.cc",
- ]
-
-LOCAL_INCLUDES += [
- "../../../third_party/content_analysis_sdk",
- "../../../third_party/content_analysis_sdk/browser/include",
- "content_analysis/sdk/",
-]
-
-XPIDL_SOURCES += [
- "nsIContentAnalysis.idl",
-]
-
-XPIDL_MODULE = "toolkit_contentanalysis"
-
-XPCOM_MANIFESTS += [
- "components.conf",
-]
-
-include("/ipc/chromium/chromium-config.mozbuild")
-
-DEFINES["GOOGLE_PROTOBUF_NO_RTTI"] = True
-DEFINES["GOOGLE_PROTOBUF_NO_STATIC_INITIALIZER"] = True
-
-FINAL_LIBRARY = "xul"
-
-TEST_DIRS += ["tests/gtest"]
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Toolkit", "General")
+
+UNIFIED_SOURCES += [
+ "content_analysis/sdk/analysis.pb.cc",
+ "ContentAnalysis.cpp",
+]
+
+UNIFIED_SOURCES += [
+ "../../../third_party/content_analysis_sdk/browser/src/client_base.cc",
+]
+
+EXPORTS += ["ContentAnalysis.h"]
+
+EXPORTS.mozilla.contentanalysis += [
+ "ContentAnalysisIPCTypes.h",
+]
+
+if CONFIG["OS_ARCH"] == "WINNT":
+ UNIFIED_SOURCES += [
+ "../../../third_party/content_analysis_sdk/browser/src/client_win.cc",
+ "../../../third_party/content_analysis_sdk/common/utils_win.cc",
+ ]
+elif CONFIG["OS_ARCH"] == "Darwin":
+ UNIFIED_SOURCES += [
+ "../../../third_party/content_analysis_sdk/browser/src/client_mac.cc",
+ ]
+else:
+ UNIFIED_SOURCES += [
+ "../../../third_party/content_analysis_sdk/browser/src/client_posix.cc",
+ ]
+
+LOCAL_INCLUDES += [
+ "../../../third_party/content_analysis_sdk",
+ "../../../third_party/content_analysis_sdk/browser/include",
+ "content_analysis/sdk/",
+]
+
+XPIDL_SOURCES += [
+ "nsIContentAnalysis.idl",
+]
+
+XPIDL_MODULE = "toolkit_contentanalysis"
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+DEFINES["GOOGLE_PROTOBUF_NO_RTTI"] = True
+DEFINES["GOOGLE_PROTOBUF_NO_STATIC_INITIALIZER"] = True
+
+FINAL_LIBRARY = "xul"
+
+TEST_DIRS += ["tests"]
diff --git a/toolkit/components/contentanalysis/nsIContentAnalysis.idl b/toolkit/components/contentanalysis/nsIContentAnalysis.idl
index 142c4c3cd3..21f98b88e6 100644
--- a/toolkit/components/contentanalysis/nsIContentAnalysis.idl
+++ b/toolkit/components/contentanalysis/nsIContentAnalysis.idl
@@ -185,6 +185,13 @@ interface nsIContentAnalysis : nsISupports
readonly attribute bool mightBeActive;
/**
+ * True if content-analysis activation was determined by enterprise policy,
+ * as opposed to enabled with the `allow-content-analysis` command-line
+ * parameter.
+ */
+ attribute bool isSetByEnterprisePolicy;
+
+ /**
* Consults content analysis server, if any, to request a permission
* decision for a network operation. Allows blocking downloading/
* uploading/printing/etc, based on the request.
@@ -236,4 +243,15 @@ interface nsIContentAnalysis : nsISupports
* whether the user wants to allow the request to go through.
*/
void respondToWarnDialog(in ACString aRequestToken, in bool aAllowContent);
+
+ /**
+ * Cancels all outstanding DLP requests. Used on shutdown.
+ */
+ void cancelAllRequests();
+
+ /**
+ * Test-only function that pretends that "-allow-content-analysis" was
+ * given to Gecko on the command line.
+ */
+ void testOnlySetCACmdLineArg(in boolean aVal);
};
diff --git a/toolkit/components/contentanalysis/tests/browser/browser.toml b/toolkit/components/contentanalysis/tests/browser/browser.toml
new file mode 100644
index 0000000000..0e21090299
--- /dev/null
+++ b/toolkit/components/contentanalysis/tests/browser/browser.toml
@@ -0,0 +1,3 @@
+[DEFAULT]
+
+["browser_content_analysis_policies.js"]
diff --git a/toolkit/components/contentanalysis/tests/browser/browser_content_analysis_policies.js b/toolkit/components/contentanalysis/tests/browser/browser_content_analysis_policies.js
new file mode 100644
index 0000000000..e2e001e9d1
--- /dev/null
+++ b/toolkit/components/contentanalysis/tests/browser/browser_content_analysis_policies.js
@@ -0,0 +1,127 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Check that CA is active if and only if:
+// 1. browser.contentanalysis.enabled is true and
+// 2. Either browser.contentanalysis.enabled was set by an enteprise
+// policy or the "-allow-content-analysis" command line arg was present
+// We can't really test command line arguments so we instead use a test-only
+// method to set the value the command-line is supposed to update.
+
+"use strict";
+
+const { EnterprisePolicyTesting } = ChromeUtils.importESModule(
+ "resource://testing-common/EnterprisePolicyTesting.sys.mjs"
+);
+
+const kEnabledPref = "enabled";
+const kPipeNamePref = "pipe_path_name";
+const kTimeoutPref = "agent_timeout";
+const kAllowUrlPref = "allow_url_regex_list";
+const kDenyUrlPref = "deny_url_regex_list";
+const kPerUserPref = "is_per_user";
+const kShowBlockedPref = "show_blocked_result";
+const kDefaultAllowPref = "default_allow";
+
+const ca = Cc["@mozilla.org/contentanalysis;1"].getService(
+ Ci.nsIContentAnalysis
+);
+
+add_task(async function test_ca_active() {
+ ok(!ca.isActive, "CA is inactive when pref and cmd line arg are missing");
+
+ // Set the pref without enterprise policy. CA should not be active.
+ Services.prefs.setBoolPref("browser.contentanalysis." + kEnabledPref, true);
+ ok(
+ !ca.isActive,
+ "CA is inactive when pref is set but cmd line arg is missing"
+ );
+
+ // Set the pref without enterprise policy but also set command line arg
+ // property. CA should be active.
+ ca.testOnlySetCACmdLineArg(true);
+ ok(ca.isActive, "CA is active when pref is set and cmd line arg is present");
+
+ // Undo test-only value before later tests.
+ ca.testOnlySetCACmdLineArg(false);
+ ok(!ca.isActive, "properly unset cmd line arg value");
+
+ // Disabled the pref with enterprise policy. CA should not be active.
+ await EnterprisePolicyTesting.setupPolicyEngineWithJson({
+ policies: {
+ ContentAnalysis: { Enabled: false },
+ },
+ });
+ ok(!ca.isActive, "CA is inactive when disabled by enterprise policy pref");
+
+ // Enabled the pref with enterprise policy. CA should be active.
+ await EnterprisePolicyTesting.setupPolicyEngineWithJson({
+ policies: {
+ ContentAnalysis: { Enabled: true },
+ },
+ });
+ ok(ca.isActive, "CA is active when enabled by enterprise policy pref");
+});
+
+add_task(async function test_ca_enterprise_config() {
+ const string1 = "this is a string";
+ const string2 = "this is another string";
+
+ await EnterprisePolicyTesting.setupPolicyEngineWithJson({
+ policies: {
+ ContentAnalysis: {
+ PipePathName: "abc",
+ AgentTimeout: 99,
+ AllowUrlRegexList: string1,
+ DenyUrlRegexList: string2,
+ IsPerUser: true,
+ ShowBlockedResult: false,
+ DefaultAllow: true,
+ },
+ },
+ });
+
+ is(
+ Services.prefs.getStringPref("browser.contentanalysis." + kPipeNamePref),
+ "abc",
+ "pipe name match"
+ );
+ is(
+ Services.prefs.getIntPref("browser.contentanalysis." + kTimeoutPref),
+ 99,
+ "timeout match"
+ );
+ is(
+ Services.prefs.getStringPref("browser.contentanalysis." + kAllowUrlPref),
+ string1,
+ "allow urls match"
+ );
+ is(
+ Services.prefs.getStringPref("browser.contentanalysis." + kDenyUrlPref),
+ string2,
+ "deny urls match"
+ );
+ is(
+ Services.prefs.getBoolPref("browser.contentanalysis." + kPerUserPref),
+ true,
+ "per user match"
+ );
+ is(
+ Services.prefs.getBoolPref("browser.contentanalysis." + kShowBlockedPref),
+ false,
+ "show blocked match"
+ );
+ is(
+ Services.prefs.getBoolPref("browser.contentanalysis." + kDefaultAllowPref),
+ true,
+ "default allow match"
+ );
+});
+
+add_task(async function test_cleanup() {
+ ca.testOnlySetCACmdLineArg(false);
+ await EnterprisePolicyTesting.setupPolicyEngineWithJson({
+ policies: {},
+ });
+});
diff --git a/toolkit/components/contentanalysis/tests/browser/moz.build b/toolkit/components/contentanalysis/tests/browser/moz.build
new file mode 100644
index 0000000000..cfd5452a0e
--- /dev/null
+++ b/toolkit/components/contentanalysis/tests/browser/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+BROWSER_CHROME_MANIFESTS += ["browser.toml"]
diff --git a/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysis.cpp b/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysis.cpp
index 1cf6d8fc22..cd083a7779 100644
--- a/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysis.cpp
+++ b/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysis.cpp
@@ -6,127 +6,121 @@
#include "gtest/gtest.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/Assertions.h"
-#include "mozilla/CmdLineAndEnvUtils.h"
-#include "content_analysis/sdk/analysis_client.h"
-#include "TestContentAnalysis.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Preferences.h"
+#include "nsNetUtil.h"
+#include "ContentAnalysis.h"
#include <processenv.h>
#include <synchapi.h>
-using namespace content_analysis::sdk;
+const char* kAllowUrlPref = "browser.contentanalysis.allow_url_regex_list";
+const char* kDenyUrlPref = "browser.contentanalysis.deny_url_regex_list";
-MozAgentInfo LaunchAgentNormal(const wchar_t* aToBlock) {
- nsString cmdLineArguments;
- if (aToBlock && aToBlock[0] != 0) {
- cmdLineArguments.Append(L" --toblock=.*");
- cmdLineArguments.Append(aToBlock);
- cmdLineArguments.Append(L".*");
+using namespace mozilla;
+using namespace mozilla::contentanalysis;
+
+class ContentAnalysisTest : public testing::Test {
+ protected:
+ ContentAnalysisTest() {
+ auto* logmodule = LogModule::Get("contentanalysis");
+ logmodule->SetLevel(LogLevel::Verbose);
+
+ nsCOMPtr<nsIContentAnalysis> caSvc =
+ do_GetService("@mozilla.org/contentanalysis;1");
+ MOZ_ASSERT(caSvc);
+ mContentAnalysis = static_cast<ContentAnalysis*>(caSvc.get());
+
+ // Tests run earlier could have altered these values
+ mContentAnalysis->mParsedUrlLists = false;
+ mContentAnalysis->mAllowUrlList = {};
+ mContentAnalysis->mDenyUrlList = {};
+
+ MOZ_ALWAYS_SUCCEEDS(Preferences::SetCString(kAllowUrlPref, ""));
+ MOZ_ALWAYS_SUCCEEDS(Preferences::SetCString(kDenyUrlPref, ""));
+ }
+
+ void TearDown() override {
+ mContentAnalysis->mParsedUrlLists = false;
+ mContentAnalysis->mAllowUrlList = {};
+ mContentAnalysis->mDenyUrlList = {};
+
+ MOZ_ALWAYS_SUCCEEDS(Preferences::SetCString(kAllowUrlPref, ""));
+ MOZ_ALWAYS_SUCCEEDS(Preferences::SetCString(kDenyUrlPref, ""));
}
- cmdLineArguments.Append(L" --user");
- cmdLineArguments.Append(L" --path=");
- nsString pipeName;
- GeneratePipeName(L"contentanalysissdk-gtest-", pipeName);
- cmdLineArguments.Append(pipeName);
- MozAgentInfo agentInfo;
- LaunchAgentWithCommandLineArguments(cmdLineArguments, pipeName, agentInfo);
- return agentInfo;
+
+ already_AddRefed<nsIContentAnalysisRequest> CreateRequest(const char* aUrl) {
+ nsCOMPtr<nsIURI> uri;
+ MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri), aUrl));
+ // We will only use the URL and, implicitly, the analysisType
+ // (behavior differs for download vs other types).
+ return RefPtr(new ContentAnalysisRequest(
+ nsIContentAnalysisRequest::AnalysisType::eFileTransfer,
+ EmptyString(), false, EmptyCString(), uri,
+ nsIContentAnalysisRequest::OperationType::eDroppedText,
+ nullptr))
+ .forget();
+ }
+
+ RefPtr<ContentAnalysis> mContentAnalysis;
+
+ // Proxies for private members of ContentAnalysis. TEST_F
+ // creates new subclasses -- they do not inherit `friend`s.
+ // (FRIEND_TEST is another more verbose solution.)
+ using UrlFilterResult = ContentAnalysis::UrlFilterResult;
+ UrlFilterResult FilterByUrlLists(nsIContentAnalysisRequest* aReq) {
+ return mContentAnalysis->FilterByUrlLists(aReq);
+ }
+};
+
+TEST_F(ContentAnalysisTest, AllowUrlList) {
+ MOZ_ALWAYS_SUCCEEDS(
+ Preferences::SetCString(kAllowUrlPref, ".*\\.org/match.*"));
+ RefPtr<nsIContentAnalysisRequest> car =
+ CreateRequest("https://example.org/matchme/");
+ ASSERT_EQ(FilterByUrlLists(car), UrlFilterResult::eAllow);
+ car = CreateRequest("https://example.com/matchme/");
+ ASSERT_EQ(FilterByUrlLists(car), UrlFilterResult::eCheck);
}
-TEST(ContentAnalysis, TextShouldNotBeBlocked)
-{
- auto MozAgentInfo = LaunchAgentNormal(L"block");
- // Exit the test early if the process failed to launch
- ASSERT_NE(MozAgentInfo.processInfo.dwProcessId, 0UL);
- ASSERT_NE(nullptr, MozAgentInfo.client.get());
-
- ContentAnalysisRequest request;
- request.set_request_token("request token");
- request.set_text_content("should succeed");
- ContentAnalysisResponse response;
- ASSERT_EQ(0, MozAgentInfo.client->Send(request, &response));
- ASSERT_STREQ("request token", response.request_token().c_str());
- ASSERT_EQ(1, response.results().size());
- ASSERT_EQ(ContentAnalysisResponse_Result_Status_SUCCESS,
- response.results().Get(0).status());
- ASSERT_EQ(0, response.results().Get(0).triggered_rules_size());
-
- BOOL terminateResult =
- ::TerminateProcess(MozAgentInfo.processInfo.hProcess, 0);
- ASSERT_NE(FALSE, terminateResult)
- << "Failed to terminate content_analysis_sdk_agent process";
+TEST_F(ContentAnalysisTest, MultipleAllowUrlList) {
+ MOZ_ALWAYS_SUCCEEDS(Preferences::SetCString(
+ kAllowUrlPref, ".*\\.org/match.* .*\\.net/match.*"));
+ RefPtr<nsIContentAnalysisRequest> car =
+ CreateRequest("https://example.org/matchme/");
+ ASSERT_EQ(FilterByUrlLists(car), UrlFilterResult::eAllow);
+ car = CreateRequest("https://example.net/matchme/");
+ ASSERT_EQ(FilterByUrlLists(car), UrlFilterResult::eAllow);
+ car = CreateRequest("https://example.com/matchme/");
+ ASSERT_EQ(FilterByUrlLists(car), UrlFilterResult::eCheck);
}
-TEST(ContentAnalysis, TextShouldBeBlocked)
-{
- auto MozAgentInfo = LaunchAgentNormal(L"block");
- // Exit the test early if the process failed to launch
- ASSERT_NE(MozAgentInfo.processInfo.dwProcessId, 0UL);
- ASSERT_NE(nullptr, MozAgentInfo.client.get());
-
- ContentAnalysisRequest request;
- request.set_request_token("request token");
- request.set_text_content("should be blocked");
- ContentAnalysisResponse response;
- ASSERT_EQ(0, MozAgentInfo.client->Send(request, &response));
- ASSERT_STREQ("request token", response.request_token().c_str());
- ASSERT_EQ(1, response.results().size());
- ASSERT_EQ(ContentAnalysisResponse_Result_Status_SUCCESS,
- response.results().Get(0).status());
- ASSERT_EQ(1, response.results().Get(0).triggered_rules_size());
- ASSERT_EQ(ContentAnalysisResponse_Result_TriggeredRule_Action_BLOCK,
- response.results().Get(0).triggered_rules(0).action());
-
- BOOL terminateResult =
- ::TerminateProcess(MozAgentInfo.processInfo.hProcess, 0);
- ASSERT_NE(FALSE, terminateResult)
- << "Failed to terminate content_analysis_sdk_agent process";
+TEST_F(ContentAnalysisTest, DenyUrlList) {
+ MOZ_ALWAYS_SUCCEEDS(
+ Preferences::SetCString(kDenyUrlPref, ".*\\.com/match.*"));
+ RefPtr<nsIContentAnalysisRequest> car =
+ CreateRequest("https://example.org/matchme/");
+ ASSERT_EQ(FilterByUrlLists(car), UrlFilterResult::eCheck);
+ car = CreateRequest("https://example.com/matchme/");
+ ASSERT_EQ(FilterByUrlLists(car), UrlFilterResult::eDeny);
}
-TEST(ContentAnalysis, FileShouldNotBeBlocked)
-{
- auto MozAgentInfo = LaunchAgentNormal(L"block");
- // Exit the test early if the process failed to launch
- ASSERT_NE(MozAgentInfo.processInfo.dwProcessId, 0UL);
- ASSERT_NE(nullptr, MozAgentInfo.client.get());
-
- ContentAnalysisRequest request;
- request.set_request_token("request token");
- request.set_file_path("..\\..\\_tests\\gtest\\allowedFile.txt");
- ContentAnalysisResponse response;
- ASSERT_EQ(0, MozAgentInfo.client->Send(request, &response));
- ASSERT_STREQ("request token", response.request_token().c_str());
- ASSERT_EQ(1, response.results().size());
- ASSERT_EQ(ContentAnalysisResponse_Result_Status_SUCCESS,
- response.results().Get(0).status());
- ASSERT_EQ(0, response.results().Get(0).triggered_rules_size());
-
- BOOL terminateResult =
- ::TerminateProcess(MozAgentInfo.processInfo.hProcess, 0);
- ASSERT_NE(FALSE, terminateResult)
- << "Failed to terminate content_analysis_sdk_agent process";
+TEST_F(ContentAnalysisTest, MultipleDenyUrlList) {
+ MOZ_ALWAYS_SUCCEEDS(Preferences::SetCString(
+ kDenyUrlPref, ".*\\.com/match.* .*\\.biz/match.*"));
+ RefPtr<nsIContentAnalysisRequest> car =
+ CreateRequest("https://example.org/matchme/");
+ ASSERT_EQ(FilterByUrlLists(car), UrlFilterResult::eCheck);
+ car = CreateRequest("https://example.com/matchme/");
+ ASSERT_EQ(FilterByUrlLists(car), UrlFilterResult::eDeny);
+ car = CreateRequest("https://example.biz/matchme/");
+ ASSERT_EQ(FilterByUrlLists(car), UrlFilterResult::eDeny);
}
-TEST(ContentAnalysis, FileShouldBeBlocked)
-{
- auto MozAgentInfo = LaunchAgentNormal(L"block");
- // Exit the test early if the process failed to launch
- ASSERT_NE(MozAgentInfo.processInfo.dwProcessId, 0UL);
- ASSERT_NE(nullptr, MozAgentInfo.client.get());
-
- ContentAnalysisRequest request;
- request.set_request_token("request token");
- request.set_file_path("..\\..\\_tests\\gtest\\blockedFile.txt");
- ContentAnalysisResponse response;
- ASSERT_EQ(0, MozAgentInfo.client->Send(request, &response));
- ASSERT_STREQ("request token", response.request_token().c_str());
- ASSERT_EQ(1, response.results().size());
- ASSERT_EQ(ContentAnalysisResponse_Result_Status_SUCCESS,
- response.results().Get(0).status());
- ASSERT_EQ(1, response.results().Get(0).triggered_rules_size());
- ASSERT_EQ(ContentAnalysisResponse_Result_TriggeredRule_Action_BLOCK,
- response.results().Get(0).triggered_rules(0).action());
-
- BOOL terminateResult =
- ::TerminateProcess(MozAgentInfo.processInfo.hProcess, 0);
- ASSERT_NE(FALSE, terminateResult)
- << "Failed to terminate content_analysis_sdk_agent process";
+TEST_F(ContentAnalysisTest, DenyOverridesAllowUrlList) {
+ MOZ_ALWAYS_SUCCEEDS(
+ Preferences::SetCString(kAllowUrlPref, ".*\\.org/match.*"));
+ MOZ_ALWAYS_SUCCEEDS(Preferences::SetCString(kDenyUrlPref, ".*.org/match.*"));
+ RefPtr<nsIContentAnalysisRequest> car =
+ CreateRequest("https://example.org/matchme/");
+ ASSERT_EQ(FilterByUrlLists(car), UrlFilterResult::eDeny);
}
diff --git a/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisAgent.cpp b/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisAgent.cpp
new file mode 100644
index 0000000000..5b2b76b963
--- /dev/null
+++ b/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisAgent.cpp
@@ -0,0 +1,132 @@
+/* -*- Mode: C++; tab-width: 8; 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 https://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/CmdLineAndEnvUtils.h"
+#include "content_analysis/sdk/analysis_client.h"
+#include "TestContentAnalysisAgent.h"
+#include <processenv.h>
+#include <synchapi.h>
+
+using namespace content_analysis::sdk;
+
+MozAgentInfo LaunchAgentNormal(const wchar_t* aToBlock) {
+ nsString cmdLineArguments;
+ if (aToBlock && aToBlock[0] != 0) {
+ cmdLineArguments.Append(L" --toblock=.*");
+ cmdLineArguments.Append(aToBlock);
+ cmdLineArguments.Append(L".*");
+ }
+ cmdLineArguments.Append(L" --user");
+ cmdLineArguments.Append(L" --path=");
+ nsString pipeName;
+ GeneratePipeName(L"contentanalysissdk-gtest-", pipeName);
+ cmdLineArguments.Append(pipeName);
+ MozAgentInfo agentInfo;
+ LaunchAgentWithCommandLineArguments(cmdLineArguments, pipeName, agentInfo);
+ return agentInfo;
+}
+
+TEST(ContentAnalysisAgent, TextShouldNotBeBlocked)
+{
+ auto MozAgentInfo = LaunchAgentNormal(L"block");
+ // Exit the test early if the process failed to launch
+ ASSERT_NE(MozAgentInfo.processInfo.dwProcessId, 0UL);
+ ASSERT_NE(nullptr, MozAgentInfo.client.get());
+
+ ContentAnalysisRequest request;
+ request.set_request_token("request token");
+ request.set_text_content("should succeed");
+ ContentAnalysisResponse response;
+ ASSERT_EQ(0, MozAgentInfo.client->Send(request, &response));
+ ASSERT_STREQ("request token", response.request_token().c_str());
+ ASSERT_EQ(1, response.results().size());
+ ASSERT_EQ(ContentAnalysisResponse_Result_Status_SUCCESS,
+ response.results().Get(0).status());
+ ASSERT_EQ(0, response.results().Get(0).triggered_rules_size());
+
+ BOOL terminateResult =
+ ::TerminateProcess(MozAgentInfo.processInfo.hProcess, 0);
+ ASSERT_NE(FALSE, terminateResult)
+ << "Failed to terminate content_analysis_sdk_agent process";
+}
+
+TEST(ContentAnalysisAgent, TextShouldBeBlocked)
+{
+ auto MozAgentInfo = LaunchAgentNormal(L"block");
+ // Exit the test early if the process failed to launch
+ ASSERT_NE(MozAgentInfo.processInfo.dwProcessId, 0UL);
+ ASSERT_NE(nullptr, MozAgentInfo.client.get());
+
+ ContentAnalysisRequest request;
+ request.set_request_token("request token");
+ request.set_text_content("should be blocked");
+ ContentAnalysisResponse response;
+ ASSERT_EQ(0, MozAgentInfo.client->Send(request, &response));
+ ASSERT_STREQ("request token", response.request_token().c_str());
+ ASSERT_EQ(1, response.results().size());
+ ASSERT_EQ(ContentAnalysisResponse_Result_Status_SUCCESS,
+ response.results().Get(0).status());
+ ASSERT_EQ(1, response.results().Get(0).triggered_rules_size());
+ ASSERT_EQ(ContentAnalysisResponse_Result_TriggeredRule_Action_BLOCK,
+ response.results().Get(0).triggered_rules(0).action());
+
+ BOOL terminateResult =
+ ::TerminateProcess(MozAgentInfo.processInfo.hProcess, 0);
+ ASSERT_NE(FALSE, terminateResult)
+ << "Failed to terminate content_analysis_sdk_agent process";
+}
+
+TEST(ContentAnalysisAgent, FileShouldNotBeBlocked)
+{
+ auto MozAgentInfo = LaunchAgentNormal(L"block");
+ // Exit the test early if the process failed to launch
+ ASSERT_NE(MozAgentInfo.processInfo.dwProcessId, 0UL);
+ ASSERT_NE(nullptr, MozAgentInfo.client.get());
+
+ ContentAnalysisRequest request;
+ request.set_request_token("request token");
+ request.set_file_path("..\\..\\_tests\\gtest\\allowedFile.txt");
+ ContentAnalysisResponse response;
+ ASSERT_EQ(0, MozAgentInfo.client->Send(request, &response));
+ ASSERT_STREQ("request token", response.request_token().c_str());
+ ASSERT_EQ(1, response.results().size());
+ ASSERT_EQ(ContentAnalysisResponse_Result_Status_SUCCESS,
+ response.results().Get(0).status());
+ ASSERT_EQ(0, response.results().Get(0).triggered_rules_size());
+
+ BOOL terminateResult =
+ ::TerminateProcess(MozAgentInfo.processInfo.hProcess, 0);
+ ASSERT_NE(FALSE, terminateResult)
+ << "Failed to terminate content_analysis_sdk_agent process";
+}
+
+TEST(ContentAnalysisAgent, FileShouldBeBlocked)
+{
+ auto MozAgentInfo = LaunchAgentNormal(L"block");
+ // Exit the test early if the process failed to launch
+ ASSERT_NE(MozAgentInfo.processInfo.dwProcessId, 0UL);
+ ASSERT_NE(nullptr, MozAgentInfo.client.get());
+
+ ContentAnalysisRequest request;
+ request.set_request_token("request token");
+ request.set_file_path("..\\..\\_tests\\gtest\\blockedFile.txt");
+ ContentAnalysisResponse response;
+ ASSERT_EQ(0, MozAgentInfo.client->Send(request, &response));
+ ASSERT_STREQ("request token", response.request_token().c_str());
+ ASSERT_EQ(1, response.results().size());
+ ASSERT_EQ(ContentAnalysisResponse_Result_Status_SUCCESS,
+ response.results().Get(0).status());
+ ASSERT_EQ(1, response.results().Get(0).triggered_rules_size());
+ ASSERT_EQ(ContentAnalysisResponse_Result_TriggeredRule_Action_BLOCK,
+ response.results().Get(0).triggered_rules(0).action());
+
+ BOOL terminateResult =
+ ::TerminateProcess(MozAgentInfo.processInfo.hProcess, 0);
+ ASSERT_NE(FALSE, terminateResult)
+ << "Failed to terminate content_analysis_sdk_agent process";
+}
diff --git a/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysis.h b/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisAgent.h
index 9e31036262..9e31036262 100644
--- a/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysis.h
+++ b/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisAgent.h
diff --git a/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisMisbehaving.cpp b/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisMisbehaving.cpp
index 0b005e1f6c..7c944ed6e3 100644
--- a/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisMisbehaving.cpp
+++ b/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisMisbehaving.cpp
@@ -8,7 +8,7 @@
#include "mozilla/Assertions.h"
#include "mozilla/CmdLineAndEnvUtils.h"
#include "content_analysis/sdk/analysis_client.h"
-#include "TestContentAnalysis.h"
+#include "TestContentAnalysisAgent.h"
#include <processenv.h>
#include <synchapi.h>
#include <windows.h>
diff --git a/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisUtils.cpp b/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisUtils.cpp
index fc0cca5acd..0e14de6b81 100644
--- a/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisUtils.cpp
+++ b/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisUtils.cpp
@@ -3,7 +3,7 @@
* 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/. */
-#include "TestContentAnalysis.h"
+#include "TestContentAnalysisAgent.h"
#include <combaseapi.h>
#include <pathcch.h>
#include <shlwapi.h>
diff --git a/toolkit/components/contentanalysis/tests/gtest/moz.build b/toolkit/components/contentanalysis/tests/gtest/moz.build
index ce701987a4..549e1e0e39 100644
--- a/toolkit/components/contentanalysis/tests/gtest/moz.build
+++ b/toolkit/components/contentanalysis/tests/gtest/moz.build
@@ -12,6 +12,10 @@ LOCAL_INCLUDES += [
if CONFIG["OS_TARGET"] == "WINNT":
UNIFIED_SOURCES += [
"TestContentAnalysis.cpp",
+ ]
+ SOURCES += [
+ # Agent SDK usings conflicts with Gecko usings
+ "TestContentAnalysisAgent.cpp",
"TestContentAnalysisMisbehaving.cpp",
"TestContentAnalysisUtils.cpp",
]
diff --git a/toolkit/components/contentanalysis/tests/moz.build b/toolkit/components/contentanalysis/tests/moz.build
new file mode 100644
index 0000000000..e8087ba5ed
--- /dev/null
+++ b/toolkit/components/contentanalysis/tests/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+TEST_DIRS += ["gtest", "browser"]
diff --git a/toolkit/components/contextualidentity/ContextualIdentityService.sys.mjs b/toolkit/components/contextualidentity/ContextualIdentityService.sys.mjs
index fefe162ea0..738f8743ee 100644
--- a/toolkit/components/contextualidentity/ContextualIdentityService.sys.mjs
+++ b/toolkit/components/contextualidentity/ContextualIdentityService.sys.mjs
@@ -90,7 +90,7 @@ _ContextualIdentityService.prototype = {
name: "userContextIdInternal.thumbnail",
accessKey: "",
},
- // This userContextId is used by ExtensionStorageIDB.jsm to create an IndexedDB database
+ // 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).
diff --git a/toolkit/components/corroborator/Corroborate.sys.mjs b/toolkit/components/corroborator/Corroborate.sys.mjs
index 1f06ce8412..51b447726c 100644
--- a/toolkit/components/corroborator/Corroborate.sys.mjs
+++ b/toolkit/components/corroborator/Corroborate.sys.mjs
@@ -33,10 +33,18 @@ export const Corroborate = {
lazy.gCertDB.openSignedAppFileAsync(
root,
file,
- (rv, _zipReader, cert) => {
+ (rv, _zipReader, signatureInfos) => {
+ // aSignatureInfos is an array of nsIAppSignatureInfo.
+ // This implementation could be modified to iterate through the array to
+ // determine if one or all of the verified signatures used a satisfactory
+ // algorithm and signing certificate.
+ // For now, though, it maintains existing behavior by inspecting the
+ // first signing certificate encountered.
resolve(
Components.isSuccessCode(rv) &&
- cert.organizationalUnit === expectedOrganizationalUnit
+ signatureInfos.length &&
+ signatureInfos[0].signerCert.organizationalUnit ==
+ expectedOrganizationalUnit
);
}
);
diff --git a/toolkit/components/crashes/CrashManager.in.sys.mjs b/toolkit/components/crashes/CrashManager.in.sys.mjs
index 253f70d07e..9e89c81d91 100644
--- a/toolkit/components/crashes/CrashManager.in.sys.mjs
+++ b/toolkit/components/crashes/CrashManager.in.sys.mjs
@@ -24,8 +24,7 @@ const AGGREGATE_STARTUP_DELAY_MS = 57000;
const MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000;
// Converts Date to days since UNIX epoch.
-// This was copied from /services/metrics.storage.jsm. The implementation
-// does not account for leap seconds.
+// The implementation does not account for leap seconds.
export function dateToDays(date) {
return Math.floor(date.getTime() / MILLISECONDS_IN_DAY);
}
diff --git a/toolkit/components/credentialmanagement/IdentityCredentialPromptService.sys.mjs b/toolkit/components/credentialmanagement/IdentityCredentialPromptService.sys.mjs
index 3a6b43b5b1..1274cc168c 100644
--- a/toolkit/components/credentialmanagement/IdentityCredentialPromptService.sys.mjs
+++ b/toolkit/components/credentialmanagement/IdentityCredentialPromptService.sys.mjs
@@ -273,7 +273,7 @@ export class IdentityCredentialPromptService {
let mainAction = {
label: acceptLabel,
accessKey: acceptKey,
- callback(event) {
+ callback(_event) {
let result = listBox.querySelector(
".identity-credential-list-item-radio:checked"
).value;
@@ -284,7 +284,7 @@ export class IdentityCredentialPromptService {
{
label: cancelLabel,
accessKey: cancelKey,
- callback(event) {
+ callback(_event) {
reject();
},
},
@@ -451,7 +451,7 @@ export class IdentityCredentialPromptService {
let mainAction = {
label: acceptLabel,
accessKey: acceptKey,
- callback(event) {
+ callback(_event) {
resolve(true);
},
};
@@ -459,7 +459,7 @@ export class IdentityCredentialPromptService {
{
label: cancelLabel,
accessKey: cancelKey,
- callback(event) {
+ callback(_event) {
resolve(false);
},
},
@@ -676,7 +676,7 @@ export class IdentityCredentialPromptService {
let mainAction = {
label: acceptLabel,
accessKey: acceptKey,
- callback(event) {
+ callback(_event) {
let result = listBox.querySelector(
".identity-credential-list-item-radio:checked"
).value;
@@ -687,7 +687,7 @@ export class IdentityCredentialPromptService {
{
label: cancelLabel,
accessKey: cancelKey,
- callback(event) {
+ callback(_event) {
reject();
},
},
diff --git a/toolkit/components/credentialmanagement/IdentityCredentialStorageService.cpp b/toolkit/components/credentialmanagement/IdentityCredentialStorageService.cpp
index f3994d1a86..f1549ad4f8 100644
--- a/toolkit/components/credentialmanagement/IdentityCredentialStorageService.cpp
+++ b/toolkit/components/credentialmanagement/IdentityCredentialStorageService.cpp
@@ -125,18 +125,23 @@ IdentityCredentialStorageService::GetAsyncShutdownBarrier() const {
nsresult IdentityCredentialStorageService::Init() {
AssertIsOnMainThread();
- if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
+ nsCOMPtr<nsIAsyncShutdownClient> asc = GetAsyncShutdownBarrier();
+ if (!asc) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // We should only allow this service to start before its
+ // shutdown barrier is closed, so it never leaks.
+ bool closed;
+ nsresult rv = asc->GetIsClosed(&closed);
+ if (closed || NS_WARN_IF(NS_FAILED(rv))) {
MonitorAutoLock lock(mMonitor);
mShuttingDown.Flip();
return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
}
- nsCOMPtr<nsIAsyncShutdownClient> asc = GetAsyncShutdownBarrier();
- if (!asc) {
- return NS_ERROR_NOT_AVAILABLE;
- }
- nsresult rv = asc->AddBlocker(this, NS_LITERAL_STRING_FROM_CSTRING(__FILE__),
- __LINE__, u""_ns);
+ rv = asc->AddBlocker(this, NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__,
+ u""_ns);
NS_ENSURE_SUCCESS(rv, rv);
rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
diff --git a/toolkit/components/downloads/DownloadCore.sys.mjs b/toolkit/components/downloads/DownloadCore.sys.mjs
index 1711a01784..62adf072f4 100644
--- a/toolkit/components/downloads/DownloadCore.sys.mjs
+++ b/toolkit/components/downloads/DownloadCore.sys.mjs
@@ -2027,7 +2027,7 @@ DownloadSaver.prototype = {
* @resolves When the download has finished successfully.
* @rejects JavaScript exception if the download failed.
*/
- async execute(aSetProgressBytesFn, aSetPropertiesFn) {
+ async execute() {
throw new Error("Not implemented.");
},
@@ -2052,7 +2052,7 @@ DownloadSaver.prototype = {
* @resolves When the operation has finished successfully.
* @rejects Never.
*/
- async removeData(canRemoveFinalTarget) {},
+ async removeData() {},
/**
* This can be called by the saver implementation when the download is already
diff --git a/toolkit/components/downloads/DownloadHistory.sys.mjs b/toolkit/components/downloads/DownloadHistory.sys.mjs
index 0077601d84..2ac1de45dc 100644
--- a/toolkit/components/downloads/DownloadHistory.sys.mjs
+++ b/toolkit/components/downloads/DownloadHistory.sys.mjs
@@ -728,7 +728,7 @@ class DownloadHistoryList extends DownloadList {
}
// nsINavHistoryResultObserver
- containerStateChanged(node, oldState, newState) {
+ containerStateChanged(node) {
this.invalidateContainer(node);
}
@@ -766,7 +766,7 @@ class DownloadHistoryList extends DownloadList {
}
// nsINavHistoryResultObserver
- nodeRemoved(parent, placesNode, aOldIndex) {
+ nodeRemoved(parent, placesNode) {
let slotsForUrl = this._slotsForUrl.get(placesNode.uri);
for (let slot of slotsForUrl) {
if (slot.sessionDownload) {
diff --git a/toolkit/components/downloads/DownloadIntegration.sys.mjs b/toolkit/components/downloads/DownloadIntegration.sys.mjs
index 5762f95cc2..8aed081df1 100644
--- a/toolkit/components/downloads/DownloadIntegration.sys.mjs
+++ b/toolkit/components/downloads/DownloadIntegration.sys.mjs
@@ -599,7 +599,7 @@ export var DownloadIntegration = {
* @param [optional] aExtension
* The file extension, which can match instead of the MIME type.
*/
- shouldViewDownloadInternally(aMimeType, aExtension) {
+ shouldViewDownloadInternally() {
// Refuse all files by default, this is meant to be replaced with a check
// for specific types via Integration.downloads.register().
return false;
diff --git a/toolkit/components/downloads/DownloadLegacy.sys.mjs b/toolkit/components/downloads/DownloadLegacy.sys.mjs
index ae38b0d26c..1cf05c5512 100644
--- a/toolkit/components/downloads/DownloadLegacy.sys.mjs
+++ b/toolkit/components/downloads/DownloadLegacy.sys.mjs
@@ -166,12 +166,7 @@ DownloadLegacyTransfer.prototype = {
onLocationChange() {},
// nsIWebProgressListener
- onStatusChange: function DLT_onStatusChange(
- aWebProgress,
- aRequest,
- aStatus,
- aMessage
- ) {
+ onStatusChange: function DLT_onStatusChange(aWebProgress, aRequest, aStatus) {
// 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.
@@ -243,12 +238,7 @@ DownloadLegacyTransfer.prototype = {
_delayedMaxTotalProgress: 0,
// nsIWebProgressListener2
- onRefreshAttempted: function DLT_onRefreshAttempted(
- aWebProgress,
- aRefreshURI,
- aMillis,
- aSameURI
- ) {
+ 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;
diff --git a/toolkit/components/downloads/DownloadList.sys.mjs b/toolkit/components/downloads/DownloadList.sys.mjs
index 46f917d16b..b042ca20e9 100644
--- a/toolkit/components/downloads/DownloadList.sys.mjs
+++ b/toolkit/components/downloads/DownloadList.sys.mjs
@@ -653,7 +653,7 @@ export class DownloadSummary {
}
// DownloadList callback
- onDownloadChanged(download) {
+ onDownloadChanged() {
this._onListChanged();
}
diff --git a/toolkit/components/downloads/test/unit/common_test_Download.js b/toolkit/components/downloads/test/unit/common_test_Download.js
index 1360271686..c824030c56 100644
--- a/toolkit/components/downloads/test/unit/common_test_Download.js
+++ b/toolkit/components/downloads/test/unit/common_test_Download.js
@@ -85,7 +85,7 @@ async function expectNonZeroDownloadTargetSize(downloadTarget) {
*/
function waitForFileLaunched() {
return new Promise(resolve => {
- let waitFn = base => ({
+ let waitFn = () => ({
launchFile(file, mimeInfo) {
Integration.downloads.unregister(waitFn);
if (
@@ -109,7 +109,7 @@ function waitForFileLaunched() {
*/
function waitForDirectoryShown() {
return new Promise(resolve => {
- let waitFn = base => ({
+ let waitFn = () => ({
showContainingDirectory(path) {
Integration.downloads.unregister(waitFn);
resolve(path);
@@ -716,7 +716,7 @@ add_task(async function test_empty_noprogress() {
aResponse.setHeader("Content-Type", "text/plain", false);
deferRequestReceived.resolve();
},
- function secondPart(aRequest, aResponse) {}
+ function secondPart() {}
);
// Start the download, without allowing the request to finish.
@@ -1892,7 +1892,7 @@ add_task(async function test_cancel_midway_restart_with_content_encoding() {
* Download with parental controls enabled.
*/
add_task(async function test_blocked_parental_controls() {
- let blockFn = base => ({
+ let blockFn = () => ({
shouldBlockForParentalControls: () => Promise.resolve(true),
});
@@ -2001,7 +2001,7 @@ add_task(async function test_blocked_applicationReputation() {
add_task(async function test_blocked_applicationReputation_race() {
let isFirstShouldBlockCall = true;
- let blockFn = base => ({
+ let blockFn = () => ({
shouldBlockForReputationCheck(download) {
if (isFirstShouldBlockCall) {
isFirstShouldBlockCall = false;
@@ -2680,23 +2680,20 @@ add_task(async function test_partitionKey() {
Services.prefs.setBoolPref("privacy.partition.network_state", true);
function promiseVerifyDownloadChannel(url, partitionKey) {
- return TestUtils.topicObserved(
- "http-on-modify-request",
- (subject, data) => {
- let httpChannel = subject.QueryInterface(Ci.nsIHttpChannel);
- if (httpChannel.URI.spec != url) {
- return false;
- }
+ return TestUtils.topicObserved("http-on-modify-request", subject => {
+ let httpChannel = subject.QueryInterface(Ci.nsIHttpChannel);
+ if (httpChannel.URI.spec != url) {
+ return false;
+ }
- let reqLoadInfo = httpChannel.loadInfo;
- let cookieJarSettings = reqLoadInfo.cookieJarSettings;
+ let reqLoadInfo = httpChannel.loadInfo;
+ let cookieJarSettings = reqLoadInfo.cookieJarSettings;
- // Check the partitionKey of the cookieJarSettings.
- Assert.equal(cookieJarSettings.partitionKey, partitionKey);
+ // Check the partitionKey of the cookieJarSettings.
+ Assert.equal(cookieJarSettings.partitionKey, partitionKey);
- return true;
- }
- );
+ return true;
+ });
}
let test_url = httpUrl("source.txt");
diff --git a/toolkit/components/downloads/test/unit/head.js b/toolkit/components/downloads/test/unit/head.js
index 19d85b6f7c..a67f600ec7 100644
--- a/toolkit/components/downloads/test/unit/head.js
+++ b/toolkit/components/downloads/test/unit/head.js
@@ -718,7 +718,7 @@ async function promiseBlockedDownload({
useLegacySaver,
verdict = Downloads.Error.BLOCK_VERDICT_UNCOMMON,
} = {}) {
- let blockFn = base => ({
+ let blockFn = () => ({
shouldBlockForReputationCheck: () =>
Promise.resolve({
shouldBlock: true,
@@ -1159,13 +1159,7 @@ add_setup(function test_common_initialize() {
// saved to disk without asking for a destination interactively.
let mock = {
QueryInterface: ChromeUtils.generateQI(["nsIHelperAppLauncherDialog"]),
- promptForSaveToFileAsync(
- aLauncher,
- aWindowContext,
- aDefaultFileName,
- aSuggestedFileExtension,
- aForcePrompt
- ) {
+ promptForSaveToFileAsync(aLauncher) {
// The dialog should create the empty placeholder file.
let file = getTempFile(TEST_TARGET_FILE_NAME);
file.create(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
diff --git a/toolkit/components/downloads/test/unit/test_DownloadHistory.js b/toolkit/components/downloads/test/unit/test_DownloadHistory.js
index 69eb1c4728..7bcf097062 100644
--- a/toolkit/components/downloads/test/unit/test_DownloadHistory.js
+++ b/toolkit/components/downloads/test/unit/test_DownloadHistory.js
@@ -44,7 +44,7 @@ class TestView {
}
this.checkForExpectedDownloads();
}
- onDownloadChanged(download) {
+ onDownloadChanged() {
this.checkForExpectedDownloads();
}
onDownloadRemoved(download) {
diff --git a/toolkit/components/downloads/test/unit/test_DownloadLegacy.js b/toolkit/components/downloads/test/unit/test_DownloadLegacy.js
index 972820f29e..839611ec22 100644
--- a/toolkit/components/downloads/test/unit/test_DownloadLegacy.js
+++ b/toolkit/components/downloads/test/unit/test_DownloadLegacy.js
@@ -11,6 +11,8 @@
// Execution of common tests
+Services.prefs.setBoolPref("dom.block_download_insecure", false);
+
// This is used in common_test_Download.js
// eslint-disable-next-line no-unused-vars
var gUseLegacySaver = true;
diff --git a/toolkit/components/downloads/test/unit/test_DownloadList.js b/toolkit/components/downloads/test/unit/test_DownloadList.js
index f7d03900af..7045824c9c 100644
--- a/toolkit/components/downloads/test/unit/test_DownloadList.js
+++ b/toolkit/components/downloads/test/unit/test_DownloadList.js
@@ -322,7 +322,7 @@ add_task(async function test_history_expiration() {
let deferred = Promise.withResolvers();
let removeNotifications = 0;
let downloadView = {
- onDownloadRemoved(aDownload) {
+ onDownloadRemoved() {
if (++removeNotifications == 2) {
deferred.resolve();
}
@@ -369,7 +369,7 @@ add_task(async function test_history_clear() {
let deferred = Promise.withResolvers();
let removeNotifications = 0;
let downloadView = {
- onDownloadRemoved(aDownload) {
+ onDownloadRemoved() {
if (++removeNotifications == 2) {
deferred.resolve();
}
diff --git a/toolkit/components/downloads/test/unit/test_Download_noext_win.js b/toolkit/components/downloads/test/unit/test_Download_noext_win.js
index 0e226229f6..b0e1466b52 100644
--- a/toolkit/components/downloads/test/unit/test_Download_noext_win.js
+++ b/toolkit/components/downloads/test/unit/test_Download_noext_win.js
@@ -47,7 +47,7 @@ add_task(async function () {
*/
function waitForDirectoryShown() {
return new Promise(resolve => {
- let waitFn = base => ({
+ let waitFn = () => ({
showContainingDirectory(path) {
Integration.downloads.unregister(waitFn);
resolve(path);
diff --git a/toolkit/components/enterprisepolicies/WindowsGPOParser.sys.mjs b/toolkit/components/enterprisepolicies/WindowsGPOParser.sys.mjs
index be2c035e7f..81d3096079 100644
--- a/toolkit/components/enterprisepolicies/WindowsGPOParser.sys.mjs
+++ b/toolkit/components/enterprisepolicies/WindowsGPOParser.sys.mjs
@@ -11,7 +11,7 @@ ChromeUtils.defineLazyGetter(lazy, "log", () => {
"resource://gre/modules/Console.sys.mjs"
);
return new ConsoleAPI({
- prefix: "GPOParser.jsm",
+ prefix: "WindowsGPOParser",
// 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",
diff --git a/toolkit/components/enterprisepolicies/macOSPoliciesParser.sys.mjs b/toolkit/components/enterprisepolicies/macOSPoliciesParser.sys.mjs
index 9786d3b374..908dc5c137 100644
--- a/toolkit/components/enterprisepolicies/macOSPoliciesParser.sys.mjs
+++ b/toolkit/components/enterprisepolicies/macOSPoliciesParser.sys.mjs
@@ -11,7 +11,7 @@ ChromeUtils.defineLazyGetter(lazy, "log", () => {
"resource://gre/modules/Console.sys.mjs"
);
return new ConsoleAPI({
- prefix: "macOSPoliciesParser.jsm",
+ prefix: "macOSPoliciesParser",
// 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",
diff --git a/toolkit/components/extensions/.eslintrc.js b/toolkit/components/extensions/.eslintrc.js
index dfa4e2a7bf..8cc2d2a16f 100644
--- a/toolkit/components/extensions/.eslintrc.js
+++ b/toolkit/components/extensions/.eslintrc.js
@@ -6,7 +6,7 @@
module.exports = {
globals: {
- // These are defined in the WebExtension script scopes by ExtensionCommon.jsm
+ // These are defined in the WebExtension script scopes by ExtensionCommon.sys.mjs
Cc: true,
Ci: true,
Cr: true,
@@ -120,12 +120,6 @@ module.exports = {
// Allow use of bitwise operators.
"no-bitwise": "off",
- // Disallow using the console API.
- "no-console": "error",
-
- // Allow using constant expressions in conditions like while (true)
- "no-constant-condition": "off",
-
// Allow use of the continue statement.
"no-continue": "off",
diff --git a/toolkit/components/extensions/ConduitsChild.sys.mjs b/toolkit/components/extensions/ConduitsChild.sys.mjs
index c5774ab39c..598804f74a 100644
--- a/toolkit/components/extensions/ConduitsChild.sys.mjs
+++ b/toolkit/components/extensions/ConduitsChild.sys.mjs
@@ -4,7 +4,7 @@
/**
* This @file implements the child side of Conduits, an abstraction over
- * Fission IPC for extension API subject. See {@link ConduitsParent.jsm}
+ * Fission IPC for extension API subject. See {@link ConduitsParent.sys.mjs}
* for more details about the overall design.
*
* @typedef {object} MessageData
diff --git a/toolkit/components/extensions/Extension.sys.mjs b/toolkit/components/extensions/Extension.sys.mjs
index 4bbaa56199..de6d4c8bfd 100644
--- a/toolkit/components/extensions/Extension.sys.mjs
+++ b/toolkit/components/extensions/Extension.sys.mjs
@@ -1459,8 +1459,7 @@ export class ExtensionData {
};
if (this.fluentL10n || this.localeData) {
- context.preprocessors.localize = (value, context) =>
- this.localize(value, locale);
+ context.preprocessors.localize = value => this.localize(value, locale);
}
return lazy.Schemas.normalize(this.rawManifest, manifestType, context);
@@ -2665,8 +2664,8 @@ const PROXIED_EVENTS = new Set([
]);
class BootstrapScope {
- install(data, reason) {}
- uninstall(data, reason) {
+ install() {}
+ uninstall(data) {
lazy.AsyncShutdown.profileChangeTeardown.addBlocker(
`Uninstalling add-on: ${data.id}`,
Management.emit("uninstall", { id: data.id }).then(() => {
@@ -2746,8 +2745,8 @@ class BootstrapScope {
}
class DictionaryBootstrapScope extends BootstrapScope {
- install(data, reason) {}
- uninstall(data, reason) {}
+ install() {}
+ uninstall() {}
startup(data, reason) {
// eslint-disable-next-line no-use-before-define
@@ -2762,9 +2761,9 @@ class DictionaryBootstrapScope extends BootstrapScope {
}
class LangpackBootstrapScope extends BootstrapScope {
- install(data, reason) {}
- uninstall(data, reason) {}
- async update(data, reason) {}
+ install() {}
+ uninstall() {}
+ async update() {}
startup(data, reason) {
// eslint-disable-next-line no-use-before-define
@@ -2780,8 +2779,8 @@ class LangpackBootstrapScope extends BootstrapScope {
// TODO(Bug 1789718): Remove after the deprecated XPIProvider-based implementation is also removed.
class SitePermissionBootstrapScope extends BootstrapScope {
- install(data, reason) {}
- uninstall(data, reason) {}
+ install() {}
+ uninstall() {}
startup(data, reason) {
// eslint-disable-next-line no-use-before-define
@@ -3068,7 +3067,7 @@ export class Extension extends ExtensionData {
return ExtensionCommon.checkLoadURL(url, this.principal, options);
}
- async promiseLocales(locale) {
+ async promiseLocales() {
let locales = await StartupCache.locales.get(
[this.id, "@@all_locales"],
() => this._promiseLocaleMap()
@@ -3206,7 +3205,7 @@ export class Extension extends ExtensionData {
// Extended serialized data which is only needed in the extensions process,
// and is never deserialized in web content processes.
- // Keep in sync with BrowserExtensionContent in ExtensionChild.jsm
+ // Keep in sync with BrowserExtensionContent in ExtensionChild.sys.mjs
serializeExtended() {
return {
backgroundScripts: this.backgroundScripts,
@@ -3232,7 +3231,7 @@ export class Extension extends ExtensionData {
children.delete(data.target);
maybeResolve();
}
- function observer(subject, topic, data) {
+ function observer(subject) {
children.delete(subject);
maybeResolve();
}
@@ -3260,7 +3259,7 @@ export class Extension extends ExtensionData {
sharedData.set(key, value);
}
- getSharedData(key, value) {
+ getSharedData(key) {
key = `extension/${this.id}/${key}`;
return sharedData.get(key);
}
@@ -3820,7 +3819,7 @@ export class Extension extends ExtensionData {
return this.cleanupGeneratedFile();
}
- observe(subject, topic, data) {
+ observe(subject, topic) {
if (topic === "xpcom-shutdown") {
this.cleanupGeneratedFile();
}
@@ -3852,7 +3851,7 @@ export class Extension extends ExtensionData {
}
export class Dictionary extends ExtensionData {
- constructor(addonData, startupReason) {
+ constructor(addonData) {
super(addonData.resourceURI);
this.id = addonData.id;
this.startupData = addonData.startupData;
@@ -3862,7 +3861,7 @@ export class Dictionary extends ExtensionData {
return new DictionaryBootstrapScope();
}
- async startup(reason) {
+ async startup() {
this.dictionaries = {};
for (let [lang, path] of Object.entries(this.startupData.dictionaries)) {
let uri = Services.io.newURI(
@@ -3886,7 +3885,7 @@ export class Dictionary extends ExtensionData {
}
export class Langpack extends ExtensionData {
- constructor(addonData, startupReason) {
+ constructor(addonData) {
super(addonData.resourceURI);
this.startupData = addonData.startupData;
this.manifestCacheKey = [addonData.id, addonData.version];
@@ -3896,7 +3895,7 @@ export class Langpack extends ExtensionData {
return new LangpackBootstrapScope();
}
- async promiseLocales(locale) {
+ async promiseLocales() {
let locales = await StartupCache.locales.get(
[this.id, "@@all_locales"],
() => this._promiseLocaleMap()
@@ -3911,7 +3910,7 @@ export class Langpack extends ExtensionData {
);
}
- async startup(reason) {
+ async startup() {
this.chromeRegistryHandle = null;
if (this.startupData.chromeEntries.length) {
const manifestURI = Services.io.newURI(
@@ -3971,7 +3970,7 @@ export class Langpack extends ExtensionData {
// TODO(Bug 1789718): Remove after the deprecated XPIProvider-based implementation is also removed.
export class SitePermission extends ExtensionData {
- constructor(addonData, startupReason) {
+ constructor(addonData) {
super(addonData.resourceURI);
this.id = addonData.id;
this.hasShutdown = false;
@@ -4011,7 +4010,7 @@ export class SitePermission extends ExtensionData {
];
}
- async startup(reason) {
+ async startup() {
await this.loadManifest();
this.ensureNoErrors();
diff --git a/toolkit/components/extensions/ExtensionActions.sys.mjs b/toolkit/components/extensions/ExtensionActions.sys.mjs
index 8e8cf3abd2..29a286442e 100644
--- a/toolkit/components/extensions/ExtensionActions.sys.mjs
+++ b/toolkit/components/extensions/ExtensionActions.sys.mjs
@@ -322,64 +322,64 @@ class PanelActionBase {
* If it only changes a parameter for a single window, `target` will be that window.
* Otherwise `target` will be null.
*
- * @param {XULElement|ChromeWindow|null} target
+ * @param {XULElement|ChromeWindow|null} _target
* Browser tab or browser chrome window, may be null.
*/
- updateOnChange(target) {}
+ updateOnChange(_target) {}
/**
* Get tab object from tabId.
*
- * @param {string} tabId
+ * @param {string} _tabId
* Internal id of the tab to get.
*/
- getTab(tabId) {}
+ getTab(_tabId) {}
/**
* Get window object from windowId
*
- * @param {string} windowId
+ * @param {string} _windowId
* Internal id of the window to get.
*/
- getWindow(windowId) {}
+ getWindow(_windowId) {}
/**
* Gets the target object corresponding to the `details` parameter of the various
* get* and set* API methods.
*
- * @param {object} details
+ * @param {object} _details
* An object with optional `tabId` or `windowId` properties.
- * @param {number} [details.tabId]
- * @param {number} [details.windowId]
+ * @param {number} [_details.tabId]
+ * @param {number} [_details.windowId]
* @throws if both `tabId` and `windowId` are specified, or if they are invalid.
* @returns {XULElement|ChromeWindow|null}
* If a `tabId` was specified, the corresponding XULElement tab.
* If a `windowId` was specified, the corresponding ChromeWindow.
* Otherwise, `null`.
*/
- getTargetFromDetails({ tabId, windowId }) {
+ getTargetFromDetails(_details) {
return null;
}
/**
* Triggers a click event.
*
- * @param {XULElement} tab
+ * @param {XULElement} _tab
* The tab where this event should be fired.
- * @param {object} clickInfo
+ * @param {object} _clickInfo
* Extra data passed to the second parameter to the action API's
* onClicked event.
*/
- dispatchClick(tab, clickInfo) {}
+ dispatchClick(_tab, _clickInfo) {}
/**
* Checks whether this action is shown.
*
- * @param {XULElement} tab
+ * @param {XULElement} _tab
* The tab to be checked
* @returns {boolean}
*/
- isShownForTab(tab) {
+ isShownForTab(_tab) {
return false;
}
}
@@ -481,7 +481,7 @@ export class PageActionBase extends PanelActionBase {
return this.globals.pinned;
}
- getTargetFromDetails({ tabId, windowId }) {
+ getTargetFromDetails({ tabId }) {
// PageActionBase doesn't support |windowId|
if (tabId != null) {
return this.getTab(tabId);
diff --git a/toolkit/components/extensions/ExtensionChild.sys.mjs b/toolkit/components/extensions/ExtensionChild.sys.mjs
index 20c3c8f2ab..70774db395 100644
--- a/toolkit/components/extensions/ExtensionChild.sys.mjs
+++ b/toolkit/components/extensions/ExtensionChild.sys.mjs
@@ -8,7 +8,7 @@
* 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.jsm instead.
+ * Don't put contentscript logic here, use ExtensionContent.sys.mjs instead.
*/
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
@@ -397,7 +397,7 @@ class BrowserExtensionContent extends EventEmitter {
this.optionalPermissions = policy.optionalPermissions;
if (WebExtensionPolicy.isExtensionProcess) {
- // Keep in sync with serializeExtended in Extension.jsm
+ // Keep in sync with serializeExtended in Extension.sys.mjs
let ed = this.getSharedData("extendedData");
this.backgroundScripts = ed.backgroundScripts;
this.backgroundWorkerScript = ed.backgroundWorkerScript;
@@ -449,7 +449,7 @@ class BrowserExtensionContent extends EventEmitter {
return this.policy.allowedOrigins;
}
- getSharedData(key, value) {
+ getSharedData(key) {
return sharedData.get(`extension/${this.id}/${key}`);
}
@@ -727,8 +727,8 @@ class ChildLocalAPIImplementation extends LocalAPIImplementation {
// 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.jsm) to communicate
-// with the ParentAPIManager singleton in ExtensionParent.jsm.
+// 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.
class ChildAPIManager {
constructor(context, messageManager, localAPICan, contextData) {
diff --git a/toolkit/components/extensions/ExtensionCommon.sys.mjs b/toolkit/components/extensions/ExtensionCommon.sys.mjs
index 86c99042b6..512d1444a5 100644
--- a/toolkit/components/extensions/ExtensionCommon.sys.mjs
+++ b/toolkit/components/extensions/ExtensionCommon.sys.mjs
@@ -7,7 +7,7 @@
/**
* This module contains utilities and base classes for logic which is
* common between the parent and child process, and in particular
- * between ExtensionParent.jsm and ExtensionChild.jsm.
+ * between ExtensionParent.sys.mjs and ExtensionChild.sys.mjs.
*/
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
@@ -369,28 +369,28 @@ class ExtensionAPI extends EventEmitter {
destroy() {}
- /** @param {string} entryName */
- onManifestEntry(entryName) {}
+ /** @param {string} _entryName */
+ onManifestEntry(_entryName) {}
- /** @param {boolean} isAppShutdown */
- onShutdown(isAppShutdown) {}
+ /** @param {boolean} _isAppShutdown */
+ onShutdown(_isAppShutdown) {}
- /** @param {BaseContext} context */
- getAPI(context) {
+ /** @param {BaseContext} _context */
+ getAPI(_context) {
throw new Error("Not Implemented");
}
- /** @param {string} id */
- static onDisable(id) {}
+ /** @param {string} _id */
+ static onDisable(_id) {}
- /** @param {string} id */
- static onUninstall(id) {}
+ /** @param {string} _id */
+ static onUninstall(_id) {}
/**
- * @param {string} id
- * @param {Record<string, JSONValue>} manifest
+ * @param {string} _id
+ * @param {Record<string, JSONValue>} _manifest
*/
- static onUpdate(id, manifest) {}
+ static onUpdate(_id, _manifest) {}
}
/**
@@ -614,7 +614,7 @@ class BaseContext {
// 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) {
+ logActivity() {
throw new Error(`Not implemented for ${this.envType}`);
}
@@ -1033,7 +1033,7 @@ class BaseContext {
/**
* An object that runs the implementation of a schema API. Instantiations of
- * this interfaces are used by Schemas.jsm.
+ * this interfaces are used by Schemas.sys.mjs.
*
* @interface
*/
@@ -1042,10 +1042,10 @@ class SchemaAPIInterface {
* Calls this as a function that returns its return value.
*
* @abstract
- * @param {Array} args The parameters for the function.
+ * @param {Array} _args The parameters for the function.
* @returns {*} The return value of the invoked function.
*/
- callFunction(args) {
+ callFunction(_args) {
throw new Error("Not implemented");
}
@@ -1053,9 +1053,9 @@ class SchemaAPIInterface {
* Calls this as a function and ignores its return value.
*
* @abstract
- * @param {Array} args The parameters for the function.
+ * @param {Array} _args The parameters for the function.
*/
- callFunctionNoReturn(args) {
+ callFunctionNoReturn(_args) {
throw new Error("Not implemented");
}
@@ -1063,15 +1063,15 @@ class SchemaAPIInterface {
* 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
+ * @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
+ * @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 = false) {
+ callAsyncFunction(_args, _callback, _requireUserInput) {
throw new Error("Not implemented");
}
@@ -1089,9 +1089,9 @@ class SchemaAPIInterface {
* Assigns the value to this as property.
*
* @abstract
- * @param {string} value The new value of the property.
+ * @param {string} _value The new value of the property.
*/
- setProperty(value) {
+ setProperty(_value) {
throw new Error("Not implemented");
}
@@ -1099,11 +1099,11 @@ class SchemaAPIInterface {
* 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.
+ * @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) {
+ addListener(_listener, _args) {
throw new Error("Not implemented");
}
@@ -1111,11 +1111,11 @@ class SchemaAPIInterface {
* Checks whether `listener` is listening to this as an event.
*
* @abstract
- * @param {Function} listener The event listener.
+ * @param {Function} _listener The event listener.
* @returns {boolean} Whether `listener` is registered with this as an event.
* @see EventManager.hasListener
*/
- hasListener(listener) {
+ hasListener(_listener) {
throw new Error("Not implemented");
}
@@ -1123,10 +1123,10 @@ class SchemaAPIInterface {
* Unregisters `listener` from this as an event.
*
* @abstract
- * @param {Function} listener The event listener.
+ * @param {Function} _listener The event listener.
* @see EventManager.removeListener
*/
- removeListener(listener) {
+ removeListener(_listener) {
throw new Error("Not implemented");
}
@@ -1865,7 +1865,7 @@ class SchemaAPIManager extends EventEmitter {
{
wantXrays: false,
wantGlobalProperties: ["ChromeUtils"],
- sandboxName: `Namespace of ext-*.js scripts for ${this.processType} (from: resource://gre/modules/ExtensionCommon.jsm)`,
+ sandboxName: `Namespace of ext-*.js scripts for ${this.processType} (from: resource://gre/modules/ExtensionCommon.sys.mjs)`,
}
);
@@ -2968,7 +2968,7 @@ class EventManager {
// Simple API for event listeners where events never fire.
function ignoreEvent(context, name) {
return {
- addListener: function (callback) {
+ 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.`;
@@ -2986,8 +2986,8 @@ function ignoreEvent(context, name) {
);
Services.console.logMessage(scriptError);
},
- removeListener: function (callback) {},
- hasListener: function (callback) {},
+ removeListener: function () {},
+ hasListener: function () {},
};
}
diff --git a/toolkit/components/extensions/ExtensionContent.sys.mjs b/toolkit/components/extensions/ExtensionContent.sys.mjs
index 131d555bf0..015d1bc7c6 100644
--- a/toolkit/components/extensions/ExtensionContent.sys.mjs
+++ b/toolkit/components/extensions/ExtensionContent.sys.mjs
@@ -787,7 +787,7 @@ var contentScripts = new DefaultWeakMap(matcher => {
* An execution context for semi-privileged extension content scripts.
*
* This is the child side of the ContentScriptContextParent class
- * defined in ExtensionParent.jsm.
+ * defined in ExtensionParent.sys.mjs.
*/
class ContentScriptContextChild extends BaseContext {
constructor(extension, contentWindow) {
@@ -1035,7 +1035,7 @@ DocumentManager = {
},
observers: {
- "inner-window-destroyed"(subject, topic, data) {
+ "inner-window-destroyed"(subject) {
let windowId = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
// Close any existent content-script context for the destroyed window.
diff --git a/toolkit/components/extensions/ExtensionDNR.sys.mjs b/toolkit/components/extensions/ExtensionDNR.sys.mjs
index cd01d52b72..d18856a2b8 100644
--- a/toolkit/components/extensions/ExtensionDNR.sys.mjs
+++ b/toolkit/components/extensions/ExtensionDNR.sys.mjs
@@ -634,20 +634,20 @@ class ModifyHeadersBase {
}
/**
- * @param {MatchedRule} matchedRule
+ * @param {MatchedRule} _matchedRule
* @returns {object[]}
*/
- headerActionsFor(matchedRule) {
+ headerActionsFor(_matchedRule) {
throw new Error("Not implemented.");
}
/**
- * @param {MatchedRule} matchedrule
- * @param {string} name
- * @param {string} value
- * @param {boolean} merge
+ * @param {MatchedRule} _matchedrule
+ * @param {string} _name
+ * @param {string} _value
+ * @param {boolean} _merge
*/
- setHeaderImpl(matchedrule, name, value, merge) {
+ setHeaderImpl(_matchedrule, _name, _value, _merge) {
throw new Error("Not implemented.");
}
@@ -1913,7 +1913,7 @@ const NetworkIntegration = {
maxEvaluatedRulesCount: 0,
register() {
- // We register via WebRequest.jsm to ensure predictable ordering of DNR and
+ // We register via WebRequest.sys.mjs to ensure predictable ordering of DNR and
// WebRequest behavior.
lazy.WebRequest.setDNRHandlingEnabled(true);
},
@@ -2034,7 +2034,7 @@ const NetworkIntegration = {
properties.setProperty("cancelledByExtension", addonId);
},
- applyUpgradeScheme(channel, matchedRule) {
+ applyUpgradeScheme(channel) {
// Request upgrade. No-op if already secure (i.e. https).
channel.upgradeToSecure();
},
diff --git a/toolkit/components/extensions/ExtensionPageChild.sys.mjs b/toolkit/components/extensions/ExtensionPageChild.sys.mjs
index d84459f1ed..17c208572b 100644
--- a/toolkit/components/extensions/ExtensionPageChild.sys.mjs
+++ b/toolkit/components/extensions/ExtensionPageChild.sys.mjs
@@ -299,7 +299,7 @@ class ExtensionPageContextChild extends ExtensionBaseContextChild {
* APIs (provided that the correct permissions have been requested).
*
* This is the child side of the ExtensionPageContextParent class
- * defined in ExtensionParent.jsm.
+ * defined in ExtensionParent.sys.mjs.
*
* @param {BrowserExtensionContent} extension This context's owner.
* @param {object} params
@@ -391,7 +391,7 @@ export var ExtensionPageChild = {
Services.obs.addObserver(this, "inner-window-destroyed"); // eslint-ignore-line mozilla/balanced-listeners
},
- observe(subject, topic, data) {
+ observe(subject, topic) {
if (topic === "inner-window-destroyed") {
let windowId = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
diff --git a/toolkit/components/extensions/ExtensionParent.sys.mjs b/toolkit/components/extensions/ExtensionParent.sys.mjs
index 22ba021e15..b4812a702a 100644
--- a/toolkit/components/extensions/ExtensionParent.sys.mjs
+++ b/toolkit/components/extensions/ExtensionParent.sys.mjs
@@ -7,7 +7,7 @@
/**
* 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.jsm.
+ * to be proxied from ExtensionChild.sys.mjs.
*/
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
@@ -481,7 +481,7 @@ GlobalManager = {
};
/**
- * The proxied parent side of a context in ExtensionChild.jsm, for the
+ * The proxied parent side of a context in ExtensionChild.sys.mjs, for the
* parent side of a proxied API.
*/
class ProxyContextParent extends BaseContext {
@@ -625,7 +625,7 @@ class ProxyContextParent extends BaseContext {
}
}
- logActivity(type, name, data) {
+ 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.
}
@@ -685,14 +685,14 @@ class ProxyContextParent extends BaseContext {
/**
* The parent side of proxied API context for extension content script
- * running in ExtensionContent.jsm.
+ * 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.jsm.
+ * ExtensionChild.sys.mjs.
*/
class ExtensionPageContextParent extends ProxyContextParent {
constructor(envType, extension, params, browsingContext) {
@@ -716,6 +716,7 @@ class ExtensionPageContextParent extends ProxyContextParent {
if (this.viewType !== "background") {
return this.appWindow;
}
+ return undefined;
}
get tabId() {
@@ -724,6 +725,7 @@ class ExtensionPageContextParent extends ProxyContextParent {
if (data.tabId >= 0) {
return data.tabId;
}
+ return undefined;
}
unload() {
@@ -739,7 +741,7 @@ class ExtensionPageContextParent extends ProxyContextParent {
/**
* The parent side of proxied API context for devtools extension page, such as a
- * devtools pages and panels running in ExtensionChild.jsm.
+ * devtools pages and panels running in ExtensionChild.sys.mjs.
*/
class DevToolsExtensionPageContextParent extends ExtensionPageContextParent {
constructor(...params) {
@@ -904,7 +906,7 @@ ParentAPIManager = {
extension.parentMessageManager = processMessageManager;
},
- async observe(subject, topic, data) {
+ async observe(subject, topic) {
if (topic === "message-manager-close") {
let mm = subject;
for (let [childId, context] of this.proxyContexts) {
@@ -1035,7 +1037,7 @@ ParentAPIManager = {
this.proxyContexts.set(childId, context);
},
- recvContextLoaded(data, { actor, sender }) {
+ recvContextLoaded(data, { actor }) {
let context = this.getContextById(data.childId);
verifyActorForContext(actor, context);
const { extension } = context;
@@ -1794,7 +1796,7 @@ function promiseMessageFromChild(messageManager, messageName) {
unregister();
resolve(message.data);
}
- function observer(subject, topic, data) {
+ function observer(subject) {
if (subject === messageManager) {
unregister();
reject(
@@ -2022,7 +2024,7 @@ let IconDetails = {
// Returns the appropriate icon URL for the given icons object and the
// screen resolution of the given window.
- getPreferredIcon(icons, extension = null, size = 16) {
+ getPreferredIcon(icons, extension, size = 16) {
const DEFAULT = "chrome://mozapps/skin/extensions/extensionGeneric.svg";
let bestSize = null;
@@ -2210,13 +2212,13 @@ var StartupCache = {
this.manifests.delete(id),
this.permissions.delete(id),
this.menus.delete(id),
- ]).catch(e => {
+ ]).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, data) {
+ observe(subject, topic) {
if (topic === "startupcache-invalidate") {
this._data = new Map();
this._dataPromise = Promise.resolve(this._data);
diff --git a/toolkit/components/extensions/ExtensionPermissions.sys.mjs b/toolkit/components/extensions/ExtensionPermissions.sys.mjs
index 8308a4369a..1ee9afdc32 100644
--- a/toolkit/components/extensions/ExtensionPermissions.sys.mjs
+++ b/toolkit/components/extensions/ExtensionPermissions.sys.mjs
@@ -133,9 +133,10 @@ class PermissionStore {
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.getOrCreate(
+ this._store = await lazy.KeyValueService.getOrCreateWithOptions(
storePath,
- "permissions"
+ "permissions",
+ { strategy: lazy.KeyValueService.RecoveryStrategy.RENAME }
);
if (!(await this._store.has(VERSION_KEY))) {
// If _shouldMigrateFromOldKVStorePath is true (default only on Nightly channel
diff --git a/toolkit/components/extensions/ExtensionPolicyService.cpp b/toolkit/components/extensions/ExtensionPolicyService.cpp
index 031abca444..370b0603d0 100644
--- a/toolkit/components/extensions/ExtensionPolicyService.cpp
+++ b/toolkit/components/extensions/ExtensionPolicyService.cpp
@@ -84,9 +84,9 @@ mozIExtensionProcessScript& ExtensionPolicyService::ProcessScript() {
MOZ_ASSERT(NS_IsMainThread());
if (MOZ_UNLIKELY(!sProcessScript)) {
- sProcessScript =
- do_ImportModule("resource://gre/modules/ExtensionProcessScript.jsm",
- "ExtensionProcessScript");
+ sProcessScript = do_ImportESModule(
+ "resource://gre/modules/ExtensionProcessScript.sys.mjs",
+ "ExtensionProcessScript");
ClearOnShutdown(&sProcessScript);
}
return *sProcessScript;
@@ -406,10 +406,9 @@ nsresult ExtensionPolicyService::InjectContentScripts(
DocInfo docInfo(win);
using RunAt = dom::ContentScriptRunAt;
- namespace RunAtValues = dom::ContentScriptRunAtValues;
using Scripts = AutoTArray<RefPtr<WebExtensionContentScript>, 8>;
- Scripts scripts[RunAtValues::Count];
+ Scripts scripts[ContiguousEnumSize<RunAt>::value];
auto GetScripts = [&](RunAt aRunAt) -> Scripts&& {
static_assert(sizeof(aRunAt) == 1, "Our cast is wrong");
diff --git a/toolkit/components/extensions/ExtensionProcessScript.sys.mjs b/toolkit/components/extensions/ExtensionProcessScript.sys.mjs
index 2fcf113a88..93746cb0ca 100644
--- a/toolkit/components/extensions/ExtensionProcessScript.sys.mjs
+++ b/toolkit/components/extensions/ExtensionProcessScript.sys.mjs
@@ -58,7 +58,7 @@ var ExtensionManager;
ExtensionManager = {
// WeakMap<WebExtensionPolicy, Map<number, WebExtensionContentScript>>
- registeredContentScripts: new DefaultWeakMap(policy => new Map()),
+ registeredContentScripts: new DefaultWeakMap(() => new Map()),
init() {
Services.cpmm.addMessageListener("Extension:Startup", this);
@@ -325,7 +325,7 @@ ExtensionManager = {
if (!policy) {
break;
}
- // In the parent process, Extension.jsm updates the policy.
+ // In the parent process, Extension.sys.mjs updates the policy.
if (lazy.isContentProcess) {
lazy.ExtensionCommon.updateAllowedOrigins(
policy,
diff --git a/toolkit/components/extensions/ExtensionScriptingStore.sys.mjs b/toolkit/components/extensions/ExtensionScriptingStore.sys.mjs
index 444af8e41f..22e60d6440 100644
--- a/toolkit/components/extensions/ExtensionScriptingStore.sys.mjs
+++ b/toolkit/components/extensions/ExtensionScriptingStore.sys.mjs
@@ -33,9 +33,10 @@ class Store {
]);
// Make sure the folder exists.
await IOUtils.makeDirectory(storePath, { ignoreExisting: true });
- this._store = await lazy.KeyValueService.getOrCreate(
+ this._store = await lazy.KeyValueService.getOrCreateWithOptions(
storePath,
- "scripting-contentScripts"
+ "scripting-contentScripts",
+ { strategy: lazy.KeyValueService.RecoveryStrategy.RENAME }
);
}
@@ -47,6 +48,11 @@ class Store {
return this._initPromise;
}
+ _uninitForTesting() {
+ this._store = null;
+ this._initPromise = null;
+ }
+
/**
* Returns all the stored scripts for a given extension (ID).
*
diff --git a/toolkit/components/extensions/ExtensionStorage.sys.mjs b/toolkit/components/extensions/ExtensionStorage.sys.mjs
index 4155fbaa24..b1b09d137f 100644
--- a/toolkit/components/extensions/ExtensionStorage.sys.mjs
+++ b/toolkit/components/extensions/ExtensionStorage.sys.mjs
@@ -398,7 +398,7 @@ export var ExtensionStorage = {
Services.obs.addObserver(this, "xpcom-shutdown");
},
- observe(subject, topic, data) {
+ observe(subject, topic) {
if (topic == "xpcom-shutdown") {
Services.obs.removeObserver(this, "extension-invalidate-storage-cache");
Services.obs.removeObserver(this, "xpcom-shutdown");
diff --git a/toolkit/components/extensions/ExtensionStorageIDB.sys.mjs b/toolkit/components/extensions/ExtensionStorageIDB.sys.mjs
index 26df3eacdb..604d29b4cf 100644
--- a/toolkit/components/extensions/ExtensionStorageIDB.sys.mjs
+++ b/toolkit/components/extensions/ExtensionStorageIDB.sys.mjs
@@ -257,7 +257,7 @@ class ExtensionStorageLocalIDB extends IndexedDB {
};
changed = true;
} catch (err) {
- transactionCompleted.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.
});
diff --git a/toolkit/components/extensions/ExtensionStorageSync.sys.mjs b/toolkit/components/extensions/ExtensionStorageSync.sys.mjs
index d41cf5af12..3f82d91fac 100644
--- a/toolkit/components/extensions/ExtensionStorageSync.sys.mjs
+++ b/toolkit/components/extensions/ExtensionStorageSync.sys.mjs
@@ -174,7 +174,7 @@ export class ExtensionStorageSync {
return this._promisify("getBytesInUse", extension, context, keys);
}
- addOnChangedListener(extension, listener, context) {
+ addOnChangedListener(extension, listener) {
let listeners = this.listeners.get(extension.id) || new Set();
listeners.add(listener);
this.listeners.set(extension.id, listeners);
diff --git a/toolkit/components/extensions/ExtensionStorageSyncKinto.sys.mjs b/toolkit/components/extensions/ExtensionStorageSyncKinto.sys.mjs
index d10b140f7e..ace6e16c2c 100644
--- a/toolkit/components/extensions/ExtensionStorageSyncKinto.sys.mjs
+++ b/toolkit/components/extensions/ExtensionStorageSyncKinto.sys.mjs
@@ -42,6 +42,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
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",
@@ -54,9 +55,6 @@ ChromeUtils.defineESModuleGetters(lazy, {
* @typedef {any} KeyBundle
* @typedef {any} SyncResultObject
*/
-XPCOMUtils.defineLazyModuleGetters(lazy, {
- Kinto: "resource://services-common/kinto-offline-client.js",
-});
ChromeUtils.defineLazyGetter(lazy, "fxAccounts", () => {
return ChromeUtils.importESModule(
@@ -434,7 +432,7 @@ const cryptoCollectionIdSchema = {
throw new Error("cannot generate IDs for system collection");
},
- validate(id) {
+ validate() {
return true;
},
};
diff --git a/toolkit/components/extensions/ExtensionTelemetry.sys.mjs b/toolkit/components/extensions/ExtensionTelemetry.sys.mjs
index 06137b9a23..57f052372c 100644
--- a/toolkit/components/extensions/ExtensionTelemetry.sys.mjs
+++ b/toolkit/components/extensions/ExtensionTelemetry.sys.mjs
@@ -104,7 +104,7 @@ export function getErrorNameForTelemetry(error) {
class ExtensionTelemetryMetric {
constructor(metric) {
this.metric = metric;
- this.gleanTimerIdsMap = new DefaultWeakMap(ext => new WeakMap());
+ this.gleanTimerIdsMap = new DefaultWeakMap(() => new WeakMap());
}
// Stopwatch methods.
@@ -325,7 +325,7 @@ const metricsCache = new Map();
* ExtensionTelemetry.browserActionPreloadResult.histogramAdd({category: "Shown", extension});
*/
export var ExtensionTelemetry = new Proxy(metricsCache, {
- get(target, prop, receiver) {
+ 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.
diff --git a/toolkit/components/extensions/ExtensionTestCommon.sys.mjs b/toolkit/components/extensions/ExtensionTestCommon.sys.mjs
index 94cb801cc5..701a85d97b 100644
--- a/toolkit/components/extensions/ExtensionTestCommon.sys.mjs
+++ b/toolkit/components/extensions/ExtensionTestCommon.sys.mjs
@@ -100,7 +100,7 @@ export class MockExtension {
this._extensionPromise.then(extension => {
extension.on(...args);
});
- // Extension.jsm emits a "startup" event on |extension| before emitting the
+ // Extension.sys.mjs emits a "startup" event on |extension| before emitting the
// "startup" event on |apiManager|. Trigger the "startup" event anyway, to
// make sure that the MockExtension behaves like an Extension with regards
// to the startup event.
diff --git a/toolkit/components/extensions/ExtensionUtils.sys.mjs b/toolkit/components/extensions/ExtensionUtils.sys.mjs
index cbdf900d14..45f22aa530 100644
--- a/toolkit/components/extensions/ExtensionUtils.sys.mjs
+++ b/toolkit/components/extensions/ExtensionUtils.sys.mjs
@@ -241,7 +241,7 @@ function promiseEvent(
element,
eventName,
useCapture = true,
- test = event => true
+ test = () => true
) {
return new Promise(resolve => {
function listener(event) {
diff --git a/toolkit/components/extensions/ExtensionWorkerChild.sys.mjs b/toolkit/components/extensions/ExtensionWorkerChild.sys.mjs
index f93f6968e9..44188d7ddb 100644
--- a/toolkit/components/extensions/ExtensionWorkerChild.sys.mjs
+++ b/toolkit/components/extensions/ExtensionWorkerChild.sys.mjs
@@ -266,7 +266,7 @@ class ChildLocalWebIDLAPIImplementation extends ChildLocalAPIImplementation {
throw new Error("Unexpected call to setProperty");
}
- hasListener(listener) {
+ hasListener() {
// hasListener is implemented in C++ by ExtensionEventManager, and so
// a call to this method is unexpected.
throw new Error("Unexpected call to hasListener");
diff --git a/toolkit/components/extensions/ExtensionXPCShellUtils.sys.mjs b/toolkit/components/extensions/ExtensionXPCShellUtils.sys.mjs
index 165c37fac1..27323dc8b3 100644
--- a/toolkit/components/extensions/ExtensionXPCShellUtils.sys.mjs
+++ b/toolkit/components/extensions/ExtensionXPCShellUtils.sys.mjs
@@ -457,10 +457,10 @@ class AOMExtensionWrapper extends ExtensionWrapper {
/**
* Override for subclasses which don't set an ID in the constructor.
*
- * @param {nsIURI} uri
- * @param {string} id
+ * @param {nsIURI} _uri
+ * @param {string} _id
*/
- maybeSetID(uri, id) {}
+ maybeSetID(_uri, _id) {}
}
class InstallableWrapper extends AOMExtensionWrapper {
diff --git a/toolkit/components/extensions/ExtensionsParent.cpp b/toolkit/components/extensions/ExtensionsParent.cpp
index 0e10af241f..62779711fe 100644
--- a/toolkit/components/extensions/ExtensionsParent.cpp
+++ b/toolkit/components/extensions/ExtensionsParent.cpp
@@ -22,8 +22,8 @@ ExtensionsParent::~ExtensionsParent() {}
extIWebNavigation* ExtensionsParent::WebNavigation() {
if (!mWebNavigation) {
- mWebNavigation = do_ImportModule("resource://gre/modules/WebNavigation.jsm",
- "WebNavigationManager");
+ mWebNavigation = do_ImportESModule(
+ "resource://gre/modules/WebNavigation.sys.mjs", "WebNavigationManager");
}
return mWebNavigation;
}
diff --git a/toolkit/components/extensions/MessageChannel.sys.mjs b/toolkit/components/extensions/MessageChannel.sys.mjs
index 65ab2720aa..90ad301402 100644
--- a/toolkit/components/extensions/MessageChannel.sys.mjs
+++ b/toolkit/components/extensions/MessageChannel.sys.mjs
@@ -322,10 +322,8 @@ class ResponseManager extends FilteringMessageManager {
/**
* Called when the event queue is idle, and dispatches any pending
* low-priority messages in a single chunk.
- *
- * @param {IdleDeadline} deadline
*/
- onIdle(deadline) {
+ onIdle() {
this.idleScheduled = false;
let messages = this.idleMessages;
@@ -370,7 +368,7 @@ class ResponseManager extends FilteringMessageManager {
this.callback(this.handlers.get(data.messageName), data);
}
- *getHandlers(messageName, sender, recipient) {
+ *getHandlers(messageName) {
let handler = this.handlers.get(messageName);
if (handler) {
yield handler;
@@ -454,7 +452,7 @@ class FilteringMessageManagerMap extends Map {
// XXXbz if target is really known to be a MessageListenerManager,
// do we need this isInstance check?
if (EventTarget.isInstance(target)) {
- let onUnload = event => {
+ let onUnload = () => {
target.removeEventListener("unload", onUnload);
this.delete(target);
};
@@ -1148,7 +1146,7 @@ MessageChannel = {
}
},
- observe(subject, topic, data) {
+ observe(subject, topic) {
switch (topic) {
case "message-manager-close":
case "message-manager-disconnect":
diff --git a/toolkit/components/extensions/MessageManagerProxy.sys.mjs b/toolkit/components/extensions/MessageManagerProxy.sys.mjs
index 387b5876e1..110a98a05d 100644
--- a/toolkit/components/extensions/MessageManagerProxy.sys.mjs
+++ b/toolkit/components/extensions/MessageManagerProxy.sys.mjs
@@ -51,7 +51,7 @@ export class MessageManagerProxy {
Services.obs.removeObserver(this, "message-manager-close");
}
- observe(subject, topic, data) {
+ observe(subject, topic) {
if (topic === "message-manager-close") {
if (subject === this.messageManager) {
this.closed = true;
diff --git a/toolkit/components/extensions/ProxyChannelFilter.sys.mjs b/toolkit/components/extensions/ProxyChannelFilter.sys.mjs
index 2f7f8cb113..444602f2fa 100644
--- a/toolkit/components/extensions/ProxyChannelFilter.sys.mjs
+++ b/toolkit/components/extensions/ProxyChannelFilter.sys.mjs
@@ -174,9 +174,9 @@ const ProxyInfoData = {
`ProxyInfoData: Invalid proxy server authorization header: "${proxyAuthorizationHeader}"`
);
}
- if (type !== "https") {
+ if (type !== "https" && type !== "http") {
throw new ExtensionError(
- `ProxyInfoData: ProxyAuthorizationHeader requires type "https"`
+ `ProxyInfoData: ProxyAuthorizationHeader requires type "https" or "http"`
);
}
},
@@ -286,8 +286,8 @@ export class ProxyChannelFilter {
);
}
- // Originally duplicated from WebRequest.jsm with small changes. Keep this
- // in sync with WebRequest.jsm as well as parent/ext-webRequest.js when
+ // 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;
diff --git a/toolkit/components/extensions/Schemas.sys.mjs b/toolkit/components/extensions/Schemas.sys.mjs
index 9107e6a347..e98dfb36f0 100644
--- a/toolkit/components/extensions/Schemas.sys.mjs
+++ b/toolkit/components/extensions/Schemas.sys.mjs
@@ -373,7 +373,7 @@ class Context {
this.path = [];
this.preprocessors = {
- localize(value, context) {
+ localize(value) {
return value;
},
...params.preprocessors,
@@ -436,12 +436,12 @@ class Context {
/**
* Checks whether this context has the given permission.
*
- * @param {string} permission
+ * @param {string} _permission
* The name of the permission to check.
*
* @returns {boolean} True if the context has the given permission.
*/
- hasPermission(permission) {
+ hasPermission(_permission) {
return false;
}
@@ -449,12 +449,12 @@ class Context {
* Checks whether the given permission can be dynamically revoked or
* granted.
*
- * @param {string} permission
+ * @param {string} _permission
* The name of the permission to check.
*
* @returns {boolean} True if the given permission is revokable.
*/
- isPermissionRevokable(permission) {
+ isPermissionRevokable(_permission) {
return false;
}
@@ -882,18 +882,18 @@ class InjectionContext extends Context {
* Check whether the API should be injected.
*
* @abstract
- * @param {string} namespace The namespace of the API. This may contain dots,
+ * @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.
+ * @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
+ * @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) {
+ shouldInject(_namespace, _name, _allowedContexts) {
throw new Error("Not implemented");
}
@@ -901,12 +901,12 @@ class InjectionContext extends Context {
* Generate the implementation for `namespace`.`name`.
*
* @abstract
- * @param {string} namespace The full path to the namespace of the API, minus
+ * @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.
+ * @param {string} _name The name of the method, property or event.
* @returns {SchemaAPIInterface} The implementation of the API.
*/
- getImplementation(namespace, name) {
+ getImplementation(_namespace, _name) {
throw new Error("Not implemented");
}
@@ -1035,7 +1035,7 @@ class InjectionContext extends Context {
* format.
*/
const FORMATS = {
- hostname(string, context) {
+ 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;
@@ -1053,7 +1053,7 @@ const FORMATS = {
return string;
},
- canonicalDomain(string, context) {
+ canonicalDomain(string) {
let valid;
try {
@@ -1129,7 +1129,7 @@ const FORMATS = {
return FORMATS.relativeUrl(string, context);
},
- unresolvedRelativeUrl(string, context) {
+ unresolvedRelativeUrl(string) {
if (!string.startsWith("//")) {
try {
new URL(string);
@@ -1191,7 +1191,7 @@ const FORMATS = {
return string;
},
- date(string, context) {
+ 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})))?$/;
@@ -1207,7 +1207,7 @@ const FORMATS = {
return string;
},
- manifestShortcutKey(string, context) {
+ manifestShortcutKey(string) {
if (lazy.ShortcutUtils.validate(string) == lazy.ShortcutUtils.IS_VALID) {
return string;
}
@@ -1374,15 +1374,15 @@ class Entry {
* 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
+ * @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) {
+ getDescriptor(_path, _context) {
return undefined;
}
}
@@ -1484,7 +1484,7 @@ class Type extends Entry {
// valid for this type. It returns true or false. It's used to fill
// in optional arguments to functions before actually type checking
- checkBaseType(baseType) {
+ checkBaseType() {
return false;
}
@@ -1517,7 +1517,7 @@ class AnyType extends Type {
return this.postprocess({ value }, context);
}
- checkBaseType(baseType) {
+ checkBaseType() {
return true;
}
}
diff --git a/toolkit/components/extensions/WebNavigation.sys.mjs b/toolkit/components/extensions/WebNavigation.sys.mjs
index 3de3c58986..7235aaeb4e 100644
--- a/toolkit/components/extensions/WebNavigation.sys.mjs
+++ b/toolkit/components/extensions/WebNavigation.sys.mjs
@@ -95,9 +95,8 @@ export var WebNavigationManager = {
*
* @param {nsIAutoCompleteInput | object} subject
* @param {string} topic
- * @param {string | undefined} data
*/
- observe: function (subject, topic, data) {
+ observe: function (subject, topic) {
if (topic == "urlbar-user-start-navigation") {
this.onURLBarUserStartNavigation(subject.wrappedJSObject);
} else if (topic == "webNavigation-createdNavigationTarget") {
diff --git a/toolkit/components/extensions/child/ext-declarativeNetRequest.js b/toolkit/components/extensions/child/ext-declarativeNetRequest.js
index 82028c6105..27e67d12f8 100644
--- a/toolkit/components/extensions/child/ext-declarativeNetRequest.js
+++ b/toolkit/components/extensions/child/ext-declarativeNetRequest.js
@@ -11,7 +11,7 @@ ChromeUtils.defineESModuleGetters(this, {
});
this.declarativeNetRequest = class extends ExtensionAPI {
- getAPI(context) {
+ getAPI() {
return {
declarativeNetRequest: {
get GUARANTEED_MINIMUM_STATIC_RULES() {
diff --git a/toolkit/components/extensions/child/ext-storage.js b/toolkit/components/extensions/child/ext-storage.js
index 2d10964d0a..3d71a1cd60 100644
--- a/toolkit/components/extensions/child/ext-storage.js
+++ b/toolkit/components/extensions/child/ext-storage.js
@@ -67,7 +67,7 @@ this.storage = class extends ExtensionAPI {
};
}
- getLocalIDBBackend(context, { fireOnChanged, serialize, storagePrincipal }) {
+ getLocalIDBBackend(context, { fireOnChanged, storagePrincipal }) {
let dbPromise;
async function getDB() {
if (dbPromise) {
@@ -185,7 +185,7 @@ this.storage = class extends ExtensionAPI {
return items;
}
// If we got here, then `items` is an object generated by `ObjectType`'s
- // `normalize` method from Schemas.jsm. The object returned by `normalize`
+ // `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
@@ -348,10 +348,10 @@ this.storage = class extends ExtensionAPI {
.callParentAsyncFunction("storage.managed.get", [serialize(keys)])
.then(deserialize);
},
- set(items) {
+ set() {
return Promise.reject({ message: "storage.managed is read-only" });
},
- remove(keys) {
+ remove() {
return Promise.reject({ message: "storage.managed is read-only" });
},
clear() {
diff --git a/toolkit/components/extensions/child/ext-test.js b/toolkit/components/extensions/child/ext-test.js
index a4178b63ff..3d9835d61f 100644
--- a/toolkit/components/extensions/child/ext-test.js
+++ b/toolkit/components/extensions/child/ext-test.js
@@ -248,7 +248,7 @@ this.test = class extends ExtensionAPI {
},
assertDeepEq(expected, actual, msg) {
- // The bindings generated by Schemas.jsm accepts any input, but the
+ // 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.
@@ -302,7 +302,7 @@ this.test = class extends ExtensionAPI {
promise = Promise.resolve(promise);
return promise.then(
- result => {
+ () => {
let message = `Promise resolved, expected rejection '${toSource(
expectedError
)}'`;
diff --git a/toolkit/components/extensions/child/ext-userScripts-content.js b/toolkit/components/extensions/child/ext-userScripts-content.js
index ee1a1b7a8f..d2edb3c515 100644
--- a/toolkit/components/extensions/child/ext-userScripts-content.js
+++ b/toolkit/components/extensions/child/ext-userScripts-content.js
@@ -375,7 +375,7 @@ this.userScriptsContent = class extends ExtensionAPI {
throw new ExtensionError(USERSCRIPT_DISABLED_ERRORMSG);
}
- let handler = (event, metadata, scriptSandbox, eventResult) => {
+ let handler = (event, metadata, scriptSandbox) => {
const us = new UserScript({
context,
metadata,
diff --git a/toolkit/components/extensions/docs/webext-storage.rst b/toolkit/components/extensions/docs/webext-storage.rst
index 9b5f2428d6..27ba3ad4d7 100644
--- a/toolkit/components/extensions/docs/webext-storage.rst
+++ b/toolkit/components/extensions/docs/webext-storage.rst
@@ -207,7 +207,7 @@ the actual result of the function (also a set of changes to send to observers, b
beyond this doc).
Ultimately, the `PuntResult` ends up back on the main thread once the call is complete
-and arranges to callback the JS implementation, which in turn resolves the promise created in `ExtensionStorageSync.jsm`
+and arranges to callback the JS implementation, which in turn resolves the promise created in `ExtensionStorageSync.sys.mjs`
End result:
-----------
diff --git a/toolkit/components/extensions/docs/webidl_bindings.rst b/toolkit/components/extensions/docs/webidl_bindings.rst
index be8c63d0a7..7f7f2e53cb 100644
--- a/toolkit/components/extensions/docs/webidl_bindings.rst
+++ b/toolkit/components/extensions/docs/webidl_bindings.rst
@@ -4,7 +4,7 @@ WebIDL WebExtensions API Bindings
While on ``manifest_version: 2`` all the extension globals (extension pages and content scripts)
that lives on the main thread and the WebExtensions API bindings can be injected into the extension
global from the JS privileged code part of the WebExtensions internals (`See Schemas.inject defined in
-Schemas.jsm <https://searchfox.org/mozilla-central/search?q=symbol:Schemas%23inject&redirect=false>`_),
+Schemas.sys.mjs <https://searchfox.org/mozilla-central/search?q=symbol:Schemas%23inject&redirect=false>`_),
in ``manifest_version: 3`` the extension will be able to declare a background service worker
instead of a background page, and the existing WebExtensions API bindings can't be injected into this
new extension global, because it lives off of the main thread.
diff --git a/toolkit/components/extensions/metrics.yaml b/toolkit/components/extensions/metrics.yaml
index 192b12f9f9..6d36dae614 100644
--- a/toolkit/components/extensions/metrics.yaml
+++ b/toolkit/components/extensions/metrics.yaml
@@ -127,7 +127,7 @@ extensions.apis.dnr:
startup_cache_read_size:
type: memory_distribution
memory_unit: byte
- expires: 126
+ expires: 138
description: |
Amount of data read from the DNR startup cache file.
lifetime: application
@@ -136,6 +136,7 @@ extensions.apis.dnr:
bugs:
- https://bugzilla.mozilla.org/1803363/
- https://bugzilla.mozilla.org/1850890/
+ - https://bugzilla.mozilla.org/1881399/
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1803363#c11
data_sensitivity:
@@ -145,7 +146,7 @@ extensions.apis.dnr:
startup_cache_read_time:
type: timing_distribution
time_unit: millisecond
- expires: 126
+ expires: 138
description: |
Amount of time it takes to read data into the DNR startup cache file.
lifetime: application
@@ -154,6 +155,7 @@ extensions.apis.dnr:
bugs:
- https://bugzilla.mozilla.org/1803363/
- https://bugzilla.mozilla.org/1850890/
+ - https://bugzilla.mozilla.org/1881399/
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1803363#c11
data_sensitivity:
@@ -163,7 +165,7 @@ extensions.apis.dnr:
startup_cache_write_size:
type: memory_distribution
memory_unit: byte
- expires: 126
+ expires: 138
description: |
Amount of data written to the DNR startup cache file.
lifetime: application
@@ -172,6 +174,7 @@ extensions.apis.dnr:
bugs:
- https://bugzilla.mozilla.org/1803363/
- https://bugzilla.mozilla.org/1850890/
+ - https://bugzilla.mozilla.org/1881399/
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1803363#c11
data_sensitivity:
@@ -181,7 +184,7 @@ extensions.apis.dnr:
startup_cache_write_time:
type: timing_distribution
time_unit: millisecond
- expires: 126
+ expires: 138
description: |
Amount of time it takes to write data into the DNR startup cache file.
lifetime: application
@@ -190,6 +193,7 @@ extensions.apis.dnr:
bugs:
- https://bugzilla.mozilla.org/1803363/
- https://bugzilla.mozilla.org/1850890/
+ - https://bugzilla.mozilla.org/1881399/
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1803363#c11
data_sensitivity:
@@ -198,7 +202,7 @@ extensions.apis.dnr:
startup_cache_entries:
type: labeled_counter
- expires: 126
+ expires: 138
description: |
Counters for startup cache data hits or misses on initializating
DNR rules for extensions loaded on application startup.
@@ -208,6 +212,7 @@ extensions.apis.dnr:
bugs:
- https://bugzilla.mozilla.org/1803363/
- https://bugzilla.mozilla.org/1850890/
+ - https://bugzilla.mozilla.org/1881399/
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1803363#c11
data_sensitivity:
@@ -220,7 +225,7 @@ extensions.apis.dnr:
validate_rules_time:
type: timing_distribution
time_unit: millisecond
- expires: 126
+ expires: 138
description: |
Amount of time it takes to validate DNR rules of individual ruleset
when dynamic or static rulesets have been loaded from disk.
@@ -230,6 +235,7 @@ extensions.apis.dnr:
bugs:
- https://bugzilla.mozilla.org/1803363/
- https://bugzilla.mozilla.org/1850890/
+ - https://bugzilla.mozilla.org/1881399/
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1803363#c11
data_sensitivity:
@@ -239,7 +245,7 @@ extensions.apis.dnr:
evaluate_rules_time:
type: timing_distribution
time_unit: millisecond
- expires: 126
+ expires: 138
description: |
Amount of time it takes to evaluate DNR rules for one network request.
lifetime: application
@@ -248,6 +254,7 @@ extensions.apis.dnr:
bugs:
- https://bugzilla.mozilla.org/1803363/
- https://bugzilla.mozilla.org/1850890/
+ - https://bugzilla.mozilla.org/1881399/
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1803363#c11
data_sensitivity:
@@ -257,7 +264,7 @@ extensions.apis.dnr:
evaluate_rules_count_max:
type: quantity
unit: rules
- expires: 126
+ expires: 138
description: |
Max amount of DNR rules being evaluated.
lifetime: ping
@@ -266,6 +273,7 @@ extensions.apis.dnr:
bugs:
- https://bugzilla.mozilla.org/1803363/
- https://bugzilla.mozilla.org/1850890/
+ - https://bugzilla.mozilla.org/1881399/
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1803363#c11
data_sensitivity:
diff --git a/toolkit/components/extensions/parent/ext-alarms.js b/toolkit/components/extensions/parent/ext-alarms.js
index 1eea8397e2..ae4800a242 100644
--- a/toolkit/components/extensions/parent/ext-alarms.js
+++ b/toolkit/components/extensions/parent/ext-alarms.js
@@ -43,7 +43,7 @@ class Alarm {
this.canceled = true;
}
- observe(subject, topic, data) {
+ observe() {
if (this.canceled) {
return;
}
@@ -97,7 +97,7 @@ this.alarms = class extends ExtensionAPIPersistent {
unregister: () => {
this.callbacks.delete(callback);
},
- convert(_fire, context) {
+ convert(_fire) {
fire = _fire;
},
};
diff --git a/toolkit/components/extensions/parent/ext-backgroundPage.js b/toolkit/components/extensions/parent/ext-backgroundPage.js
index 155220c67a..c10912aa3c 100644
--- a/toolkit/components/extensions/parent/ext-backgroundPage.js
+++ b/toolkit/components/extensions/parent/ext-backgroundPage.js
@@ -718,7 +718,7 @@ class BackgroundBuilder {
}
}
- observe(subject, topic, data) {
+ observe(subject, topic) {
if (topic == "timer-callback") {
let { extension } = this;
this.clearIdleTimer();
@@ -1028,7 +1028,7 @@ class BackgroundBuilder {
}
this.backgroundPage = class extends ExtensionAPI {
- async onManifestEntry(entryName) {
+ async onManifestEntry() {
let { extension } = this;
// When in PPB background pages all run in a private context. This check
diff --git a/toolkit/components/extensions/parent/ext-browsingData.js b/toolkit/components/extensions/parent/ext-browsingData.js
index d06f7a3a1b..6d4b721e4f 100644
--- a/toolkit/components/extensions/parent/ext-browsingData.js
+++ b/toolkit/components/extensions/parent/ext-browsingData.js
@@ -80,7 +80,7 @@ const clearCache = options => {
const clearCookies = async function (options) {
let cookieMgr = Services.cookies;
- // This code has been borrowed from Sanitizer.jsm.
+ // This code has been borrowed from Sanitizer.sys.mjs.
let yieldCounter = 0;
if (options.since || options.hostnames || options.cookieStoreId) {
@@ -126,7 +126,7 @@ const clearCookies = async function (options) {
}
};
-// Ideally we could reuse the logic in Sanitizer.jsm or nsIClearDataService,
+// 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) {
diff --git a/toolkit/components/extensions/parent/ext-captivePortal.js b/toolkit/components/extensions/parent/ext-captivePortal.js
index 547abaa594..e703f074f5 100644
--- a/toolkit/components/extensions/parent/ext-captivePortal.js
+++ b/toolkit/components/extensions/parent/ext-captivePortal.js
@@ -55,7 +55,7 @@ this.captivePortal = class extends ExtensionAPIPersistent {
onStateChanged({ fire }) {
this.checkCaptivePortalEnabled();
- let observer = (subject, topic) => {
+ let observer = () => {
fire.async({ state: this.nameForCPSState(gCPS.state) });
};
@@ -70,7 +70,7 @@ this.captivePortal = class extends ExtensionAPIPersistent {
"ipc:network:captive-portal-set-state"
);
},
- convert(_fire, context) {
+ convert(_fire) {
fire = _fire;
},
};
@@ -90,13 +90,13 @@ this.captivePortal = class extends ExtensionAPIPersistent {
"network:captive-portal-connectivity"
);
},
- convert(_fire, context) {
+ convert(_fire) {
fire = _fire;
},
};
},
"captiveURL.onChange": ({ fire }) => {
- let listener = (text, id) => {
+ let listener = () => {
fire.async({
levelOfControl: "not_controllable",
value: Services.prefs.getStringPref(CAPTIVE_URL_PREF),
@@ -107,7 +107,7 @@ this.captivePortal = class extends ExtensionAPIPersistent {
unregister: () => {
Services.prefs.removeObserver(CAPTIVE_URL_PREF, listener);
},
- convert(_fire, context) {
+ convert(_fire) {
fire = _fire;
},
};
diff --git a/toolkit/components/extensions/parent/ext-clipboard.js b/toolkit/components/extensions/parent/ext-clipboard.js
index 9916b14be7..cab6bf22d3 100644
--- a/toolkit/components/extensions/parent/ext-clipboard.js
+++ b/toolkit/components/extensions/parent/ext-clipboard.js
@@ -19,7 +19,7 @@ const Transferable = Components.Constructor(
);
this.clipboard = class extends ExtensionAPI {
- getAPI(context) {
+ getAPI() {
return {
clipboard: {
async setImageData(imageData, imageType) {
diff --git a/toolkit/components/extensions/parent/ext-contextualIdentities.js b/toolkit/components/extensions/parent/ext-contextualIdentities.js
index c7f28d5e90..c1d6262b6e 100644
--- a/toolkit/components/extensions/parent/ext-contextualIdentities.js
+++ b/toolkit/components/extensions/parent/ext-contextualIdentities.js
@@ -126,7 +126,7 @@ ExtensionPreferencesManager.addSetting(CONTAINERS_ENABLED_SETTING_NAME, {
this.contextualIdentities = class extends ExtensionAPIPersistent {
eventRegistrar(eventName) {
return ({ fire }) => {
- let observer = (subject, topic) => {
+ let observer = subject => {
let convertedIdentity = convertIdentityFromObserver(subject);
if (convertedIdentity) {
fire.async({ contextualIdentity: convertedIdentity });
diff --git a/toolkit/components/extensions/parent/ext-declarativeNetRequest.js b/toolkit/components/extensions/parent/ext-declarativeNetRequest.js
index 766a43d98a..fab1d941a4 100644
--- a/toolkit/components/extensions/parent/ext-declarativeNetRequest.js
+++ b/toolkit/components/extensions/parent/ext-declarativeNetRequest.js
@@ -39,7 +39,7 @@ this.declarativeNetRequest = class extends ExtensionAPI {
ExtensionDNR.clearRuleManager(this.extension);
}
- getAPI(context) {
+ getAPI() {
const { extension } = this;
return {
diff --git a/toolkit/components/extensions/parent/ext-dns.js b/toolkit/components/extensions/parent/ext-dns.js
index f32243c032..6a6cf5c894 100644
--- a/toolkit/components/extensions/parent/ext-dns.js
+++ b/toolkit/components/extensions/parent/ext-dns.js
@@ -25,7 +25,7 @@ function getErrorString(nsresult) {
}
this.dns = class extends ExtensionAPI {
- getAPI(context) {
+ getAPI() {
return {
dns: {
resolve: function (hostname, flags) {
diff --git a/toolkit/components/extensions/parent/ext-downloads.js b/toolkit/components/extensions/parent/ext-downloads.js
index 9cd96e0d65..02af2d1076 100644
--- a/toolkit/components/extensions/parent/ext-downloads.js
+++ b/toolkit/components/extensions/parent/ext-downloads.js
@@ -212,6 +212,7 @@ class DownloadItem {
let timeLeftInSeconds = sizeLeft / this.download.speed;
return new Date(Date.now() + timeLeftInSeconds * 1000);
}
+ return undefined;
}
get state() {
@@ -460,7 +461,7 @@ const downloadQuery = query => {
// an explicit value to match.
function makeMatch(regex, value, field) {
if (value == null && regex == null) {
- return input => true;
+ return () => true;
}
let re;
@@ -477,7 +478,7 @@ const downloadQuery = query => {
if (re.test(value)) {
return input => value == input;
}
- return input => false;
+ return () => false;
}
const matchFilename = makeMatch(
@@ -940,7 +941,11 @@ this.downloads = class extends ExtensionAPIPersistent {
const picker = Cc["@mozilla.org/filepicker;1"].createInstance(
Ci.nsIFilePicker
);
- picker.init(window, null, Ci.nsIFilePicker.modeSave);
+ picker.init(
+ window.browsingContext,
+ null,
+ Ci.nsIFilePicker.modeSave
+ );
if (lastFilePickerDirectory) {
picker.displayDirectory = lastFilePickerDirectory;
} else {
diff --git a/toolkit/components/extensions/parent/ext-geckoProfiler.js b/toolkit/components/extensions/parent/ext-geckoProfiler.js
index 91f2e6e594..2123f1c376 100644
--- a/toolkit/components/extensions/parent/ext-geckoProfiler.js
+++ b/toolkit/components/extensions/parent/ext-geckoProfiler.js
@@ -25,7 +25,7 @@ ChromeUtils.defineLazyGetter(this, "symbolicationService", () => {
const isRunningObserver = {
_observers: new Set(),
- observe(subject, topic, data) {
+ observe(subject, topic) {
switch (topic) {
case "profiler-started":
case "profiler-stopped":
diff --git a/toolkit/components/extensions/parent/ext-identity.js b/toolkit/components/extensions/parent/ext-identity.js
index 5bc643811a..bd53163305 100644
--- a/toolkit/components/extensions/parent/ext-identity.js
+++ b/toolkit/components/extensions/parent/ext-identity.js
@@ -94,7 +94,7 @@ const openOAuthWindow = (details, redirectURI) => {
};
httpObserver = {
- observeActivity(channel, type, subtype, timestamp, sizeData, stringData) {
+ observeActivity(channel) {
try {
channel.QueryInterface(Ci.nsIChannel);
} catch {
@@ -123,7 +123,7 @@ const openOAuthWindow = (details, redirectURI) => {
};
this.identity = class extends ExtensionAPI {
- getAPI(context) {
+ getAPI() {
return {
identity: {
launchWebAuthFlowInParent: function (details, redirectURI) {
diff --git a/toolkit/components/extensions/parent/ext-idle.js b/toolkit/components/extensions/parent/ext-idle.js
index f68ea293d7..a0f9dcfff4 100644
--- a/toolkit/components/extensions/parent/ext-idle.js
+++ b/toolkit/components/extensions/parent/ext-idle.js
@@ -29,7 +29,7 @@ const getIdleObserver = extension => {
if (!observer) {
observer = new (class extends ExtensionCommon.EventEmitter {
- observe(subject, topic, data) {
+ observe(subject, topic) {
if (topic == "idle" || topic == "active") {
this.emit("stateChanged", topic);
}
diff --git a/toolkit/components/extensions/parent/ext-management.js b/toolkit/components/extensions/parent/ext-management.js
index e0834d378f..fb28eca92c 100644
--- a/toolkit/components/extensions/parent/ext-management.js
+++ b/toolkit/components/extensions/parent/ext-management.js
@@ -188,7 +188,7 @@ this.management = class extends ExtensionAPIPersistent {
unregister: () => {
this.addonListener.off(eventName, listener);
},
- convert(_fire, context) {
+ convert(_fire) {
fire = _fire;
},
};
diff --git a/toolkit/components/extensions/parent/ext-networkStatus.js b/toolkit/components/extensions/parent/ext-networkStatus.js
index 7379d746f5..92e631aa52 100644
--- a/toolkit/components/extensions/parent/ext-networkStatus.js
+++ b/toolkit/components/extensions/parent/ext-networkStatus.js
@@ -55,7 +55,7 @@ this.networkStatus = class extends ExtensionAPI {
context,
name: "networkStatus.onConnectionChanged",
register: fire => {
- let observerStatus = (subject, topic, data) => {
+ let observerStatus = () => {
fire.async(getLinkInfo());
};
diff --git a/toolkit/components/extensions/parent/ext-protocolHandlers.js b/toolkit/components/extensions/parent/ext-protocolHandlers.js
index 36cdf25d42..099e193f0f 100644
--- a/toolkit/components/extensions/parent/ext-protocolHandlers.js
+++ b/toolkit/components/extensions/parent/ext-protocolHandlers.js
@@ -37,7 +37,7 @@ const hasHandlerApp = handlerConfig => {
};
this.protocolHandlers = class extends ExtensionAPI {
- onManifestEntry(entryName) {
+ onManifestEntry() {
let { extension } = this;
let { manifest } = extension;
diff --git a/toolkit/components/extensions/parent/ext-runtime.js b/toolkit/components/extensions/parent/ext-runtime.js
index f4f9ea6616..d1c03d9e0d 100644
--- a/toolkit/components/extensions/parent/ext-runtime.js
+++ b/toolkit/components/extensions/parent/ext-runtime.js
@@ -90,7 +90,7 @@ this.runtime = class extends ExtensionAPIPersistent {
onPerformanceWarning({ fire }) {
let { extension } = this;
- let observer = (subject, topic) => {
+ let observer = subject => {
let report = subject.QueryInterface(Ci.nsIHangReport);
if (report?.addonId !== extension.id) {
@@ -119,7 +119,7 @@ this.runtime = class extends ExtensionAPIPersistent {
unregister: () => {
Services.obs.removeObserver(observer, "process-hang-report");
},
- convert(_fire, context) {
+ convert(_fire) {
fire = _fire;
},
};
diff --git a/toolkit/components/extensions/parent/ext-scripting.js b/toolkit/components/extensions/parent/ext-scripting.js
index baa05f3aad..1563ec1e90 100644
--- a/toolkit/components/extensions/parent/ext-scripting.js
+++ b/toolkit/components/extensions/parent/ext-scripting.js
@@ -250,10 +250,8 @@ this.scripting = class extends ExtensionAPI {
const scriptIdsMap = gScriptIdsMap.get(extension);
return Array.from(scriptIdsMap.entries())
- .filter(
- ([id, scriptId]) => !details?.ids || details.ids.includes(id)
- )
- .map(([id, scriptId]) => {
+ .filter(([id]) => !details?.ids || details.ids.includes(id))
+ .map(([, scriptId]) => {
const options = extension.registeredContentScripts.get(scriptId);
return makePublicContentScript(extension, options);
diff --git a/toolkit/components/extensions/parent/ext-storage.js b/toolkit/components/extensions/parent/ext-storage.js
index 350ca0acfa..b4ee9ab422 100644
--- a/toolkit/components/extensions/parent/ext-storage.js
+++ b/toolkit/components/extensions/parent/ext-storage.js
@@ -192,8 +192,8 @@ this.storage = class extends ExtensionAPIPersistent {
extension,
onStorageSyncChanged
);
- // May be void if ExtensionStorageSyncKinto.jsm was not used.
- // ExtensionStorageSync.jsm does not use the context.
+ // May be void if ExtensionStorageSyncKinto.sys.mjs was not used.
+ // ExtensionStorageSync.sys.mjs does not use the context.
closeCallback?.();
};
}
diff --git a/toolkit/components/extensions/parent/ext-tabs-base.js b/toolkit/components/extensions/parent/ext-tabs-base.js
index 64ca9c0627..3d0fe3531d 100644
--- a/toolkit/components/extensions/parent/ext-tabs-base.js
+++ b/toolkit/components/extensions/parent/ext-tabs-base.js
@@ -199,15 +199,16 @@ class TabBase {
}
/**
- * @property {string | null} url
+ * @property {string | undefined} url
* Returns the current URL of this tab if the extension has permission
- * to read it, or null otherwise.
+ * to read it, or undefined otherwise.
* @readonly
*/
get url() {
if (this.hasTabPermission) {
return this._url;
}
+ return undefined;
}
/**
@@ -230,15 +231,16 @@ class TabBase {
}
/**
- * @property {nsIURI | null} title
+ * @property {nsIURI | undefined} title
* Returns the current title of this tab if the extension has permission
- * to read it, or null otherwise.
+ * to read it, or undefined otherwise.
* @readonly
*/
get title() {
if (this.hasTabPermission) {
return this._title;
}
+ return undefined;
}
/**
@@ -253,15 +255,16 @@ class TabBase {
}
/**
- * @property {nsIURI | null} faviconUrl
+ * @property {nsIURI | undefined} faviconUrl
* Returns the current faviron URL of this tab if the extension has permission
- * to read it, or null otherwise.
+ * to read it, or undefined otherwise.
* @readonly
*/
get favIconUrl() {
if (this.hasTabPermission) {
return this._favIconUrl;
}
+ return undefined;
}
/**
@@ -1165,9 +1168,9 @@ class WindowBase {
}
/**
- * @property {nsIURI | null} title
+ * @property {nsIURI | undefined} title
* Returns the current title of this window if the extension has permission
- * to read it, or null otherwise.
+ * to read it, or undefined otherwise.
* @readonly
*/
get title() {
@@ -1176,6 +1179,7 @@ class WindowBase {
if (this.activeTab && this.activeTab.hasTabPermission) {
return this._title;
}
+ return undefined;
}
// The JSDoc validator does not support @returns tags in abstract functions or
@@ -1184,7 +1188,7 @@ class WindowBase {
/**
* Returns the window state of the given window.
*
- * @param {DOMWindow} window
+ * @param {DOMWindow} _window
* The window for which to return a state.
*
* @returns {string}
@@ -1193,7 +1197,7 @@ class WindowBase {
* @static
* @abstract
*/
- static getState(window) {
+ static getState(_window) {
throw new Error("Not implemented");
}
@@ -1225,12 +1229,12 @@ class WindowBase {
/**
* Returns the window's tab at the specified index.
*
- * @param {integer} index
+ * @param {integer} _index
* The index of the desired tab.
*
* @returns {TabBase|undefined}
*/
- getTabAtIndex(index) {
+ getTabAtIndex(_index) {
throw new Error("Not implemented");
}
/* eslint-enable valid-jsdoc */
@@ -1354,23 +1358,23 @@ class TabTrackerBase extends EventEmitter {
/**
* Returns the numeric ID for the given native tab.
*
- * @param {NativeTab} nativeTab
+ * @param {NativeTab} _nativeTab
* The native tab for which to return an ID.
*
* @returns {integer}
* The tab's numeric ID.
* @abstract
*/
- getId(nativeTab) {
+ getId(_nativeTab) {
throw new Error("Not implemented");
}
/**
* Returns the native tab with the given numeric ID.
*
- * @param {integer} tabId
+ * @param {integer} _tabId
* The numeric ID of the tab to return.
- * @param {*} default_
+ * @param {*} _default
* The value to return if no tab exists with the given ID.
*
* @returns {NativeTab}
@@ -1379,7 +1383,7 @@ class TabTrackerBase extends EventEmitter {
* provided.
* @abstract
*/
- getTab(tabId, default_ = undefined) {
+ getTab(_tabId, _default) {
throw new Error("Not implemented");
}
@@ -1394,7 +1398,7 @@ class TabTrackerBase extends EventEmitter {
* @abstract
*/
/* eslint-enable valid-jsdoc */
- getBrowserData(browser) {
+ getBrowserData() {
throw new Error("Not implemented");
}
@@ -1450,7 +1454,7 @@ class StatusListener {
}
}
- onLocationChange(browser, webProgress, request, locationURI, flags) {
+ onLocationChange(browser, webProgress, request, locationURI) {
if (webProgress.isTopLevel) {
let status = webProgress.isLoadingDocument ? "loading" : "complete";
this.listener({ browser, status, url: locationURI.spec });
@@ -1884,26 +1888,26 @@ class WindowTrackerBase extends EventEmitter {
/**
* Adds a tab progress listener to the given browser window.
*
- * @param {DOMWindow} window
+ * @param {DOMWindow} _window
* The browser window to which to add the listener.
- * @param {object} listener
+ * @param {object} _listener
* The tab progress listener to add.
* @abstract
*/
- addProgressListener(window, listener) {
+ addProgressListener(_window, _listener) {
throw new Error("Not implemented");
}
/**
* Removes a tab progress listener from the given browser window.
*
- * @param {DOMWindow} window
+ * @param {DOMWindow} _window
* The browser window from which to remove the listener.
- * @param {object} listener
+ * @param {object} _listener
* The tab progress listener to remove.
* @abstract
*/
- removeProgressListener(window, listener) {
+ removeProgressListener(_window, _listener) {
throw new Error("Not implemented");
}
}
@@ -2030,14 +2034,14 @@ class TabManagerBase {
/**
* Determines access using extension context.
*
- * @param {NativeTab} nativeTab
+ * @param {NativeTab} _nativeTab
* The tab to check access on.
* @returns {boolean}
* True if the extension has permissions for this tab.
* @protected
* @abstract
*/
- canAccessTab(nativeTab) {
+ canAccessTab(_nativeTab) {
throw new Error("Not implemented");
}
@@ -2131,7 +2135,7 @@ class TabManagerBase {
/**
* Returns a TabBase wrapper for the tab with the given ID.
*
- * @param {integer} tabId
+ * @param {integer} _tabId
* The ID of the tab for which to return a wrapper.
*
* @returns {TabBase}
@@ -2139,22 +2143,21 @@ class TabManagerBase {
* If no tab exists with the given ID.
* @abstract
*/
- get(tabId) {
+ get(_tabId) {
throw new Error("Not implemented");
}
/**
* Returns a new TabBase instance wrapping the given native tab.
*
- * @param {NativeTab} nativeTab
+ * @param {NativeTab} _nativeTab
* The native tab for which to return a wrapper.
*
* @returns {TabBase}
* @protected
* @abstract
*/
- /* eslint-enable valid-jsdoc */
- wrapTab(nativeTab) {
+ wrapTab(_nativeTab) {
throw new Error("Not implemented");
}
}
@@ -2272,9 +2275,9 @@ class WindowManagerBase {
/**
* Returns a WindowBase wrapper for the browser window with the given ID.
*
- * @param {integer} windowId
+ * @param {integer} _windowId
* The ID of the browser window for which to return a wrapper.
- * @param {BaseContext} context
+ * @param {BaseContext} _context
* The extension context for which the matching is being performed.
* Used to determine the current window for relevant properties.
*
@@ -2283,7 +2286,7 @@ class WindowManagerBase {
* If no window exists with the given ID.
* @abstract
*/
- get(windowId, context) {
+ get(_windowId, _context) {
throw new Error("Not implemented");
}
@@ -2301,14 +2304,14 @@ class WindowManagerBase {
/**
* Returns a new WindowBase instance wrapping the given browser window.
*
- * @param {DOMWindow} window
+ * @param {DOMWindow} _window
* The browser window for which to return a wrapper.
*
* @returns {WindowBase}
* @protected
* @abstract
*/
- wrapWindow(window) {
+ wrapWindow(_window) {
throw new Error("Not implemented");
}
/* eslint-enable valid-jsdoc */
diff --git a/toolkit/components/extensions/parent/ext-theme.js b/toolkit/components/extensions/parent/ext-theme.js
index 1280563dd0..febb04064c 100644
--- a/toolkit/components/extensions/parent/ext-theme.js
+++ b/toolkit/components/extensions/parent/ext-theme.js
@@ -435,7 +435,7 @@ this.theme = class extends ExtensionAPIPersistent {
},
};
- onManifestEntry(entryName) {
+ onManifestEntry() {
let { extension } = this;
let { manifest } = extension;
diff --git a/toolkit/components/extensions/parent/ext-webNavigation.js b/toolkit/components/extensions/parent/ext-webNavigation.js
index c65b61041b..6947149915 100644
--- a/toolkit/components/extensions/parent/ext-webNavigation.js
+++ b/toolkit/components/extensions/parent/ext-webNavigation.js
@@ -227,7 +227,7 @@ this.webNavigation = class extends ExtensionAPIPersistent {
onTabReplaced: new EventManager({
context,
name: "webNavigation.onTabReplaced",
- register: fire => {
+ register: () => {
return () => {};
},
}).api(),
diff --git a/toolkit/components/extensions/storage/webext_storage_bridge/src/lib.rs b/toolkit/components/extensions/storage/webext_storage_bridge/src/lib.rs
index 94133ef1e9..c1998fb636 100644
--- a/toolkit/components/extensions/storage/webext_storage_bridge/src/lib.rs
+++ b/toolkit/components/extensions/storage/webext_storage_bridge/src/lib.rs
@@ -16,11 +16,11 @@
//! and `storage.managed`, which is provisioned in a native manifest and
//! read-only.
//!
-//! * `storage.local` is implemented in `ExtensionStorageIDB.jsm`.
+//! * `storage.local` is implemented in `ExtensionStorageIDB.sys.mjs`.
//! * `storage.sync` is implemented in a Rust component, `webext_storage`. This
//! Rust component is vendored in m-c, and exposed to JavaScript via an XPCOM
//! API in `webext_storage_bridge` (this crate). Eventually, we'll change
-//! `ExtensionStorageSync.jsm` to call the XPCOM API instead of using the
+//! `ExtensionStorageSync.sys.mjs` to call the XPCOM API instead of using the
//! old Kinto storage adapter.
//! * `storage.managed` is implemented directly in `parent/ext-storage.js`.
//!
diff --git a/toolkit/components/extensions/test/browser/browser_ext_background_serviceworker.js b/toolkit/components/extensions/test/browser/browser_ext_background_serviceworker.js
index 153818f4de..6a39a668e7 100644
--- a/toolkit/components/extensions/test/browser/browser_ext_background_serviceworker.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_background_serviceworker.js
@@ -175,7 +175,7 @@ async function testServiceWorker({ extension, expectMessageReply }) {
let msgFromV1 = await SpecialPowers.spawn(
browser,
[swRegInfo.scriptURL],
- async url => {
+ async () => {
const { active } = await content.navigator.serviceWorker.ready;
const { port1, port2 } = new content.MessageChannel();
diff --git a/toolkit/components/extensions/test/browser/browser_ext_downloads_filters.js b/toolkit/components/extensions/test/browser/browser_ext_downloads_filters.js
index 48dd6b88ee..a3ee11aa66 100644
--- a/toolkit/components/extensions/test/browser/browser_ext_downloads_filters.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_downloads_filters.js
@@ -10,7 +10,7 @@ async function testAppliedFilters(ext, expectedFilter, expectedFilterCount) {
let filterCount = 0;
let MockFilePicker = SpecialPowers.MockFilePicker;
- MockFilePicker.init(window);
+ MockFilePicker.init(window.browsingContext);
MockFilePicker.displayDirectory = tempDir;
MockFilePicker.returnValue = MockFilePicker.returnCancel;
MockFilePicker.appendFiltersCallback = function (fp, val) {
diff --git a/toolkit/components/extensions/test/browser/browser_ext_downloads_referrer.js b/toolkit/components/extensions/test/browser/browser_ext_downloads_referrer.js
index 9690df6376..24d52dd8e0 100644
--- a/toolkit/components/extensions/test/browser/browser_ext_downloads_referrer.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_downloads_referrer.js
@@ -4,8 +4,8 @@
"use strict";
const URL_PATH = "browser/toolkit/components/extensions/test/browser/data";
-const TEST_URL = `http://example.com/${URL_PATH}/test_downloads_referrer.html`;
-const DOWNLOAD_URL = `http://example.com/${URL_PATH}/test-download.txt`;
+const TEST_URL = `https://example.com/${URL_PATH}/test_downloads_referrer.html`;
+const DOWNLOAD_URL = `https://example.com/${URL_PATH}/test-download.txt`;
async function triggerSaveAs({ selector }) {
const contextMenu = window.document.getElementById("contentAreaContextMenu");
@@ -28,7 +28,7 @@ add_setup(() => {
}
let MockFilePicker = SpecialPowers.MockFilePicker;
- MockFilePicker.init(window);
+ MockFilePicker.init(window.browsingContext);
registerCleanupFunction(function () {
MockFilePicker.cleanup();
diff --git a/toolkit/components/extensions/test/browser/browser_ext_extension_page_tab_navigated.js b/toolkit/components/extensions/test/browser/browser_ext_extension_page_tab_navigated.js
index 9742d42b2e..429d584a17 100644
--- a/toolkit/components/extensions/test/browser/browser_ext_extension_page_tab_navigated.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_extension_page_tab_navigated.js
@@ -73,7 +73,7 @@ function createTestExtPageScript(name) {
// Triggers a WebRequest listener registered by the test extensions by
// opening a tab on the given web page URL and then closing it after
// it did load.
-async function triggerWebRequestListener(webPageURL, pause) {
+async function triggerWebRequestListener(webPageURL) {
let webPageTab = await BrowserTestUtils.openNewForegroundTab(
{
gBrowser,
diff --git a/toolkit/components/extensions/test/browser/browser_ext_management_themes.js b/toolkit/components/extensions/test/browser/browser_ext_management_themes.js
index d3cfa536b8..7fc656f141 100644
--- a/toolkit/components/extensions/test/browser/browser_ext_management_themes.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_management_themes.js
@@ -91,7 +91,7 @@ add_task(async function test_management_themes() {
return found;
}
- browser.test.onMessage.addListener(async msg => {
+ browser.test.onMessage.addListener(async () => {
let theme = await getAddon("theme");
browser.test.assertEq(
theme.description,
diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_arrowpanels.js b/toolkit/components/extensions/test/browser/browser_ext_themes_arrowpanels.js
index 6665fb3092..9ddd1d62d3 100644
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_arrowpanels.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_arrowpanels.js
@@ -23,7 +23,7 @@ function closeIdentityPopup() {
// This test checks applied WebExtension themes that attempt to change
// popup properties
-add_task(async function test_popup_styling(browser, accDoc) {
+add_task(async function test_popup_styling() {
const POPUP_BACKGROUND_COLOR = "#FF0000";
const POPUP_TEXT_COLOR = "#008000";
const POPUP_BORDER_COLOR = "#0000FF";
@@ -50,7 +50,7 @@ add_task(async function test_popup_styling(browser, accDoc) {
await BrowserTestUtils.withNewTab(
{ gBrowser, url: "https://example.com" },
- async function (browser) {
+ async function () {
await extension.startup();
// Open the information arrow panel
diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_incognito.js b/toolkit/components/extensions/test/browser/browser_ext_themes_incognito.js
index d9beb0f9a8..6daa6dd812 100644
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_incognito.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_incognito.js
@@ -36,7 +36,7 @@ add_task(async function test_theme_incognito_not_allowed() {
},
};
- browser.theme.onUpdated.addListener(info => {
+ browser.theme.onUpdated.addListener(() => {
browser.test.log("got theme onChanged");
browser.test.fail("theme");
});
diff --git a/toolkit/components/extensions/test/browser/browser_ext_themes_ntp_colors_perwindow.js b/toolkit/components/extensions/test/browser/browser_ext_themes_ntp_colors_perwindow.js
index 9d28cf50c8..3b739322d6 100644
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_ntp_colors_perwindow.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_ntp_colors_perwindow.js
@@ -71,10 +71,9 @@ function test_ntp_theme(browser, theme, isBrightText) {
* Test whether a given browser has the default theme applied
*
* @param {object} browser to test against
- * @param {string} url being tested
* @returns {Promise} The task as a promise
*/
-function test_ntp_default_theme(browser, url) {
+function test_ntp_default_theme(browser) {
Services.ppmm.sharedData.flush();
return SpecialPowers.spawn(
browser,
diff --git a/toolkit/components/extensions/test/mochitest/chrome_cleanup_script.js b/toolkit/components/extensions/test/mochitest/chrome_cleanup_script.js
index 9afa95f302..2c54bea61c 100644
--- a/toolkit/components/extensions/test/mochitest/chrome_cleanup_script.js
+++ b/toolkit/components/extensions/test/mochitest/chrome_cleanup_script.js
@@ -35,7 +35,7 @@ for (let win of iterBrowserWindows()) {
initialTabs.set(win, new Set(getBrowserApp(win).tabs));
}
-addMessageListener("check-cleanup", extensionId => {
+addMessageListener("check-cleanup", () => {
Services.console.unregisterListener(listener);
let results = {
diff --git a/toolkit/components/extensions/test/mochitest/file_indexedDB.html b/toolkit/components/extensions/test/mochitest/file_indexedDB.html
index 65b7e0ad2f..ce4b96f079 100644
--- a/toolkit/components/extensions/test/mochitest/file_indexedDB.html
+++ b/toolkit/components/extensions/test/mochitest/file_indexedDB.html
@@ -15,7 +15,7 @@ request.onupgradeneeded = event => {
let objectStore = db.createObjectStore(objectStoreName,
{autoIncrement: 0});
request = objectStore.add(test.value, test.key);
- request.onsuccess = event => {
+ request.onsuccess = () => {
db.close();
window.postMessage("indexedDBCreated", "*");
};
diff --git a/toolkit/components/extensions/test/mochitest/file_simple_iframe_worker.html b/toolkit/components/extensions/test/mochitest/file_simple_iframe_worker.html
index 2ecc24e648..d0c2f59844 100644
--- a/toolkit/components/extensions/test/mochitest/file_simple_iframe_worker.html
+++ b/toolkit/components/extensions/test/mochitest/file_simple_iframe_worker.html
@@ -11,12 +11,12 @@
fetch("file_simple_iframe.txt");
const worker = new Worker("file_simple_worker.js?iniframe=true");
-worker.onmessage = (msg) => {
+worker.onmessage = () => {
worker.postMessage("file_simple_iframe_worker.txt");
}
const sharedworker = new SharedWorker("file_simple_sharedworker.js?iniframe=true");
-sharedworker.port.onmessage = (msg) => {
+sharedworker.port.onmessage = () => {
sharedworker.port.postMessage("file_simple_iframe_sharedworker.txt");
}
sharedworker.port.start();
diff --git a/toolkit/components/extensions/test/mochitest/file_simple_webrequest_worker.html b/toolkit/components/extensions/test/mochitest/file_simple_webrequest_worker.html
index a90c4509be..792fc5621b 100644
--- a/toolkit/components/extensions/test/mochitest/file_simple_webrequest_worker.html
+++ b/toolkit/components/extensions/test/mochitest/file_simple_webrequest_worker.html
@@ -11,12 +11,12 @@
fetch("file_simple_toplevel.txt");
const worker = new Worker("file_simple_worker.js");
-worker.onmessage = (msg) => {
+worker.onmessage = () => {
worker.postMessage("file_simple_worker.txt");
}
const sharedworker = new SharedWorker("file_simple_sharedworker.js");
-sharedworker.port.onmessage = (msg) => {
+sharedworker.port.onmessage = () => {
dump(`postMessage to sharedworker\n`);
sharedworker.port.postMessage("file_simple_sharedworker.txt");
}
diff --git a/toolkit/components/extensions/test/mochitest/head_cookies.js b/toolkit/components/extensions/test/mochitest/head_cookies.js
index 610c800c94..80c7f96266 100644
--- a/toolkit/components/extensions/test/mochitest/head_cookies.js
+++ b/toolkit/components/extensions/test/mochitest/head_cookies.js
@@ -30,7 +30,7 @@ async function testCookies(options) {
let { url, domain, secure } = backgroundOptions;
let failures = 0;
- let tallyFailure = error => {
+ let tallyFailure = () => {
failures++;
};
diff --git a/toolkit/components/extensions/test/mochitest/head_webrequest.js b/toolkit/components/extensions/test/mochitest/head_webrequest.js
index 9e6b5cc910..c40fd06619 100644
--- a/toolkit/components/extensions/test/mochitest/head_webrequest.js
+++ b/toolkit/components/extensions/test/mochitest/head_webrequest.js
@@ -200,7 +200,7 @@ function background(events) {
}
let listeners = {
- onBeforeRequest(expected, details, result) {
+ onBeforeRequest(expected, details) {
// Save some values to test request consistency in later events.
browser.test.assertTrue(
details.tabId !== undefined,
@@ -263,7 +263,7 @@ function background(events) {
}
},
onBeforeRedirect() {},
- onSendHeaders(expected, details, result) {
+ onSendHeaders(expected, details) {
if (expected.headers && expected.headers.request) {
checkHeaders("request", expected, details);
}
@@ -287,7 +287,7 @@ function background(events) {
onAuthRequired(expected, details, result) {
result.authCredentials = expected.authInfo;
},
- onCompleted(expected, details, result) {
+ onCompleted(expected, details) {
// If we have already completed a GET request for this url,
// and it was found, we expect for the response to come fromCache.
// expected.cached may be undefined, force boolean.
@@ -322,7 +322,7 @@ function background(events) {
checkHeaders("response", expected, details);
}
},
- onErrorOccurred(expected, details, result) {
+ onErrorOccurred(expected, details) {
if (expected.error) {
if (Array.isArray(expected.error)) {
browser.test.assertTrue(
diff --git a/toolkit/components/extensions/test/mochitest/mochitest-common.toml b/toolkit/components/extensions/test/mochitest/mochitest-common.toml
index 51a851a74b..782069a79c 100644
--- a/toolkit/components/extensions/test/mochitest/mochitest-common.toml
+++ b/toolkit/components/extensions/test/mochitest/mochitest-common.toml
@@ -130,6 +130,7 @@ skip-if = ["os == 'android'"] # only the current window is supported - bug 17959
["test_ext_browserAction_openPopup_without_pref.html"]
["test_ext_browserSettings_overrideDocumentColors.html"]
+skip-if = ["os == 'android'"] # bug 1876317
["test_ext_browsingData_indexedDB.html"]
skip-if = [
diff --git a/toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_saveAs.html b/toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_saveAs.html
index 4b5d90814c..b026ea6245 100644
--- a/toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_saveAs.html
+++ b/toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_saveAs.html
@@ -78,7 +78,7 @@ add_task(async function test_downloads_saveAs() {
defaultFile.append(DOWNLOAD_FILENAME);
const {MockFilePicker} = SpecialPowers;
- MockFilePicker.init(window);
+ MockFilePicker.init(SpecialPowers.wrap(window).browsingContext);
function mockFilePickerCallback(expectedStartingDir, pickedFile) {
return fp => {
diff --git a/toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_uniquify.html b/toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_uniquify.html
index 99a6c48500..35a0bb13df 100644
--- a/toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_uniquify.html
+++ b/toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_uniquify.html
@@ -42,7 +42,7 @@ add_task(async function test_downloads_uniquify() {
unique.append("file_download(1).txt");
const {MockFilePicker} = SpecialPowers;
- MockFilePicker.init(window);
+ MockFilePicker.init(SpecialPowers.wrap(window).browsingContext);
MockFilePicker.returnValue = MockFilePicker.returnOK;
MockFilePicker.showCallback = fp => {
diff --git a/toolkit/components/extensions/test/mochitest/test_chrome_ext_permissions.html b/toolkit/components/extensions/test/mochitest/test_chrome_ext_permissions.html
index 65bf0a50d0..98a9fed30e 100644
--- a/toolkit/components/extensions/test/mochitest/test_chrome_ext_permissions.html
+++ b/toolkit/components/extensions/test/mochitest/test_chrome_ext_permissions.html
@@ -67,7 +67,7 @@ function makeTest(manifestPermissions, optionalPermissions, checkFetch = true) {
let url = new URL(window.location.pathname, "http://example.com/");
fetch(url, {}).then(response => {
browser.test.sendMessage("fetch.result", response.ok);
- }).catch(err => {
+ }).catch(() => {
browser.test.sendMessage("fetch.result", false);
});
},
diff --git a/toolkit/components/extensions/test/mochitest/test_chrome_ext_webrequest_background_events.html b/toolkit/components/extensions/test/mochitest/test_chrome_ext_webrequest_background_events.html
index 4caa4d2464..3f824276d6 100644
--- a/toolkit/components/extensions/test/mochitest/test_chrome_ext_webrequest_background_events.html
+++ b/toolkit/components/extensions/test/mochitest/test_chrome_ext_webrequest_background_events.html
@@ -38,7 +38,7 @@ let testExtension = {
"onCompleted",
];
- function listener(name, details) {
+ function listener(name) {
// If we get anything, we failed. Removing the system principal check
// in ext-webrequest triggers this failure.
browser.test.fail(`received ${name}`);
@@ -58,7 +58,7 @@ add_task(async function test_webRequest_chromeworker_events() {
await extension.startup();
await new Promise(resolve => {
let worker = new ChromeWorker("webrequest_chromeworker.js");
- worker.onmessage = event => {
+ worker.onmessage = () => {
ok("chrome worker fetch finished");
resolve();
};
diff --git a/toolkit/components/extensions/test/mochitest/test_chrome_ext_webrequest_mozextension.html b/toolkit/components/extensions/test/mochitest/test_chrome_ext_webrequest_mozextension.html
index 6a41b9cf08..36de902180 100644
--- a/toolkit/components/extensions/test/mochitest/test_chrome_ext_webrequest_mozextension.html
+++ b/toolkit/components/extensions/test/mochitest/test_chrome_ext_webrequest_mozextension.html
@@ -82,7 +82,7 @@ add_task(async function test_webRequest_mozextension_fetch() {
browser.test.sendMessage("request-complete");
}, {urls: [browser.runtime.getURL("*")]});
- browser.test.onMessage.addListener((msg, data) => {
+ browser.test.onMessage.addListener(() => {
fetch(page).then(() => {
browser.test.notifyPass("fetch success");
browser.test.sendMessage("done");
diff --git a/toolkit/components/extensions/test/mochitest/test_ext_activityLog.html b/toolkit/components/extensions/test/mochitest/test_ext_activityLog.html
index c426913373..ac0ec974dc 100644
--- a/toolkit/components/extensions/test/mochitest/test_ext_activityLog.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_activityLog.html
@@ -27,7 +27,7 @@ add_task(async function test_api() {
// This privileged test extension should not affect the webRequest
// data received by non-privileged extensions (See Bug 1576272).
browser.webRequest.onBeforeRequest.addListener(
- details => {
+ () => {
return { cancel: false };
},
{ urls: ["http://mochi.test/*/file_sample.html"] },
@@ -79,7 +79,7 @@ add_task(async function test_api() {
browser.storage.onChanged.removeListener(listen);
// Test a parent event manager.
- let webRequestListener = details => {
+ let webRequestListener = () => {
browser.webRequest.onBeforeRequest.removeListener(webRequestListener);
return { cancel: false };
};
diff --git a/toolkit/components/extensions/test/mochitest/test_ext_async_clipboard.html b/toolkit/components/extensions/test/mochitest/test_ext_async_clipboard.html
index 4bd8339357..708b5522c3 100644
--- a/toolkit/components/extensions/test/mochitest/test_ext_async_clipboard.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_async_clipboard.html
@@ -154,7 +154,7 @@ add_task(async function test_contentscript_clipboard_permission_writetext() {
clipboardWriteText(str).then(function() {
// nothing here
browser.test.sendMessage("ready");
- }, function(err) {
+ }, function() {
browser.test.fail("WriteText promise rejected");
browser.test.sendMessage("ready");
}); // clipboardWriteText
@@ -196,7 +196,7 @@ add_task(async function test_contentscript_clipboard_permission_readtext() {
browser.test.fail("ReadText read the wrong thing from clipboard:" + strData);
}
browser.test.sendMessage("ready");
- }, function(err) {
+ }, function() {
browser.test.fail("ReadText promise rejected");
browser.test.sendMessage("ready");
}); // clipboardReadText
@@ -237,7 +237,7 @@ add_task(async function test_contentscript_clipboard_permission_write() {
clipboardWrite([item]).then(function() {
// nothing here
browser.test.sendMessage("ready");
- }, function(err) { // clipboardWrite promise error function
+ }, function() { // clipboardWrite promise error function
browser.test.fail("Write promise rejected");
browser.test.sendMessage("ready");
}); // clipboard write
@@ -280,7 +280,7 @@ add_task(async function test_contentscript_clipboard_permission_read() {
browser.test.fail("Read read the wrong string from clipboard:" + s);
}
browser.test.sendMessage("ready");
- }, function(err) { // clipboardRead promise error function
+ }, function() { // clipboardRead promise error function
browser.test.fail("Read promise rejected");
browser.test.sendMessage("ready");
}); // clipboard read
diff --git a/toolkit/components/extensions/test/mochitest/test_ext_browsingData_indexedDB.html b/toolkit/components/extensions/test/mochitest/test_ext_browsingData_indexedDB.html
index f8ea41ddab..0d417cbb5f 100644
--- a/toolkit/components/extensions/test/mochitest/test_ext_browsingData_indexedDB.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_browsingData_indexedDB.html
@@ -52,7 +52,7 @@ add_task(async function testIndexedDB() {
// eslint-disable-next-line mozilla/balanced-listeners
window.addEventListener(
"message",
- msg => {
+ () => {
browser.test.sendMessage("indexedDBCreated");
},
true
diff --git a/toolkit/components/extensions/test/mochitest/test_ext_browsingData_localStorage.html b/toolkit/components/extensions/test/mochitest/test_ext_browsingData_localStorage.html
index 2fd608f125..0e36139f14 100644
--- a/toolkit/components/extensions/test/mochitest/test_ext_browsingData_localStorage.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_browsingData_localStorage.html
@@ -177,7 +177,7 @@ add_task(async function testLocalStorage() {
function awaitLoad(tabId) {
return new Promise(resolve => {
- browser.tabs.onUpdated.addListener(function listener(tabId_, changed, tab) {
+ browser.tabs.onUpdated.addListener(function listener(tabId_, changed) {
if (tabId == tabId_ && changed.status == "complete") {
browser.tabs.onUpdated.removeListener(listener);
resolve();
diff --git a/toolkit/components/extensions/test/mochitest/test_ext_browsingData_serviceWorkers.html b/toolkit/components/extensions/test/mochitest/test_ext_browsingData_serviceWorkers.html
index d8ebd8e225..36ec448ccd 100644
--- a/toolkit/components/extensions/test/mochitest/test_ext_browsingData_serviceWorkers.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_browsingData_serviceWorkers.html
@@ -33,7 +33,7 @@ add_task(async function testServiceWorkers() {
const PAGE =
"/tests/toolkit/components/extensions/test/mochitest/file_serviceWorker.html";
- browser.runtime.onMessage.addListener(msg => {
+ browser.runtime.onMessage.addListener(() => {
browser.test.sendMessage("serviceWorkerRegistered");
});
diff --git a/toolkit/components/extensions/test/mochitest/test_ext_browsingData_settings.html b/toolkit/components/extensions/test/mochitest/test_ext_browsingData_settings.html
index 11c690e5bf..b36daf9b8d 100644
--- a/toolkit/components/extensions/test/mochitest/test_ext_browsingData_settings.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_browsingData_settings.html
@@ -42,16 +42,16 @@ add_task(async function testSettings() {
// Verify that we get the keys back we expect.
isDeeply(
Object.entries(settings.dataToRemove)
- .filter(([key, value]) => value)
- .map(([key, value]) => key)
+ .filter(([, value]) => value)
+ .map(([key]) => key)
.sort(),
SETTINGS_LIST,
"dataToRemove contains expected properties."
);
isDeeply(
Object.entries(settings.dataRemovalPermitted)
- .filter(([key, value]) => value)
- .map(([key, value]) => key)
+ .filter(([, value]) => value)
+ .map(([key]) => key)
.sort(),
SETTINGS_LIST,
"dataToRemove contains expected properties."
diff --git a/toolkit/components/extensions/test/mochitest/test_ext_exclude_include_globs.html b/toolkit/components/extensions/test/mochitest/test_ext_exclude_include_globs.html
index f87b5620d6..8d425656e0 100644
--- a/toolkit/components/extensions/test/mochitest/test_ext_exclude_include_globs.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_exclude_include_globs.html
@@ -14,7 +14,7 @@
add_task(async function test_contentscript() {
function background() {
- browser.runtime.onMessage.addListener(([script], sender) => {
+ browser.runtime.onMessage.addListener(([script]) => {
browser.test.sendMessage("run", {script});
browser.test.sendMessage("run-" + script);
});
@@ -65,7 +65,7 @@ add_task(async function test_contentscript() {
let extension = ExtensionTestUtils.loadExtension(extensionData);
let ran = 0;
- extension.onMessage("run", ({script}) => {
+ extension.onMessage("run", () => {
ran++;
});
diff --git a/toolkit/components/extensions/test/mochitest/test_ext_extension_iframe_messaging.html b/toolkit/components/extensions/test/mochitest/test_ext_extension_iframe_messaging.html
index 403782ab7d..2b47a9c0f7 100644
--- a/toolkit/components/extensions/test/mochitest/test_ext_extension_iframe_messaging.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_extension_iframe_messaging.html
@@ -72,7 +72,7 @@ add_task(async function test_moz_extension_iframe_messaging() {
},
},
background() {
- browser.test.onMessage.addListener(async msg => {
+ browser.test.onMessage.addListener(async () => {
await browser.test.assertRejects(
browser.runtime.sendMessage("from-background"),
diff --git a/toolkit/components/extensions/test/mochitest/test_ext_redirect_jar.html b/toolkit/components/extensions/test/mochitest/test_ext_redirect_jar.html
index 18ff14a6de..c21b18de95 100644
--- a/toolkit/components/extensions/test/mochitest/test_ext_redirect_jar.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_redirect_jar.html
@@ -46,7 +46,7 @@ function getExtension() {
},
background: async () => {
let redirectUrl = browser.runtime.getURL("finished.html");
- browser.webRequest.onBeforeRequest.addListener(details => {
+ browser.webRequest.onBeforeRequest.addListener(() => {
return {redirectUrl};
}, {urls: ["*://*/intercept*"]}, ["blocking"]);
diff --git a/toolkit/components/extensions/test/mochitest/test_ext_request_urlClassification.html b/toolkit/components/extensions/test/mochitest/test_ext_request_urlClassification.html
index a139e94687..fa4bd0a05d 100644
--- a/toolkit/components/extensions/test/mochitest/test_ext_request_urlClassification.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_request_urlClassification.html
@@ -115,7 +115,7 @@ add_task(async function teardown() {
/* eslint-env mozilla/chrome-script */
// Cleanup cache
await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value => resolve());
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () => resolve());
});
const {UrlClassifierTestUtils} = ChromeUtils.importESModule(
diff --git a/toolkit/components/extensions/test/mochitest/test_ext_sendmessage_doublereply.html b/toolkit/components/extensions/test/mochitest/test_ext_sendmessage_doublereply.html
index ffdbc90efb..6958e906b9 100644
--- a/toolkit/components/extensions/test/mochitest/test_ext_sendmessage_doublereply.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_sendmessage_doublereply.html
@@ -41,7 +41,7 @@ function background() {
}
let done_count = 0;
- browser.runtime.onMessage.addListener((msg, sender, sendReply) => {
+ browser.runtime.onMessage.addListener((msg, sender) => {
browser.test.assertTrue(sender.tab.url.endsWith("file_sample.html"), "sender url correct");
if (msg == "done") {
diff --git a/toolkit/components/extensions/test/mochitest/test_ext_sendmessage_reply2.html b/toolkit/components/extensions/test/mochitest/test_ext_sendmessage_reply2.html
index 8cce833b49..5da378de2b 100644
--- a/toolkit/components/extensions/test/mochitest/test_ext_sendmessage_reply2.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_sendmessage_reply2.html
@@ -54,7 +54,7 @@ function backgroundScript(token, id, otherId) {
function contentScript(token, id, otherId) {
let gotContentMessage = false;
- browser.runtime.onMessage.addListener((msg, sender, sendReply) => {
+ browser.runtime.onMessage.addListener((msg, sender) => {
browser.test.assertEq(id, sender.id, `${id}: Got expected sender ID`);
browser.test.assertEq(`${token}-contentMessage`, msg,
@@ -83,7 +83,7 @@ function contentScript(token, id, otherId) {
async function tabScript(token, id, otherId) {
let gotTabMessage = false;
- browser.runtime.onMessage.addListener((msg, sender, sendReply) => {
+ browser.runtime.onMessage.addListener((msg, sender) => {
browser.test.assertEq(id, sender.id, `${id}: Got expected sender ID`);
if (String(msg).startsWith("content-")) {
diff --git a/toolkit/components/extensions/test/mochitest/test_ext_storage_cleanup.html b/toolkit/components/extensions/test/mochitest/test_ext_storage_cleanup.html
index 33029cf61e..e6a885a3c7 100644
--- a/toolkit/components/extensions/test/mochitest/test_ext_storage_cleanup.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_storage_cleanup.html
@@ -71,7 +71,7 @@ const storageTestHelpers = {
reject(new Error(`indexedDB open failed with ${e.errorCode}`));
};
- req.onupgradeneeded = e => {
+ req.onupgradeneeded = () => {
// no database, data is not present
resolve(false);
};
diff --git a/toolkit/components/extensions/test/mochitest/test_ext_streamfilter_multiple.html b/toolkit/components/extensions/test/mochitest/test_ext_streamfilter_multiple.html
index d1bfbd824b..50d7d6cb7d 100644
--- a/toolkit/components/extensions/test/mochitest/test_ext_streamfilter_multiple.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_streamfilter_multiple.html
@@ -53,7 +53,7 @@ add_task(async () => {
filter.ondata = event => {
filter.write(event.data);
};
- filter.onstop = event => {
+ filter.onstop = () => {
filter.write(new TextEncoder().encode(" End"));
filter.close();
};
diff --git a/toolkit/components/extensions/test/mochitest/test_ext_streamfilter_processswitch.html b/toolkit/components/extensions/test/mochitest/test_ext_streamfilter_processswitch.html
index 049178cad0..7240e01c02 100644
--- a/toolkit/components/extensions/test/mochitest/test_ext_streamfilter_processswitch.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_streamfilter_processswitch.html
@@ -36,7 +36,7 @@ add_task(async () => {
filter.ondata = event => {
filter.write(event.data);
};
- filter.onstop = event => {
+ filter.onstop = () => {
filter.write(new TextEncoder().encode(" End"));
filter.close();
};
diff --git a/toolkit/components/extensions/test/mochitest/test_ext_subframes_privileges.html b/toolkit/components/extensions/test/mochitest/test_ext_subframes_privileges.html
index fd034f0b65..f791d08602 100644
--- a/toolkit/components/extensions/test/mochitest/test_ext_subframes_privileges.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_subframes_privileges.html
@@ -303,7 +303,7 @@ add_task(async function test_sub_subframe_conduit_verified_env() {
`,
};
- async function expectErrors(ext, log) {
+ async function expectErrors(ext) {
let err = await ext.awaitMessage("content_child");
is(err, "Bad sender context envType: content_child");
diff --git a/toolkit/components/extensions/test/mochitest/test_ext_tabs_executeScript_good.html b/toolkit/components/extensions/test/mochitest/test_ext_tabs_executeScript_good.html
index 9b0f41f789..0704b93be9 100644
--- a/toolkit/components/extensions/test/mochitest/test_ext_tabs_executeScript_good.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_tabs_executeScript_good.html
@@ -16,7 +16,7 @@ async function testHasPermission(params) {
let contentSetup = params.contentSetup || (() => Promise.resolve());
async function background(contentSetup) {
- browser.runtime.onMessage.addListener((msg, sender) => {
+ browser.runtime.onMessage.addListener((msg) => {
browser.test.assertEq(msg, "script ran", "script ran");
browser.test.notifyPass("executeScript");
});
diff --git a/toolkit/components/extensions/test/mochitest/test_ext_tabs_permissions.html b/toolkit/components/extensions/test/mochitest/test_ext_tabs_permissions.html
index 217139f12b..062e3a79e3 100644
--- a/toolkit/components/extensions/test/mochitest/test_ext_tabs_permissions.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_tabs_permissions.html
@@ -69,8 +69,7 @@ const helperExtensionDef = {
hasTitleChangeInfo = false;
browser.tabs.onUpdated.addListener(function listener(
tabId,
- changeInfo,
- tab
+ changeInfo
) {
if (changeInfo.url?.endsWith(message.data.urlHash)) {
hasURLChangeInfo = true;
@@ -413,7 +412,7 @@ async function test_restricted_properties(
let hasURLChangeInfo = false,
hasTitleChangeInfo = false;
- function onUpdateListener(tabId, changeInfo, tab) {
+ function onUpdateListener(tabId, changeInfo) {
if (changeInfo.url?.endsWith(urlHash)) {
hasURLChangeInfo = true;
}
@@ -550,7 +549,7 @@ async function test_onUpdateFilter(testCases, permissions) {
async background() {
let listenerGotCalled = false;
- function onUpdateListener(tabId, changeInfo, tab) {
+ function onUpdateListener() {
listenerGotCalled = true;
}
diff --git a/toolkit/components/extensions/test/mochitest/test_ext_web_accessible_incognito.html b/toolkit/components/extensions/test/mochitest/test_ext_web_accessible_incognito.html
index d1c41d2030..1009eb0496 100644
--- a/toolkit/components/extensions/test/mochitest/test_ext_web_accessible_incognito.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_web_accessible_incognito.html
@@ -17,7 +17,7 @@ let image = atob("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAA" +
const IMAGE_ARRAYBUFFER = Uint8Array.from(image, byte => byte.charCodeAt(0)).buffer;
async function testImageLoading(src, expectedAction) {
- let imageLoadingPromise = new Promise((resolve, reject) => {
+ let imageLoadingPromise = new Promise((resolve) => {
let cleanupListeners;
let testImage = new window.Image();
// Set the src via wrappedJSObject so the load is triggered with the
diff --git a/toolkit/components/extensions/test/mochitest/test_ext_web_accessible_resources.html b/toolkit/components/extensions/test/mochitest/test_ext_web_accessible_resources.html
index c13e40e265..c8ab397e4a 100644
--- a/toolkit/components/extensions/test/mochitest/test_ext_web_accessible_resources.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_web_accessible_resources.html
@@ -27,7 +27,7 @@ const IMAGE_ARRAYBUFFER = Uint8Array.from(image, byte => byte.charCodeAt(0))
const ANDROID = navigator.userAgent.includes("Android");
async function testImageLoading(src, expectedAction) {
- let imageLoadingPromise = new Promise((resolve, reject) => {
+ let imageLoadingPromise = new Promise((resolve) => {
let cleanupListeners;
let testImage = document.createElement("img");
// Set the src via wrappedJSObject so the load is triggered with the
diff --git a/toolkit/components/extensions/test/mochitest/test_ext_webrequest_auth.html b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_auth.html
index f260f040a1..ec5f00a18c 100644
--- a/toolkit/components/extensions/test/mochitest/test_ext_webrequest_auth.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_auth.html
@@ -79,13 +79,13 @@ add_task(async function test_webRequest_auth_nonblocking_forwardAuthProvider() {
"nsIAuthPrompt2"]),
getInterface: ChromeUtils.generateQI(["nsIAuthPromptProvider",
"nsIAuthPrompt2"]),
- promptAuth(channel, level, authInfo) {
+ promptAuth() {
throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
},
- getAuthPrompt(reason, iid) {
+ getAuthPrompt() {
return this;
},
- asyncPromptAuth(channel, callback, context, level, authInfo) {
+ asyncPromptAuth(channel, callback, context) {
// We just cancel here, we're only ensuring that non-webrequest
// notificationcallbacks get called if webrequest doesn't handle it.
Promise.resolve().then(() => {
@@ -138,10 +138,10 @@ add_task(async function test_webRequest_auth_nonblocking_forwardAuthPrompt2() {
QueryInterface: ChromeUtils.generateQI(["nsIInterfaceRequestor",
"nsIAuthPrompt2"]),
getInterface: ChromeUtils.generateQI(["nsIAuthPrompt2"]),
- promptAuth(request, level, authInfo) {
+ promptAuth() {
throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
},
- asyncPromptAuth(request, callback, context, level, authInfo) {
+ asyncPromptAuth(request) {
// We just cancel here, we're only ensuring that non-webrequest
// notificationcallbacks get called if webrequest doesn't handle it.
Promise.resolve().then(() => {
diff --git a/toolkit/components/extensions/test/mochitest/test_ext_webrequest_background_events.html b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_background_events.html
index 86cec62fb4..5ac3dccaa7 100644
--- a/toolkit/components/extensions/test/mochitest/test_ext_webrequest_background_events.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_background_events.html
@@ -35,7 +35,7 @@ add_task(async function test_webRequest_serviceworker_events() {
"onErrorOccurred",
]);
- function listener(name, details) {
+ function listener(name) {
browser.test.assertTrue(eventNames.has(name), `received ${name}`);
eventNames.delete(name);
if (name == "onCompleted") {
diff --git a/toolkit/components/extensions/test/mochitest/test_ext_webrequest_basic.html b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_basic.html
index 9d57d55681..1e769a3156 100644
--- a/toolkit/components/extensions/test/mochitest/test_ext_webrequest_basic.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_basic.html
@@ -303,7 +303,7 @@ add_task(async function test_webRequest_tabId() {
add_task(async function test_webRequest_tabId_browser() {
async function background(url) {
let tabId;
- browser.test.onMessage.addListener(async (msg, expected) => {
+ browser.test.onMessage.addListener(async (msg) => {
if (msg == "create") {
let tab = await browser.tabs.create({url});
tabId = tab.id;
diff --git a/toolkit/components/extensions/test/mochitest/test_ext_webrequest_errors.html b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_errors.html
index cbfc5c17e7..d29638b408 100644
--- a/toolkit/components/extensions/test/mochitest/test_ext_webrequest_errors.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_errors.html
@@ -23,7 +23,7 @@ async function test_connection_refused(url, expectedError) {
}, {urls: ["<all_urls>"]});
let tabId;
- browser.test.onMessage.addListener(async (msg, expected) => {
+ browser.test.onMessage.addListener(async () => {
await browser.tabs.remove(tabId);
browser.test.sendMessage("done");
});
diff --git a/toolkit/components/extensions/test/mochitest/test_ext_webrequest_hsts.html b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_hsts.html
index e66b5c471a..7b06a30551 100644
--- a/toolkit/components/extensions/test/mochitest/test_ext_webrequest_hsts.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_hsts.html
@@ -15,13 +15,13 @@ function getExtension() {
async function background() {
let expect;
let urls = ["*://*.example.org/tests/*"];
- browser.webRequest.onBeforeRequest.addListener(details => {
+ browser.webRequest.onBeforeRequest.addListener(() => {
browser.test.assertEq(expect.shift(), "onBeforeRequest");
}, {urls}, ["blocking"]);
- browser.webRequest.onBeforeSendHeaders.addListener(details => {
+ browser.webRequest.onBeforeSendHeaders.addListener(() => {
browser.test.assertEq(expect.shift(), "onBeforeSendHeaders");
}, {urls}, ["blocking", "requestHeaders"]);
- browser.webRequest.onSendHeaders.addListener(details => {
+ browser.webRequest.onSendHeaders.addListener(() => {
browser.test.assertEq(expect.shift(), "onSendHeaders");
}, {urls}, ["requestHeaders"]);
@@ -82,10 +82,10 @@ function getExtension() {
}
return {responseHeaders: headers};
}, {urls}, ["blocking", "responseHeaders"]);
- browser.webRequest.onBeforeRedirect.addListener(details => {
+ browser.webRequest.onBeforeRedirect.addListener(() => {
browser.test.assertEq(expect.shift(), "onBeforeRedirect");
}, {urls});
- browser.webRequest.onResponseStarted.addListener(details => {
+ browser.webRequest.onResponseStarted.addListener(() => {
browser.test.assertEq(expect.shift(), "onResponseStarted");
}, {urls});
browser.webRequest.onCompleted.addListener(details => {
diff --git a/toolkit/components/extensions/test/mochitest/test_ext_webrequest_redirect_bypass_cors.html b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_redirect_bypass_cors.html
index 87dbbd6598..7ba92f5c80 100644
--- a/toolkit/components/extensions/test/mochitest/test_ext_webrequest_redirect_bypass_cors.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_webrequest_redirect_bypass_cors.html
@@ -57,7 +57,7 @@ add_task(async function test_webRequest_redirect_cors_bypass() {
let win = window.open(WIN_URL);
// Creating a message channel to the new tab.
const channel = new BroadcastChannel("test_bus");
- await new Promise((resolve, reject) => {
+ await new Promise((resolve) => {
channel.onmessage = async function(fetch_result) {
// Fetch result data will either be the text content of file_sample.txt -> 'Sample'
// or a network-Error.
diff --git a/toolkit/components/extensions/test/mochitest/webrequest_chromeworker.js b/toolkit/components/extensions/test/mochitest/webrequest_chromeworker.js
index 14d3ad2bab..9fb71430bf 100644
--- a/toolkit/components/extensions/test/mochitest/webrequest_chromeworker.js
+++ b/toolkit/components/extensions/test/mochitest/webrequest_chromeworker.js
@@ -2,7 +2,7 @@
/* eslint-env worker */
-onmessage = function (event) {
+onmessage = function () {
fetch("https://example.com/example.txt").then(() => {
postMessage("Done!");
});
diff --git a/toolkit/components/extensions/test/xpcshell/data/file_page_xhr.html b/toolkit/components/extensions/test/xpcshell/data/file_page_xhr.html
index 387b5285f5..58b053f7d9 100644
--- a/toolkit/components/extensions/test/xpcshell/data/file_page_xhr.html
+++ b/toolkit/components/extensions/test/xpcshell/data/file_page_xhr.html
@@ -8,7 +8,7 @@
<script>
"use strict";
-addEventListener("message", async function(event) {
+addEventListener("message", async function() {
const url = new URL("/return_headers.sjs", location).href;
const webpageFetchResult = await fetch(url).then(res => res.json());
diff --git a/toolkit/components/extensions/test/xpcshell/data/file_permission_xhr.html b/toolkit/components/extensions/test/xpcshell/data/file_permission_xhr.html
index 6f1bb4648b..8ae5955024 100644
--- a/toolkit/components/extensions/test/xpcshell/data/file_permission_xhr.html
+++ b/toolkit/components/extensions/test/xpcshell/data/file_permission_xhr.html
@@ -12,7 +12,7 @@
/* globals privilegedFetch, privilegedXHR */
/* eslint-disable mozilla/balanced-listeners */
-addEventListener("message", function rcv(event) {
+addEventListener("message", function rcv() {
removeEventListener("message", rcv, false);
function assertTrue(condition, description) {
diff --git a/toolkit/components/extensions/test/xpcshell/head.js b/toolkit/components/extensions/test/xpcshell/head.js
index 6935e3f0da..ff58d36f7d 100644
--- a/toolkit/components/extensions/test/xpcshell/head.js
+++ b/toolkit/components/extensions/test/xpcshell/head.js
@@ -3,8 +3,9 @@
promiseQuotaManagerServiceReset, promiseQuotaManagerServiceClear,
runWithPrefs, testEnv, withHandlingUserInput, resetHandlingUserInput,
assertPersistentListeners, promiseExtensionEvent, assertHasPersistedScriptsCachedFlag,
- assertIsPersistedScriptsCachedFlag
- setup_crash_reporter_override_and_cleaner crashFrame crashExtensionBackground
+ assertIsPersistedScriptsCachedFlag,
+ setup_crash_reporter_override_and_cleaner, crashFrame, crashExtensionBackground,
+ makeRkvDatabaseDir
*/
var { AppConstants } = ChromeUtils.importESModule(
@@ -81,6 +82,19 @@ var createHttpServer = (...args) => {
return AddonTestUtils.createHttpServer(...args);
};
+async function makeRkvDatabaseDir(name, { mockCorrupted = false } = {}) {
+ const databaseDir = PathUtils.join(PathUtils.profileDir, name);
+ await IOUtils.makeDirectory(databaseDir);
+ if (mockCorrupted) {
+ // Mock a corrupted db.
+ await IOUtils.write(
+ PathUtils.join(databaseDir, "data.safe.bin"),
+ new Uint8Array([0x00, 0x00, 0x00, 0x00])
+ );
+ }
+ return databaseDir;
+}
+
// Some tests load non-moz-extension:-URLs in their extension document. When
// extensions run in-process (extensions.webextensions.remote set to false),
// that fails.
@@ -285,7 +299,7 @@ function handlingUserInputFrameScript() {
let handle;
MessageChannel.addListener(this, "ExtensionTest:HandleUserInput", {
- receiveMessage({ name, data }) {
+ receiveMessage({ data }) {
if (data) {
handle = content.windowUtils.setHandlingUserInput(true);
} else if (handle) {
@@ -367,7 +381,7 @@ const optionalPermissionsPromptHandler = {
});
},
- observe(subject, topic, data) {
+ observe(subject, topic) {
if (topic == "webextension-optional-permission-prompt") {
this.sawPrompt = true;
let { resolve } = subject.wrappedJSObject;
diff --git a/toolkit/components/extensions/test/xpcshell/head_schemas.js b/toolkit/components/extensions/test/xpcshell/head_schemas.js
index 94af4a631a..b74d461444 100644
--- a/toolkit/components/extensions/test/xpcshell/head_schemas.js
+++ b/toolkit/components/extensions/test/xpcshell/head_schemas.js
@@ -102,7 +102,7 @@ function getContextWrapper(manifestVersion = 2) {
},
preprocessors: {
- localize(value, context) {
+ localize(value) {
return value.replace(
/__MSG_(.*?)__/g,
(m0, m1) => `${m1.toUpperCase()}`
@@ -118,7 +118,7 @@ function getContextWrapper(manifestVersion = 2) {
return this.permissions.has(permission);
},
- shouldInject(ns, name, allowedContexts) {
+ shouldInject(ns, name) {
return name != "do-not-inject";
},
diff --git a/toolkit/components/extensions/test/xpcshell/head_service_worker.js b/toolkit/components/extensions/test/xpcshell/head_service_worker.js
index aa1cf5cb18..771f3b1179 100644
--- a/toolkit/components/extensions/test/xpcshell/head_service_worker.js
+++ b/toolkit/components/extensions/test/xpcshell/head_service_worker.js
@@ -150,7 +150,7 @@ class TestWorkerWatcher extends ExtensionCommon.EventEmitter {
}
}
- observe(subject, topic, childIDString) {
+ observe() {
// Keep the watched process and related test child process actor updated
// when a process is created or destroyed.
this.getAndWatchExtensionProcess();
diff --git a/toolkit/components/extensions/test/xpcshell/test_csp_validator.js b/toolkit/components/extensions/test/xpcshell/test_csp_validator.js
index 12ba3f93e9..011628f027 100644
--- a/toolkit/components/extensions/test/xpcshell/test_csp_validator.js
+++ b/toolkit/components/extensions/test/xpcshell/test_csp_validator.js
@@ -7,7 +7,7 @@ const cps = Cc["@mozilla.org/addons/content-policy;1"].getService(
);
add_task(async function test_csp_validator_flags() {
- let checkPolicy = (policy, flags, expectedResult, message = null) => {
+ let checkPolicy = (policy, flags, expectedResult) => {
info(`Checking policy: ${policy}`);
let result = cps.validateAddonCSP(policy, flags);
@@ -76,7 +76,7 @@ add_task(async function test_csp_validator_flags() {
});
add_task(async function test_csp_validator() {
- let checkPolicy = (policy, expectedResult, message = null) => {
+ let checkPolicy = (policy, expectedResult) => {
info(`Checking policy: ${policy}`);
let result = cps.validateAddonCSP(
@@ -199,7 +199,7 @@ add_task(async function test_csp_validator() {
});
add_task(async function test_csp_validator_extension_pages() {
- let checkPolicy = (policy, expectedResult, message = null) => {
+ let checkPolicy = (policy, expectedResult) => {
info(`Checking policy: ${policy}`);
// While Schemas.jsm uses Ci.nsIAddonContentPolicy.CSP_ALLOW_WASM, we don't
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_alarms_does_not_fire.js b/toolkit/components/extensions/test/xpcshell/test_ext_alarms_does_not_fire.js
index fe385004ba..77be9b22bd 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_alarms_does_not_fire.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_alarms_does_not_fire.js
@@ -7,7 +7,7 @@ add_task(async function test_cleared_alarm_does_not_fire() {
async function backgroundScript() {
let ALARM_NAME = "test_ext_alarms";
- browser.alarms.onAlarm.addListener(alarm => {
+ browser.alarms.onAlarm.addListener(() => {
browser.test.fail("cleared alarm does not fire");
browser.test.notifyFail("alarm-cleared");
});
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_api_events_listener_calls_exceptions.js b/toolkit/components/extensions/test/xpcshell/test_ext_api_events_listener_calls_exceptions.js
index 44ff592d83..02970f9144 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_api_events_listener_calls_exceptions.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_api_events_listener_calls_exceptions.js
@@ -141,6 +141,7 @@ add_task(async function test_api_listener_call_exception() {
// catch with a failure if we are running the extension code as a side effect
// of logging the error to the console service.
const nonError = {
+ // eslint-disable-next-line getter-return
get message() {
browser.test.fail(`Unexpected extension code executed`);
},
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_captivePortal.js b/toolkit/components/extensions/test/xpcshell/test_ext_captivePortal.js
index dfb5c4c415..9141aa89cb 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_captivePortal.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_captivePortal.js
@@ -1,10 +1,5 @@
"use strict";
-Services.prefs.setBoolPref(
- "extensions.webextensions.background-delayed-startup",
- true
-);
-
AddonTestUtils.init(this);
AddonTestUtils.overrideCertDB();
AddonTestUtils.createAppInfo(
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_contentScripts_register.js b/toolkit/components/extensions/test/xpcshell/test_ext_contentScripts_register.js
index c92ed11022..819b51ac8c 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_contentScripts_register.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_contentScripts_register.js
@@ -305,7 +305,7 @@ add_task(async function test_contentscripts_unregister_on_context_unload() {
add_task(async function test_contentscripts_register_js() {
async function background() {
browser.runtime.onMessage.addListener(
- ([msg, expectedStates, readyState], sender) => {
+ ([msg, expectedStates, readyState]) => {
if (msg == "chrome-namespace-ok") {
browser.test.sendMessage(msg);
return;
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_contentscript.js b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript.js
index cb2f342d4e..734e084b57 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_contentscript.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript.js
@@ -12,7 +12,7 @@ ExtensionTestUtils.mockAppInfo();
add_task(async function test_contentscript_runAt() {
function background() {
browser.runtime.onMessage.addListener(
- ([msg, expectedStates, readyState], sender) => {
+ ([msg, expectedStates, readyState]) => {
if (msg == "chrome-namespace-ok") {
browser.test.sendMessage(msg);
return;
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_context.js b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_context.js
index fc27b84200..44ecde6fcd 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_context.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_context.js
@@ -340,7 +340,7 @@ add_task(async function test_contentscript_context_valid_during_execution() {
await extension.startup();
await extension.awaitMessage("content-script-ready");
- await contentPage.legacySpawn(extension.id, async extensionId => {
+ await contentPage.legacySpawn(extension.id, async () => {
// Navigate so that the content page is frozen in the bfcache.
this.content.location = "http://example.org/dummy?second";
});
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_create_iframe.js b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_create_iframe.js
index 41d9901c80..2bd475ec15 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_create_iframe.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_create_iframe.js
@@ -5,7 +5,7 @@ server.registerDirectory("/data/", do_get_file("data"));
add_task(async function test_contentscript_create_iframe() {
function background() {
- browser.runtime.onMessage.addListener((msg, sender) => {
+ browser.runtime.onMessage.addListener(msg => {
let { name, availableAPIs, manifest, testGetManifest } = msg;
let hasExtTabsAPI = availableAPIs.indexOf("tabs") > 0;
let hasExtWindowsAPI = availableAPIs.indexOf("windows") > 0;
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_importmap.js b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_importmap.js
index ba7f7120d9..2f80840bf6 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_importmap.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_importmap.js
@@ -52,7 +52,7 @@ server.registerPathHandler("/importmap.html", (request, response) => {
response.write(importMapHtml);
});
-server.registerPathHandler("/simple.js", (request, response) => {
+server.registerPathHandler("/simple.js", () => {
ok(false, "Unexpected request to /simple.js");
});
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_module_import.js b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_module_import.js
index 3e4e5dd983..80183567c4 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_module_import.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_module_import.js
@@ -8,7 +8,7 @@ server.registerPathHandler("/dummy", (request, response) => {
response.write("<!DOCTYPE html><html></html>");
});
-server.registerPathHandler("/script.js", (request, response) => {
+server.registerPathHandler("/script.js", () => {
ok(false, "Unexpected request to /script.js");
});
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_triggeringPrincipal.js b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_triggeringPrincipal.js
index 3b8721ad8d..4ebe6df636 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_triggeringPrincipal.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_triggeringPrincipal.js
@@ -989,7 +989,7 @@ function awaitLoads(urlsPromise, origins) {
}
});
- observer = (channel, topic, data) => {
+ observer = channel => {
if (expectedURLs) {
checkChannel(channel.QueryInterface(Ci.nsIChannel));
} else {
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_contexts.js b/toolkit/components/extensions/test/xpcshell/test_ext_contexts.js
index 028f5b5638..7cb244aa3a 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_contexts.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_contexts.js
@@ -18,7 +18,7 @@ class StubContext extends BaseContext {
this.sandbox = Cu.Sandbox(global);
}
- logActivity(type, name, data) {
+ logActivity() {
// no-op required by subclass
}
@@ -128,7 +128,7 @@ class Context extends BaseContext {
this.sandbox = Cu.Sandbox(principal, { wantXrays: false });
}
- logActivity(type, name, data) {
+ logActivity() {
// no-op required by subclass
}
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_contexts_gc.js b/toolkit/components/extensions/test/xpcshell/test_ext_contexts_gc.js
index a828584ced..2a36f51637 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_contexts_gc.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_contexts_gc.js
@@ -187,7 +187,7 @@ add_task(async function test_ExtensionPageContextChild_in_child_frame() {
);
await extension.awaitMessage("extensionPageLoaded");
- await contentPage.legacySpawn(extension.id, async extensionId => {
+ await contentPage.legacySpawn(extension.id, async () => {
let { ExtensionPageChild } = ChromeUtils.importESModule(
"resource://gre/modules/ExtensionPageChild.sys.mjs"
);
@@ -237,7 +237,7 @@ add_task(async function test_ExtensionPageContextChild_in_toplevel() {
);
await extension.awaitMessage("extensionPageLoaded");
- await contentPage.legacySpawn(extension.id, async extensionId => {
+ await contentPage.legacySpawn(extension.id, async () => {
let { ExtensionPageChild } = ChromeUtils.importESModule(
"resource://gre/modules/ExtensionPageChild.sys.mjs"
);
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_dnr_allowAllRequests.js b/toolkit/components/extensions/test/xpcshell/test_ext_dnr_allowAllRequests.js
index ccb380180f..b67b28a811 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_dnr_allowAllRequests.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_dnr_allowAllRequests.js
@@ -18,7 +18,7 @@ add_setup(() => {
const server = createHttpServer({
hosts: ["example.com", "example.net", "example.org"],
});
-server.registerPathHandler("/never_reached", (req, res) => {
+server.registerPathHandler("/never_reached", () => {
Assert.ok(false, "Server should never have been reached");
});
server.registerPathHandler("/allowed", (req, res) => {
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_dnr_download.js b/toolkit/components/extensions/test/xpcshell/test_ext_dnr_download.js
index cd24b75855..5a5ac79473 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_dnr_download.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_dnr_download.js
@@ -2,7 +2,7 @@
let server = createHttpServer({ hosts: ["example.com"] });
let downloadReqCount = 0;
-server.registerPathHandler("/downloadtest", (req, res) => {
+server.registerPathHandler("/downloadtest", () => {
++downloadReqCount;
});
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_dnr_modifyHeaders.js b/toolkit/components/extensions/test/xpcshell/test_ext_dnr_modifyHeaders.js
index 236cda4e37..4b7bebe188 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_dnr_modifyHeaders.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_dnr_modifyHeaders.js
@@ -73,7 +73,7 @@ server.registerPathHandler("/setcookie", (req, res) => {
res.setHeader("Set-Cookie", "second=serving; max-age=999", /* merge */ true);
res.write(req.hasHeader("Cookie") ? req.getHeader("Cookie") : "");
});
-server.registerPathHandler("/empty", (req, res) => {});
+server.registerPathHandler("/empty", () => {});
add_setup(() => {
Services.prefs.setBoolPref("extensions.manifestV3.enabled", true);
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_dnr_webrequest.js b/toolkit/components/extensions/test/xpcshell/test_ext_dnr_webrequest.js
index 415ab42c5f..6573286d24 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_dnr_webrequest.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_dnr_webrequest.js
@@ -8,7 +8,7 @@ add_setup(() => {
const server = createHttpServer({
hosts: ["example.com", "redir"],
});
-server.registerPathHandler("/never_reached", (req, res) => {
+server.registerPathHandler("/never_reached", () => {
Assert.ok(false, "Server should never have been reached");
});
server.registerPathHandler("/source", (req, res) => {
@@ -223,7 +223,7 @@ add_task(async function redirect_with_webRequest_after_failing_dnr_redirect() {
const VERY_LONG_STRING = "x".repeat(network_standard_url_max_length - 20);
browser.webRequest.onBeforeRequest.addListener(
- d => {
+ () => {
return { redirectUrl: "http://redir/destination?by-webrequest" };
},
{ urls: ["*://example.com/*"] },
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_dns.js b/toolkit/components/extensions/test/xpcshell/test_ext_dns.js
index 4b8599b0c5..c9e9d29ed5 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_dns.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_dns.js
@@ -14,7 +14,7 @@ AddonTestUtils.createAppInfo(
"42"
);
-function getExtension(background = undefined) {
+function getExtension() {
let manifest = {
permissions: ["dns", "proxy"],
};
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_downloads_download.js b/toolkit/components/extensions/test/xpcshell/test_ext_downloads_download.js
index e2867d1f03..6bf313511a 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_downloads_download.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_downloads_download.js
@@ -453,7 +453,7 @@ async function testHttpErrors(allowHttpErrors) {
response.write(content);
});
- function background(code) {
+ function background() {
let dlid = 0;
let expectedState;
browser.test.onMessage.addListener(async options => {
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_downloads_partitionKey.js b/toolkit/components/extensions/test/xpcshell/test_ext_downloads_partitionKey.js
index 3326ed0ce9..169a147e0d 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_downloads_partitionKey.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_downloads_partitionKey.js
@@ -29,7 +29,7 @@ let downloadDir;
function observeDownloadChannel(uri, partitionKey, isPrivate) {
return new Promise(resolve => {
let observer = {
- observe(subject, topic, data) {
+ observe(subject, topic) {
if (topic === "http-on-modify-request") {
let httpChannel = subject.QueryInterface(Ci.nsIHttpChannel);
if (httpChannel.URI.spec != uri) {
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_downloads_search.js b/toolkit/components/extensions/test/xpcshell/test_ext_downloads_search.js
index 37c497a9b6..2ca18abf86 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_downloads_search.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_downloads_search.js
@@ -77,7 +77,7 @@ function backgroundScript() {
browser.test.sendMessage("ready");
}
-async function clearDownloads(callback) {
+async function clearDownloads() {
let list = await Downloads.getList(Downloads.ALL);
let downloads = await list.getAll();
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_downloads_urlencoded.js b/toolkit/components/extensions/test/xpcshell/test_ext_downloads_urlencoded.js
index 03288fb5d5..ae40faf909 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_downloads_urlencoded.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_downloads_urlencoded.js
@@ -62,7 +62,7 @@ function backgroundScript() {
browser.test.sendMessage("ready");
}
-async function clearDownloads(callback) {
+async function clearDownloads() {
let list = await Downloads.getList(Downloads.ALL);
let downloads = await list.getAll();
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_eventpage_messaging.js b/toolkit/components/extensions/test/xpcshell/test_ext_eventpage_messaging.js
index c343f19a5c..b3ad24c461 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_eventpage_messaging.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_eventpage_messaging.js
@@ -107,7 +107,7 @@ add_task(async function test_runtime_onConnect_cancels_suspend() {
// extension API. This ensures that if the event page suspend is canceled,
// that it was intentionally done by the listener, and not as a side
// effect of an unrelated extension API call.
- browser.runtime.onConnect.addListener(port => {
+ browser.runtime.onConnect.addListener(() => {
// Set by extensionPageScript before runtime.connect():
globalThis.notify_extensionPage_got_onConnect();
});
@@ -162,7 +162,7 @@ add_task(async function test_runtime_Port_onMessage_cancels_suspend() {
// that it was intentionally done by the listener, and not as a side
// effect of an unrelated extension API call.
browser.runtime.onConnect.addListener(port => {
- port.onMessage.addListener(msg => {
+ port.onMessage.addListener(() => {
// Set by extensionPageScript before runtime.connect():
globalThis.notify_extensionPage_got_port_onMessage();
});
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_experiments.js b/toolkit/components/extensions/test/xpcshell/test_ext_experiments.js
index cd2eb4dbb7..25ac794d66 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_experiments.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_experiments.js
@@ -76,7 +76,7 @@ let fooExperimentFiles = {
/* globals ExtensionAPI */
"parent.js": () => {
this.foo = class extends ExtensionAPI {
- getAPI(context) {
+ getAPI() {
return {
experiments: {
foo: {
@@ -129,7 +129,7 @@ let fooExperimentFiles = {
onChildEvent: new EventManagerWithAssertions({
context,
name: `experiments.foo.onChildEvent`,
- register: fire => {
+ register: () => {
return () => {};
},
}).api(),
@@ -379,7 +379,7 @@ add_task(async function test_unbundled_experiments() {
"parent.js": () => {
this.crunk = class extends ExtensionAPI {
- getAPI(context) {
+ getAPI() {
return {
experiments: {
crunk: {
@@ -395,7 +395,7 @@ add_task(async function test_unbundled_experiments() {
"child.js": () => {
this.crunk = class extends ExtensionAPI {
- getAPI(context) {
+ getAPI() {
return {
experiments: {
crunk: {
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_i18n.js b/toolkit/components/extensions/test/xpcshell/test_ext_i18n.js
index 048e675a3e..1e46e19527 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_i18n.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_i18n.js
@@ -306,7 +306,7 @@ add_task(async function test_get_accept_languages() {
}
function background(checkResultsFn) {
- browser.test.onMessage.addListener(([msg, expected]) => {
+ browser.test.onMessage.addListener(([, expected]) => {
browser.i18n.getAcceptLanguages().then(results => {
checkResultsFn("background", results, expected);
@@ -316,7 +316,7 @@ add_task(async function test_get_accept_languages() {
}
function content(checkResultsFn) {
- browser.test.onMessage.addListener(([msg, expected]) => {
+ browser.test.onMessage.addListener(([, expected]) => {
browser.i18n.getAcceptLanguages().then(results => {
checkResultsFn("contentScript", results, expected);
@@ -392,7 +392,7 @@ add_task(async function test_get_ui_language() {
}
function background(getResultsFn, checkResultsFn) {
- browser.test.onMessage.addListener(([msg, expected]) => {
+ browser.test.onMessage.addListener(([, expected]) => {
checkResultsFn("background", getResultsFn(), expected);
browser.test.sendMessage("background-done");
@@ -400,7 +400,7 @@ add_task(async function test_get_ui_language() {
}
function content(getResultsFn, checkResultsFn) {
- browser.test.onMessage.addListener(([msg, expected]) => {
+ browser.test.onMessage.addListener(([, expected]) => {
checkResultsFn("contentScript", getResultsFn(), expected);
browser.test.sendMessage("content-done");
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_indexedDB_principal.js b/toolkit/components/extensions/test/xpcshell/test_ext_indexedDB_principal.js
index f355b1d43a..2428dcc461 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_indexedDB_principal.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_indexedDB_principal.js
@@ -30,13 +30,13 @@ add_task(async function test_indexedDB_principal() {
let store = tx.objectStore("TestStore");
tx.oncomplete = () => browser.test.sendMessage("storage-created");
store.add("foo", "bar");
- tx.onerror = function (e) {
+ tx.onerror = function () {
browser.test.fail(`Failed with error ${tx.error.message}`);
// Don't wait for timeout
browser.test.sendMessage("storage-created");
};
};
- request.onerror = function (e) {
+ request.onerror = function () {
browser.test.fail(`Failed with error ${request.error.message}`);
// Don't wait for timeout
browser.test.sendMessage("storage-created");
@@ -52,7 +52,7 @@ add_task(async function test_indexedDB_principal() {
dbRequest.onsuccess = function (e) {
let db = e.target.result;
let transaction = db.transaction("TestStore");
- transaction.onerror = function (e) {
+ transaction.onerror = function () {
browser.test.fail(
`Failed with error ${transaction.error.message}`
);
@@ -60,7 +60,7 @@ add_task(async function test_indexedDB_principal() {
};
let objectStore = transaction.objectStore("TestStore");
let request = objectStore.get("bar");
- request.onsuccess = function (event) {
+ request.onsuccess = function () {
browser.test.assertEq(
request.result,
"foo",
@@ -68,12 +68,12 @@ add_task(async function test_indexedDB_principal() {
);
browser.test.notifyPass("done");
};
- request.onerror = function (e) {
+ request.onerror = function () {
browser.test.fail(`Failed with error ${request.error.message}`);
browser.test.notifyFail("done");
};
};
- dbRequest.onerror = function (e) {
+ dbRequest.onerror = function () {
browser.test.fail(`Failed with error ${dbRequest.error.message}`);
browser.test.notifyFail("done");
};
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_management.js b/toolkit/components/extensions/test/xpcshell/test_ext_management.js
index 8fb6b0d9a1..89c3403f1f 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_management.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_management.js
@@ -65,7 +65,7 @@ add_task(async function test_management_permission() {
await testAvailable();
- browser.test.onMessage.addListener(async msg => {
+ browser.test.onMessage.addListener(async () => {
browser.test.log("test with permission");
// get permission
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_management_uninstall_self.js b/toolkit/components/extensions/test/xpcshell/test_ext_management_uninstall_self.js
index 45c981811b..0d923d60fa 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_management_uninstall_self.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_management_uninstall_self.js
@@ -59,7 +59,7 @@ add_task(async function setup() {
add_task(async function test_management_uninstall_no_prompt() {
function background() {
- browser.test.onMessage.addListener(msg => {
+ browser.test.onMessage.addListener(() => {
browser.management.uninstallSelf();
});
}
@@ -82,7 +82,7 @@ add_task(async function test_management_uninstall_prompt_uninstall() {
promptService._response = 0;
function background() {
- browser.test.onMessage.addListener(msg => {
+ browser.test.onMessage.addListener(() => {
browser.management.uninstallSelf({ showConfirmDialog: true });
});
}
@@ -114,7 +114,7 @@ add_task(async function test_management_uninstall_prompt_keep() {
promptService._response = 1;
function background() {
- browser.test.onMessage.addListener(async msg => {
+ browser.test.onMessage.addListener(async () => {
await browser.test.assertRejects(
browser.management.uninstallSelf({ showConfirmDialog: true }),
"User cancelled uninstall of extension",
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_messaging_startup.js b/toolkit/components/extensions/test/xpcshell/test_ext_messaging_startup.js
index 120bebb431..59d6794c7f 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_messaging_startup.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_messaging_startup.js
@@ -158,7 +158,7 @@ add_task(function test_onMessage() {
}
async function background() {
- browser.runtime.onMessage.addListener((msg, sender) => {
+ browser.runtime.onMessage.addListener(msg => {
browser.test.assertEq(
msg,
"ping",
@@ -217,7 +217,7 @@ add_task(async function test_other_startup() {
useAddonManager: "permanent",
async background() {
- browser.runtime.onMessage.addListener(msg => {
+ browser.runtime.onMessage.addListener(() => {
browser.test.notifyPass("startup");
});
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_native_messaging.js b/toolkit/components/extensions/test/xpcshell/test_ext_native_messaging.js
index cb08a70151..5b6719a1b6 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_native_messaging.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_native_messaging.js
@@ -531,7 +531,7 @@ add_task(async function test_disconnect() {
);
browser.test.sendMessage("message", msg);
});
- port.onDisconnect.addListener(msgPort => {
+ port.onDisconnect.addListener(() => {
browser.test.fail("onDisconnect should not be called for disconnect()");
});
browser.test.onMessage.addListener((what, payload) => {
@@ -660,7 +660,7 @@ add_task(async function test_read_limit() {
);
browser.test.sendMessage("result", "disconnected");
});
- port.onMessage.addListener(msg => {
+ port.onMessage.addListener(() => {
browser.test.sendMessage("result", "message");
});
port.postMessage(PAYLOAD);
@@ -881,7 +881,7 @@ add_task(async function test_connect_native_from_content_script() {
);
browser.test.sendMessage("result", "disconnected");
});
- port.onMessage.addListener(msg => {
+ port.onMessage.addListener(() => {
browser.test.sendMessage("result", "message");
});
port.postMessage({ test: "test" });
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_notifications_incognito.js b/toolkit/components/extensions/test/xpcshell/test_ext_notifications_incognito.js
index fda60c3a82..b287e0a6ad 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_notifications_incognito.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_notifications_incognito.js
@@ -33,7 +33,7 @@ const mockAlertsService = {
this.showAlert({ cookie, title, text, privateBrowsing }, alertListener);
},
- closeAlert(name) {
+ closeAlert() {
// This mock immediately close the alert on show, so this is empty.
},
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_permissions.js b/toolkit/components/extensions/test/xpcshell/test_ext_permissions.js
index 7fb8d4ca07..948b75978a 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_permissions.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_permissions.js
@@ -49,6 +49,32 @@ add_setup(async () => {
AddonTestUtils.usePrivilegedSignatures = false;
});
+add_task(
+ {
+ skip_if: () => ExtensionPermissions._useLegacyStorageBackend,
+ },
+ async function test_permissions_rkv_recovery_rename() {
+ const databaseDir = await makeRkvDatabaseDir(
+ "extension-store-permissions",
+ {
+ mockCorrupted: true,
+ }
+ );
+ const res = await ExtensionPermissions.get("@testextension");
+ Assert.deepEqual(
+ res,
+ { permissions: [], origins: [] },
+ "Expect ExtensionPermissions get promise to be resolved"
+ );
+ Assert.ok(
+ await IOUtils.exists(
+ PathUtils.join(databaseDir, "data.safe.bin.corrupt")
+ ),
+ "Expect corrupt file to be found"
+ );
+ }
+);
+
add_task(async function test_permissions_on_startup() {
let extensionId = "@permissionTest";
let extension = ExtensionTestUtils.loadExtension({
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_persistent_events.js b/toolkit/components/extensions/test/xpcshell/test_ext_persistent_events.js
index 07cc29bfe2..d1660ccb6d 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_persistent_events.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_persistent_events.js
@@ -41,7 +41,7 @@ const API = class extends ExtensionAPI {
const FIRE_TOPIC = `fire-${namespace}.${event}`;
- async function listener(subject, topic, data) {
+ async function listener(subject) {
try {
if (subject.wrappedJSObject.waitForBackground) {
await fire.wakeup();
@@ -257,7 +257,7 @@ const global = this;
async function promiseObservable(topic, count, fn = null) {
let _countResolve;
let results = [];
- function listener(subject, _topic, data) {
+ function listener(subject, _topic) {
const eventDetails = subject.wrappedJSObject;
results.push(eventDetails);
if (results.length > count) {
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_privacy_disable.js b/toolkit/components/extensions/test/xpcshell/test_ext_privacy_disable.js
index 637751f473..4e05954b7e 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_privacy_disable.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_privacy_disable.js
@@ -32,7 +32,7 @@ function awaitEvent(eventName) {
function awaitPrefChange(prefName) {
return new Promise(resolve => {
- let listener = args => {
+ let listener = () => {
Preferences.ignore(prefName, listener);
resolve();
};
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_proxy_authorization_via_proxyinfo.js b/toolkit/components/extensions/test/xpcshell/test_ext_proxy_authorization_via_proxyinfo.js
index 27f537b73b..e6d2653445 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_proxy_authorization_via_proxyinfo.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_proxy_authorization_via_proxyinfo.js
@@ -73,7 +73,7 @@ add_task(async function test_webRequest_auth_proxy() {
);
browser.webRequest.onAuthRequired.addListener(
- details => {
+ () => {
// Using proxyAuthorizationHeader should prevent an auth request coming to us in the extension.
browser.test.fail("onAuthRequired");
},
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_proxy_onauthrequired.js b/toolkit/components/extensions/test/xpcshell/test_ext_proxy_onauthrequired.js
index db041d20d0..4b1128a349 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_proxy_onauthrequired.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_proxy_onauthrequired.js
@@ -191,7 +191,7 @@ add_task(async function test_webRequest_auth_proxy_https() {
let authReceived = false;
browser.webRequest.onBeforeSendHeaders.addListener(
- details => {
+ () => {
if (authReceived) {
browser.test.sendMessage("done");
return { cancel: true };
@@ -202,7 +202,7 @@ add_task(async function test_webRequest_auth_proxy_https() {
);
browser.webRequest.onAuthRequired.addListener(
- details => {
+ () => {
authReceived = true;
return { authCredentials: { username: "puser", password: "ppass" } };
},
@@ -241,14 +241,14 @@ add_task(async function test_webRequest_auth_proxy_https() {
add_task(async function test_webRequest_auth_proxy_system() {
async function background(port) {
browser.webRequest.onBeforeRequest.addListener(
- details => {
+ () => {
browser.test.fail("onBeforeRequest");
},
{ urls: ["<all_urls>"] }
);
browser.webRequest.onAuthRequired.addListener(
- details => {
+ () => {
browser.test.sendMessage("onAuthRequired");
// cancel is silently ignored, if it were not (e.g someone messes up in
// WebRequest.jsm and allows cancel) this test would fail.
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_proxy_settings.js b/toolkit/components/extensions/test/xpcshell/test_ext_proxy_settings.js
index 33c91309f0..2db12c4b57 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_proxy_settings.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_proxy_settings.js
@@ -45,13 +45,13 @@ add_task(async function test_proxy_settings() {
{ urls: ["http://example.com/*"] }
);
browser.webRequest.onCompleted.addListener(
- details => {
+ () => {
browser.test.notifyPass("proxytest");
},
{ urls: ["http://example.com/*"] }
);
browser.webRequest.onErrorOccurred.addListener(
- details => {
+ () => {
browser.test.notifyFail("proxytest");
},
{ urls: ["http://example.com/*"] }
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_proxy_socks.js b/toolkit/components/extensions/test/xpcshell/test_ext_proxy_socks.js
index 6ebd9fbfcc..fd0aff709a 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_proxy_socks.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_proxy_socks.js
@@ -46,7 +46,7 @@ class SocksClient {
this.state = STATE_WAIT_GREETING;
this.socket = socket;
- socket.onclose = event => {
+ socket.onclose = () => {
this.server.requestCompleted(this);
};
socket.ondata = event => {
@@ -566,7 +566,7 @@ add_task(async function test_webRequest_socks_proxy() {
{ urls: ["<all_urls>"] }
);
browser.webRequest.onAuthRequired.addListener(
- details => {
+ () => {
// We should never get onAuthRequired for socks proxy
browser.test.fail("onAuthRequired");
},
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_proxy_startup.js b/toolkit/components/extensions/test/xpcshell/test_ext_proxy_startup.js
index 4130d407b7..3c48adf56d 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_proxy_startup.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_proxy_startup.js
@@ -175,7 +175,7 @@ add_task(async function webRequest_before_proxy() {
function background() {
browser.webRequest.onBeforeRequest.addListener(
- details => {
+ () => {
return { redirectUrl: "data:,response_from_webRequest" };
},
{
@@ -185,7 +185,7 @@ add_task(async function webRequest_before_proxy() {
["blocking"]
);
browser.proxy.onRequest.addListener(
- details => {
+ () => {
browser.test.sendMessage("seen_proxy_request");
},
{
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_redirects.js b/toolkit/components/extensions/test/xpcshell/test_ext_redirects.js
index 7b950355f3..d563df5b53 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_redirects.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_redirects.js
@@ -52,10 +52,10 @@ function onStopListener(channel) {
}
async function onModifyListener(originUrl, redirectToUrl) {
- return TestUtils.topicObserved("http-on-modify-request", (subject, data) => {
+ return TestUtils.topicObserved("http-on-modify-request", subject => {
let channel = subject.QueryInterface(Ci.nsIHttpChannel);
return channel.URI && channel.URI.spec == originUrl;
- }).then(([subject, data]) => {
+ }).then(([subject]) => {
let channel = subject.QueryInterface(Ci.nsIHttpChannel);
if (redirectToUrl) {
channel.redirectTo(Services.io.newURI(redirectToUrl));
@@ -229,7 +229,7 @@ add_task(async function test_extension_302_redirect_web() {
{ urls: [serverUrl] }
);
browser.webRequest.onHeadersReceived.addListener(
- details => {
+ () => {
browser.test.assertEq(
expected.shift(),
"onHeadersReceived",
@@ -239,7 +239,7 @@ add_task(async function test_extension_302_redirect_web() {
{ urls: [serverUrl] }
);
browser.webRequest.onResponseStarted.addListener(
- details => {
+ () => {
browser.test.assertEq(
expected.shift(),
"onResponseStarted",
@@ -527,7 +527,7 @@ add_task(async function test_extension_redirect() {
let myuri = browser.runtime.getURL("*");
let exturi = browser.runtime.getURL("finished.html");
browser.webRequest.onBeforeRequest.addListener(
- details => {
+ () => {
return { redirectUrl: exturi };
},
{ urls: ["<all_urls>", myuri] },
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_runtime_onInstalled_and_onStartup.js b/toolkit/components/extensions/test/xpcshell/test_ext_runtime_onInstalled_and_onStartup.js
index c330aaafde..1b6d5c331b 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_runtime_onInstalled_and_onStartup.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_runtime_onInstalled_and_onStartup.js
@@ -51,7 +51,7 @@ function background() {
}
});
- browser.runtime.onUpdateAvailable.addListener(details => {
+ browser.runtime.onUpdateAvailable.addListener(() => {
browser.test.sendMessage("reloading");
browser.runtime.reload();
});
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_runtime_ports.js b/toolkit/components/extensions/test/xpcshell/test_ext_runtime_ports.js
index 7365a13f93..d871acf2cf 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_runtime_ports.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_runtime_ports.js
@@ -17,7 +17,7 @@ add_task(async function test_port_disconnected_from_wrong_window() {
done = true;
});
- port.onDisconnect.addListener(err => {
+ port.onDisconnect.addListener(() => {
if (port === ports[1]) {
browser.test.log("Port 1 disconnected, sending message via port 2");
ports[2].postMessage("port-2-msg");
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_runtime_ports_gc.js b/toolkit/components/extensions/test/xpcshell/test_ext_runtime_ports_gc.js
index dd47744a97..ca63e683be 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_runtime_ports_gc.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_runtime_ports_gc.js
@@ -56,7 +56,7 @@ let gcExperimentFiles = {
);
/* globals ExtensionAPI */
this.gcHelper = class extends ExtensionAPI {
- getAPI(context) {
+ getAPI() {
let witnesses = new Map();
return {
gcHelper: {
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_runtime_sendMessage.js b/toolkit/components/extensions/test/xpcshell/test_ext_runtime_sendMessage.js
index 2bbc9864d7..2f9f07046c 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_runtime_sendMessage.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_runtime_sendMessage.js
@@ -49,7 +49,7 @@ add_task(async function runtimeSendMessageReply() {
}
});
- browser.runtime.onMessage.addListener((msg, sender, respond) => {
+ browser.runtime.onMessage.addListener(msg => {
if (msg == "respond-now") {
// If a response from another listener is received first, this
// exception should be ignored. Test fails if it is not.
@@ -271,7 +271,7 @@ add_task(async function sendMessageResponseGC() {
savedResolve("saved-resolve");
return;
case "promise-never":
- return new Promise(r => {});
+ return new Promise(() => {});
case "callback-save":
savedRespond = respond;
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_same_site_redirects.js b/toolkit/components/extensions/test/xpcshell/test_ext_same_site_redirects.js
index a3000e4e1f..f42a74e0bb 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_same_site_redirects.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_same_site_redirects.js
@@ -97,7 +97,7 @@ server.registerPathHandler("/final", (request, response) => {
function promiseFinalResponse() {
Assert.deepEqual(receivedCookies, [], "Test starts without observed cookies");
return new Promise(resolve => {
- server.registerPathHandler("/final_and_clean", (request, response) => {
+ server.registerPathHandler("/final_and_clean", request => {
Assert.equal(request.host, SITE_FINAL);
Assert.equal(getCookies(request), "", "Cookies cleaned up");
resolve(receivedCookies.splice(0));
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_schemas.js b/toolkit/components/extensions/test/xpcshell/test_ext_schemas.js
index a89ddf0728..24fcf9e1a9 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_schemas.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_schemas.js
@@ -1898,7 +1898,7 @@ add_task(async function testDefaults() {
let localWrapper = {
manifestVersion: 2,
cloneScope: global,
- shouldInject(ns) {
+ shouldInject() {
return true;
},
getImplementation(ns, name) {
@@ -1973,7 +1973,7 @@ add_task(async function testReturns() {
const localWrapper = {
manifestVersion: 2,
cloneScope: global,
- shouldInject(ns) {
+ shouldInject() {
return true;
},
getImplementation(ns, name) {
@@ -2096,7 +2096,7 @@ add_task(async function testCrossOriginArguments() {
let localWrapper = {
manifestVersion: 2,
cloneScope: sandbox,
- shouldInject(ns) {
+ shouldInject() {
return true;
},
getImplementation(ns, name) {
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_schemas_interactive.js b/toolkit/components/extensions/test/xpcshell/test_ext_schemas_interactive.js
index 986dc74bc5..712daecf72 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_schemas_interactive.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_schemas_interactive.js
@@ -46,7 +46,7 @@ let experimentFiles = {
/* globals ExtensionAPI */
"parent.js": () => {
this.userinputtest = class extends ExtensionAPI {
- getAPI(context) {
+ getAPI() {
return {
userinputtest: {
test() {},
@@ -58,7 +58,7 @@ let experimentFiles = {
"child.js": () => {
this.userinputtest = class extends ExtensionAPI {
- getAPI(context) {
+ getAPI() {
return {
userinputtest: {
child() {},
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_schemas_manifest_permissions.js b/toolkit/components/extensions/test/xpcshell/test_ext_schemas_manifest_permissions.js
index 562ab5c36d..85e645e67b 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_schemas_manifest_permissions.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_schemas_manifest_permissions.js
@@ -41,7 +41,7 @@ add_task(async function () {
];
class FakeAPI extends ExtensionAPI {
- getAPI(context) {
+ getAPI() {
return {
testManifestPermission: {
get testProperty() {
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_schemas_privileged.js b/toolkit/components/extensions/test/xpcshell/test_ext_schemas_privileged.js
index e2da7e5a74..14cbca7443 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_schemas_privileged.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_schemas_privileged.js
@@ -26,7 +26,7 @@ add_setup(async () => {
];
class API extends ExtensionAPI {
- getAPI(context) {
+ getAPI() {
return {
privileged: {
test: "hello",
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_schemas_roots.js b/toolkit/components/extensions/test/xpcshell/test_ext_schemas_roots.js
index 21434228a3..86f52bde7b 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_schemas_roots.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_schemas_roots.js
@@ -149,7 +149,7 @@ let wrapper = {
},
preprocessors: {
- localize(value, context) {
+ localize(value) {
return value.replace(/__MSG_(.*?)__/g, (m0, m1) => `${m1.toUpperCase()}`);
},
},
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_scripting_persistAcrossSessions.js b/toolkit/components/extensions/test/xpcshell/test_ext_scripting_persistAcrossSessions.js
index cae09b5d2e..1527be54d5 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_scripting_persistAcrossSessions.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_scripting_persistAcrossSessions.js
@@ -18,6 +18,18 @@ const { TestUtils } = ChromeUtils.importESModule(
"resource://testing-common/TestUtils.sys.mjs"
);
+add_task(async function test_scriptingstore_rkv_recovery_rename() {
+ ExtensionScriptingStore._getStoreForTesting()._uninitForTesting();
+ const databaseDir = await makeRkvDatabaseDir("extension-store", {
+ mockCorrupted: true,
+ });
+ await ExtensionScriptingStore._getStoreForTesting().lazyInit();
+ Assert.ok(
+ await IOUtils.exists(PathUtils.join(databaseDir, "data.safe.bin.corrupt")),
+ "Expect corrupt file to be found"
+ );
+});
+
const makeExtension = ({ manifest: manifestProps, ...otherProps }) => {
return ExtensionTestUtils.loadExtension({
manifest: {
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_storage_idb_data_migration.js b/toolkit/components/extensions/test/xpcshell/test_ext_storage_idb_data_migration.js
index 8a6631f26b..2a2aead4b5 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_storage_idb_data_migration.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_storage_idb_data_migration.js
@@ -397,8 +397,7 @@ add_task(async function test_storage_local_data_migration() {
Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
true
);
- const filterByCategory = ([timestamp, category]) =>
- category === EVENT_CATEGORY;
+ const filterByCategory = ([, category]) => category === EVENT_CATEGORY;
ok(
!snapshot.parent || snapshot.parent.filter(filterByCategory).length === 0,
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_storage_sanitizer.js b/toolkit/components/extensions/test/xpcshell/test_ext_storage_sanitizer.js
index 6c69ad1a4c..1150ed570d 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_storage_sanitizer.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_storage_sanitizer.js
@@ -81,7 +81,7 @@ add_task(async function test_sanitize_offlineApps_extension_indexedDB() {
const store = tx.objectStore("TestStore");
return new Promise((resolve, reject) => {
const req = store.get(k);
- tx.oncomplete = evt => resolve(req.result);
+ tx.oncomplete = () => resolve(req.result);
tx.onerror = evt => reject(evt.target.error);
});
});
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_storage_sync_kinto.js b/toolkit/components/extensions/test/xpcshell/test_ext_storage_sync_kinto.js
index 9e26eedfcf..64a325bf71 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_storage_sync_kinto.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_storage_sync_kinto.js
@@ -254,7 +254,7 @@ class KintoServer {
}
installCatchAll() {
- this.httpServer.registerPathHandler("/", (request, response) => {
+ this.httpServer.registerPathHandler("/", request => {
dump(
`got request: ${request.method}:${request.path}?${request.queryString}\n`
);
@@ -774,76 +774,65 @@ add_task(async function ensureCanSync_clearAll() {
const extension2 = { id: extensionId2 };
await withContextAndServer(async function (context, server) {
- await withSignedInUser(
- loggedInUser,
- async function (extensionStorageSync, fxaService) {
- async function assertSetAndGetData(extension, data) {
- await extensionStorageSync.set(extension, data, context);
- let storedData = await extensionStorageSync.get(
- extension,
- Object.keys(data),
- context
- );
- const extId = extensionId;
- deepEqual(
- storedData,
- data,
- `${extId} should get back the data we set`
- );
- }
-
- async function assertDataCleared(extension, keys) {
- const storedData = await extensionStorageSync.get(
- extension,
- keys,
- context
- );
- deepEqual(
- storedData,
- {},
- `${extension.id} should have lost the data`
- );
- }
-
- server.installCollection("storage-sync-crypto");
- server.etag = 1000;
-
- let newKeys = await extensionStorageSync.ensureCanSync([
- extensionId,
- extensionId2,
- ]);
- ok(
- newKeys.hasKeysFor([extensionId]),
- `key isn't present for ${extensionId}`
+ await withSignedInUser(loggedInUser, async function (extensionStorageSync) {
+ async function assertSetAndGetData(extension, data) {
+ await extensionStorageSync.set(extension, data, context);
+ let storedData = await extensionStorageSync.get(
+ extension,
+ Object.keys(data),
+ context
);
- ok(
- newKeys.hasKeysFor([extensionId2]),
- `key isn't present for ${extensionId2}`
+ const extId = extensionId;
+ deepEqual(storedData, data, `${extId} should get back the data we set`);
+ }
+
+ async function assertDataCleared(extension, keys) {
+ const storedData = await extensionStorageSync.get(
+ extension,
+ keys,
+ context
);
+ deepEqual(storedData, {}, `${extension.id} should have lost the data`);
+ }
- let posts = server.getPosts();
- equal(posts.length, 1);
- assertPostedNewRecord(posts[0]);
+ server.installCollection("storage-sync-crypto");
+ server.etag = 1000;
+
+ let newKeys = await extensionStorageSync.ensureCanSync([
+ extensionId,
+ extensionId2,
+ ]);
+ ok(
+ newKeys.hasKeysFor([extensionId]),
+ `key isn't present for ${extensionId}`
+ );
+ ok(
+ newKeys.hasKeysFor([extensionId2]),
+ `key isn't present for ${extensionId2}`
+ );
- await assertSetAndGetData(extension, { "my-key": 1 });
- await assertSetAndGetData(extension2, { "my-key": 2 });
+ let posts = server.getPosts();
+ equal(posts.length, 1);
+ assertPostedNewRecord(posts[0]);
- // Call cleanup for the first extension, to double check it has
- // been wiped out even without an active extension context.
- cleanUpForContext(extension, context);
+ await assertSetAndGetData(extension, { "my-key": 1 });
+ await assertSetAndGetData(extension2, { "my-key": 2 });
- // clear everything.
- await extensionStorageSync.clearAll();
+ // Call cleanup for the first extension, to double check it has
+ // been wiped out even without an active extension context.
+ cleanUpForContext(extension, context);
- // Assert that the data is gone for both the extensions.
- await assertDataCleared(extension, ["my-key"]);
- await assertDataCleared(extension2, ["my-key"]);
+ // clear everything.
+ await extensionStorageSync.clearAll();
- // should have been no posts caused by the clear.
- posts = server.getPosts();
- equal(posts.length, 1);
- }
- );
+ // Assert that the data is gone for both the extensions.
+ await assertDataCleared(extension, ["my-key"]);
+ await assertDataCleared(extension2, ["my-key"]);
+
+ // should have been no posts caused by the clear.
+ posts = server.getPosts();
+ equal(posts.length, 1);
+ });
});
await testExtension.unload();
@@ -1407,7 +1396,7 @@ add_task(async function checkSyncKeyRing_overwrites_on_conflict() {
// overwrite it with our keys.
const extensionId = uuid();
let extensionKey;
- await withSyncContext(async function (context) {
+ await withSyncContext(async function () {
await withServer(async function (server) {
// The old device has this kbHash, which is very similar to the
// current kbHash but with the last character changed.
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_storage_tab.js b/toolkit/components/extensions/test/xpcshell/test_ext_storage_tab.js
index cfa49c334b..4ff080887f 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_storage_tab.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_storage_tab.js
@@ -90,7 +90,7 @@ async function test_multiple_pages() {
contentPage = await ExtensionTestUtils.loadContentPage(url, { extension });
extension.sendMessage("page-loaded");
});
- extension.onMessage("remove-page", async url => {
+ extension.onMessage("remove-page", async () => {
await contentPage.close();
extension.sendMessage("page-removed");
});
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_userScripts_exports.js b/toolkit/components/extensions/test/xpcshell/test_ext_userScripts_exports.js
index 5950377f85..d6aa7a038c 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_userScripts_exports.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_userScripts_exports.js
@@ -225,7 +225,7 @@ add_task(async function test_apiScript_async_method() {
browser.userScripts.onBeforeScript.addListener(script => {
script.defineGlobals({
...sharedTestAPIMethods,
- testAPIMethod(param, cb, cb2, objWithCb) {
+ testAPIMethod(param, cb, cb2) {
browser.test.assertEq(
"function",
typeof cb,
@@ -696,7 +696,7 @@ add_task(
getPrototypeOf() {
throw new Error("Proxy's getPrototypeOf trap");
},
- get(target, prop, receiver) {
+ get() {
throw new Error("Proxy's get trap");
},
});
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_auth.js b/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_auth.js
index c616d162a5..578e69ebdf 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_auth.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_auth.js
@@ -14,7 +14,7 @@ server.registerPathHandler("/authenticate.sjs", (request, response) => {
let realm = url.searchParams.get("realm") || "mochitest";
let proxy_realm = url.searchParams.get("proxy_realm");
- function checkAuthorization(authorization) {
+ function checkAuthorization() {
let expected_user = url.searchParams.get("user");
if (!expected_user) {
return true;
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_cancelWithReason.js b/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_cancelWithReason.js
index a8405e5962..2ad729d0b4 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_cancelWithReason.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_cancelWithReason.js
@@ -41,9 +41,9 @@ add_task(async function test_cancel_with_reason() {
channel.asyncOpen({
QueryInterface: ChromeUtils.generateQI(["nsIStreamListener"]),
- onStartRequest(request) {},
+ onStartRequest() {},
- onStopRequest(request, statusCode) {
+ onStopRequest(request) {
let properties = request.QueryInterface(Ci.nsIPropertyBag);
let id = properties.getProperty("cancelledByExtension");
let reason = request.loadInfo.requestBlockingReason;
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_download.js b/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_download.js
index 3485996f56..f1415ca87a 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_download.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_download.js
@@ -15,7 +15,7 @@ add_task(async function testDownload() {
},
background: async function () {
browser.webRequest.onBeforeRequest.addListener(
- details => {
+ () => {
browser.test.sendMessage("request_intercepted");
return { cancel: true };
},
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_filterResponseData.js b/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_filterResponseData.js
index 0b826be08f..0ca0b36de8 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_filterResponseData.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_filterResponseData.js
@@ -371,7 +371,7 @@ add_task(async function test_filter_302() {
let filter = browser.webRequest.filterResponseData(details.requestId);
browser.test.sendMessage("filter-created");
- filter.ondata = event => {
+ filter.ondata = () => {
const script = "forceError();";
filter.write(new Uint8Array(new TextEncoder().encode(script)));
filter.close();
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_permission.js b/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_permission.js
index bfb4b55856..17c22e156d 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_permission.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_permission.js
@@ -47,7 +47,7 @@ add_task(async function test_permissions() {
const frameScript = () => {
const messageListener = {
- async receiveMessage({ target, messageName, recipient, data, name }) {
+ async receiveMessage() {
/* globals content */
let doc = content.document;
let iframe = doc.createElement("iframe");
@@ -130,7 +130,7 @@ add_task(async function test_no_webRequestBlocking_error() {
browser.test.assertThrows(
() => {
browser.webRequest[eventName].addListener(
- details => {},
+ () => {},
{ urls: ["<all_urls>"] },
["blocking"]
);
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_redirectProperty.js b/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_redirectProperty.js
index 5a448abb2a..91d13e8296 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_redirectProperty.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_redirectProperty.js
@@ -42,9 +42,9 @@ add_task(async function test_redirect_property() {
channel.asyncOpen({
QueryInterface: ChromeUtils.generateQI(["nsIStreamListener"]),
- onStartRequest(request) {},
+ onStartRequest() {},
- onStopRequest(request, statusCode) {
+ onStopRequest(request) {
let properties = request.QueryInterface(Ci.nsIPropertyBag);
let id = properties.getProperty("redirectedByExtension");
resolve({ id, url: request.QueryInterface(Ci.nsIChannel).URI.spec });
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_responseBody.js b/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_responseBody.js
index 8995870ba6..3f94615db9 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_responseBody.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_responseBody.js
@@ -128,7 +128,7 @@ const TASKS = [
`(${num}): Got expected initial status`
);
- filter.onstart = event => {
+ filter.onstart = () => {
browser.test.assertEq(
"transferringdata",
filter.status,
@@ -136,7 +136,7 @@ const TASKS = [
);
};
- filter.onstop = event => {
+ filter.onstop = () => {
browser.test.fail(
`(${num}): Got unexpected onStop event while disconnected`
);
@@ -207,7 +207,7 @@ const TASKS = [
}
};
- filter.onerror = event => {
+ filter.onerror = () => {
browser.test.fail(
`(${num}): Got unexpected error event: ${filter.error}`
);
@@ -222,7 +222,7 @@ const TASKS = [
task(filter, resolve, num) {
let decoder = new TextDecoder("utf-8");
- filter.onstop = event => {
+ filter.onstop = () => {
browser.test.fail(
`(${num}): Got unexpected onStop event while disconnected`
);
@@ -254,7 +254,7 @@ const TASKS = [
}
};
- filter.onerror = event => {
+ filter.onerror = () => {
browser.test.fail(
`(${num}): Got unexpected error event: ${filter.error}`
);
@@ -269,7 +269,7 @@ const TASKS = [
task(filter, resolve, num) {
let encoder = new TextEncoder();
- filter.onstop = event => {
+ filter.onstop = () => {
browser.test.fail(
`(${num}): Got unexpected onStop event while disconnected`
);
@@ -330,7 +330,7 @@ const TASKS = [
}
};
- filter.onerror = event => {
+ filter.onerror = () => {
browser.test.fail(
`(${num}): Got unexpected error event: ${filter.error}`
);
@@ -346,7 +346,7 @@ const TASKS = [
let encoder = new TextEncoder();
let decoder = new TextDecoder("utf-8");
- filter.onstop = event => {
+ filter.onstop = () => {
browser.test.fail(`(${num}): Got unexpected onStop event while closed`);
};
@@ -406,7 +406,7 @@ const TASKS = [
}
};
- filter.onerror = event => {
+ filter.onerror = () => {
browser.test.fail(
`(${num}): Got unexpected error event: ${filter.error}`
);
@@ -422,11 +422,11 @@ const TASKS = [
let response = "";
let decoder = new TextDecoder("utf-8");
- filter.onstart = event => {
+ filter.onstart = () => {
browser.test.log(`(${num}): Request start`);
};
- filter.onstop = event => {
+ filter.onstop = () => {
browser.test.assertEq(
"finishedtransferringdata",
filter.status,
@@ -456,7 +456,7 @@ const TASKS = [
filter.write(event.data);
};
- filter.onerror = event => {
+ filter.onerror = () => {
browser.test.fail(
`(${num}): Got unexpected error event: ${filter.error}`
);
@@ -469,11 +469,11 @@ const TASKS = [
{
url: "multipart",
task(filter, resolve, num) {
- filter.onstart = event => {
+ filter.onstart = () => {
browser.test.log(`(${num}): Request start`);
};
- filter.onstop = event => {
+ filter.onstop = () => {
filter.disconnect();
resolve();
};
@@ -482,7 +482,7 @@ const TASKS = [
filter.write(event.data);
};
- filter.onerror = event => {
+ filter.onerror = () => {
browser.test.fail(
`(${num}): Got unexpected error event: ${filter.error}`
);
@@ -499,11 +499,11 @@ const TASKS = [
{
url: "multipart2",
task(filter, resolve, num) {
- filter.onstart = event => {
+ filter.onstart = () => {
browser.test.log(`(${num}): Request start`);
};
- filter.onstop = event => {
+ filter.onstop = () => {
filter.disconnect();
resolve();
};
@@ -512,7 +512,7 @@ const TASKS = [
filter.write(event.data);
};
- filter.onerror = event => {
+ filter.onerror = () => {
browser.test.fail(
`(${num}): Got unexpected error event: ${filter.error}`
);
@@ -628,7 +628,7 @@ add_task(async function test_cachedResponse() {
data => {
let filter = browser.webRequest.filterResponseData(data.requestId);
- filter.onstop = event => {
+ filter.onstop = () => {
filter.close();
};
filter.ondata = event => {
@@ -669,7 +669,7 @@ add_task(async function test_late_close() {
data => {
let filter = browser.webRequest.filterResponseData(data.requestId);
- filter.onstop = event => {
+ filter.onstop = () => {
browser.test.fail("Should not receive onstop after close()");
browser.test.assertEq(
"closed",
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_startup.js b/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_startup.js
index 616dc1fb50..2765fe949d 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_startup.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_startup.js
@@ -89,7 +89,7 @@ add_task(async function test_nonblocking() {
background() {
browser.webRequest.onBeforeRequest.addListener(
- details => {
+ () => {
browser.test.sendMessage("got-request");
},
{ urls: ["http://example.com/data/file_sample.html"] }
@@ -163,7 +163,7 @@ add_task(async function test_eventpage_nonblocking() {
background() {
browser.webRequest.onBeforeRequest.addListener(
- details => {
+ () => {
browser.test.sendMessage("got-request");
},
{ urls: ["http://example.com/data/file_sample.html"] }
@@ -237,7 +237,7 @@ add_task(async function test_persistent_blocking() {
background() {
browser.webRequest.onBeforeRequest.addListener(
- details => {
+ () => {
browser.test.fail("Listener should not have been called");
},
{ urls: ["http://test1.example.com/*"] },
@@ -290,7 +290,7 @@ add_task(async function test_persistent_listener_after_sideload_upgrade() {
background() {
browser.webRequest.onBeforeRequest.addListener(
- details => {
+ () => {
browser.test.sendMessage("got-request");
},
{ urls: ["http://example.com/data/file_sample.html"] },
@@ -405,7 +405,7 @@ add_task(
});
browser.webRequest.onBeforeRequest.addListener(
- details => {
+ () => {
browser.test.sendMessage("got-request");
},
{ urls: ["http://example.com/data/file_sample.html"] },
@@ -501,14 +501,14 @@ add_task(async function test_persistent_listener_after_staged_upgrade() {
background() {
browser.webRequest.onBeforeRequest.addListener(
- details => {
+ () => {
browser.test.sendMessage("got-request");
},
{ urls: ["http://example.com/data/file_sample.html"] },
["blocking"]
);
browser.webRequest.onSendHeaders.addListener(
- details => {
+ () => {
browser.test.sendMessage("got-sendheaders");
},
{ urls: ["http://example.com/data/file_sample.html"] }
@@ -540,20 +540,20 @@ add_task(async function test_persistent_listener_after_staged_upgrade() {
delete extensionData.manifest.optional_permissions;
extensionData.background = function () {
browser.webRequest.onBeforeRequest.addListener(
- details => {
+ () => {
browser.test.sendMessage("got-request");
},
{ urls: ["http://example.com/data/file_sample.html"] },
["blocking"]
);
browser.webRequest.onBeforeSendHeaders.addListener(
- details => {
+ () => {
browser.test.sendMessage("got-beforesendheaders");
},
{ urls: ["http://example.com/data/file_sample.html"] }
);
browser.webRequest.onSendHeaders.addListener(
- details => {
+ () => {
browser.test.sendMessage("got-sendheaders");
},
{ urls: ["http://example.com/data/file_sample.html"] }
@@ -688,7 +688,7 @@ add_task(async function test_persistent_listener_after_permission_removal() {
background() {
browser.webRequest.onBeforeRequest.addListener(
- details => {
+ () => {
browser.test.sendMessage("got-request");
},
{ urls: ["http://example.com/data/file_sample.html"] },
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_suspend.js b/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_suspend.js
index f8116aced0..52af6a9b3d 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_suspend.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_suspend.js
@@ -31,7 +31,7 @@ add_task(async function test_suspend() {
background() {
browser.webRequest.onBeforeSendHeaders.addListener(
- details => {
+ () => {
// Make sure that returning undefined or a promise that resolves to
// undefined does not break later handlers.
},
@@ -40,7 +40,7 @@ add_task(async function test_suspend() {
);
browser.webRequest.onBeforeSendHeaders.addListener(
- details => {
+ () => {
return Promise.resolve();
},
{ urls: ["<all_urls>"] },
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_viewsource.js b/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_viewsource.js
index 35b713e59b..5c59cbd924 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_viewsource.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_viewsource.js
@@ -51,7 +51,7 @@ add_task(async function test_webRequest_viewsource() {
);
browser.webRequest.onCompleted.addListener(
- details => {
+ () => {
// If cancel fails we get onCompleted.
browser.test.fail("onCompleted received");
},
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_webSocket.js b/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_webSocket.js
index 7e34d2b0b3..1bd5b54f2b 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_webSocket.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_webSocket.js
@@ -28,14 +28,14 @@ add_task(async function test_webSocket() {
["blocking"]
);
- browser.test.onMessage.addListener(msg => {
+ browser.test.onMessage.addListener(() => {
let ws = new WebSocket("ws://example.com/dummy");
- ws.onopen = e => {
+ ws.onopen = () => {
ws.send("data");
};
- ws.onclose = e => {};
- ws.onerror = e => {};
- ws.onmessage = e => {
+ ws.onclose = () => {};
+ ws.onerror = () => {};
+ ws.onmessage = () => {
ws.close();
};
});
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_web_accessible_resources.js b/toolkit/components/extensions/test/xpcshell/test_ext_web_accessible_resources.js
index 0b34dd8127..e7f35da711 100644
--- a/toolkit/components/extensions/test/xpcshell/test_ext_web_accessible_resources.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_web_accessible_resources.js
@@ -12,7 +12,7 @@ const IMAGE_ARRAYBUFFER = Uint8Array.from(image, byte =>
).buffer;
async function testImageLoading(src, expectedAction) {
- let imageLoadingPromise = new Promise((resolve, reject) => {
+ let imageLoadingPromise = new Promise(resolve => {
let cleanupListeners;
let testImage = document.createElement("img");
// Set the src via wrappedJSObject so the load is triggered with the
@@ -50,7 +50,7 @@ async function testImageLoading(src, expectedAction) {
add_task(async function test_web_accessible_resources_csp() {
function background() {
- browser.runtime.onMessage.addListener((msg, sender) => {
+ browser.runtime.onMessage.addListener(msg => {
if (msg.name === "image-loading") {
browser.test.assertTrue(msg.success, `Image was ${msg.expectedAction}`);
browser.test.sendMessage(`image-${msg.expectedAction}`);
@@ -63,7 +63,7 @@ add_task(async function test_web_accessible_resources_csp() {
}
function content() {
- window.addEventListener("message", function rcv(event) {
+ window.addEventListener("message", function rcv() {
browser.runtime.sendMessage("script-ran");
window.removeEventListener("message", rcv);
});
@@ -116,7 +116,7 @@ add_task(async function test_web_accessible_resources_csp() {
await page.legacySpawn(null, () => {
this.obs = {
events: [],
- observe(subject, topic, data) {
+ observe(subject) {
this.events.push(subject.QueryInterface(Ci.nsIURI).spec);
},
done() {
diff --git a/toolkit/components/extensions/test/xpcshell/test_native_manifests.js b/toolkit/components/extensions/test/xpcshell/test_native_manifests.js
index 6a6fb91e3f..1f5bc88740 100644
--- a/toolkit/components/extensions/test/xpcshell/test_native_manifests.js
+++ b/toolkit/components/extensions/test/xpcshell/test_native_manifests.js
@@ -313,7 +313,7 @@ add_task(async function test_manifest_with_invalid_utf_8() {
);
equal(result, null, "lookupApplication should reject file with invalid UTF8");
let errorPattern =
- /NotReadableError: Could not read file.* because it is not UTF-8 encoded/;
+ /NotReadableError: Could not read `.*': file is not UTF-8 encoded/;
let utf8Errors = messages.filter(({ message }) => errorPattern.test(message));
equal(utf8Errors.length, 1, "lookupApplication logs error about UTF-8");
});
diff --git a/toolkit/components/extensions/test/xpcshell/test_proxy_failover.js b/toolkit/components/extensions/test/xpcshell/test_proxy_failover.js
index bca71df63e..699b57b757 100644
--- a/toolkit/components/extensions/test/xpcshell/test_proxy_failover.js
+++ b/toolkit/components/extensions/test/xpcshell/test_proxy_failover.js
@@ -95,7 +95,7 @@ add_task(async function setup() {
async function getProxyExtension(proxyDetails) {
async function background(proxyDetails) {
browser.proxy.onRequest.addListener(
- details => {
+ () => {
return proxyDetails;
},
{ urls: ["<all_urls>"] }
@@ -165,7 +165,7 @@ add_task(
contentUrl,
`${contentUrl}?t=${Math.random()}`
)
- .then(text => {
+ .then(() => {
ok(false, "xhr unexpectedly completed");
})
.catch(e => {
@@ -196,7 +196,7 @@ add_task(
equal(req.proxy.type, "direct", "proxy failover to direct");
equal(req.text, "ok!", "xhr completed");
})
- .catch(req => {
+ .catch(() => {
ok(false, "xhr failed");
});
diff --git a/toolkit/components/extensions/test/xpcshell/test_proxy_info_results.js b/toolkit/components/extensions/test/xpcshell/test_proxy_info_results.js
index 0a7e1422d2..7a4aea9c4f 100644
--- a/toolkit/components/extensions/test/xpcshell/test_proxy_info_results.js
+++ b/toolkit/components/extensions/test/xpcshell/test_proxy_info_results.js
@@ -57,14 +57,14 @@ async function testProxyResolution(test) {
if (expected.error) {
errorMsg = extension.awaitMessage("proxy-error-received");
}
- let proxyInfo = await new Promise((resolve, reject) => {
+ let proxyInfo = await new Promise(resolve => {
let channel = NetUtil.newChannel({
uri,
loadUsingSystemPrincipal: true,
});
gProxyService.asyncResolve(channel, 0, {
- onProxyAvailable(req, uri, pi, status) {
+ onProxyAvailable(req, uri, pi) {
resolve(pi && pi.QueryInterface(Ci.nsIProxyInfo));
},
});
@@ -186,14 +186,15 @@ add_task(async function test_proxyInfo_results() {
{
proxy: [
{
- type: "http",
+ type: "socks",
host: "foo.bar",
port: 3128,
proxyAuthorizationHeader: "test",
},
],
expected: {
- error: 'ProxyInfoData: ProxyAuthorizationHeader requires type "https"',
+ error:
+ 'ProxyInfoData: ProxyAuthorizationHeader requires type "https" or "http"',
},
},
{
@@ -447,6 +448,26 @@ add_task(async function test_proxyInfo_results() {
},
},
},
+ {
+ proxy: [
+ {
+ type: "http",
+ host: "foo.bar",
+ port: 3128,
+ proxyAuthorizationHeader: "test",
+ connectionIsolationKey: "key",
+ },
+ ],
+ expected: {
+ proxyInfo: {
+ host: "foo.bar",
+ port: "3128",
+ type: "http",
+ proxyAuthorizationHeader: "test",
+ connectionIsolationKey: "key",
+ },
+ },
+ },
];
for (let test of tests) {
await setupProxyResult(test.proxy);
diff --git a/toolkit/components/extensions/test/xpcshell/test_proxy_listener.js b/toolkit/components/extensions/test/xpcshell/test_proxy_listener.js
index 8cc46d45e7..ef72e9ab75 100644
--- a/toolkit/components/extensions/test/xpcshell/test_proxy_listener.js
+++ b/toolkit/components/extensions/test/xpcshell/test_proxy_listener.js
@@ -11,14 +11,14 @@ const TRANSPARENT_PROXY_RESOLVES_HOST =
Ci.nsIProxyInfo.TRANSPARENT_PROXY_RESOLVES_HOST;
function getProxyInfo(url = "http://www.mozilla.org/") {
- return new Promise((resolve, reject) => {
+ return new Promise(resolve => {
let channel = NetUtil.newChannel({
uri: url,
loadUsingSystemPrincipal: true,
});
gProxyService.asyncResolve(channel, 0, {
- onProxyAvailable(req, uri, pi, status) {
+ onProxyAvailable(req, uri, pi) {
resolve(pi);
},
});
@@ -214,7 +214,7 @@ async function getExtension(expectedProxyInfo) {
`testing proxy.onRequest with proxyInfo = ${JSON.stringify(proxyInfo)}`
);
browser.proxy.onRequest.addListener(
- details => {
+ () => {
return proxyInfo;
},
{ urls: ["<all_urls>"] }
diff --git a/toolkit/components/extensions/test/xpcshell/test_webRequest_filtering.js b/toolkit/components/extensions/test/xpcshell/test_webRequest_filtering.js
index a7157f19a4..46a72a5926 100644
--- a/toolkit/components/extensions/test/xpcshell/test_webRequest_filtering.js
+++ b/toolkit/components/extensions/test/xpcshell/test_webRequest_filtering.js
@@ -73,7 +73,7 @@ function compareLists(list1, list2, kind) {
equal(String(list1), String(list2), `${kind} URLs correct`);
}
-async function openAndCloseContentPage(url) {
+async function openAndCloseContentPage() {
let contentPage = await ExtensionTestUtils.loadContentPage(URL);
// Clear the sheet cache so that it doesn't interact with following tests: A
// stylesheet with the same URI loaded from the same origin doesn't otherwise
diff --git a/toolkit/components/extensions/test/xpcshell/webidl-api/test_ext_webidl_api.js b/toolkit/components/extensions/test/xpcshell/webidl-api/test_ext_webidl_api.js
index 489cc3a754..57aed8a700 100644
--- a/toolkit/components/extensions/test/xpcshell/webidl-api/test_ext_webidl_api.js
+++ b/toolkit/components/extensions/test/xpcshell/webidl-api/test_ext_webidl_api.js
@@ -55,7 +55,7 @@ add_task(async function test_propagated_extension_error() {
throw err;
}
},
- mockAPIRequestHandler(policy, request) {
+ mockAPIRequestHandler() {
return {
type: Ci.mozIExtensionAPIRequestResult.EXTENSION_ERROR,
value: new Error("Fake Extension Error"),
@@ -76,7 +76,7 @@ add_task(async function test_system_errors_donot_leak() {
);
}
- function mockAPIRequestHandler(policy, request) {
+ function mockAPIRequestHandler() {
throw new Error("Fake handleAPIRequest exception");
}
@@ -183,7 +183,7 @@ add_task(async function test_call_sync_fn_missing_return() {
backgroundScript() {
self.browser.mockExtensionAPI.methodSyncWithReturn("arg0");
},
- mockAPIRequestHandler(policy, request) {
+ mockAPIRequestHandler() {
return undefined;
},
assertResults({ testError }) {
@@ -208,7 +208,7 @@ add_task(async function test_call_async_throw_extension_error() {
throw err;
}
},
- mockAPIRequestHandler(policy, request) {
+ mockAPIRequestHandler() {
return {
type: Ci.mozIExtensionAPIRequestResult.EXTENSION_ERROR,
value: new Error("Fake Param Validation Error"),
@@ -233,7 +233,7 @@ add_task(async function test_call_async_reject_error() {
throw err;
}
},
- mockAPIRequestHandler(policy, request) {
+ mockAPIRequestHandler() {
return {
type: Ci.mozIExtensionAPIRequestResult.RETURN_VALUE,
value: Promise.reject(new Error("Fake API rejected error object")),
@@ -311,7 +311,7 @@ add_task(async function test_call_no_return_throw_extension_error() {
throw err;
}
},
- mockAPIRequestHandler(policy, request) {
+ mockAPIRequestHandler() {
return {
type: Ci.mozIExtensionAPIRequestResult.EXTENSION_ERROR,
value: new Error("Fake Param Validation Error"),
@@ -331,7 +331,7 @@ add_task(async function test_call_no_return_without_errors() {
backgroundScript() {
self.browser.mockExtensionAPI.methodNoReturn("arg0");
},
- mockAPIRequestHandler(policy, request) {
+ mockAPIRequestHandler() {
return undefined;
},
assertResults({ testError }) {
@@ -446,7 +446,7 @@ add_task(async function test_get_property() {
backgroundScript() {
return self.browser.mockExtensionAPI.propertyAsString;
},
- mockAPIRequestHandler(policy, request) {
+ mockAPIRequestHandler() {
return {
type: Ci.mozIExtensionAPIRequestResult.RETURN_VALUE,
value: "property-value",
@@ -478,7 +478,7 @@ add_task(async function test_get_property() {
value: ChromeUtils.createError("fake extension error", savedFrame),
};
},
- assertResults({ testError, testResult }) {
+ assertResults({ testError }) {
Assert.deepEqual(testError, null, "Got no error as expected");
},
}
diff --git a/toolkit/components/extensions/test/xpcshell/webidl-api/test_ext_webidl_api_event_callback.js b/toolkit/components/extensions/test/xpcshell/webidl-api/test_ext_webidl_api_event_callback.js
index 576ec760d3..88c89a77e0 100644
--- a/toolkit/components/extensions/test/xpcshell/webidl-api/test_ext_webidl_api_event_callback.js
+++ b/toolkit/components/extensions/test/xpcshell/webidl-api/test_ext_webidl_api_event_callback.js
@@ -18,7 +18,7 @@ add_task(async function setup() {
add_task(async function test_api_event_manager_methods() {
await runExtensionAPITest("extension event manager methods", {
- backgroundScript({ testAsserts, testLog }) {
+ backgroundScript({ testAsserts }) {
const api = browser.mockExtensionAPI;
const listener = () => {};
@@ -48,7 +48,7 @@ add_task(async function test_api_event_manager_methods() {
);
}
},
- assertResults({ testError, testResult }) {
+ assertResults({ testError }) {
Assert.deepEqual(testError, null, "Got no error as expected");
},
});
@@ -104,7 +104,7 @@ add_task(async function test_api_event_eventListener_call_with_result() {
await runExtensionAPITest(
"extension event eventListener wrapper forwarded call result",
{
- backgroundScript({ testAsserts, testLog }) {
+ backgroundScript({ testLog }) {
const api = browser.mockExtensionAPI;
let listener;
@@ -206,11 +206,11 @@ add_task(async function test_api_event_eventListener_result_rejected() {
await runExtensionAPITest(
"extension event eventListener throws (mozIExtensionCallback.call)",
{
- backgroundScript({ testAsserts, testLog }) {
+ backgroundScript({ testLog }) {
const api = browser.mockExtensionAPI;
let listener;
- return new Promise((resolve, reject) => {
+ return new Promise(resolve => {
testLog("addListener and wait for event to be fired");
listener = (msg, arg1) => {
if (msg === "test-done") {
@@ -263,7 +263,7 @@ add_task(async function test_api_event_eventListener_throws_on_call() {
await runExtensionAPITest(
"extension event eventListener throws (mozIExtensionCallback.call)",
{
- backgroundScript({ testAsserts, testLog }) {
+ backgroundScript({ testLog }) {
const api = browser.mockExtensionAPI;
let listener;
@@ -280,7 +280,7 @@ add_task(async function test_api_event_eventListener_throws_on_call() {
api.onTestEvent.addListener(listener);
});
},
- assertResults({ testError, testResult }) {
+ assertResults({ testError }) {
Assert.deepEqual(testError, null, "Got no error as expected");
},
mockAPIRequestHandler(policy, request) {
@@ -305,7 +305,7 @@ add_task(async function test_send_response_eventListener() {
await runExtensionAPITest(
"extension event eventListener sendResponse eventListener argument",
{
- backgroundScript({ testAsserts, testLog }) {
+ backgroundScript({ testLog }) {
const api = browser.mockExtensionAPI;
let listener;
@@ -353,14 +353,14 @@ add_task(async function test_send_response_eventListener() {
add_task(async function test_send_response_multiple_eventListener() {
await runExtensionAPITest("multiple extension event eventListeners", {
- backgroundScript({ testAsserts, testLog }) {
+ backgroundScript({ testLog }) {
const api = browser.mockExtensionAPI;
let listenerNoReply;
let listenerSendResponseReply;
return new Promise(resolve => {
testLog("addListener and wait for event to be fired");
- listenerNoReply = (msg, sendResponse) => {
+ listenerNoReply = () => {
return false;
};
listenerSendResponseReply = (msg, sendResponse) => {
diff --git a/toolkit/components/extensions/test/xpcshell/webidl-api/test_ext_webidl_api_request_handler.js b/toolkit/components/extensions/test/xpcshell/webidl-api/test_ext_webidl_api_request_handler.js
index 070a45fa95..4886db37d0 100644
--- a/toolkit/components/extensions/test/xpcshell/webidl-api/test_ext_webidl_api_request_handler.js
+++ b/toolkit/components/extensions/test/xpcshell/webidl-api/test_ext_webidl_api_request_handler.js
@@ -29,7 +29,7 @@ add_task(async function test_sw_api_request_handling_local_process_api() {
files: {
"page.html": "<!DOCTYPE html><body></body>",
"sw.js": async function () {
- browser.test.onMessage.addListener(async msg => {
+ browser.test.onMessage.addListener(async () => {
browser.test.succeed("call to test.succeed");
browser.test.assertTrue(true, "call to test.assertTrue");
browser.test.assertFalse(false, "call to test.assertFalse");
diff --git a/toolkit/components/extensions/test/xpcshell/webidl-api/test_ext_webidl_api_schema_errors.js b/toolkit/components/extensions/test/xpcshell/webidl-api/test_ext_webidl_api_schema_errors.js
index d8684c1574..b532ca5203 100644
--- a/toolkit/components/extensions/test/xpcshell/webidl-api/test_ext_webidl_api_schema_errors.js
+++ b/toolkit/components/extensions/test/xpcshell/webidl-api/test_ext_webidl_api_schema_errors.js
@@ -17,7 +17,7 @@ AddonTestUtils.createAppInfo(
// is no JSON schema for this namespace so we add one here that is tailored for
// our testing needs.
const API = class extends ExtensionAPI {
- getAPI(context) {
+ getAPI() {
return {
mockExtensionAPI: {
methodAsync: () => {
diff --git a/toolkit/components/extensions/test/xpcshell/webidl-api/test_ext_webidl_api_schema_formatters.js b/toolkit/components/extensions/test/xpcshell/webidl-api/test_ext_webidl_api_schema_formatters.js
index a7310f345e..d6f0d83896 100644
--- a/toolkit/components/extensions/test/xpcshell/webidl-api/test_ext_webidl_api_schema_formatters.js
+++ b/toolkit/components/extensions/test/xpcshell/webidl-api/test_ext_webidl_api_schema_formatters.js
@@ -17,7 +17,7 @@ AddonTestUtils.createAppInfo(
// is no JSON schema for this namespace so we add one here that is tailored for
// our testing needs.
const API = class extends ExtensionAPI {
- getAPI(context) {
+ getAPI() {
return {
mockExtensionAPI: {
methodAsync: files => {
diff --git a/toolkit/components/extensions/test/xpcshell/webidl-api/test_ext_webidl_runtime_port.js b/toolkit/components/extensions/test/xpcshell/webidl-api/test_ext_webidl_runtime_port.js
index 0d88014f32..1ca5994f60 100644
--- a/toolkit/components/extensions/test/xpcshell/webidl-api/test_ext_webidl_runtime_port.js
+++ b/toolkit/components/extensions/test/xpcshell/webidl-api/test_ext_webidl_runtime_port.js
@@ -16,7 +16,7 @@ add_task(async function setup() {
add_task(async function test_method_return_runtime_port() {
await runExtensionAPITest("API method returns an ExtensionPort instance", {
- backgroundScript({ testAsserts, testLog }) {
+ backgroundScript({ testAsserts }) {
try {
browser.mockExtensionAPI.methodReturnsPort("port-create-error");
throw new Error("methodReturnsPort should have raised an exception");
diff --git a/toolkit/components/extensions/webidl-api/ExtensionAPIRequestForwarder.cpp b/toolkit/components/extensions/webidl-api/ExtensionAPIRequestForwarder.cpp
index 5f6a966fc8..2c36c10d7b 100644
--- a/toolkit/components/extensions/webidl-api/ExtensionAPIRequestForwarder.cpp
+++ b/toolkit/components/extensions/webidl-api/ExtensionAPIRequestForwarder.cpp
@@ -88,9 +88,9 @@ ExtensionAPIRequestForwarder::APIRequestHandler() {
MOZ_ASSERT(NS_IsMainThread());
if (MOZ_UNLIKELY(!sAPIRequestHandler)) {
- sAPIRequestHandler =
- do_ImportModule("resource://gre/modules/ExtensionProcessScript.jsm",
- "ExtensionAPIRequestHandler");
+ sAPIRequestHandler = do_ImportESModule(
+ "resource://gre/modules/ExtensionProcessScript.sys.mjs",
+ "ExtensionAPIRequestHandler");
MOZ_RELEASE_ASSERT(sAPIRequestHandler);
ClearOnShutdown(&sAPIRequestHandler);
}
diff --git a/toolkit/components/extensions/webidl-api/GenerateWebIDLBindings.py b/toolkit/components/extensions/webidl-api/GenerateWebIDLBindings.py
index 3b31bd924d..6a4a404ac8 100644
--- a/toolkit/components/extensions/webidl-api/GenerateWebIDLBindings.py
+++ b/toolkit/components/extensions/webidl-api/GenerateWebIDLBindings.py
@@ -139,8 +139,8 @@ def read_json(json_file_path):
"""
Helper function used to read the WebExtensions API schema JSON files
by ignoring the license comment on the top of some of those files.
- Same helper as the one available in Schemas.jsm:
- https://searchfox.org/mozilla-central/rev/3434a9df60373a997263107e6f124fb164ddebf2/toolkit/components/extensions/Schemas.jsm#70
+ Same helper as the one available in Schemas.sys.mjs:
+ https://searchfox.org/mozilla-central/rev/b60cb73160843adb5a5a3ec8058e75a69b46acf7/toolkit/components/extensions/Schemas.sys.mjs#53
"""
with open(json_file_path) as json_file:
txt = json_file.read()
diff --git a/toolkit/components/extensions/webrequest/WebRequest.sys.mjs b/toolkit/components/extensions/webrequest/WebRequest.sys.mjs
index 1d9bbb2260..bbe044ddea 100644
--- a/toolkit/components/extensions/webrequest/WebRequest.sys.mjs
+++ b/toolkit/components/extensions/webrequest/WebRequest.sys.mjs
@@ -15,7 +15,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
WebRequestUpload: "resource://gre/modules/WebRequestUpload.sys.mjs",
});
-// WebRequest.jsm's only consumer is ext-webRequest.js, so we can depend on
+// 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;
@@ -1226,7 +1226,7 @@ HttpObserverManager = {
return false;
},
- examine(channel, topic, data) {
+ examine(channel) {
if (this.listeners.onHeadersReceived.size || this.dnrActive) {
this.runChannelListener(channel, "onHeadersReceived");
}
diff --git a/toolkit/components/find/nsFind.cpp b/toolkit/components/find/nsFind.cpp
index 13745e7fbe..e819d4d970 100644
--- a/toolkit/components/find/nsFind.cpp
+++ b/toolkit/components/find/nsFind.cpp
@@ -8,7 +8,6 @@
#include "nsFind.h"
#include "mozilla/Likely.h"
-#include "nsContentCID.h"
#include "nsIContent.h"
#include "nsINode.h"
#include "nsIFrame.h"
diff --git a/toolkit/components/find/nsWebBrowserFind.cpp b/toolkit/components/find/nsWebBrowserFind.cpp
index 2e06f65fc8..39553579eb 100644
--- a/toolkit/components/find/nsWebBrowserFind.cpp
+++ b/toolkit/components/find/nsWebBrowserFind.cpp
@@ -22,7 +22,6 @@
#include "nsITextControlFrame.h"
#include "nsReadableUtils.h"
#include "nsIContent.h"
-#include "nsContentCID.h"
#include "nsIObserverService.h"
#include "nsISupportsPrimitives.h"
#include "nsFind.h"
diff --git a/toolkit/components/formautofill/AutofillProfileAutoComplete.sys.mjs b/toolkit/components/formautofill/AutofillProfileAutoComplete.sys.mjs
index 2d87f7931d..fc3f0454b0 100644
--- a/toolkit/components/formautofill/AutofillProfileAutoComplete.sys.mjs
+++ b/toolkit/components/formautofill/AutofillProfileAutoComplete.sys.mjs
@@ -349,7 +349,7 @@ export const ProfileAutocomplete = {
Services.obs.removeObserver(this, "autocomplete-will-enter-text");
},
- async observe(subject, topic, data) {
+ async observe(_subject, topic, _data) {
switch (topic) {
case "autocomplete-will-enter-text": {
if (!lazy.FormAutofillContent.activeInput) {
diff --git a/toolkit/components/formautofill/Constants.ios.mjs b/toolkit/components/formautofill/Constants.ios.mjs
index b78e47198d..290e690ea6 100644
--- a/toolkit/components/formautofill/Constants.ios.mjs
+++ b/toolkit/components/formautofill/Constants.ios.mjs
@@ -17,7 +17,7 @@ const IOS_DEFAULT_PREFERENCES = {
"browser.search.region": "US",
"extensions.formautofill.creditCards.supportedCountries": "US,CA,GB,FR,DE",
"extensions.formautofill.addresses.enabled": true,
- "extensions.formautofill.addresses.experiments.enabled": false, // TODO(FXCM-765): fetch this value from swift
+ "extensions.formautofill.addresses.experiments.enabled": true,
"extensions.formautofill.addresses.capture.enabled": false,
"extensions.formautofill.addresses.supportedCountries": "",
"extensions.formautofill.creditCards.enabled": true,
@@ -31,6 +31,7 @@ const IOS_DEFAULT_PREFERENCES = {
"extensions.formautofill.heuristics.captureOnFormRemoval": false,
"extensions.formautofill.heuristics.captureOnPageNavigation": false,
"extensions.formautofill.focusOnAutofill": false,
+ "extensions.formautofill.test.ignoreVisibilityCheck": false,
};
// Used Mimic the behavior of .getAutocompleteInfo()
diff --git a/toolkit/components/formautofill/FormAutofill.ios.sys.mjs b/toolkit/components/formautofill/FormAutofill.ios.sys.mjs
index 8e205c16c6..0b87fee30a 100644
--- a/toolkit/components/formautofill/FormAutofill.ios.sys.mjs
+++ b/toolkit/components/formautofill/FormAutofill.ios.sys.mjs
@@ -4,7 +4,7 @@
import { FormAutofill } from "resource://autofill/FormAutofill.sys.mjs";
-FormAutofill.defineLogGetter = (scope, logPrefix) => ({
+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: () => {},
diff --git a/toolkit/components/formautofill/FormAutofill.sys.mjs b/toolkit/components/formautofill/FormAutofill.sys.mjs
index 77502afbbe..8f50aad7bd 100644
--- a/toolkit/components/formautofill/FormAutofill.sys.mjs
+++ b/toolkit/components/formautofill/FormAutofill.sys.mjs
@@ -81,7 +81,9 @@ export const FormAutofill = {
return false;
},
isAutofillAddressesAvailableInCountry(country) {
- return FormAutofill._addressAutofillSupportedCountries.includes(country);
+ return FormAutofill._addressAutofillSupportedCountries.includes(
+ country.toUpperCase()
+ );
},
get isAutofillEnabled() {
return this.isAutofillAddressesEnabled || this.isAutofillCreditCardsEnabled;
diff --git a/toolkit/components/formautofill/FormAutofillChild.ios.sys.mjs b/toolkit/components/formautofill/FormAutofillChild.ios.sys.mjs
index 1aa713b5b7..3183319fd9 100644
--- a/toolkit/components/formautofill/FormAutofillChild.ios.sys.mjs
+++ b/toolkit/components/formautofill/FormAutofillChild.ios.sys.mjs
@@ -6,6 +6,7 @@
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 { AddressRecord } from "resource://gre/modules/shared/AddressRecord.sys.mjs";
export class FormAutofillChild {
/**
@@ -77,7 +78,7 @@ export class FormAutofillChild {
this._doIdentifyAutofillFields(element);
}
- onSubmit(evt) {
+ onSubmit(_event) {
if (!this.fieldDetailsManager.activeHandler) {
return;
}
@@ -100,6 +101,17 @@ export class FormAutofillChild {
}
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.fieldDetailsManager.activeFieldDetail?.fieldName
+ )
+ ) {
+ AddressRecord.computeFields(payload);
+ }
this.fieldDetailsManager.activeHandler.autofillFormFields(payload);
}
}
diff --git a/toolkit/components/formautofill/FormAutofillChild.sys.mjs b/toolkit/components/formautofill/FormAutofillChild.sys.mjs
index c40bfddbce..8678a7bd45 100644
--- a/toolkit/components/formautofill/FormAutofillChild.sys.mjs
+++ b/toolkit/components/formautofill/FormAutofillChild.sys.mjs
@@ -2,16 +2,36 @@
* License, v. 2.0. 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, {
AutoCompleteChild: "resource://gre/actors/AutoCompleteChild.sys.mjs",
+ AutofillTelemetry: "resource://gre/modules/shared/AutofillTelemetry.sys.mjs",
FormAutofill: "resource://autofill/FormAutofill.sys.mjs",
FormAutofillContent: "resource://autofill/FormAutofillContent.sys.mjs",
FormAutofillUtils: "resource://gre/modules/shared/FormAutofillUtils.sys.mjs",
+ FormStateManager: "resource://gre/modules/shared/FormStateManager.sys.mjs",
+ PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
+ ProfileAutocomplete:
+ "resource://autofill/AutofillProfileAutoComplete.sys.mjs",
setTimeout: "resource://gre/modules/Timer.sys.mjs",
+ FORM_SUBMISSION_REASON: "resource://gre/actors/FormHandlerChild.sys.mjs",
});
+XPCOMUtils.defineLazyPreferenceGetter(
+ lazy,
+ "DELEGATE_AUTOCOMPLETE",
+ "toolkit.autocomplete.delegate",
+ false
+);
+
+const formFillController = Cc[
+ "@mozilla.org/satchel/form-fill-controller;1"
+].getService(Ci.nsIFormFillController);
+
const observer = {
QueryInterface: ChromeUtils.generateQI([
"nsIWebProgressListener",
@@ -31,7 +51,7 @@ const observer = {
formAutofillChild.onPageNavigation();
},
- onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
+ onStateChange(aWebProgress, aRequest, aStateFlags, _aStatus) {
if (
// if restoring a previously-rendered presentation (bfcache)
aStateFlags & Ci.nsIWebProgressListener.STATE_RESTORING &&
@@ -77,21 +97,34 @@ export class FormAutofillChild extends JSWindowActorChild {
constructor() {
super();
+ this.log = lazy.FormAutofill.defineLogGetter(this, "FormAutofillChild");
+ this.debug("init");
+
this._nextHandleElement = null;
- this._alreadyDOMContentLoaded = false;
this._hasDOMContentLoadedHandler = false;
this._hasPendingTask = false;
- this.testListener = null;
+
+ // Flag indicating whether the form is waiting to be filled by Autofill.
+ this._autofillPending = false;
+
+ /**
+ * @type {FormAutofillFieldDetailsManager} handling state management of current forms and handlers.
+ */
+ this._fieldDetailsManager = new lazy.FormStateManager(
+ this.formSubmitted.bind(this),
+ this.formAutofilled.bind(this)
+ );
lazy.AutoCompleteChild.addPopupStateListener(this);
}
didDestroy() {
+ this._fieldDetailsManager.didDestroy();
+
lazy.AutoCompleteChild.removePopupStateListener(this);
- lazy.FormAutofillContent.didDestroy();
}
- popupStateChanged(messageName, data, target) {
+ popupStateChanged(messageName, data, _target) {
let docShell;
try {
docShell = this.docShell;
@@ -108,50 +141,56 @@ export class FormAutofillChild extends JSWindowActorChild {
switch (messageName) {
case "FormAutoComplete:PopupClosed": {
- lazy.FormAutofillContent.onPopupClosed(data.selectedRowStyle);
+ this.onPopupClosed(data.selectedRowStyle);
Services.tm.dispatchToMainThread(() => {
- chromeEventHandler.removeEventListener(
- "keydown",
- lazy.FormAutofillContent._onKeyDown,
- true
- );
+ chromeEventHandler.removeEventListener("keydown", this, true);
});
break;
}
case "FormAutoComplete:PopupOpened": {
- lazy.FormAutofillContent.onPopupOpened();
- chromeEventHandler.addEventListener(
- "keydown",
- lazy.FormAutofillContent._onKeyDown,
- true
- );
+ this.onPopupOpened();
+ chromeEventHandler.addEventListener("keydown", this, true);
break;
}
}
}
/**
- * Invokes the FormAutofillContent to identify the autofill fields
- * and consider opening the dropdown menu for the focused field
- *
+ * Identifies and marks each autofill field
*/
- _doIdentifyAutofillFields() {
+ identifyAutofillFields() {
if (this._hasPendingTask) {
return;
}
this._hasPendingTask = true;
lazy.setTimeout(() => {
- const isAnyFieldIdentified =
- lazy.FormAutofillContent.identifyAutofillFields(
- this._nextHandleElement
- );
- if (isAnyFieldIdentified) {
+ const element = this._nextHandleElement;
+ this.debug(
+ `identifyAutofillFields: ${element.ownerDocument.location?.hostname}`
+ );
+
+ 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");
+ }
+
+ const validDetails =
+ this._fieldDetailsManager.identifyAutofillFields(element);
+
+ validDetails?.forEach(detail =>
+ this._markAsAutofillField(detail.element)
+ );
+ if (validDetails.length) {
if (lazy.FormAutofill.captureOnFormRemoval) {
- this.registerDOMDocFetchSuccessEventListener(
- this._nextHandleElement.ownerDocument
- );
+ this.registerDOMDocFetchSuccessEventListener();
}
if (lazy.FormAutofill.captureOnPageNavigation) {
this.registerProgressListener();
@@ -163,7 +202,7 @@ export class FormAutofillChild extends JSWindowActorChild {
// 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");
- lazy.FormAutofillContent.updateActiveInput();
+ this.updateActiveInput();
});
}
@@ -192,13 +231,18 @@ export class FormAutofillChild extends JSWindowActorChild {
/**
* After being notified of a page navigation, we check whether
* the navigated window is the active window or one of its parents
- * (active window = FormAutofillContent.activeHandler.window)
+ * (active window = activeHandler.window)
*
* @returns {boolean} whether the navigation affects the active window
*/
isActiveWindowNavigation() {
- const activeWindow = lazy.FormAutofillContent.activeHandler.window;
+ const activeWindow = lazy.FormAutofillContent.activeHandler?.window;
const navigatedWindow = this.document.defaultView;
+
+ if (!activeWindow || !navigatedWindow) {
+ return false;
+ }
+
const navigatedBrowsingContext =
BrowsingContext.getFromWindow(navigatedWindow);
@@ -218,19 +262,23 @@ export class FormAutofillChild extends JSWindowActorChild {
* Infer a form submission after document is navigated
*/
onPageNavigation() {
- const activeElement =
- lazy.FormAutofillContent.activeFieldDetail?.elementWeakRef.deref();
-
if (!this.isActiveWindowNavigation()) {
return;
}
- const formSubmissionReason =
- lazy.FormAutofillUtils.FORM_SUBMISSION_REASON.PAGE_NAVIGATION;
+ // TODO: We should not use FormAutofillContent and let the
+ // parent decides which child to notify
+ const activeChild = lazy.FormAutofillContent.activeAutofillChild;
+ const activeElement = activeChild.activeFieldDetail?.elementWeakRef.deref();
+ if (!activeElement) {
+ return;
+ }
+
+ const formSubmissionReason = lazy.FORM_SUBMISSION_REASON.PAGE_NAVIGATION;
// We only capture the form of the active field right now,
// this means that we might miss some fields (see bug 1871356)
- lazy.FormAutofillContent.formSubmitted(activeElement, formSubmissionReason);
+ activeChild.formSubmitted(activeElement, formSubmissionReason);
}
/**
@@ -267,11 +315,9 @@ export class FormAutofillChild extends JSWindowActorChild {
/**
* After a focusin event and after we identify formautofill fields,
* we set up an event listener for the DOMDocFetchSuccess event
- *
- * @param {Document} document The document we want to be notified by of a DOMDocFetchSuccess event
*/
- registerDOMDocFetchSuccessEventListener(document) {
- document.setNotifyFetchSuccess(true);
+ registerDOMDocFetchSuccessEventListener() {
+ this.document.setNotifyFetchSuccess(true);
// Is removed after a DOMDocFetchSuccess event (bug 1864855)
/* eslint-disable mozilla/balanced-listeners */
@@ -284,11 +330,9 @@ export class FormAutofillChild extends JSWindowActorChild {
/**
* After a DOMDocFetchSuccess event, we register an event listener for the DOMFormRemoved event
- *
- * @param {Document} document The document we want to be notified by of a DOMFormRemoved event
*/
- registerDOMFormRemovedEventListener(document) {
- document.setNotifyFormOrPasswordRemoved(true);
+ registerDOMFormRemovedEventListener() {
+ this.document.setNotifyFormOrPasswordRemoved(true);
// Is removed after a DOMFormRemoved event (bug 1864855)
/* eslint-disable mozilla/balanced-listeners */
@@ -301,11 +345,9 @@ export class FormAutofillChild extends JSWindowActorChild {
/**
* After a DOMDocFetchSuccess event we remove the DOMDocFetchSuccess event listener
- *
- * @param {Document} document The document we are notified by of a DOMDocFetchSuccess event
*/
- unregisterDOMDocFetchSuccessEventListener(document) {
- document.setNotifyFetchSuccess(false);
+ unregisterDOMDocFetchSuccessEventListener() {
+ this.document.setNotifyFetchSuccess(false);
this.docShell.chromeEventHandler.removeEventListener(
"DOMDocFetchSuccess",
this
@@ -314,11 +356,9 @@ export class FormAutofillChild extends JSWindowActorChild {
/**
* After a DOMFormRemoved event we remove the DOMFormRemoved event listener
- *
- * @param {Document} document The document we are notified by of a DOMFormRemoved event
*/
- unregisterDOMFormRemovedEventListener(document) {
- document.setNotifyFormOrPasswordRemoved(false);
+ unregisterDOMFormRemovedEventListener() {
+ this.document.setNotifyFormOrPasswordRemoved(false);
this.docShell.chromeEventHandler.removeEventListener(
"DOMFormRemoved",
this
@@ -327,11 +367,7 @@ export class FormAutofillChild extends JSWindowActorChild {
shouldIgnoreFormAutofillEvent(event) {
let nodePrincipal = event.target.nodePrincipal;
- return (
- nodePrincipal.isSystemPrincipal ||
- nodePrincipal.isNullPrincipal ||
- nodePrincipal.schemeIs("about")
- );
+ return nodePrincipal.isSystemPrincipal || nodePrincipal.schemeIs("about");
}
handleEvent(evt) {
@@ -342,16 +378,20 @@ export class FormAutofillChild extends JSWindowActorChild {
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);
- }
+ case "keydown": {
+ this._onKeyDown(evt);
break;
}
- case "DOMFormBeforeSubmit": {
+ case "focusin": {
if (lazy.FormAutofill.isAutofillEnabled) {
- this.onDOMFormBeforeSubmit(evt);
+ this.onFocusIn(evt);
}
break;
}
@@ -360,7 +400,13 @@ export class FormAutofillChild extends JSWindowActorChild {
break;
}
case "DOMDocFetchSuccess": {
- this.onDOMDocFetchSuccess(evt);
+ this.onDOMDocFetchSuccess();
+ break;
+ }
+ case "form-submission-detected": {
+ if (lazy.FormAutofill.isAutofillEnabled) {
+ this.onFormSubmission(evt);
+ }
break;
}
@@ -371,45 +417,42 @@ export class FormAutofillChild extends JSWindowActorChild {
}
onFocusIn(evt) {
- lazy.FormAutofillContent.updateActiveInput();
+ this.updateActiveInput();
- let element = evt.target;
+ const element = evt.target;
if (!lazy.FormAutofillUtils.isCreditCardOrAddressFieldType(element)) {
return;
}
- this._nextHandleElement = element;
- if (!this._alreadyDOMContentLoaded) {
- let doc = element.ownerDocument;
- if (doc.readyState === "loading") {
- if (!this._hasDOMContentLoadedHandler) {
- this._hasDOMContentLoadedHandler = true;
- doc.addEventListener(
- "DOMContentLoaded",
- () => this._doIdentifyAutofillFields(),
- { once: true }
- );
- }
- return;
+ this._nextHandleElement = 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 }
+ );
}
- this._alreadyDOMContentLoaded = true;
+ return;
}
- this._doIdentifyAutofillFields();
+ this.identifyAutofillFields();
}
/**
- * Handle the DOMFormBeforeSubmit event.
+ * Handle form-submission-detected event (dispatched by FormHandlerChild)
*
- * @param {Event} evt
+ * @param {CustomEvent} evt form-submission-detected event
*/
- onDOMFormBeforeSubmit(evt) {
- const formElement = evt.target;
-
- const formSubmissionReason =
- lazy.FormAutofillUtils.FORM_SUBMISSION_REASON.FORM_SUBMIT_EVENT;
+ onFormSubmission(evt) {
+ const formElement = evt.detail.form;
+ const formSubmissionReason = evt.detail.reason;
- lazy.FormAutofillContent.formSubmitted(formElement, formSubmissionReason);
+ this.formSubmitted(formElement, formSubmissionReason);
}
/**
@@ -421,14 +464,10 @@ export class FormAutofillChild extends JSWindowActorChild {
* @param {Event} evt DOMFormRemoved
*/
onDOMFormRemoved(evt) {
- const document = evt.composedTarget.ownerDocument;
-
const formSubmissionReason =
- lazy.FormAutofillUtils.FORM_SUBMISSION_REASON.FORM_REMOVAL_AFTER_FETCH;
-
- lazy.FormAutofillContent.formSubmitted(evt.target, formSubmissionReason);
+ lazy.FORM_SUBMISSION_REASON.FORM_REMOVAL_AFTER_FETCH;
- this.unregisterDOMFormRemovedEventListener(document);
+ this.formSubmitted(evt.target, formSubmissionReason);
}
/**
@@ -436,15 +475,21 @@ export class FormAutofillChild extends JSWindowActorChild {
*
* Sets up an event listener for the DOMFormRemoved event
* and unregisters the event listener for DOMDocFetchSuccess event.
- *
- * @param {Event} evt DOMDocFetchSuccess
*/
- onDOMDocFetchSuccess(evt) {
- const document = evt.target;
+ onDOMDocFetchSuccess() {
+ this.registerDOMFormRemovedEventListener();
- this.registerDOMFormRemovedEventListener(document);
+ this.unregisterDOMDocFetchSuccessEventListener();
+ }
- this.unregisterDOMDocFetchSuccessEventListener(document);
+ /**
+ * Unregister all listeners that notify of a form submission,
+ * because we just detected and acted on a form submission
+ */
+ unregisterFormSubmissionListeners() {
+ this.unregisterDOMDocFetchSuccessEventListener();
+ this.unregisterDOMFormRemovedEventListener();
+ this.unregisterProgressListener();
}
receiveMessage(message) {
@@ -456,17 +501,284 @@ export class FormAutofillChild extends JSWindowActorChild {
switch (message.name) {
case "FormAutofill:PreviewProfile": {
- lazy.FormAutofillContent.previewProfile(doc);
+ this.previewProfile(doc);
break;
}
case "FormAutofill:ClearForm": {
- lazy.FormAutofillContent.clearForm();
+ this.clearForm();
break;
}
case "FormAutofill:FillForm": {
- lazy.FormAutofillContent.activeHandler.autofillFormFields(message.data);
+ this.activeHandler.autofillFormFields(message.data);
break;
}
}
}
+
+ get activeFieldDetail() {
+ return this._fieldDetailsManager.activeFieldDetail;
+ }
+
+ get activeFormDetails() {
+ return this._fieldDetailsManager.activeFormDetails;
+ }
+
+ get activeInput() {
+ return this._fieldDetailsManager.activeInput;
+ }
+
+ get activeHandler() {
+ return this._fieldDetailsManager.activeHandler;
+ }
+
+ get activeSection() {
+ return this._fieldDetailsManager.activeSection;
+ }
+
+ /**
+ * 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 {Window} domWin Content window; passed for unit tests and when
+ * invoked by the FormAutofillSection
+ * @param {object} handler FormAutofillHander, if known by caller
+ */
+ formSubmitted(
+ formElement,
+ formSubmissionReason,
+ domWin = formElement.ownerGlobal,
+ 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.
+ if (domWin && lazy.PrivateBrowsingUtils.isContentWindowPrivate(domWin)) {
+ this.debug("Ignoring submission in a private window");
+ return;
+ }
+
+ handler = handler || this._fieldDetailsManager._getFormHandler(formElement);
+ const records = this._fieldDetailsManager.getRecords(formElement, handler);
+
+ if (!records || !handler) {
+ this.debug("Form element could not map to an existing handler");
+ return;
+ }
+
+ // Unregister the form submission listeners after handling a form submission
+ this.debug("Unregistering form submission listeners");
+ this.unregisterFormSubmissionListeners();
+
+ [records.address, records.creditCard].forEach((rs, idx) => {
+ lazy.AutofillTelemetry.recordSubmittedSectionCount(
+ idx == 0
+ ? lazy.AutofillTelemetry.ADDRESS
+ : lazy.AutofillTelemetry.CREDIT_CARD,
+ rs?.length
+ );
+
+ rs?.forEach(r => {
+ lazy.AutofillTelemetry.recordFormInteractionEvent(
+ "submitted",
+ r.section,
+ {
+ record: r,
+ form: handler.form,
+ }
+ );
+ delete r.section;
+ });
+ });
+
+ this.sendAsyncMessage("FormAutofill:OnFormSubmit", records);
+ }
+
+ formAutofilled() {
+ lazy.FormAutofillContent.showPopup();
+ }
+
+ /**
+ * All active items should be updated according the active element of
+ * `formFillController.focusedInput`. All of them including element,
+ * handler, section, and field detail, can be retrieved by their own getters.
+ *
+ * @param {HTMLElement|null} element The active item should be updated based
+ * on this or `formFillController.focusedInput` will be taken.
+ */
+ updateActiveInput(element) {
+ element = element || formFillController.focusedInput;
+ if (!element) {
+ this.debug("updateActiveElement: no element selected");
+ return;
+ }
+ lazy.FormAutofillContent.updateActiveAutofillChild(this);
+
+ this._fieldDetailsManager.updateActiveInput(element);
+ this.debug("updateActiveElement: checking for popup-on-focus");
+ // We know this element just received focus. If it's a credit card field,
+ // open its popup.
+ if (this._autofillPending) {
+ this.debug("updateActiveElement: skipping check; autofill is imminent");
+ } else if (element.value?.length !== 0) {
+ this.debug(
+ `updateActiveElement: Not opening popup because field is not empty.`
+ );
+ } else {
+ this.debug(
+ "updateActiveElement: checking if empty field is cc-*: ",
+ this.activeFieldDetail?.fieldName
+ );
+
+ if (
+ this.activeFieldDetail?.fieldName?.startsWith("cc-") ||
+ AppConstants.platform === "android"
+ ) {
+ lazy.FormAutofillContent.showPopup();
+ }
+ }
+ }
+
+ set autofillPending(flag) {
+ this.debug("Setting autofillPending to", flag);
+ this._autofillPending = flag;
+ }
+
+ clearForm() {
+ let focusedInput =
+ this.activeInput ||
+ lazy.ProfileAutocomplete._lastAutoCompleteFocusedInput;
+ if (!focusedInput) {
+ return;
+ }
+
+ this.activeSection.clearPopulatedForm();
+
+ let fieldName = this.activeFieldDetail?.fieldName;
+ if (lazy.FormAutofillUtils.isCreditCardField(fieldName)) {
+ lazy.AutofillTelemetry.recordFormInteractionEvent(
+ "cleared",
+ this.activeSection,
+ { fieldName }
+ );
+ }
+ }
+
+ previewProfile(doc) {
+ let docWin = doc.ownerGlobal;
+ let selectedIndex = lazy.ProfileAutocomplete._getSelectedIndex(docWin);
+ let lastAutoCompleteResult =
+ lazy.ProfileAutocomplete.lastProfileAutoCompleteResult;
+ let focusedInput = this.activeInput;
+
+ if (
+ selectedIndex === -1 ||
+ !focusedInput ||
+ !lastAutoCompleteResult ||
+ lastAutoCompleteResult.getStyleAt(selectedIndex) != "autofill-profile"
+ ) {
+ this.sendAsyncMessage("FormAutofill:UpdateWarningMessage", {});
+
+ lazy.ProfileAutocomplete._clearProfilePreview();
+ } else {
+ let focusedInputDetails = this.activeFieldDetail;
+ let profile = JSON.parse(
+ lastAutoCompleteResult.getCommentAt(selectedIndex)
+ );
+ let allFieldNames = this.activeSection.allFieldNames;
+ let profileFields = allFieldNames.filter(
+ fieldName => !!profile[fieldName]
+ );
+
+ let focusedCategory = lazy.FormAutofillUtils.getCategoryFromFieldName(
+ focusedInputDetails.fieldName
+ );
+ let categories =
+ lazy.FormAutofillUtils.getCategoriesFromFieldNames(profileFields);
+ this.sendAsyncMessage("FormAutofill:UpdateWarningMessage", {
+ focusedCategory,
+ categories,
+ });
+
+ lazy.ProfileAutocomplete._previewSelectedProfile(selectedIndex);
+ }
+ }
+
+ onPopupClosed(selectedRowStyle) {
+ this.debug("Popup has closed.");
+ lazy.ProfileAutocomplete._clearProfilePreview();
+
+ let lastAutoCompleteResult =
+ lazy.ProfileAutocomplete.lastProfileAutoCompleteResult;
+ let focusedInput = this.activeInput;
+ if (
+ lastAutoCompleteResult &&
+ this._keyDownEnterForInput &&
+ focusedInput === this._keyDownEnterForInput &&
+ focusedInput ===
+ lazy.ProfileAutocomplete.lastProfileAutoCompleteFocusedInput
+ ) {
+ if (selectedRowStyle == "autofill-footer") {
+ this.sendAsyncMessage("FormAutofill:OpenPreferences");
+ } else if (selectedRowStyle == "autofill-clear-button") {
+ this.clearForm();
+ }
+ }
+ }
+
+ onPopupOpened() {
+ this.debug(
+ "Popup has opened, automatic =",
+ formFillController.passwordPopupAutomaticallyOpened
+ );
+
+ let fieldName = this.activeFieldDetail?.fieldName;
+ if (fieldName && this.activeSection) {
+ lazy.AutofillTelemetry.recordFormInteractionEvent(
+ "popup_shown",
+ this.activeSection,
+ { fieldName }
+ );
+ }
+ }
+
+ _markAsAutofillField(field) {
+ // Since Form Autofill popup is only for input element, any non-Input
+ // element should be excluded here.
+ if (!HTMLInputElement.isInstance(field)) {
+ return;
+ }
+
+ formFillController.markAsAutofillField(field);
+ }
+
+ _onKeyDown(e) {
+ delete this._keyDownEnterForInput;
+ let lastAutoCompleteResult =
+ lazy.ProfileAutocomplete.lastProfileAutoCompleteResult;
+ let focusedInput = this.activeInput;
+ if (
+ e.keyCode != e.DOM_VK_RETURN ||
+ !lastAutoCompleteResult ||
+ !focusedInput ||
+ focusedInput !=
+ lazy.ProfileAutocomplete.lastProfileAutoCompleteFocusedInput
+ ) {
+ return;
+ }
+ this._keyDownEnterForInput = focusedInput;
+ }
}
diff --git a/toolkit/components/formautofill/FormAutofillContent.sys.mjs b/toolkit/components/formautofill/FormAutofillContent.sys.mjs
index 133e5e1d0a..c07e0d67b3 100644
--- a/toolkit/components/formautofill/FormAutofillContent.sys.mjs
+++ b/toolkit/components/formautofill/FormAutofillContent.sys.mjs
@@ -8,43 +8,18 @@
/* eslint-disable no-use-before-define */
-import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
-import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
-
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
FormAutofill: "resource://autofill/FormAutofill.sys.mjs",
- FormAutofillUtils: "resource://gre/modules/shared/FormAutofillUtils.sys.mjs",
- PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
- FormStateManager: "resource://gre/modules/shared/FormStateManager.sys.mjs",
ProfileAutocomplete:
"resource://autofill/AutofillProfileAutoComplete.sys.mjs",
- AutofillTelemetry: "resource://autofill/AutofillTelemetry.sys.mjs",
});
-XPCOMUtils.defineLazyPreferenceGetter(
- lazy,
- "DELEGATE_AUTOCOMPLETE",
- "toolkit.autocomplete.delegate",
- false
-);
-
const formFillController = Cc[
"@mozilla.org/satchel/form-fill-controller;1"
].getService(Ci.nsIFormFillController);
-function getActorFromWindow(contentWindow, name = "FormAutofill") {
- // In unit tests, contentWindow isn't a real window.
- if (!contentWindow) {
- return null;
- }
-
- return contentWindow.windowGlobalChild
- ? contentWindow.windowGlobalChild.getActor(name)
- : null;
-}
-
/**
* Handles content's interactions for the process.
*
@@ -63,12 +38,6 @@ export var FormAutofillContent = {
*/
_popupPending: false,
- /**
- * @type {boolean} Flag indicating whether the form is waiting to be
- * filled by Autofill.
- */
- _autofillPending: false,
-
init() {
this.log = lazy.FormAutofill.defineLogGetter(this, "FormAutofillContent");
this.debug("init");
@@ -76,11 +45,13 @@ export var FormAutofillContent = {
// eslint-disable-next-line mozilla/balanced-listeners
Services.cpmm.sharedData.addEventListener("change", this);
- let autofillEnabled = Services.cpmm.sharedData.get("FormAutofill:enabled");
+ const autofillEnabled = Services.cpmm.sharedData.get(
+ "FormAutofill:enabled"
+ );
// If storage hasn't be initialized yet autofillEnabled is undefined but we need to ensure
// autocomplete is registered before the focusin so register it in this case as long as the
// pref is true.
- let shouldEnableAutofill =
+ const shouldEnableAutofill =
autofillEnabled === undefined &&
(lazy.FormAutofill.isAutofillAddressesEnabled ||
lazy.FormAutofill.isAutofillCreditCardsEnabled);
@@ -88,120 +59,49 @@ export var FormAutofillContent = {
lazy.ProfileAutocomplete.ensureRegistered();
}
- /**
- * @type {FormAutofillFieldDetailsManager} handling state management of current forms and handlers.
- */
- this._fieldDetailsManager = new lazy.FormStateManager(
- this.formSubmitted.bind(this),
- this._showPopup.bind(this)
- );
+ this.activeAutofillChild = null;
},
get activeFieldDetail() {
- return this._fieldDetailsManager.activeFieldDetail;
+ return this.activeAutofillChild?.activeFieldDetail;
},
get activeFormDetails() {
- return this._fieldDetailsManager.activeFormDetails;
+ return this.activeAutofillChild?.activeFormDetails;
},
get activeInput() {
- return this._fieldDetailsManager.activeInput;
+ return this.activeAutofillChild?.activeInput;
},
get activeHandler() {
- return this._fieldDetailsManager.activeHandler;
+ return this.activeAutofillChild?.activeHandler;
},
get activeSection() {
- return this._fieldDetailsManager.activeSection;
- },
-
- /**
- * Send the profile to parent for doorhanger and storage saving/updating.
- *
- * @param {object} profile Submitted form's address/creditcard guid and record.
- * @param {object} domWin Current content window.
- */
- _onFormSubmit(profile, domWin) {
- let actor = getActorFromWindow(domWin);
- actor.sendAsyncMessage("FormAutofill:OnFormSubmit", profile);
+ return this.activeAutofillChild?.activeSection;
},
- /**
- * 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 {Window} domWin Content window; passed for unit tests and when
- * invoked by the FormAutofillSection
- * @param {object} handler FormAutofillHander, if known by caller
- */
- formSubmitted(
- formElement,
- formSubmissionReason,
- domWin = formElement.ownerGlobal,
- handler = undefined
- ) {
- this.debug(`Handling form submission - infered by ${formSubmissionReason}`);
-
- // Unregister the progress listener since we detected a form submission
- // (domWin is null in unit tests)
- getActorFromWindow(domWin)?.unregisterProgressListener();
-
- 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.
- if (domWin && lazy.PrivateBrowsingUtils.isContentWindowPrivate(domWin)) {
- this.debug("Ignoring submission in a private window");
- return;
- }
-
- handler = handler || this._fieldDetailsManager._getFormHandler(formElement);
- const records = this._fieldDetailsManager.getRecords(formElement, handler);
-
- if (!records || !handler) {
- this.debug("Form element could not map to an existing handler");
- return;
+ set autofillPending(flag) {
+ if (this.activeAutofillChild) {
+ this.activeAutofillChild.autofillPending = flag;
}
+ },
- [records.address, records.creditCard].forEach((rs, idx) => {
- lazy.AutofillTelemetry.recordSubmittedSectionCount(
- idx == 0
- ? lazy.AutofillTelemetry.ADDRESS
- : lazy.AutofillTelemetry.CREDIT_CARD,
- rs?.length
- );
-
- rs?.forEach(r => {
- lazy.AutofillTelemetry.recordFormInteractionEvent(
- "submitted",
- r.section,
- {
- record: r,
- form: handler.form,
- }
- );
- delete r.section;
- });
- });
-
- this._onFormSubmit(records, domWin);
+ updateActiveAutofillChild(autofillChild) {
+ this.activeAutofillChild = autofillChild;
},
- _showPopup() {
- formFillController.showPopup();
+ 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) {
@@ -215,7 +115,7 @@ export var FormAutofillContent = {
if (this._popupPending) {
this._popupPending = false;
this.debug("handleEvent: Opening deferred popup");
- this._showPopup();
+ formFillController.showPopup();
}
} else {
lazy.ProfileAutocomplete.ensureUnregistered();
@@ -224,219 +124,6 @@ export var FormAutofillContent = {
}
}
},
-
- /**
- * All active items should be updated according the active element of
- * `formFillController.focusedInput`. All of them including element,
- * handler, section, and field detail, can be retrieved by their own getters.
- *
- * @param {HTMLElement|null} element The active item should be updated based
- * on this or `formFillController.focusedInput` will be taken.
- */
- updateActiveInput(element) {
- element = element || formFillController.focusedInput;
- if (!element) {
- this.debug("updateActiveElement: no element selected");
- return;
- }
- this._fieldDetailsManager.updateActiveInput(element);
- this.debug("updateActiveElement: checking for popup-on-focus");
- // We know this element just received focus. If it's a credit card field,
- // open its popup.
- if (this._autofillPending) {
- this.debug("updateActiveElement: skipping check; autofill is imminent");
- } else if (element.value?.length !== 0) {
- this.debug(
- `updateActiveElement: Not opening popup because field is not empty.`
- );
- } else {
- this.debug(
- "updateActiveElement: checking if empty field is cc-*: ",
- this.activeFieldDetail?.fieldName
- );
-
- if (
- this.activeFieldDetail?.fieldName?.startsWith("cc-") ||
- AppConstants.platform === "android"
- ) {
- if (Services.cpmm.sharedData.get("FormAutofill:enabled")) {
- this.debug("updateActiveElement: opening pop up");
- this._showPopup();
- } else {
- this.debug(
- "updateActiveElement: Deferring pop-up until Autofill is ready"
- );
- this._popupPending = true;
- }
- }
- }
- },
-
- set autofillPending(flag) {
- this.debug("Setting autofillPending to", flag);
- this._autofillPending = flag;
- },
-
- /**
- * Identifies and marks each autofill field
- *
- * @param {HTMLElement} element
- * Element that serves as an anchor for the formautofill heuristics to retrieve
- * the root form and run the formautofill heuristics on the form elements
- * @returns {boolean}
- * whether any autofill fields were identified
- */
- identifyAutofillFields(element) {
- this.debug(
- `identifyAutofillFields: ${element.ownerDocument.location?.hostname}`
- );
-
- if (lazy.DELEGATE_AUTOCOMPLETE || !this.savedFieldNames) {
- this.debug("identifyAutofillFields: savedFieldNames are not known yet");
- let actor = getActorFromWindow(element.ownerGlobal);
- if (actor) {
- actor.sendAsyncMessage("FormAutofill:InitStorage");
- }
- }
-
- const validDetails =
- this._fieldDetailsManager.identifyAutofillFields(element);
-
- validDetails?.forEach(detail => this._markAsAutofillField(detail.element));
-
- return !!validDetails.length;
- },
-
- clearForm() {
- let focusedInput =
- this.activeInput ||
- lazy.ProfileAutocomplete._lastAutoCompleteFocusedInput;
- if (!focusedInput) {
- return;
- }
-
- this.activeSection.clearPopulatedForm();
-
- let fieldName = FormAutofillContent.activeFieldDetail?.fieldName;
- if (lazy.FormAutofillUtils.isCreditCardField(fieldName)) {
- lazy.AutofillTelemetry.recordFormInteractionEvent(
- "cleared",
- this.activeSection,
- { fieldName }
- );
- }
- },
-
- previewProfile(doc) {
- let docWin = doc.ownerGlobal;
- let selectedIndex = lazy.ProfileAutocomplete._getSelectedIndex(docWin);
- let lastAutoCompleteResult =
- lazy.ProfileAutocomplete.lastProfileAutoCompleteResult;
- let focusedInput = this.activeInput;
- let actor = getActorFromWindow(docWin);
-
- if (
- selectedIndex === -1 ||
- !focusedInput ||
- !lastAutoCompleteResult ||
- lastAutoCompleteResult.getStyleAt(selectedIndex) != "autofill-profile"
- ) {
- actor.sendAsyncMessage("FormAutofill:UpdateWarningMessage", {});
-
- lazy.ProfileAutocomplete._clearProfilePreview();
- } else {
- let focusedInputDetails = this.activeFieldDetail;
- let profile = JSON.parse(
- lastAutoCompleteResult.getCommentAt(selectedIndex)
- );
- let allFieldNames = FormAutofillContent.activeSection.allFieldNames;
- let profileFields = allFieldNames.filter(
- fieldName => !!profile[fieldName]
- );
-
- let focusedCategory = lazy.FormAutofillUtils.getCategoryFromFieldName(
- focusedInputDetails.fieldName
- );
- let categories =
- lazy.FormAutofillUtils.getCategoriesFromFieldNames(profileFields);
- actor.sendAsyncMessage("FormAutofill:UpdateWarningMessage", {
- focusedCategory,
- categories,
- });
-
- lazy.ProfileAutocomplete._previewSelectedProfile(selectedIndex);
- }
- },
-
- onPopupClosed(selectedRowStyle) {
- this.debug("Popup has closed.");
- lazy.ProfileAutocomplete._clearProfilePreview();
-
- let lastAutoCompleteResult =
- lazy.ProfileAutocomplete.lastProfileAutoCompleteResult;
- let focusedInput = FormAutofillContent.activeInput;
- if (
- lastAutoCompleteResult &&
- FormAutofillContent._keyDownEnterForInput &&
- focusedInput === FormAutofillContent._keyDownEnterForInput &&
- focusedInput ===
- lazy.ProfileAutocomplete.lastProfileAutoCompleteFocusedInput
- ) {
- if (selectedRowStyle == "autofill-footer") {
- let actor = getActorFromWindow(focusedInput.ownerGlobal);
- actor.sendAsyncMessage("FormAutofill:OpenPreferences");
- } else if (selectedRowStyle == "autofill-clear-button") {
- FormAutofillContent.clearForm();
- }
- }
- },
-
- onPopupOpened() {
- this.debug(
- "Popup has opened, automatic =",
- formFillController.passwordPopupAutomaticallyOpened
- );
-
- let fieldName = FormAutofillContent.activeFieldDetail?.fieldName;
- if (fieldName && this.activeSection) {
- lazy.AutofillTelemetry.recordFormInteractionEvent(
- "popup_shown",
- this.activeSection,
- { fieldName }
- );
- }
- },
-
- _markAsAutofillField(field) {
- // Since Form Autofill popup is only for input element, any non-Input
- // element should be excluded here.
- if (!HTMLInputElement.isInstance(field)) {
- return;
- }
-
- formFillController.markAsAutofillField(field);
- },
-
- _onKeyDown(e) {
- delete FormAutofillContent._keyDownEnterForInput;
- let lastAutoCompleteResult =
- lazy.ProfileAutocomplete.lastProfileAutoCompleteResult;
- let focusedInput = FormAutofillContent.activeInput;
- if (
- e.keyCode != e.DOM_VK_RETURN ||
- !lastAutoCompleteResult ||
- !focusedInput ||
- focusedInput !=
- lazy.ProfileAutocomplete.lastProfileAutoCompleteFocusedInput
- ) {
- return;
- }
- FormAutofillContent._keyDownEnterForInput = focusedInput;
- },
-
- didDestroy() {
- this._fieldDetailsManager.didDestroy();
- },
};
FormAutofillContent.init();
diff --git a/toolkit/components/formautofill/FormAutofillNative.cpp b/toolkit/components/formautofill/FormAutofillNative.cpp
index 57af789861..08c462aa44 100644
--- a/toolkit/components/formautofill/FormAutofillNative.cpp
+++ b/toolkit/components/formautofill/FormAutofillNative.cpp
@@ -200,14 +200,16 @@ enum class CCExpYearParams : uint8_t {
};
struct AutofillParams {
- EnumeratedArray<CCNumberParams, CCNumberParams::Count, double>
+ EnumeratedArray<CCNumberParams, double, size_t(CCNumberParams::Count)>
mCCNumberParams;
- EnumeratedArray<CCNameParams, CCNameParams::Count, double> mCCNameParams;
- EnumeratedArray<CCTypeParams, CCTypeParams::Count, double> mCCTypeParams;
- EnumeratedArray<CCExpParams, CCExpParams::Count, double> mCCExpParams;
- EnumeratedArray<CCExpMonthParams, CCExpMonthParams::Count, double>
+ EnumeratedArray<CCNameParams, double, size_t(CCNameParams::Count)>
+ mCCNameParams;
+ EnumeratedArray<CCTypeParams, double, size_t(CCTypeParams::Count)>
+ mCCTypeParams;
+ EnumeratedArray<CCExpParams, double, size_t(CCExpParams::Count)> mCCExpParams;
+ EnumeratedArray<CCExpMonthParams, double, size_t(CCExpMonthParams::Count)>
mCCExpMonthParams;
- EnumeratedArray<CCExpYearParams, CCExpYearParams::Count, double>
+ EnumeratedArray<CCExpYearParams, double, size_t(CCExpYearParams::Count)>
mCCExpYearParams;
};
@@ -667,13 +669,11 @@ class FormAutofillImpl {
// Array contains regular expressions to match the corresponding
// field. Ex, CC number, CC type, etc.
using RegexStringArray =
- EnumeratedArray<RegexKey, RegexKey::Count, nsCString>;
+ EnumeratedArray<RegexKey, nsCString, size_t(RegexKey::Count)>;
RegexStringArray mRuleMap;
// Array that holds RegexWrapper that created by regex::ffi::regex_new
- using RegexWrapperArray =
- EnumeratedArray<RegexKey, RegexKey::Count,
- RustRegex>;
+ using RegexWrapperArray = EnumeratedArray<RegexKey, RustRegex, size_t(RegexKey::Count)>;
RegexWrapperArray mRegexes;
};
diff --git a/toolkit/components/formautofill/FormAutofillParent.sys.mjs b/toolkit/components/formautofill/FormAutofillParent.sys.mjs
index ba0d769906..61c4bd2943 100644
--- a/toolkit/components/formautofill/FormAutofillParent.sys.mjs
+++ b/toolkit/components/formautofill/FormAutofillParent.sys.mjs
@@ -5,10 +5,10 @@
/*
* Implements a service used to access storage and communicate with content.
*
- * A "fields" array is used to communicate with FormAutofillContent. Each item
+ * 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
- * FormAutofillContent.js for more details.
+ * FormAutofillChild.js for more details.
*
* [
* {
@@ -293,7 +293,7 @@ export class FormAutofillParent extends JSWindowActorParent {
}
/**
- * Handles the message coming from FormAutofillContent.
+ * Handles the message coming from FormAutofillChild.
*
* @param {object} message
* @param {string} message.name The name of the message.
diff --git a/toolkit/components/formautofill/FormAutofillStorageBase.sys.mjs b/toolkit/components/formautofill/FormAutofillStorageBase.sys.mjs
index 591bfc1578..f360be4fa6 100644
--- a/toolkit/components/formautofill/FormAutofillStorageBase.sys.mjs
+++ b/toolkit/components/formautofill/FormAutofillStorageBase.sys.mjs
@@ -130,18 +130,19 @@
*/
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://autofill/AutofillTelemetry.sys.mjs",
+ 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://autofill/phonenumberutils/PhoneNumber.sys.mjs",
+ PhoneNumber: "resource://gre/modules/shared/PhoneNumber.sys.mjs",
});
const CryptoHash = Components.Constructor(
@@ -166,23 +167,6 @@ export const ADDRESS_SCHEMA_VERSION = 1;
// Please talk to the sync team before changing this!
export const CREDIT_CARD_SCHEMA_VERSION = 3;
-const NAME_COMPONENTS = ["given-name", "additional-name", "family-name"];
-
-const STREET_ADDRESS_COMPONENTS = [
- "address-line1",
- "address-line2",
- "address-line3",
-];
-
-const TEL_COMPONENTS = [
- "tel-country-code",
- "tel-national",
- "tel-area-code",
- "tel-local",
- "tel-local-prefix",
- "tel-local-suffix",
-];
-
const VALID_ADDRESS_FIELDS = [
"name",
"organization",
@@ -198,9 +182,9 @@ const VALID_ADDRESS_FIELDS = [
const VALID_ADDRESS_COMPUTED_FIELDS = [
"country-name",
- ...NAME_COMPONENTS,
- ...STREET_ADDRESS_COMPONENTS,
- ...TEL_COMPONENTS,
+ ...AddressRecord.NAME_COMPONENTS,
+ ...AddressRecord.STREET_ADDRESS_COMPONENTS,
+ ...AddressRecord.TEL_COMPONENTS,
];
const VALID_CREDIT_CARD_FIELDS = [
@@ -299,20 +283,18 @@ class AutofillRecords {
});
}
- observe(subject, topic, data) {
- switch (topic) {
- case "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);
- break;
+ 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);
}
}
@@ -675,7 +657,7 @@ class AutofillRecords {
// 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 = NAME_COMPONENTS;
+ const fieldsToKeep = AddressRecord.NAME_COMPONENTS;
await this._stripComputedFields(clonedRecord, fieldsToKeep);
} else {
this._recordReadProcessor(clonedRecord);
@@ -703,7 +685,7 @@ class AutofillRecords {
await Promise.all(
clonedRecords.map(async record => {
if (rawData) {
- const fieldsToKeep = NAME_COMPONENTS;
+ const fieldsToKeep = AddressRecord.NAME_COMPONENTS;
await this._stripComputedFields(record, fieldsToKeep);
} else {
this._recordReadProcessor(record);
@@ -1398,7 +1380,12 @@ class AutofillRecords {
return hasChanges;
}
- hasChanges |= await this.computeFields(record);
+ const originalNumFields = Object.keys(record).length;
+ await this.computeFields(record);
+ const hasNewComputedFields =
+ Object.keys(record).length != originalNumFields;
+
+ hasChanges |= hasNewComputedFields;
return hasChanges;
}
@@ -1486,36 +1473,36 @@ class AutofillRecords {
}
// An interface to be inherited.
- _recordReadProcessor(record) {}
+ _recordReadProcessor(_record) {}
// An interface to be inherited.
- async computeFields(record) {}
+ 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
+ * @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) {}
+ _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
+ * @param {object} _record containing the complete record that would be stored
* if this doesn't throw due to an error.
* @throws
*/
- _validateFields(record) {}
+ _validateFields(_record) {}
// An interface to be inherited.
- migrateRemoteRecord(remoteRecord) {}
+ migrateRemoteRecord(_remoteRecord) {}
}
export class AddressesBase extends AutofillRecords {
@@ -1578,99 +1565,9 @@ export class AddressesBase extends AutofillRecords {
// NOTE: Computed fields should be always present in the storage no matter
// it's empty or not.
- let hasNewComputedFields = false;
-
- if (address.deleted) {
- return hasNewComputedFields;
- }
-
- // Compute split names
- if (!("given-name" in address)) {
- const nameParts = lazy.FormAutofillNameUtils.splitName(address.name);
- address["given-name"] = nameParts.given;
- address["additional-name"] = nameParts.middle;
- address["family-name"] = nameParts.family;
- hasNewComputedFields = true;
- }
-
- // 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"] = lazy.FormAutofillUtils.toOneLineAddress(
- streetAddress.slice(2)
- );
- }
- hasNewComputedFields = true;
- }
-
- // Compute country name
- if (!("country-name" in address)) {
- if (address.country) {
- try {
- address["country-name"] = Services.intl.getRegionDisplayNames(
- undefined,
- [address.country]
- );
- } catch (e) {
- address["country-name"] = "";
- }
- } else {
- address["country-name"] = "";
- }
- hasNewComputedFields = true;
+ if (!address.deleted) {
+ AddressRecord.computeFields(address);
}
-
- // Compute tel
- if (!("tel-national" in address)) {
- if (address.tel) {
- let tel = lazy.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;
- }
- }
-
- TEL_COMPONENTS.forEach(c => {
- address[c] = address[c] || "";
- });
- }
-
- return hasNewComputedFields;
}
_normalizeFields(address) {
@@ -1700,7 +1597,7 @@ export class AddressesBase extends AutofillRecords {
}
_normalizeAddressFields(address) {
- if (STREET_ADDRESS_COMPONENTS.some(c => !!address[c])) {
+ 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 (
@@ -1714,14 +1611,14 @@ export class AddressesBase extends AutofillRecords {
// Concatenate "address-line*" if "street-address" is omitted.
if (!address["street-address"]) {
- address["street-address"] = STREET_ADDRESS_COMPONENTS.map(
+ address["street-address"] = AddressRecord.STREET_ADDRESS_COMPONENTS.map(
c => address[c]
)
.join("\n")
.replace(/\n+$/, "");
}
}
- STREET_ADDRESS_COMPONENTS.forEach(c => delete address[c]);
+ AddressRecord.STREET_ADDRESS_COMPONENTS.forEach(c => delete address[c]);
}
_normalizeCountryFields(address) {
@@ -1753,7 +1650,7 @@ export class AddressesBase extends AutofillRecords {
}
_normalizeTelFields(address) {
- if (address.tel || TEL_COMPONENTS.some(c => !!address[c])) {
+ if (address.tel || AddressRecord.TEL_COMPONENTS.some(c => !!address[c])) {
lazy.FormAutofillUtils.compressTel(address);
let possibleRegion = address.country || FormAutofill.DEFAULT_REGION;
@@ -1764,7 +1661,7 @@ export class AddressesBase extends AutofillRecords {
address.tel = tel.internationalNumber;
}
}
- TEL_COMPONENTS.forEach(c => delete address[c]);
+ AddressRecord.TEL_COMPONENTS.forEach(c => delete address[c]);
}
/**
@@ -1793,12 +1690,14 @@ export class AddressesBase extends AutofillRecords {
// we will rebuild it and replace the local `name` field with "Jane Poe".
if (
!("name" in remoteRecord) &&
- NAME_COMPONENTS.some(c => c in remoteRecord)
+ AddressRecord.NAME_COMPONENTS.some(c => c in remoteRecord)
) {
const localRecord = this._findByGUID(remoteRecord.guid);
if (
localRecord &&
- NAME_COMPONENTS.every(c => remoteRecord[c] == localRecord[c])
+ AddressRecord.NAME_COMPONENTS.every(
+ c => remoteRecord[c] == localRecord[c]
+ )
) {
remoteRecord.name = localRecord.name;
} else {
@@ -1815,7 +1714,7 @@ export class AddressesBase extends AutofillRecords {
// 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.
- NAME_COMPONENTS.forEach(f => delete remoteRecord[f]);
+ AddressRecord.NAME_COMPONENTS.forEach(f => delete remoteRecord[f]);
}
}
@@ -1879,7 +1778,7 @@ export class CreditCardsBase extends AutofillRecords {
return hasNewComputedFields;
}
- async _encryptNumber(creditCard) {
+ async _encryptNumber(_creditCard) {
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
}
diff --git a/toolkit/components/formautofill/FormAutofillSync.sys.mjs b/toolkit/components/formautofill/FormAutofillSync.sys.mjs
index 4540737e38..15ae9b60b5 100644
--- a/toolkit/components/formautofill/FormAutofillSync.sys.mjs
+++ b/toolkit/components/formautofill/FormAutofillSync.sys.mjs
@@ -244,7 +244,7 @@ class AutofillChangeset extends Changeset {
super();
}
- getModifiedTimestamp(id) {
+ getModifiedTimestamp(_id) {
throw new Error("Don't use timestamps to resolve autofill merge conflicts");
}
diff --git a/toolkit/components/formautofill/Helpers.ios.mjs b/toolkit/components/formautofill/Helpers.ios.mjs
index 4144d3e98c..56bb49f0e9 100644
--- a/toolkit/components/formautofill/Helpers.ios.mjs
+++ b/toolkit/components/formautofill/Helpers.ios.mjs
@@ -45,6 +45,12 @@ HTMLElement.prototype.getAutocompleteInfo = function () {
};
};
+// Bug 1835024. Webkit doesn't support `checkVisibility` API
+// https://drafts.csswg.org/cssom-view-1/#dom-element-checkvisibility
+HTMLElement.prototype.checkVisibility = function (_options) {
+ throw new Error(`Not implemented: WebKit doesn't support checkVisibility `);
+};
+
// This function helps us debug better when an error occurs because a certain mock is missing
const withNotImplementedError = obj =>
new Proxy(obj, {
@@ -58,6 +64,15 @@ const withNotImplementedError = obj =>
},
});
+// This function will create a proxy for each undefined property
+// This is useful when the accessed property name is unkonwn beforehand
+const undefinedProxy = () =>
+ new Proxy(() => {}, {
+ get() {
+ return undefinedProxy();
+ },
+ });
+
// Webpack needs to be able to statically analyze require statements in order to build the dependency graph
// In order to require modules dynamically at runtime, we use require.context() to create a dynamic require
// that is still able to be parsed by Webpack at compile time. The "./" and ".mjs" tells webpack that files
@@ -128,23 +143,10 @@ export const OSKeyStore = withNotImplementedError({
ensureLoggedIn: () => true,
});
-// Checks an element's focusability and accessibility via keyboard navigation
-const checkFocusability = element => {
- return (
- !element.disabled &&
- !element.hidden &&
- element.style.display != "none" &&
- element.tabIndex != "-1"
- );
-};
-
// Define mock for Services
// NOTE: Services is a global so we need to attach it to the window
// eslint-disable-next-line no-shadow
export const Services = withNotImplementedError({
- focus: withNotImplementedError({
- elementIsFocusable: checkFocusability,
- }),
locale: withNotImplementedError({ isAppLocaleRTL: false }),
prefs: withNotImplementedError({ prefIsLocked: () => false }),
strings: withNotImplementedError({
@@ -154,7 +156,64 @@ export const Services = withNotImplementedError({
formatStringFromName: () => "",
}),
}),
- uuid: withNotImplementedError({ generateUUID: () => "" }),
+ telemetry: withNotImplementedError({
+ scalarAdd: (scalarName, scalarValue) => {
+ // For now, we only care about the address form telemetry
+ // TODO(FXCM-935): move address telemetry to Glean so we can remove this
+ // Data format of the sent message is:
+ // {
+ // type: "scalar",
+ // name: "formautofill.addresses.detected_sections_count",
+ // value: Number,
+ // }
+ if (scalarName !== "formautofill.addresses.detected_sections_count") {
+ return;
+ }
+
+ // eslint-disable-next-line no-undef
+ webkit.messageHandlers.addressFormTelemetryMessageHandler.postMessage(
+ JSON.stringify({
+ type: "scalar",
+ object: scalarName,
+ value: scalarValue,
+ })
+ );
+ },
+ recordEvent: (category, method, object, value, extra) => {
+ // For now, we only care about the address form telemetry
+ // TODO(FXCM-935): move address telemetry to Glean so we can remove this
+ // Data format of the sent message is:
+ // {
+ // type: "event",
+ // category: "address",
+ // method: "detected" | "filled" | "filled_modified",
+ // object: "address_form" | "address_form_ext",
+ // value: String,
+ // extra: Any,
+ // }
+ if (category !== "address") {
+ return;
+ }
+
+ // eslint-disable-next-line no-undef
+ webkit.messageHandlers.addressFormTelemetryMessageHandler.postMessage(
+ JSON.stringify({
+ type: "event",
+ category,
+ method,
+ object,
+ value,
+ extra,
+ })
+ );
+ },
+ }),
+ // TODO(FXCM-936): we should use crypto.randomUUID() instead of Services.uuid.generateUUID() in our codebase
+ // Underneath crypto.randomUUID() uses the same implementation as generateUUID()
+ // https://searchfox.org/mozilla-central/rev/d405168c4d3c0fb900a7354ae17bb34e939af996/dom/base/Crypto.cpp#96
+ // The only limitation is that it's not available in insecure contexts, which should be fine for both iOS and Desktop
+ // since we only autofill in secure contexts
+ uuid: withNotImplementedError({ generateUUID: () => crypto.randomUUID() }),
});
window.Services = Services;
@@ -163,15 +222,18 @@ window.Localization = function () {
return { formatValueSync: () => "" };
};
+// For now, we ignore all calls to glean.
+// TODO(FXCM-935): move address telemetry to Glean so we can create a universal mock for glean that
+// dispatches telemetry messages to the iOS.
+window.Glean = {
+ formautofillCreditcards: undefinedProxy(),
+ formautofill: undefinedProxy(),
+};
+
export const windowUtils = withNotImplementedError({
removeManuallyManagedState: () => {},
addManuallyManagedState: () => {},
});
window.windowUtils = windowUtils;
-export const AutofillTelemetry = withNotImplementedError({
- recordFormInteractionEvent: () => {},
- recordDetectedSectionCount: () => {},
-});
-
export { IOSAppConstants as AppConstants } from "resource://gre/modules/shared/Constants.ios.mjs";
diff --git a/toolkit/components/formautofill/Overrides.ios.js b/toolkit/components/formautofill/Overrides.ios.js
index a0023a267c..ae5998992b 100644
--- a/toolkit/components/formautofill/Overrides.ios.js
+++ b/toolkit/components/formautofill/Overrides.ios.js
@@ -7,7 +7,6 @@
// This array defines overrides that webpack will use when bundling the JS on iOS
// in order to load the right modules
const ModuleOverrides = {
- "AutofillTelemetry.sys.mjs": "Helpers.ios.mjs",
"AppConstants.sys.mjs": "Helpers.ios.mjs",
"XPCOMUtils.sys.mjs": "Helpers.ios.mjs",
"Region.sys.mjs": "Helpers.ios.mjs",
diff --git a/toolkit/components/formautofill/ProfileAutoCompleteResult.sys.mjs b/toolkit/components/formautofill/ProfileAutoCompleteResult.sys.mjs
index 15fc1a520c..52ed8bed03 100644
--- a/toolkit/components/formautofill/ProfileAutoCompleteResult.sys.mjs
+++ b/toolkit/components/formautofill/ProfileAutoCompleteResult.sys.mjs
@@ -12,7 +12,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
ChromeUtils.defineLazyGetter(
lazy,
"l10n",
- () => new Localization(["browser/preferences/formAutofill.ftl"], true)
+ () => new Localization(["toolkit/formautofill/formAutofill.ftl"], true)
);
class ProfileAutoCompleteResult {
@@ -100,16 +100,16 @@ class ProfileAutoCompleteResult {
* 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.
+ * @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) {
+ _getSecondaryLabel(_focusedFieldName, _allFieldNames, _profile) {
return "";
}
- _generateLabels(focusedFieldName, allFieldNames, profiles) {}
+ _generateLabels(_focusedFieldName, _allFieldNames, _profiles) {}
/**
* Get the value of the result at the given index.
@@ -190,19 +190,19 @@ class ProfileAutoCompleteResult {
/**
* Returns true if the value at the given index is removable
*
- * @param {number} index The index of the result to remove
+ * @param {number} _index The index of the result to remove
* @returns {boolean} True if the value is removable
*/
- isRemovableAt(index) {
+ isRemovableAt(_index) {
return false;
}
/**
* Removes a result from the resultset
*
- * @param {number} index The index of the result to remove
+ * @param {number} _index The index of the result to remove
*/
- removeValueAt(index) {
+ removeValueAt(_index) {
// There is no plan to support removing profiles via autocomplete.
}
}
@@ -277,10 +277,19 @@ export class AddressResult extends ProfileAutoCompleteResult {
}
_generateLabels(focusedFieldName, allFieldNames, profiles) {
+ const manageLabel = lazy.l10n.formatValueSync(
+ "autofill-manage-addresses-label"
+ );
+
if (this._isInputAutofilled) {
return [
{ primary: "", secondary: "" }, // Clear button
- { primary: "", secondary: "" }, // Footer
+ // Footer
+ {
+ primary: "",
+ secondary: "",
+ manageLabel,
+ },
];
}
@@ -306,6 +315,10 @@ export class AddressResult extends ProfileAutoCompleteResult {
),
};
});
+
+ const focusedCategory =
+ lazy.FormAutofillUtils.getCategoryFromFieldName(focusedFieldName);
+
// Add an empty result entry for footer. Its content will come from
// the footer binding, so don't assign any value to it.
// The additional properties: categories and focusedCategory are required of
@@ -313,12 +326,11 @@ export class AddressResult extends ProfileAutoCompleteResult {
labels.push({
primary: "",
secondary: "",
+ manageLabel,
categories: lazy.FormAutofillUtils.getCategoriesFromFieldNames(
this._allFieldNames
),
- focusedCategory: lazy.FormAutofillUtils.getCategoryFromFieldName(
- this._focusedFieldName
- ),
+ focusedCategory,
});
return labels;
@@ -385,10 +397,19 @@ export class CreditCardResult extends ProfileAutoCompleteResult {
];
}
+ const manageLabel = lazy.l10n.formatValueSync(
+ "autofill-manage-payment-methods-label"
+ );
+
if (this._isInputAutofilled) {
return [
{ primary: "", secondary: "" }, // Clear button
- { primary: "", secondary: "" }, // Footer
+ // Footer
+ {
+ primary: "",
+ secondary: "",
+ manageLabel,
+ },
];
}
@@ -431,8 +452,17 @@ export class CreditCardResult extends ProfileAutoCompleteResult {
image,
};
});
+
+ const focusedCategory =
+ lazy.FormAutofillUtils.getCategoryFromFieldName(focusedFieldName);
+
// Add an empty result entry for footer.
- labels.push({ primary: "", secondary: "" });
+ labels.push({
+ primary: "",
+ secondary: "",
+ manageLabel,
+ focusedCategory,
+ });
return labels;
}
diff --git a/toolkit/components/formautofill/android/FormAutofillPrompter.sys.mjs b/toolkit/components/formautofill/android/FormAutofillPrompter.sys.mjs
index 6bb0e991b1..5e737a018b 100644
--- a/toolkit/components/formautofill/android/FormAutofillPrompter.sys.mjs
+++ b/toolkit/components/formautofill/android/FormAutofillPrompter.sys.mjs
@@ -34,10 +34,10 @@ export let FormAutofillPrompter = {
},
async promptToSaveAddress(
- browser,
- storage,
- flowId,
- { oldRecord, newRecord }
+ _browser,
+ _storage,
+ _flowId,
+ { _oldRecord, _newRecord }
) {
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
},
diff --git a/toolkit/components/formautofill/android/FormAutofillStorage.sys.mjs b/toolkit/components/formautofill/android/FormAutofillStorage.sys.mjs
index 0d11880ff5..964be31d06 100644
--- a/toolkit/components/formautofill/android/FormAutofillStorage.sys.mjs
+++ b/toolkit/components/formautofill/android/FormAutofillStorage.sys.mjs
@@ -115,17 +115,17 @@ class Addresses extends AddressesBase {
return super.getSavedFieldNames();
}
- async reconcile(remoteRecord) {
+ async reconcile(_remoteRecord) {
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
}
- async findDuplicateGUID(remoteRecord) {
+ async findDuplicateGUID(_remoteRecord) {
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
}
}
class CreditCards extends CreditCardsBase {
- async _encryptNumber(creditCard) {
+ async _encryptNumber(_creditCard) {
// Don't encrypt or obfuscate for GV, since we don't store or show
// the number. The API has to always provide the original number.
}
@@ -220,11 +220,11 @@ class CreditCards extends CreditCardsBase {
return null;
}
- async reconcile(remoteRecord) {
+ async reconcile(_remoteRecord) {
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
}
- async findDuplicateGUID(remoteRecord) {
+ async findDuplicateGUID(_remoteRecord) {
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
}
}
diff --git a/toolkit/components/formautofill/default/FormAutofillPrompter.sys.mjs b/toolkit/components/formautofill/default/FormAutofillPrompter.sys.mjs
index ecf787137e..f166716de5 100644
--- a/toolkit/components/formautofill/default/FormAutofillPrompter.sys.mjs
+++ b/toolkit/components/formautofill/default/FormAutofillPrompter.sys.mjs
@@ -11,7 +11,7 @@ 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://autofill/AutofillTelemetry.sys.mjs";
+import { AutofillTelemetry } from "resource://gre/modules/shared/AutofillTelemetry.sys.mjs";
import { showConfirmation } from "resource://gre/modules/FillHelpers.sys.mjs";
const lazy = {};
@@ -187,7 +187,7 @@ export class AutofillDoorhanger {
renderHeader() {
// Render the header text
- const text = this.header.querySelector(`p`);
+ const text = this.header.querySelector(`h1`);
this.doc.l10n.setAttributes(text, this.ui.header.l10nId);
// Render the menu button
@@ -529,6 +529,8 @@ export class AddressSaveDoorhanger extends AutofillDoorhanger {
//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
diff --git a/toolkit/components/formautofill/moz.build b/toolkit/components/formautofill/moz.build
index 542fc595e0..d2091dc850 100644
--- a/toolkit/components/formautofill/moz.build
+++ b/toolkit/components/formautofill/moz.build
@@ -15,6 +15,8 @@ EXTRA_JS_MODULES.shared += [
"shared/AddressMetaDataExtension.sys.mjs",
"shared/AddressMetaDataLoader.sys.mjs",
"shared/AddressParser.sys.mjs",
+ "shared/AddressRecord.sys.mjs",
+ "shared/AutofillTelemetry.sys.mjs",
"shared/CreditCardRecord.sys.mjs",
"shared/CreditCardRuleset.sys.mjs",
"shared/FieldScanner.sys.mjs",
@@ -26,6 +28,9 @@ EXTRA_JS_MODULES.shared += [
"shared/FormStateManager.sys.mjs",
"shared/HeuristicsRegExp.sys.mjs",
"shared/LabelUtils.sys.mjs",
+ "shared/PhoneNumber.sys.mjs",
+ "shared/PhoneNumberMetaData.sys.mjs",
+ "shared/PhoneNumberNormalizer.sys.mjs",
]
EXPORTS.mozilla += ["FormAutofillNative.h"]
diff --git a/toolkit/components/formautofill/shared/AddressComponent.sys.mjs b/toolkit/components/formautofill/shared/AddressComponent.sys.mjs
index a849e889b2..40e00b66a0 100644
--- a/toolkit/components/formautofill/shared/AddressComponent.sys.mjs
+++ b/toolkit/components/formautofill/shared/AddressComponent.sys.mjs
@@ -11,9 +11,9 @@ ChromeUtils.defineESModuleGetters(lazy, {
FormAutofillNameUtils:
"resource://gre/modules/shared/FormAutofillNameUtils.sys.mjs",
FormAutofillUtils: "resource://gre/modules/shared/FormAutofillUtils.sys.mjs",
- PhoneNumber: "resource://autofill/phonenumberutils/PhoneNumber.sys.mjs",
+ PhoneNumber: "resource://gre/modules/shared/PhoneNumber.sys.mjs",
PhoneNumberNormalizer:
- "resource://autofill/phonenumberutils/PhoneNumberNormalizer.sys.mjs",
+ "resource://gre/modules/shared/PhoneNumberNormalizer.sys.mjs",
});
/**
@@ -201,7 +201,7 @@ class StreetAddress extends AddressField {
super(value, region);
this.#structuredStreetAddress = lazy.AddressParser.parseStreetAddress(
- lazy.AddressParser.replaceControlCharacters(this.userValue, " ")
+ lazy.AddressParser.replaceControlCharacters(this.userValue)
);
}
@@ -491,7 +491,7 @@ class Country extends AddressField {
return this.country_code == other.country_code;
}
- contains(other) {
+ contains(_other) {
return false;
}
@@ -841,7 +841,7 @@ class Email extends AddressField {
);
}
- contains(other) {
+ contains(_other) {
return false;
}
diff --git a/toolkit/components/formautofill/shared/AddressParser.sys.mjs b/toolkit/components/formautofill/shared/AddressParser.sys.mjs
index 5cb76934c1..1d36b71bba 100644
--- a/toolkit/components/formautofill/shared/AddressParser.sys.mjs
+++ b/toolkit/components/formautofill/shared/AddressParser.sys.mjs
@@ -271,7 +271,7 @@ export class AddressParser {
return s?.replace(/[.,\/#!$%\^&\*;:{}=\-_~()]/g, "");
}
- static replaceControlCharacters(s, replace) {
+ static replaceControlCharacters(s) {
return s?.replace(/[\t\n\r]/g, " ");
}
diff --git a/toolkit/components/formautofill/shared/AddressRecord.sys.mjs b/toolkit/components/formautofill/shared/AddressRecord.sys.mjs
new file mode 100644
index 0000000000..599a802dcd
--- /dev/null
+++ b/toolkit/components/formautofill/shared/AddressRecord.sys.mjs
@@ -0,0 +1,119 @@
+/* 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] || "";
+ });
+ }
+ }
+}
diff --git a/toolkit/components/formautofill/AutofillTelemetry.sys.mjs b/toolkit/components/formautofill/shared/AutofillTelemetry.sys.mjs
index 93aa99a4b8..6a1fa974cc 100644
--- a/toolkit/components/formautofill/AutofillTelemetry.sys.mjs
+++ b/toolkit/components/formautofill/shared/AutofillTelemetry.sys.mjs
@@ -140,7 +140,7 @@ class AutofillTelemetryBase {
this.recordGleanFormEvent("formFilledModified", section.flowId, extra);
}
- recordFormSubmitted(section, record, form) {
+ recordFormSubmitted(section, record, _form) {
let extra = this.#initFormEventExtra("unavailable");
if (record.guid !== null) {
@@ -185,7 +185,7 @@ class AutofillTelemetryBase {
);
}
- recordGleanFormEvent(eventName, flowId, extra) {
+ recordGleanFormEvent(_eventName, _flowId, _extra) {
throw new Error("Not implemented.");
}
@@ -222,7 +222,7 @@ class AutofillTelemetryBase {
Services.telemetry.recordEvent(this.EVENT_CATEGORY, method, "manage");
}
- recordAutofillProfileCount(count) {
+ recordAutofillProfileCount(_count) {
throw new Error("Not implemented.");
}
@@ -311,7 +311,7 @@ export class AddressTelemetry extends AutofillTelemetryBase {
"tel",
];
- recordGleanFormEvent(eventName, flowId, extra) {
+ recordGleanFormEvent(_eventName, _flowId, _extra) {
// To be implemented when migrating the legacy event address.address_form to Glean
}
diff --git a/toolkit/components/formautofill/shared/FieldScanner.sys.mjs b/toolkit/components/formautofill/shared/FieldScanner.sys.mjs
index 22adfdabe8..2118de3de8 100644
--- a/toolkit/components/formautofill/shared/FieldScanner.sys.mjs
+++ b/toolkit/components/formautofill/shared/FieldScanner.sys.mjs
@@ -2,6 +2,11 @@
* License, v. 2.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.
@@ -73,6 +78,14 @@ export class FieldDetail {
get sectionName() {
return this.section || this.addressType;
}
+
+ #isVisible = null;
+ get isVisible() {
+ if (this.#isVisible == null) {
+ this.#isVisible = lazy.FormAutofillUtils.isFieldVisible(this.element);
+ }
+ return this.#isVisible;
+ }
}
/**
diff --git a/toolkit/components/formautofill/shared/FormAutofillHeuristics.sys.mjs b/toolkit/components/formautofill/shared/FormAutofillHeuristics.sys.mjs
index 4ee1fc1fe1..fb96e47cae 100644
--- a/toolkit/components/formautofill/shared/FormAutofillHeuristics.sys.mjs
+++ b/toolkit/components/formautofill/shared/FormAutofillHeuristics.sys.mjs
@@ -182,7 +182,7 @@ export const FormAutofillHeuristics = {
* Return true if there is any field can be recognized in the parser,
* otherwise false.
*/
- _parsePhoneFields(scanner, detail) {
+ _parsePhoneFields(scanner, _fieldDetail) {
let matchingResult;
const GRAMMARS = this.PHONE_FIELD_GRAMMARS;
@@ -277,7 +277,7 @@ export const FormAutofillHeuristics = {
* Return true if there is any field can be recognized in the parser,
* otherwise false.
*/
- _parseStreetAddressFields(scanner, fieldDetail) {
+ _parseStreetAddressFields(scanner, _fieldDetail) {
const INTERESTED_FIELDS = [
"street-address",
"address-line1",
@@ -547,7 +547,9 @@ export const FormAutofillHeuristics = {
* all sections within its field details in the form.
*/
getFormInfo(form) {
- let elements = this.getFormElements(form);
+ const elements = Array.from(form.elements).filter(element =>
+ lazy.FormAutofillUtils.isCreditCardOrAddressFieldType(element)
+ );
const scanner = new lazy.FieldScanner(elements, element =>
this.inferFieldInfo(element, elements)
@@ -597,22 +599,6 @@ export const FormAutofillHeuristics = {
},
/**
- * Get focusable form elements that are of credit card or address type
- *
- * @param {HTMLElement} form
- * @returns {Array<HTMLElement>} focusable elements
- */
- getFormElements(form) {
- let elements = Array.from(form.elements).filter(
- element =>
- lazy.FormAutofillUtils.isCreditCardOrAddressFieldType(element) &&
- lazy.FormAutofillUtils.isFieldFocusable(element)
- );
-
- return elements;
- },
-
- /**
* The result is an array contains the sections with its belonging field details.
*
* @param {Array<FieldDetails>} fieldDetails field detail array to be classified
@@ -621,46 +607,54 @@ export const FormAutofillHeuristics = {
_classifySections(fieldDetails) {
let sections = [];
for (let i = 0; i < fieldDetails.length; i++) {
- const fieldName = fieldDetails[i].fieldName;
- const sectionName = fieldDetails[i].sectionName;
-
+ const cur = fieldDetails[i];
const [currentSection] = sections.slice(-1);
- // The section this field might belong to
+ // The section this field might be placed into.
let candidateSection = null;
- // If the field doesn't have a section name, MAYBE put it to the previous
- // section if exists. If the field has a section name, maybe put it to the
- // nearest section that either has the same name or it doesn't has a name.
- // Otherwise, create a new section.
- if (!currentSection || !sectionName) {
+ // 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 (sectionName) {
+ } 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 == sectionName) {
+ if (!sections[idx].name || sections[idx].name == cur.sectionName) {
candidateSection = sections[idx];
break;
}
}
}
- // We got an candidate section to put the field to, check whether the section
- // already has a field with the same field name. If yes, only add the field to when
- // the type of the field might appear multiple times in a row.
if (candidateSection) {
let createNewSection = true;
- if (candidateSection.fieldDetails.find(f => f.fieldName == fieldName)) {
+
+ // We might create a new section instead of placing the field in the candiate 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
+ // wanht 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 [lastFieldDetail] = candidateSection.fieldDetails.slice(-1);
- if (lastFieldDetail.fieldName == fieldName) {
- if (MULTI_FIELD_NAMES.includes(fieldName)) {
+ if (lastFieldDetail.fieldName == cur.fieldName) {
+ if (MULTI_FIELD_NAMES.includes(cur.fieldName)) {
createNewSection = false;
- } else if (fieldName in MULTI_N_FIELD_NAMES) {
+ } else if (cur.fieldName in MULTI_N_FIELD_NAMES) {
// 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[fieldName];
+ const N = MULTI_N_FIELD_NAMES[cur.fieldName];
if (lastFieldDetail.part) {
// If `part` is set, we have already identified this field can be
// merged previously
@@ -673,7 +667,7 @@ export const FormAutofillHeuristics = {
N == 2 ||
fieldDetails
.slice(i + 1, i + N - 1)
- .every(f => f.fieldName == fieldName)
+ .every(f => f.fieldName == cur.fieldName)
) {
lastFieldDetail.part = 1;
fieldDetails[i].part = 2;
diff --git a/toolkit/components/formautofill/shared/FormAutofillSection.sys.mjs b/toolkit/components/formautofill/shared/FormAutofillSection.sys.mjs
index 1c7696432a..7bda4c167b 100644
--- a/toolkit/components/formautofill/shared/FormAutofillSection.sys.mjs
+++ b/toolkit/components/formautofill/shared/FormAutofillSection.sys.mjs
@@ -7,7 +7,7 @@ import { FormAutofill } from "resource://autofill/FormAutofill.sys.mjs";
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
- AutofillTelemetry: "resource://autofill/AutofillTelemetry.sys.mjs",
+ AutofillTelemetry: "resource://gre/modules/shared/AutofillTelemetry.sys.mjs",
CreditCard: "resource://gre/modules/CreditCard.sys.mjs",
FormAutofillNameUtils:
"resource://gre/modules/shared/FormAutofillNameUtils.sys.mjs",
@@ -79,40 +79,40 @@ export class FormAutofillSection {
* Examine the section is createable for storing the profile. This method
* must be overrided.
*
- * @param {Object} record The record for examining createable
+ * @param {Object} _record The record for examining createable
* @returns {boolean} True for the record is createable, otherwise false
*
*/
- isRecordCreatable(record) {
+ isRecordCreatable(_record) {
throw new TypeError("isRecordCreatable method must be overridden");
}
/**
* Override this method if the profile is needed to apply some transformers.
*
- * @param {object} profile
+ * @param {object} _profile
* A profile should be converted based on the specific requirement.
*/
- applyTransformers(profile) {}
+ applyTransformers(_profile) {}
/**
* Override this method if the profile is needed to be customized for
* previewing values.
*
- * @param {object} profile
+ * @param {object} _profile
* A profile for pre-processing before previewing values.
*/
- preparePreviewProfile(profile) {}
+ preparePreviewProfile(_profile) {}
/**
* Override this method if the profile is needed to be customized for filling
* values.
*
- * @param {object} profile
+ * @param {object} _profile
* A profile for pre-processing before filling values.
* @returns {boolean} Whether the profile should be filled.
*/
- async prepareFillingProfile(profile) {
+ async prepareFillingProfile(_profile) {
return true;
}
@@ -846,6 +846,10 @@ export class FormAutofillAddressSection extends FormAutofillSection {
value =
FormAutofillUtils.getAbbreviatedSubregionName([value, text]) || text;
}
+ } else if (fieldDetail.fieldName == "country") {
+ // This is a temporary fix. Ideally we should have either case-insensitive comparaison of country codes
+ // or handle this elsewhere see Bug 1889234 for more context.
+ value = value.toUpperCase();
}
return value;
}
@@ -884,7 +888,7 @@ export class FormAutofillCreditCardSection extends FormAutofillSection {
}
}
- _handlePageHide(event) {
+ _handlePageHide(_event) {
this.handler.window.removeEventListener(
"pagehide",
this._handlePageHide.bind(this)
diff --git a/toolkit/components/formautofill/shared/FormAutofillUtils.sys.mjs b/toolkit/components/formautofill/shared/FormAutofillUtils.sys.mjs
index ce10c71ce1..e86f14975c 100644
--- a/toolkit/components/formautofill/shared/FormAutofillUtils.sys.mjs
+++ b/toolkit/components/formautofill/shared/FormAutofillUtils.sys.mjs
@@ -192,7 +192,7 @@ FormAutofillUtils = {
getAddressLabel(address) {
// TODO: Implement a smarter way for deciding what to display
// as option text. Possibly improve the algorithm in
- // ProfileAutoCompleteResult.jsm and reuse it here.
+ // ProfileAutoCompleteResult.sys.mjs and reuse it here.
let fieldOrder = [
"name",
"-moz-street-address-one-line", // Street address
@@ -302,20 +302,27 @@ FormAutofillUtils = {
},
/**
- * Determines if an element is focusable
- * and accessible via keyboard navigation or not.
+ * Determines if an element is visually hidden or not.
*
* @param {HTMLElement} element
- *
- * @returns {bool} true if the element is focusable and accessible
+ * @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
*/
- isFieldFocusable(element) {
- return (
- // The Services.focus.elementIsFocusable API considers elements with
- // tabIndex="-1" set as focusable. But since they are not accessible
- // via keyboard navigation we treat them as non-interactive
- Services.focus.elementIsFocusable(element, 0) && element.tabIndex != "-1"
- );
+ isFieldVisible(element, visibilityCheck = true) {
+ if (
+ visibilityCheck &&
+ element.checkVisibility &&
+ !FormAutofillUtils.ignoreVisibilityCheck
+ ) {
+ return element.checkVisibility({
+ checkOpacity: true,
+ checkVisibilityCSS: true,
+ });
+ }
+
+ return !element.hidden && element.style.display != "none";
},
/**
@@ -1127,3 +1134,11 @@ XPCOMUtils.defineLazyPreferenceGetter(
"extensions.formautofill.focusOnAutofill",
true
);
+
+// This is only used for testing
+XPCOMUtils.defineLazyPreferenceGetter(
+ FormAutofillUtils,
+ "ignoreVisibilityCheck",
+ "extensions.formautofill.test.ignoreVisibilityCheck",
+ false
+);
diff --git a/toolkit/components/formautofill/shared/FormStateManager.sys.mjs b/toolkit/components/formautofill/shared/FormStateManager.sys.mjs
index 064b4e5356..7481a5981c 100644
--- a/toolkit/components/formautofill/shared/FormStateManager.sys.mjs
+++ b/toolkit/components/formautofill/shared/FormStateManager.sys.mjs
@@ -150,7 +150,7 @@ export class FormStateManager {
}
didDestroy() {
- this._activeItems = null;
+ this._activeItems = {};
}
}
diff --git a/toolkit/components/formautofill/phonenumberutils/PhoneNumber.sys.mjs b/toolkit/components/formautofill/shared/PhoneNumber.sys.mjs
index 80b5e43acb..5288765181 100644
--- a/toolkit/components/formautofill/phonenumberutils/PhoneNumber.sys.mjs
+++ b/toolkit/components/formautofill/shared/PhoneNumber.sys.mjs
@@ -5,13 +5,13 @@
// 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://autofill/phonenumberutils/PhoneNumberMetaData.sys.mjs";
+import { PHONE_NUMBER_META_DATA } from "resource://gre/modules/shared/PhoneNumberMetaData.sys.mjs";
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
PhoneNumberNormalizer:
- "resource://autofill/phonenumberutils/PhoneNumberNormalizer.sys.mjs",
+ "resource://gre/modules/shared/PhoneNumberNormalizer.sys.mjs",
});
export var PhoneNumber = (function (dataBase) {
diff --git a/toolkit/components/formautofill/phonenumberutils/PhoneNumberMetaData.sys.mjs b/toolkit/components/formautofill/shared/PhoneNumberMetaData.sys.mjs
index 3338ce7c16..3338ce7c16 100644
--- a/toolkit/components/formautofill/phonenumberutils/PhoneNumberMetaData.sys.mjs
+++ b/toolkit/components/formautofill/shared/PhoneNumberMetaData.sys.mjs
diff --git a/toolkit/components/formautofill/phonenumberutils/PhoneNumberNormalizer.sys.mjs b/toolkit/components/formautofill/shared/PhoneNumberNormalizer.sys.mjs
index 604eefe314..604eefe314 100644
--- a/toolkit/components/formautofill/phonenumberutils/PhoneNumberNormalizer.sys.mjs
+++ b/toolkit/components/formautofill/shared/PhoneNumberNormalizer.sys.mjs
diff --git a/toolkit/components/glean/Cargo.toml b/toolkit/components/glean/Cargo.toml
index 45423a183b..98b8d8a95b 100644
--- a/toolkit/components/glean/Cargo.toml
+++ b/toolkit/components/glean/Cargo.toml
@@ -6,7 +6,7 @@ edition = "2018"
license = "MPL-2.0"
[dependencies]
-glean = "57.0.0"
+glean = "58.1.0"
log = "0.4"
nserror = { path = "../../../xpcom/rust/nserror" }
nsstring = { path = "../../../xpcom/rust/nsstring" }
@@ -18,6 +18,10 @@ cstr = "0.2"
viaduct = "0.1"
url = "2.1"
thin-vec = { version = "0.2.1", features = ["gecko-ffi"] }
+ohttp = { version = "0.3", default-features = false, features = ["gecko", "nss", "client"] }
+bhttp = "0.3"
+thiserror = "1.0"
+mozbuild = "0.1"
[features]
# Leave data collection enabled, but disable upload.
diff --git a/toolkit/components/glean/api/Cargo.toml b/toolkit/components/glean/api/Cargo.toml
index 403168fb90..dc688dc41e 100644
--- a/toolkit/components/glean/api/Cargo.toml
+++ b/toolkit/components/glean/api/Cargo.toml
@@ -9,7 +9,7 @@ license = "MPL-2.0"
[dependencies]
bincode = "1.0"
chrono = "0.4.10"
-glean = "57.0.0"
+glean = "58.1.0"
inherent = "1.0.0"
log = "0.4"
nsstring = { path = "../../../../xpcom/rust/nsstring", optional = true }
@@ -19,6 +19,7 @@ uuid = { version = "1.0", features = ["v4"] }
xpcom = { path = "../../../../xpcom/rust/xpcom", optional = true }
thin-vec = { version = "0.2.1", features = ["gecko-ffi"] }
mozbuild = "0.1"
+serde_json = "1"
[dev-dependencies]
tempfile = "3.1.0"
diff --git a/toolkit/components/glean/api/src/ffi/custom_distribution.rs b/toolkit/components/glean/api/src/ffi/custom_distribution.rs
index 853a6e9845..643ebfbff5 100644
--- a/toolkit/components/glean/api/src/ffi/custom_distribution.rs
+++ b/toolkit/components/glean/api/src/ffi/custom_distribution.rs
@@ -22,6 +22,7 @@ pub extern "C" fn fog_custom_distribution_test_get_value(
id: u32,
ping_name: &nsACString,
sum: &mut u64,
+ count: &mut u64,
buckets: &mut ThinVec<u64>,
counts: &mut ThinVec<u64>,
) {
@@ -33,6 +34,7 @@ pub extern "C" fn fog_custom_distribution_test_get_value(
);
// FIXME(bug 1771885): Glean should use `u64` where it can.
*sum = val.sum as _;
+ *count = val.count as _;
for (&bucket, &count) in val.values.iter() {
buckets.push(bucket as _);
counts.push(count as _);
diff --git a/toolkit/components/glean/api/src/ffi/memory_distribution.rs b/toolkit/components/glean/api/src/ffi/memory_distribution.rs
index cf09d3f8de..35c8326c4d 100644
--- a/toolkit/components/glean/api/src/ffi/memory_distribution.rs
+++ b/toolkit/components/glean/api/src/ffi/memory_distribution.rs
@@ -22,6 +22,7 @@ pub extern "C" fn fog_memory_distribution_test_get_value(
id: u32,
ping_name: &nsACString,
sum: &mut u64,
+ count: &mut u64,
buckets: &mut ThinVec<u64>,
counts: &mut ThinVec<u64>,
) {
@@ -32,6 +33,7 @@ pub extern "C" fn fog_memory_distribution_test_get_value(
test_get!(metric, ping_name)
);
*sum = val.sum as _;
+ *count = val.count as _;
for (&bucket, &count) in val.values.iter() {
buckets.push(bucket as _);
counts.push(count as _);
diff --git a/toolkit/components/glean/api/src/ffi/mod.rs b/toolkit/components/glean/api/src/ffi/mod.rs
index 23235fc2f1..4eb614aefc 100644
--- a/toolkit/components/glean/api/src/ffi/mod.rs
+++ b/toolkit/components/glean/api/src/ffi/mod.rs
@@ -16,6 +16,7 @@ mod event;
mod labeled;
mod memory_distribution;
mod numerator;
+mod object;
mod ping;
mod quantity;
mod rate;
diff --git a/toolkit/components/glean/api/src/ffi/object.rs b/toolkit/components/glean/api/src/ffi/object.rs
new file mode 100644
index 0000000000..85f5269da9
--- /dev/null
+++ b/toolkit/components/glean/api/src/ffi/object.rs
@@ -0,0 +1,68 @@
+// This Source Code Form is subject to the terms of 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/.
+
+#![cfg(feature = "with_gecko")]
+
+use nsstring::nsACString;
+
+use crate::metrics::__glean_metric_maps as metric_maps;
+
+#[no_mangle]
+pub extern "C" fn fog_object_set_string(id: u32, value: &nsACString) {
+ if id & (1 << crate::factory::DYNAMIC_METRIC_BIT) > 0 {
+ panic!("No dynamic metric for objects");
+ }
+
+ let value = value.to_utf8().to_string();
+ if metric_maps::set_object_by_id(id, value).is_err() {
+ panic!("No object for id {}", id);
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn fog_object_test_has_value(id: u32, ping_name: &nsACString) -> bool {
+ let storage = if ping_name.is_empty() {
+ None
+ } else {
+ Some(ping_name.to_utf8().into_owned())
+ };
+ if id & (1 << crate::factory::DYNAMIC_METRIC_BIT) > 0 {
+ panic!("No dynamic metric for objects");
+ } else {
+ metric_maps::object_test_get_value(id, storage).is_some()
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn fog_object_test_get_value(
+ id: u32,
+ ping_name: &nsACString,
+ value: &mut nsACString,
+) {
+ let storage = if ping_name.is_empty() {
+ None
+ } else {
+ Some(ping_name.to_utf8().into_owned())
+ };
+
+ let object = if id & (1 << crate::factory::DYNAMIC_METRIC_BIT) > 0 {
+ panic!("No dynamic metric for objects");
+ } else {
+ match metric_maps::object_test_get_value(id, storage) {
+ Some(object) => object,
+ None => return,
+ }
+ };
+ value.assign(&object);
+}
+
+#[no_mangle]
+pub extern "C" fn fog_object_test_get_error(id: u32, error_str: &mut nsACString) -> bool {
+ let err = if id & (1 << crate::factory::DYNAMIC_METRIC_BIT) > 0 {
+ panic!("No dynamic metric for objects");
+ } else {
+ metric_maps::object_test_get_error(id)
+ };
+ err.map(|err_str| error_str.assign(&err_str)).is_some()
+}
diff --git a/toolkit/components/glean/api/src/ffi/timing_distribution.rs b/toolkit/components/glean/api/src/ffi/timing_distribution.rs
index 4ac5d03986..4391985efa 100644
--- a/toolkit/components/glean/api/src/ffi/timing_distribution.rs
+++ b/toolkit/components/glean/api/src/ffi/timing_distribution.rs
@@ -58,6 +58,7 @@ pub extern "C" fn fog_timing_distribution_test_get_value(
id: u32,
ping_name: &nsACString,
sum: &mut u64,
+ count: &mut u64,
buckets: &mut ThinVec<u64>,
counts: &mut ThinVec<u64>,
) {
@@ -68,6 +69,7 @@ pub extern "C" fn fog_timing_distribution_test_get_value(
test_get!(metric, ping_name)
);
*sum = val.sum as _;
+ *count = val.count as _;
for (&bucket, &count) in val.values.iter() {
buckets.push(bucket as _);
counts.push(count as _);
diff --git a/toolkit/components/glean/api/src/pings.rs b/toolkit/components/glean/api/src/pings.rs
index f1d0332695..21eb3855ee 100644
--- a/toolkit/components/glean/api/src/pings.rs
+++ b/toolkit/components/glean/api/src/pings.rs
@@ -6,7 +6,7 @@
//!
//! The contents of this module are generated by
//! `toolkit/components/glean/build_scripts/glean_parser_ext/run_glean_parser.py`, from
-//! 'toolkit/components/glean/pings.yaml`.
+//! ping definitions files identified by `toolkit/components/glean/metrics_index.py`.
include!(mozbuild::objdir_path!(
"toolkit/components/glean/api/src/pings.rs"
diff --git a/toolkit/components/glean/api/src/private/custom_distribution.rs b/toolkit/components/glean/api/src/private/custom_distribution.rs
index 2114430898..aeaf9b58c2 100644
--- a/toolkit/components/glean/api/src/private/custom_distribution.rs
+++ b/toolkit/components/glean/api/src/private/custom_distribution.rs
@@ -92,6 +92,10 @@ impl CustomDistribution for CustomDistributionMetric {
}
}
+ pub fn accumulate_single_sample_signed(&self, _sample: i64) {
+ unimplemented!("bug 1884183: expose this to FOG")
+ }
+
pub fn test_get_value<'a, S: Into<Option<&'a str>>>(
&self,
ping_name: S,
diff --git a/toolkit/components/glean/api/src/private/mod.rs b/toolkit/components/glean/api/src/private/mod.rs
index b0b1e11393..e86e121d72 100644
--- a/toolkit/components/glean/api/src/private/mod.rs
+++ b/toolkit/components/glean/api/src/private/mod.rs
@@ -24,6 +24,7 @@ mod labeled;
mod labeled_counter;
mod memory_distribution;
mod numerator;
+mod object;
mod ping;
mod quantity;
mod rate;
@@ -46,6 +47,7 @@ pub use self::labeled::LabeledMetric;
pub use self::labeled_counter::LabeledCounterMetric;
pub use self::memory_distribution::MemoryDistributionMetric;
pub use self::numerator::NumeratorMetric;
+pub use self::object::ObjectMetric;
pub use self::ping::Ping;
pub use self::quantity::QuantityMetric;
pub use self::rate::RateMetric;
diff --git a/toolkit/components/glean/api/src/private/object.rs b/toolkit/components/glean/api/src/private/object.rs
new file mode 100644
index 0000000000..5199cfad31
--- /dev/null
+++ b/toolkit/components/glean/api/src/private/object.rs
@@ -0,0 +1,83 @@
+// This Source Code Form is subject to the terms of 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/.
+
+use super::{CommonMetricData, MetricId};
+
+use crate::ipc::need_ipc;
+
+use glean::traits::ObjectSerialize;
+
+/// An object metric.
+pub enum ObjectMetric<K> {
+ Parent {
+ id: MetricId,
+ inner: glean::private::ObjectMetric<K>,
+ },
+ Child,
+}
+
+impl<K: ObjectSerialize> ObjectMetric<K> {
+ /// Create a new object metric.
+ pub fn new(id: MetricId, meta: CommonMetricData) -> Self {
+ if need_ipc() {
+ ObjectMetric::Child
+ } else {
+ let inner = glean::private::ObjectMetric::new(meta);
+ ObjectMetric::Parent { id, inner }
+ }
+ }
+
+ pub fn set(&self, value: K) {
+ match self {
+ ObjectMetric::Parent { inner, .. } => {
+ inner.set(value);
+ }
+ ObjectMetric::Child => {
+ log::error!("Unable to set object metric in non-main process. This operation will be ignored.");
+ // TODO: Record an error.
+ }
+ };
+ }
+
+ pub fn set_string(&self, value: String) {
+ match self {
+ ObjectMetric::Parent { inner, .. } => {
+ inner.set_string(value);
+ }
+ ObjectMetric::Child => {
+ log::error!("Unable to set object metric in non-main process. This operation will be ignored.");
+ // TODO: Record an error.
+ }
+ };
+ }
+
+ pub fn test_get_value<'a, S: Into<Option<&'a str>>>(
+ &self,
+ ping_name: S,
+ ) -> Option<serde_json::Value> {
+ match self {
+ ObjectMetric::Parent { inner, .. } => inner.test_get_value(ping_name),
+ ObjectMetric::Child => {
+ panic!("Cannot get test value for object metric in non-parent process!",)
+ }
+ }
+ }
+
+ pub fn test_get_value_as_str<'a, S: Into<Option<&'a str>>>(
+ &self,
+ ping_name: S,
+ ) -> Option<String> {
+ self.test_get_value(ping_name)
+ .map(|val| serde_json::to_string(&val).unwrap())
+ }
+
+ pub fn test_get_num_recorded_errors(&self, error: glean::ErrorType) -> i32 {
+ match self {
+ ObjectMetric::Parent { inner, .. } => inner.test_get_num_recorded_errors(error),
+ ObjectMetric::Child => {
+ panic!("Cannot get the number of recorded errors in non-parent process!")
+ }
+ }
+ }
+}
diff --git a/toolkit/components/glean/api/src/private/ping.rs b/toolkit/components/glean/api/src/private/ping.rs
index cc9585eea1..7e03c1ff00 100644
--- a/toolkit/components/glean/api/src/private/ping.rs
+++ b/toolkit/components/glean/api/src/private/ping.rs
@@ -30,6 +30,7 @@ impl Ping {
include_client_id: bool,
send_if_empty: bool,
precise_timestamps: bool,
+ include_info_sections: bool,
reason_codes: Vec<String>,
) -> Self {
if need_ipc() {
@@ -40,6 +41,7 @@ impl Ping {
include_client_id,
send_if_empty,
precise_timestamps,
+ include_info_sections,
reason_codes,
))
}
@@ -103,7 +105,7 @@ mod test {
// Smoke test for what should be the generated code.
static PROTOTYPE_PING: Lazy<Ping> =
- Lazy::new(|| Ping::new("prototype", false, true, true, vec![]));
+ Lazy::new(|| Ping::new("prototype", false, true, true, true, vec![]));
#[test]
fn smoke_test_custom_ping() {
diff --git a/toolkit/components/glean/api/src/private/timing_distribution.rs b/toolkit/components/glean/api/src/private/timing_distribution.rs
index 0ab25cc900..6707560e41 100644
--- a/toolkit/components/glean/api/src/private/timing_distribution.rs
+++ b/toolkit/components/glean/api/src/private/timing_distribution.rs
@@ -374,6 +374,10 @@ impl TimingDistribution for TimingDistributionMetric {
}
}
+ pub fn accumulate_single_sample(&self, _sample: i64) {
+ unimplemented!("bug 1884183: expose this to FOG")
+ }
+
/// **Exported for test purposes.**
///
/// Gets the currently stored value of the metric.
diff --git a/toolkit/components/glean/bindings/GleanMetric.h b/toolkit/components/glean/bindings/GleanMetric.h
index 65ac75191d..c6bf6b4066 100644
--- a/toolkit/components/glean/bindings/GleanMetric.h
+++ b/toolkit/components/glean/bindings/GleanMetric.h
@@ -11,6 +11,7 @@
#include "nsIGlobalObject.h"
#include "nsWrapperCache.h"
#include "nsClassHashtable.h"
+#include "nsGlobalWindowInner.h"
#include "nsTHashMap.h"
#include "mozilla/DataMutex.h"
diff --git a/toolkit/components/glean/bindings/MetricTypes.h b/toolkit/components/glean/bindings/MetricTypes.h
index a7ae09fe19..6d855c2bf4 100644
--- a/toolkit/components/glean/bindings/MetricTypes.h
+++ b/toolkit/components/glean/bindings/MetricTypes.h
@@ -14,6 +14,7 @@
#include "mozilla/glean/bindings/Labeled.h"
#include "mozilla/glean/bindings/MemoryDistribution.h"
#include "mozilla/glean/bindings/Numerator.h"
+#include "mozilla/glean/bindings/Object.h"
#include "mozilla/glean/bindings/Quantity.h"
#include "mozilla/glean/bindings/Rate.h"
#include "mozilla/glean/bindings/String.h"
diff --git a/toolkit/components/glean/bindings/jog/src/lib.rs b/toolkit/components/glean/bindings/jog/src/lib.rs
index 4f2d439d80..b62e54f6e8 100644
--- a/toolkit/components/glean/bindings/jog/src/lib.rs
+++ b/toolkit/components/glean/bindings/jog/src/lib.rs
@@ -138,6 +138,7 @@ pub extern "C" fn jog_test_register_ping(
include_client_id: bool,
send_if_empty: bool,
precise_timestamps: bool,
+ include_info_sections: bool,
reason_codes: &ThinVec<nsCString>,
) -> u32 {
let ping_name = name.to_string();
@@ -150,6 +151,7 @@ pub extern "C" fn jog_test_register_ping(
include_client_id,
send_if_empty,
precise_timestamps,
+ include_info_sections,
reason_codes,
)
.expect("Creation or registration of ping failed.") // permitted to panic in test-only method.
@@ -160,6 +162,7 @@ fn create_and_register_ping(
include_client_id: bool,
send_if_empty: bool,
precise_timestamps: bool,
+ include_info_sections: bool,
reason_codes: Vec<String>,
) -> Result<u32, Box<dyn std::error::Error>> {
let ns_name = nsCString::from(&ping_name);
@@ -168,6 +171,7 @@ fn create_and_register_ping(
include_client_id,
send_if_empty,
precise_timestamps,
+ include_info_sections,
reason_codes,
);
extern "C" {
@@ -214,6 +218,7 @@ struct PingDefinitionData {
include_client_id: bool,
send_if_empty: bool,
precise_timestamps: bool,
+ include_info_sections: bool,
reason_codes: Option<Vec<String>>,
}
@@ -260,6 +265,7 @@ pub extern "C" fn jog_load_jogfile(jogfile_path: &nsAString) -> bool {
ping.include_client_id,
ping.send_if_empty,
ping.precise_timestamps,
+ ping.include_info_sections,
ping.reason_codes.unwrap_or_else(Vec::new),
);
}
diff --git a/toolkit/components/glean/bindings/private/CustomDistribution.cpp b/toolkit/components/glean/bindings/private/CustomDistribution.cpp
index 2f0226cb58..a5a821a558 100644
--- a/toolkit/components/glean/bindings/private/CustomDistribution.cpp
+++ b/toolkit/components/glean/bindings/private/CustomDistribution.cpp
@@ -59,9 +59,10 @@ CustomDistributionMetric::TestGetValue(const nsACString& aPingName) const {
nsTArray<uint64_t> buckets;
nsTArray<uint64_t> counts;
uint64_t sum;
- fog_custom_distribution_test_get_value(mId, &aPingName, &sum, &buckets,
- &counts);
- return Some(DistributionData(buckets, counts, sum));
+ uint64_t count;
+ fog_custom_distribution_test_get_value(mId, &aPingName, &sum, &count,
+ &buckets, &counts);
+ return Some(DistributionData(buckets, counts, sum, count));
}
} // namespace impl
@@ -92,6 +93,7 @@ void GleanCustomDistribution::TestGetValue(
dom::GleanDistributionData ret;
ret.mSum = optresult.ref().sum;
+ ret.mCount = optresult.ref().count;
auto& data = optresult.ref().values;
for (const auto& entry : data) {
dom::binding_detail::RecordEntry<nsCString, uint64_t> bucket;
diff --git a/toolkit/components/glean/bindings/private/DistributionData.h b/toolkit/components/glean/bindings/private/DistributionData.h
index 6ff995f222..fb9bba720e 100644
--- a/toolkit/components/glean/bindings/private/DistributionData.h
+++ b/toolkit/components/glean/bindings/private/DistributionData.h
@@ -12,6 +12,7 @@ namespace mozilla::glean {
struct DistributionData final {
uint64_t sum;
+ uint64_t count;
nsTHashMap<nsUint64HashKey, uint64_t> values;
/**
@@ -19,8 +20,9 @@ struct DistributionData final {
* as returned by `fog_*_distribution_test_get_value`.
*/
DistributionData(const nsTArray<uint64_t>& aBuckets,
- const nsTArray<uint64_t>& aCounts, uint64_t aSum)
- : sum(aSum) {
+ const nsTArray<uint64_t>& aCounts, uint64_t aSum,
+ uint64_t aCount)
+ : sum(aSum), count(aCount) {
for (size_t i = 0; i < aBuckets.Length(); ++i) {
this->values.InsertOrUpdate(aBuckets[i], aCounts[i]);
}
diff --git a/toolkit/components/glean/bindings/private/MemoryDistribution.cpp b/toolkit/components/glean/bindings/private/MemoryDistribution.cpp
index a580c5df3c..64f3bf241c 100644
--- a/toolkit/components/glean/bindings/private/MemoryDistribution.cpp
+++ b/toolkit/components/glean/bindings/private/MemoryDistribution.cpp
@@ -44,9 +44,10 @@ MemoryDistributionMetric::TestGetValue(const nsACString& aPingName) const {
nsTArray<uint64_t> buckets;
nsTArray<uint64_t> counts;
uint64_t sum;
- fog_memory_distribution_test_get_value(mId, &aPingName, &sum, &buckets,
- &counts);
- return Some(DistributionData(buckets, counts, sum));
+ uint64_t count;
+ fog_memory_distribution_test_get_value(mId, &aPingName, &sum, &count,
+ &buckets, &counts);
+ return Some(DistributionData(buckets, counts, sum, count));
}
} // namespace impl
@@ -76,6 +77,7 @@ void GleanMemoryDistribution::TestGetValue(
dom::GleanDistributionData ret;
ret.mSum = optresult.ref().sum;
+ ret.mCount = optresult.ref().count;
auto& data = optresult.ref().values;
for (const auto& entry : data) {
dom::binding_detail::RecordEntry<nsCString, uint64_t> bucket;
diff --git a/toolkit/components/glean/bindings/private/Object.cpp b/toolkit/components/glean/bindings/private/Object.cpp
new file mode 100644
index 0000000000..817b14dc0f
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/Object.cpp
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 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/. */
+
+#include "mozilla/glean/bindings/Object.h"
+
+#include "Common.h"
+#include "mozilla/dom/GleanMetricsBinding.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/Logging.h"
+#include "jsapi.h"
+#include "js/JSON.h"
+#include "nsContentUtils.h"
+
+using namespace mozilla::dom;
+
+namespace mozilla::glean {
+
+/* virtual */
+JSObject* GleanObject::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return dom::GleanObject_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void GleanObject::Set(JSContext* aCx, JS::Handle<JSObject*> aObj) {
+ // We take in an `object`. Cannot be `null`!
+ // But at this point the type system doesn't know that.
+ JS::Rooted<JS::Value> value(aCx);
+ value.setObjectOrNull(aObj);
+
+ nsAutoString serializedValue;
+ bool res = nsContentUtils::StringifyJSON(aCx, value, serializedValue,
+ UndefinedIsNullStringLiteral);
+ if (!res) {
+ // JS_Stringify throws an exception, e.g. on cyclic objects.
+ // We don't want this rethrown.
+ JS_ClearPendingException(aCx);
+
+ LogToBrowserConsole(nsIScriptError::warningFlag,
+ u"passed in object cannot be serialized"_ns);
+ return;
+ }
+
+ NS_ConvertUTF16toUTF8 payload(serializedValue);
+ mObject.SetStr(payload);
+}
+
+void GleanObject::TestGetValue(JSContext* aCx, const nsACString& aPingName,
+ JS::MutableHandle<JSObject*> aResult,
+ ErrorResult& aRv) {
+ aResult.set(nullptr);
+
+ auto result = mObject.TestGetValue(aPingName);
+ if (result.isErr()) {
+ aRv.ThrowDataError(result.unwrapErr());
+ return;
+ }
+ auto optresult = result.unwrap();
+ if (optresult.isNothing()) {
+ return;
+ }
+
+ const NS_ConvertUTF8toUTF16 str(optresult.ref());
+ JS::Rooted<JS::Value> json(aCx);
+ bool res = JS_ParseJSON(aCx, str.get(), str.Length(), &json);
+ if (!res) {
+ aRv.ThrowDataError("couldn't parse stored object");
+ return;
+ }
+ if (!json.isObject()) {
+ aRv.ThrowDataError("stored data does not represent a valid object");
+ return;
+ }
+
+ aResult.set(&json.toObject());
+}
+
+} // namespace mozilla::glean
diff --git a/toolkit/components/glean/bindings/private/Object.h b/toolkit/components/glean/bindings/private/Object.h
new file mode 100644
index 0000000000..4c81f8b096
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/Object.h
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 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/. */
+
+#ifndef mozilla_glean_GleanObject_h
+#define mozilla_glean_GleanObject_h
+
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/glean/bindings/GleanMetric.h"
+#include "mozilla/glean/fog_ffi_generated.h"
+#include "mozilla/ResultVariant.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+namespace mozilla::glean {
+
+// forward declaration
+class GleanObject;
+
+namespace impl {
+
+template <class T>
+class ObjectMetric {
+ friend class mozilla::glean::GleanObject;
+
+ public:
+ constexpr explicit ObjectMetric(uint32_t id) : mId(id) {}
+
+ private:
+ const uint32_t mId;
+
+ /* TODO(bug 1881023): Turn this into the public C++ API */
+ /**
+ * **Test-only API**
+ *
+ * Gets the currently stored object as a JSON-encoded string.
+ *
+ * This function will attempt to await the last parent-process task (if any)
+ * writing to the the metric's storage engine before returning a value.
+ * This function will not wait for data from child processes.
+ *
+ * This doesn't clear the stored value.
+ * Parent process only. Panics in child processes.
+ *
+ * @param aPingName The (optional) name of the ping to retrieve the metric
+ * for. Defaults to the first value in `send_in_pings`.
+ *
+ * @return value of the stored metric, or Nothing() if there is no value.
+ */
+ Result<Maybe<nsCString>, nsCString> TestGetValue(
+ const nsACString& aPingName) const {
+ nsCString err;
+ if (fog_object_test_get_error(mId, &err)) {
+ return Err(err);
+ }
+ if (!fog_object_test_has_value(mId, &aPingName)) {
+ return Maybe<nsCString>();
+ }
+ nsCString ret;
+ fog_object_test_get_value(mId, &aPingName, &ret);
+ return Some(ret);
+ }
+
+ void SetStr(const nsACString& aValue) const {
+ fog_object_set_string(mId, &aValue);
+ }
+};
+
+} // namespace impl
+
+class GleanObject final : public GleanMetric {
+ public:
+ explicit GleanObject(uint32_t aId, nsISupports* aParent)
+ : GleanMetric(aParent), mObject(aId) {}
+
+ virtual JSObject* WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override final;
+
+ void Set(JSContext* aCx, JS::Handle<JSObject*> aObj);
+
+ void TestGetValue(JSContext* aCx, const nsACString& aPingName,
+ JS::MutableHandle<JSObject*> aResult, ErrorResult& aRv);
+
+ virtual ~GleanObject() = default;
+
+ private:
+ const impl::ObjectMetric<void> mObject;
+};
+
+} // namespace mozilla::glean
+
+#endif /* mozilla_glean_GleanObject.h */
diff --git a/toolkit/components/glean/bindings/private/Timespan.cpp b/toolkit/components/glean/bindings/private/Timespan.cpp
index 13e57317fa..2ab1f0dbba 100644
--- a/toolkit/components/glean/bindings/private/Timespan.cpp
+++ b/toolkit/components/glean/bindings/private/Timespan.cpp
@@ -36,6 +36,7 @@ class ScalarIDHashKey : public PLDHashEntryHdr {
return static_cast<std::underlying_type<ScalarID>::type>(*aKey);
}
enum { ALLOW_MEMMOVE = true };
+ static_assert(std::is_trivially_copyable_v<ScalarID>);
private:
const ScalarID mValue;
diff --git a/toolkit/components/glean/bindings/private/TimingDistribution.cpp b/toolkit/components/glean/bindings/private/TimingDistribution.cpp
index f7a78165ae..036db5f9db 100644
--- a/toolkit/components/glean/bindings/private/TimingDistribution.cpp
+++ b/toolkit/components/glean/bindings/private/TimingDistribution.cpp
@@ -21,7 +21,10 @@
namespace mozilla::glean {
using MetricId = uint32_t; // Same type as in api/src/private/mod.rs
-using MetricTimerTuple = std::tuple<MetricId, TimerId>;
+struct MetricTimerTuple {
+ MetricId mMetricId;
+ TimerId mTimerId;
+};
class MetricTimerTupleHashKey : public PLDHashEntryHdr {
public:
using KeyType = const MetricTimerTuple&;
@@ -34,16 +37,17 @@ class MetricTimerTupleHashKey : public PLDHashEntryHdr {
KeyType GetKey() const { return mValue; }
bool KeyEquals(KeyTypePointer aKey) const {
- return std::get<0>(*aKey) == std::get<0>(mValue) &&
- std::get<1>(*aKey) == std::get<1>(mValue);
+ return aKey->mMetricId == mValue.mMetricId &&
+ aKey->mTimerId == mValue.mTimerId;
}
static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
static PLDHashNumber HashKey(KeyTypePointer aKey) {
// Chosen because this is how nsIntegralHashKey does it.
- return HashGeneric(std::get<0>(*aKey), std::get<1>(*aKey));
+ return HashGeneric(aKey->mMetricId, aKey->mTimerId);
}
enum { ALLOW_MEMMOVE = true };
+ static_assert(std::is_trivially_copyable_v<MetricTimerTuple>);
private:
const MetricTimerTuple mValue;
@@ -103,7 +107,7 @@ extern "C" NS_EXPORT void GIFFT_TimingDistributionStart(
auto mirrorId = mozilla::glean::HistogramIdForMetric(aMetricId);
if (mirrorId) {
mozilla::glean::GetTimerIdToStartsLock().apply([&](auto& lock) {
- auto tuple = std::make_tuple(aMetricId, aTimerId);
+ auto tuple = mozilla::glean::MetricTimerTuple{aMetricId, aTimerId};
// It should be all but impossible for anyone to have already inserted
// this timer for this metric given the monotonicity of timer ids.
(void)NS_WARN_IF(lock.ref()->Remove(tuple));
@@ -118,7 +122,8 @@ extern "C" NS_EXPORT void GIFFT_TimingDistributionStopAndAccumulate(
auto mirrorId = mozilla::glean::HistogramIdForMetric(aMetricId);
if (mirrorId) {
mozilla::glean::GetTimerIdToStartsLock().apply([&](auto& lock) {
- auto optStart = lock.ref()->Extract(std::make_tuple(aMetricId, aTimerId));
+ auto tuple = mozilla::glean::MetricTimerTuple{aMetricId, aTimerId};
+ auto optStart = lock.ref()->Extract(tuple);
// The timer might not be in the map to be removed if it's already been
// cancelled or stop_and_accumulate'd.
if (!NS_WARN_IF(!optStart)) {
@@ -145,8 +150,8 @@ extern "C" NS_EXPORT void GIFFT_TimingDistributionCancel(
mozilla::glean::GetTimerIdToStartsLock().apply([&](auto& lock) {
// The timer might not be in the map to be removed if it's already been
// cancelled or stop_and_accumulate'd.
- (void)NS_WARN_IF(
- !lock.ref()->Remove(std::make_tuple(aMetricId, aTimerId)));
+ auto tuple = mozilla::glean::MetricTimerTuple{aMetricId, aTimerId};
+ (void)NS_WARN_IF(!lock.ref()->Remove(tuple));
});
}
}
@@ -187,9 +192,10 @@ TimingDistributionMetric::TestGetValue(const nsACString& aPingName) const {
nsTArray<uint64_t> buckets;
nsTArray<uint64_t> counts;
uint64_t sum;
- fog_timing_distribution_test_get_value(mId, &aPingName, &sum, &buckets,
- &counts);
- return Some(DistributionData(buckets, counts, sum));
+ uint64_t count;
+ fog_timing_distribution_test_get_value(mId, &aPingName, &sum, &count,
+ &buckets, &counts);
+ return Some(DistributionData(buckets, counts, sum, count));
}
} // namespace impl
@@ -225,6 +231,7 @@ void GleanTimingDistribution::TestGetValue(
dom::GleanDistributionData ret;
ret.mSum = optresult.ref().sum;
+ ret.mCount = optresult.ref().count;
auto& data = optresult.ref().values;
for (const auto& entry : data) {
dom::binding_detail::RecordEntry<nsCString, uint64_t> bucket;
diff --git a/toolkit/components/glean/build_scripts/glean_parser_ext/jog.py b/toolkit/components/glean/build_scripts/glean_parser_ext/jog.py
index 81d92b0f5d..b42827989e 100644
--- a/toolkit/components/glean/build_scripts/glean_parser_ext/jog.py
+++ b/toolkit/components/glean/build_scripts/glean_parser_ext/jog.py
@@ -55,6 +55,7 @@ known_ping_args = [
"include_client_id",
"send_if_empty",
"precise_timestamps",
+ "include_info_sections",
"reason_codes",
]
diff --git a/toolkit/components/glean/build_scripts/glean_parser_ext/run_glean_parser.py b/toolkit/components/glean/build_scripts/glean_parser_ext/run_glean_parser.py
index 1d7d97cf73..bc9f09f0d3 100644
--- a/toolkit/components/glean/build_scripts/glean_parser_ext/run_glean_parser.py
+++ b/toolkit/components/glean/build_scripts/glean_parser_ext/run_glean_parser.py
@@ -230,5 +230,32 @@ def jog_file(output_fd, *args):
return get_deps()
+def ohttp_pings(output_fd, *args):
+ all_objs, options = parse(args)
+ ohttp_pings = []
+ for ping in all_objs["pings"].values():
+ if ping.metadata.get("use_ohttp", False):
+ if ping.include_info_sections:
+ raise ParserError(
+ "Cannot send pings with OHTTP that contain {client|ping}_info sections. Specify `metadata: include_info_sections: false`"
+ )
+ ohttp_pings.append(ping.name)
+
+ env = jinja2.Environment(
+ loader=jinja2.PackageLoader("run_glean_parser", "templates"),
+ trim_blocks=True,
+ lstrip_blocks=True,
+ )
+ env.filters["quote_and_join"] = lambda l: "\n| ".join(f'"{x}"' for x in l)
+ template = env.get_template("ohttp.jinja2")
+ output_fd.write(
+ template.render(
+ ohttp_pings=ohttp_pings,
+ )
+ )
+ output_fd.write("\n")
+ return get_deps()
+
+
if __name__ == "__main__":
main(sys.stdout, *sys.argv[1:])
diff --git a/toolkit/components/glean/build_scripts/glean_parser_ext/rust.py b/toolkit/components/glean/build_scripts/glean_parser_ext/rust.py
index 615784b481..4b2b35886b 100644
--- a/toolkit/components/glean/build_scripts/glean_parser_ext/rust.py
+++ b/toolkit/components/glean/build_scripts/glean_parser_ext/rust.py
@@ -68,9 +68,10 @@ def rust_datatypes_filter(value):
# CowString is also a 'str' but is a special case.
# Ensure its case is handled before str's (below).
elif isinstance(value, CowString):
- yield f'::std::borrow::Cow::from("{value.inner}")'
+ value = json.dumps(value)
+ yield f"::std::borrow::Cow::from({value})"
elif isinstance(value, str):
- yield '"' + value + '".into()'
+ yield f"{json.dumps(value)}.into()"
elif isinstance(value, Rate):
yield "CommonMetricData {"
for arg_name in common_metric_data_args:
@@ -118,6 +119,10 @@ def type_name(obj):
return "{}<{}>".format(
class_name(obj.type), util.Camelize(obj.name) + suffix
)
+ generate_structure = getattr(obj, "_generate_structure", [])
+ if len(generate_structure):
+ generic = util.Camelize(obj.name) + "Object"
+ return "{}<{}>".format(class_name(obj.type), generic)
return class_name(obj.type)
@@ -136,6 +141,21 @@ def extra_type_name(typ: str) -> str:
return "UNSUPPORTED"
+def structure_type_name(typ: str) -> str:
+ """
+ Returns the corresponding Rust type for structure items.
+ """
+
+ if typ == "boolean":
+ return "bool"
+ elif typ == "string":
+ return "String"
+ elif typ == "number":
+ return "i64"
+ else:
+ return "UNSUPPORTED"
+
+
def class_name(obj_type):
"""
Returns the Rust class name for a given metric or ping type.
@@ -208,6 +228,14 @@ def output_rust(objs, output_fd, ping_names_by_app_id, options={}):
# 17 -> "test_only::an_event"
events_by_id = {}
+ # Map from a metric ID to the fully qualified path of the object metric in Rust.
+ # Required for the special handling of object lookups.
+ #
+ # Example:
+ #
+ # 18 -> "test_only::an_object"
+ objects_by_id = {}
+
# Map from a labeled type (e.g. "counter") to a map from metric ID to the
# fully qualified path of the labeled metric object in Rust paired with
# whether the labeled metric has an enum.
@@ -238,6 +266,9 @@ def output_rust(objs, output_fd, ping_names_by_app_id, options={}):
if metric.type == "event":
events_by_id[get_metric_id(metric)] = full_path
continue
+ if metric.type == "object":
+ objects_by_id[get_metric_id(metric)] = full_path
+ continue
if getattr(metric, "labeled", False):
labeled_type = metric.type[8:]
@@ -261,6 +292,7 @@ def output_rust(objs, output_fd, ping_names_by_app_id, options={}):
("snake_case", util.snake_case),
("type_name", type_name),
("extra_type_name", extra_type_name),
+ ("structure_type_name", structure_type_name),
("ctor", ctor),
("extra_keys", extra_keys),
("metric_id", get_metric_id),
@@ -275,6 +307,7 @@ def output_rust(objs, output_fd, ping_names_by_app_id, options={}):
metric_by_type=objs_by_type,
extra_args=util.extra_args,
events_by_id=events_by_id,
+ objects_by_id=objects_by_id,
labeleds_by_id_by_type=labeleds_by_id_by_type,
submetric_bit=ID_BITS - ID_SIGNAL_BITS,
ping_names_by_app_id=ping_names_by_app_id,
diff --git a/toolkit/components/glean/build_scripts/glean_parser_ext/templates/cpp.jinja2 b/toolkit/components/glean/build_scripts/glean_parser_ext/templates/cpp.jinja2
index 2a4e40d6ac..3e9d573232 100644
--- a/toolkit/components/glean/build_scripts/glean_parser_ext/templates/cpp.jinja2
+++ b/toolkit/components/glean/build_scripts/glean_parser_ext/templates/cpp.jinja2
@@ -78,6 +78,7 @@ enum class DynamicLabel: uint16_t { };
{% for category_name, objs in all_objs.items() %}
namespace {{ category_name|snake_case }} {
{% for obj in objs.values() %}
+ {% if obj.type != "object" %}{# TODO(bug 1881023): Add C++ support #}
/**
* generated from {{ category_name }}.{{ obj.name }}
*/
@@ -91,6 +92,7 @@ namespace {{ category_name|snake_case }} {
* {{ obj.description|wordwrap() | replace('\n', '\n * ') }}
*/
constexpr impl::{{ obj|type_name }} {{obj.name|snake_case }}({{obj|metric_id}});
+ {% endif %}
{% endfor %}
}
diff --git a/toolkit/components/glean/build_scripts/glean_parser_ext/templates/jog_factory.jinja2 b/toolkit/components/glean/build_scripts/glean_parser_ext/templates/jog_factory.jinja2
index 4b7838a47d..a31bdbabf0 100644
--- a/toolkit/components/glean/build_scripts/glean_parser_ext/templates/jog_factory.jinja2
+++ b/toolkit/components/glean/build_scripts/glean_parser_ext/templates/jog_factory.jinja2
@@ -23,7 +23,9 @@ use crate::private::{
Ping,
LabeledMetric,
{% for metric_type_name in metric_types.keys() if not metric_type_name.startswith('labeled_') %}
+ {% if metric_type_name != "object" %}{# TODO(bug 1883857): Add JOG support #}
{{ metric_type_name|Camelize }}Metric,
+ {% endif %}
{% endfor %}};
use crate::private::traits::HistogramType;
@@ -50,7 +52,7 @@ pub(crate) mod __jog_metric_maps {
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
-{% for metric_type_name in metric_types.keys() if metric_type_name != "event" and not metric_type_name.startswith('labeled_') %}
+{% for metric_type_name in metric_types.keys() if metric_type_name != "event" and not metric_type_name.startswith('labeled_') and metric_type_name != "object" %}
pub static {{ metric_type_name.upper() }}_MAP: Lazy<Arc<RwLock<HashMap<MetricId, {{ metric_type_name|Camelize }}Metric>>>> =
Lazy::new(|| Arc::new(RwLock::new(HashMap::new())));
@@ -67,6 +69,10 @@ pub(crate) mod __jog_metric_maps {
{# Event metrics are special because they're EventMetric<K> #}
pub static EVENT_MAP: Lazy<Arc<RwLock<HashMap<MetricId, EventMetric<NoExtraKeys>>>>> =
Lazy::new(|| Arc::new(RwLock::new(HashMap::new())));
+{# Object metrics are special because they're ObjectMetric<K> #}
+ #[allow(dead_code)]
+ pub static OBJECT_MAP: Lazy<Arc<RwLock<HashMap<MetricId, ObjectMetric<()>>>>> =
+ Lazy::new(|| Arc::new(RwLock::new(HashMap::new())));
}
#[derive(Debug)]
@@ -105,6 +111,9 @@ map of argument name to argument type. I may regret this if I need it again. #}
let metric32 = match metric_type {
{% for metric_type_name, metric_type in metric_types.items() %}
"{{ metric_type_name }}" => {
+ {% if metric_type_name == "object" %}{# TODO(bug 1883857): Add JOG support #}
+ return Err(Box::new(MetricTypeNotFoundError(metric_type.to_string())));
+ {% else %}
let metric = {{ metric_type_name|Camelize if not metric_type_name.startswith('labeled_') else "Labeled"}}Metric::{% if metric_type_name == 'event' %}with_runtime_extra_keys{% else %}new{% endif %}(metric_id.into(), CommonMetricData {
{% for arg_name in common_metric_data_args if arg_name in metric_type.args %}
{{ arg_name }},
@@ -124,6 +133,7 @@ map of argument name to argument type. I may regret this if I need it again. #}
"We should never insert a runtime metric with an already-used id."
);
metric32
+ {% endif %}
}
{% endfor %}
_ => return Err(Box::new(MetricTypeNotFoundError(metric_type.to_string())))
@@ -137,10 +147,11 @@ pub fn create_and_register_ping(
include_client_id: bool,
send_if_empty: bool,
precise_timestamps: bool,
+ include_info_sections: bool,
reason_codes: Vec<String>,
) -> Result<u32, Box<dyn std::error::Error>> {
let ping_id = NEXT_PING_ID.fetch_add(1, Ordering::SeqCst);
- let ping = Ping::new(ping_name, include_client_id, send_if_empty, precise_timestamps, reason_codes);
+ let ping = Ping::new(ping_name, include_client_id, send_if_empty, precise_timestamps, include_info_sections, reason_codes);
assert!(
__jog_metric_maps::PING_MAP.write()?.insert(ping_id.into(), ping).is_none(),
"We should never insert a runtime ping with an already-used id."
diff --git a/toolkit/components/glean/build_scripts/glean_parser_ext/templates/ohttp.jinja2 b/toolkit/components/glean/build_scripts/glean_parser_ext/templates/ohttp.jinja2
new file mode 100644
index 0000000000..d6e1248021
--- /dev/null
+++ b/toolkit/components/glean/build_scripts/glean_parser_ext/templates/ohttp.jinja2
@@ -0,0 +1,13 @@
+// -*- mode: Rust -*-
+
+// AUTOGENERATED BY glean_parser. DO NOT EDIT.
+{# The rendered source is autogenerated, but this
+Jinja2 template is not. Please file bugs! #}
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+pub fn uses_ohttp(ping_name: &str) -> bool {
+ matches!(ping_name, {{ ohttp_pings|quote_and_join }})
+}
diff --git a/toolkit/components/glean/build_scripts/glean_parser_ext/templates/rust.jinja2 b/toolkit/components/glean/build_scripts/glean_parser_ext/templates/rust.jinja2
index cc29805099..5723ff5d58 100644
--- a/toolkit/components/glean/build_scripts/glean_parser_ext/templates/rust.jinja2
+++ b/toolkit/components/glean/build_scripts/glean_parser_ext/templates/rust.jinja2
@@ -8,6 +8,41 @@ Jinja2 template is not. Please file bugs! #}
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+{% macro generate_structure(name, struct) %}
+{% if struct.type == "array" %}
+ pub type {{ name }} = Vec<{{ name }}Item>;
+
+ {{ generate_structure(name ~ "Item", struct["items"]) -}}
+
+{% elif struct.type == "object" %}
+ #[derive(Debug, Hash, Eq, PartialEq, ::glean::traits::__serde::Serialize, ::glean::traits::__serde::Deserialize)]
+ #[serde(deny_unknown_fields)]
+ pub struct {{ name }} {
+ {% for itemname, val in struct.properties.items() %}
+ {% if val.type == "object" %}
+ pub {{itemname|snake_case}}: {{ name ~ "Item" ~ itemname|Camelize ~ "Object" }},
+ {% elif val.type == "array" %}
+ pub {{itemname|snake_case}}: {{ name ~ "Item" ~ itemname|Camelize }},
+ {% else %}
+ pub {{itemname|snake_case}}: Option<{{val.type|structure_type_name}}>,
+ {% endif %}
+ {% endfor %}
+ }
+
+ {% for itemname, val in struct.properties.items() %}
+ {% if val.type == "array" %}
+ {% set nested_name = name ~ "Item" ~ itemname|Camelize %}
+ {{ generate_structure(nested_name, val) -}}
+ {% elif val.type == "object" %}
+ {% set nested_name = name ~ "Item" ~ itemname|Camelize ~ "Object" %}
+ {{ generate_structure(nested_name, val) -}}
+ {% endif %}
+ {% endfor %}
+{% else %}
+pub type {{ name }} = {{ struct.type|structure_type_name }};
+{% endif %}
+{% endmacro %}
+
{% macro generate_extra_keys(obj) -%}
{% for name, _ in obj["_generate_enums"] %}
{# we always use the `extra` suffix, because we only expose the new event API #}
@@ -81,6 +116,9 @@ pub mod {{ category_name|snake_case }} {
use glean::HistogramType;
use once_cell::sync::Lazy;
+ #[allow(unused_imports)]
+ use std::convert::TryFrom;
+
{% for obj in objs.values() %}
{% if obj|attr("_generate_enums") %}
{{ generate_extra_keys(obj) }}
@@ -88,6 +126,9 @@ pub mod {{ category_name|snake_case }} {
{% if obj.labeled and obj.labels and obj.labels|length %}
{{ generate_label_enum(obj)|indent }}
{% endif %}
+ {% if obj|attr("_generate_structure") %}
+ {{ generate_structure(obj.name|Camelize ~ "Object", obj._generate_structure) -}}
+ {% endif %}
#[allow(non_upper_case_globals)]
/// generated from {{ category_name }}.{{ obj.name }}
///
@@ -148,6 +189,71 @@ pub(crate) mod __glean_metric_maps {
{% endfor %}
+ pub(crate) fn set_object_by_id(metric_id: u32, value: String) -> Result<(), ()> {
+ match metric_id {
+{% for metric_id, object in objects_by_id.items() %}
+ {{metric_id}} => {
+ super::{{object}}.set_string(value);
+ Ok(())
+ }
+{% endfor %}
+ _ => Err(()),
+ }
+ }
+
+ /// Wrapper to get the currently stored object for object metric as a string.
+ ///
+ /// # Arguments
+ ///
+ /// * `metric_id` - The metric's ID to look up
+ /// * `ping_name` - (Optional) The ping name to look into.
+ /// Defaults to the first value in `send_in_pings`.
+ ///
+ /// # Returns
+ ///
+ /// Returns the recorded object serialized as a JSON string or `None` if nothing stored.
+ ///
+ /// # Panics
+ ///
+ /// Panics if no object by the given metric ID could be found.
+ pub(crate) fn object_test_get_value(metric_id: u32, ping_name: Option<String>) -> Option<String> {
+ match metric_id {
+{% for metric_id, object in objects_by_id.items() %}
+ {{metric_id}} => super::{{object}}.test_get_value_as_str(ping_name.as_deref()),
+{% endfor %}
+ _ => panic!("No object for metric id {}", metric_id),
+ }
+ }
+
+ /// Check the provided object for errors.
+ ///
+ /// # Arguments
+ ///
+ /// * `metric_id` - The metric's ID to look up
+ ///
+ /// # Returns
+ ///
+ /// Returns a string for the recorded error or `None`.
+ ///
+ /// # Panics
+ ///
+ /// Panics if no object by the given metric ID could be found.
+ #[allow(unused_variables)]
+ pub(crate) fn object_test_get_error(metric_id: u32) -> Option<String> {
+ #[cfg(feature = "with_gecko")]
+ match metric_id {
+{% for metric_id, object in objects_by_id.items() %}
+ {{metric_id}} => test_get_errors!(super::{{object}}),
+{% endfor %}
+ _ => panic!("No object for metric id {}", metric_id),
+ }
+
+ #[cfg(not(feature = "with_gecko"))]
+ {
+ return None;
+ }
+ }
+
/// Wrapper to record an event based on its metric ID.
///
/// # Arguments
diff --git a/toolkit/components/glean/build_scripts/glean_parser_ext/templates/rust_pings.jinja2 b/toolkit/components/glean/build_scripts/glean_parser_ext/templates/rust_pings.jinja2
index c041f663a6..8afdae61ae 100644
--- a/toolkit/components/glean/build_scripts/glean_parser_ext/templates/rust_pings.jinja2
+++ b/toolkit/components/glean/build_scripts/glean_parser_ext/templates/rust_pings.jinja2
@@ -20,6 +20,7 @@ pub static {{ obj.name|snake_case }}: Lazy<Ping> = Lazy::new(|| {
{{ obj.include_client_id|rust }},
{{ obj.send_if_empty|rust }},
{{ obj.precise_timestamps|rust }},
+ {{ obj.include_info_sections|rust }},
{{ obj.reason_codes|rust }},
)
});
diff --git a/toolkit/components/glean/build_scripts/mach_commands.py b/toolkit/components/glean/build_scripts/mach_commands.py
index d385e53605..4a0f6dbc68 100644
--- a/toolkit/components/glean/build_scripts/mach_commands.py
+++ b/toolkit/components/glean/build_scripts/mach_commands.py
@@ -183,8 +183,13 @@ def update_glean(command_context, version):
)
replace_in_file_or_die(
topsrcdir / "gfx" / "wr" / "webrender" / "Cargo.toml",
- r'^glean = "[0-9.]+"',
- f'glean = "{version}"',
+ r'^glean = { version = "[0-9.]+"(.+)}',
+ f'glean = {{ version = "{version}"\\1}}',
+ )
+ replace_in_file_or_die(
+ topsrcdir / "gfx" / "wr" / "wr_glyph_rasterizer" / "Cargo.toml",
+ r'^glean = { version = "[0-9.]+"(.+)}',
+ f'glean = {{ version = "{version}"\\1}}',
)
replace_in_file_or_die(
topsrcdir / "python" / "sites" / "mach.txt",
@@ -193,13 +198,9 @@ def update_glean(command_context, version):
)
instructions = f"""
- We've edited most of the necessary files to require Glean SDK {version}.
-
- You will have to edit the following files yourself:
-
- gfx/wr/wr_glyph_rasterizer/Cargo.toml
+ We've edited the necessary files to require Glean SDK {version}.
- Then, to ensure Glean and Firefox's other Rust dependencies are appropriately vendored,
+ To ensure Glean and Firefox's other Rust dependencies are appropriately vendored,
please run the following commands:
cargo update -p glean
diff --git a/toolkit/components/glean/metrics_index.py b/toolkit/components/glean/metrics_index.py
index aa7eea6645..8e20658810 100644
--- a/toolkit/components/glean/metrics_index.py
+++ b/toolkit/components/glean/metrics_index.py
@@ -22,6 +22,7 @@ gecko_metrics = [
"dom/media/webrtc/metrics.yaml",
"dom/metrics.yaml",
"dom/performance/metrics.yaml",
+ "dom/security/metrics.yaml",
"gfx/metrics.yaml",
"image/decoders/metrics.yaml",
"js/xpconnect/metrics.yaml",
@@ -50,6 +51,7 @@ gecko_metrics = [
# Metrics that are sent by Firefox Desktop
# Order is lexicographical, enforced by t/c/glean/tests/pytest/test_yaml_indices.py
firefox_desktop_metrics = [
+ "browser/components/backup/metrics.yaml",
"browser/components/metrics.yaml",
"browser/components/migration/metrics.yaml",
"browser/components/newtab/metrics.yaml",
@@ -124,6 +126,7 @@ firefox_desktop_pings = [
"browser/components/search/pings.yaml",
"browser/components/urlbar/pings.yaml",
"toolkit/components/crashes/pings.yaml",
+ "toolkit/components/resistfingerprinting/pings.yaml",
"toolkit/components/telemetry/pings.yaml",
"toolkit/modules/pings.yaml",
]
diff --git a/toolkit/components/glean/moz.build b/toolkit/components/glean/moz.build
index d6876167e6..df76dbcbc4 100644
--- a/toolkit/components/glean/moz.build
+++ b/toolkit/components/glean/moz.build
@@ -42,6 +42,7 @@ EXPORTS.mozilla.glean.bindings += [
"bindings/private/Labeled.h",
"bindings/private/MemoryDistribution.h",
"bindings/private/Numerator.h",
+ "bindings/private/Object.h",
"bindings/private/Ping.h",
"bindings/private/Quantity.h",
"bindings/private/Rate.h",
@@ -88,6 +89,7 @@ UNIFIED_SOURCES += [
"bindings/private/Labeled.cpp",
"bindings/private/MemoryDistribution.cpp",
"bindings/private/Numerator.cpp",
+ "bindings/private/Object.cpp",
"bindings/private/Ping.cpp",
"bindings/private/Quantity.cpp",
"bindings/private/Rate.cpp",
@@ -197,6 +199,17 @@ if CONFIG["MOZ_ARTIFACT_BUILDS"]:
# Once generated, it needs to be placed in GreD so it can be found.
FINAL_TARGET_FILES += ["!jogfile.json"]
+# OHTTP support requires the fog_control crate know which pings wish to be sent
+# using OHTTP. fog_control has no access to the firefox_on_glean crate, so it
+# needs its own codegen.
+GeneratedFile(
+ "src/ohttp_pings.rs",
+ script="build_scripts/glean_parser_ext/run_glean_parser.py",
+ entry_point="ohttp_pings",
+ flags=[CONFIG["MOZ_APP_VERSION"]],
+ inputs=pings_yamls + tags_yamls,
+)
+
DIRS += [
"tests", # Must be in DIRS, not TEST_DIRS or python-test won't find it.
"xpcom",
diff --git a/toolkit/components/glean/pings.yaml b/toolkit/components/glean/pings.yaml
index d4710a47c2..22c49aab7e 100644
--- a/toolkit/components/glean/pings.yaml
+++ b/toolkit/components/glean/pings.yaml
@@ -6,8 +6,9 @@
# automatically converted to platform-specific code at build time using the
# `glean_parser` PyPI package.
-# This file is presently for Internal FOG Use Only.
-# You should not add pings here until probably about January of 2021.
+# This file is for Internal FOG Use Only.
+# You probably don't want to add pings here. For more information consult:
+# https://firefox-source-docs.mozilla.org/toolkit/components/glean/user/new_definitions_file.html
---
$schema: moz://mozilla.org/schemas/glean/pings/2-0-0
diff --git a/toolkit/components/glean/src/init/viaduct_uploader.rs b/toolkit/components/glean/src/init/viaduct_uploader.rs
index d9ce4e0488..a009e664bc 100644
--- a/toolkit/components/glean/src/init/viaduct_uploader.rs
+++ b/toolkit/components/glean/src/init/viaduct_uploader.rs
@@ -2,7 +2,9 @@
// 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/.
-use glean::net::{PingUploader, UploadResult};
+use glean::net::{PingUploadRequest, PingUploader, UploadResult};
+use once_cell::sync::OnceCell;
+use std::sync::Once;
use url::Url;
use viaduct::{Error::*, Request};
@@ -19,55 +21,176 @@ impl PingUploader for ViaductUploader {
///
/// # Arguments
///
- /// * `url` - the URL path to upload the data to.
- /// * `body` - the serialized text data to send.
- /// * `headers` - a vector of tuples containing the headers to send with
- /// the request, i.e. (Name, Value).
- fn upload(&self, url: String, body: Vec<u8>, headers: Vec<(String, String)>) -> UploadResult {
- log::trace!("FOG Ping Uploader uploading to {}", url);
- let url_clone = url.clone();
- let result: std::result::Result<UploadResult, viaduct::Error> = (move || {
- // SAFETY NOTE: Safe because it returns a primitive by value.
- if unsafe { FOG_TooLateToSend() } {
- log::trace!("Attempted to send ping too late into shutdown.");
- return Ok(UploadResult::done());
- }
- let debug_tagged = headers.iter().any(|(name, _)| name == "X-Debug-ID");
- let localhost_port = static_prefs::pref!("telemetry.fog.test.localhost_port");
- if localhost_port < 0
- || (localhost_port == 0 && !debug_tagged && cfg!(feature = "disable_upload"))
- {
- log::info!("FOG Ping uploader faking success");
- return Ok(UploadResult::http_status(200));
- }
- let parsed_url = Url::parse(&url_clone)?;
-
- log::info!("FOG Ping uploader uploading to {:?}", parsed_url);
-
- let mut req = Request::post(parsed_url.clone()).body(body.clone());
- for (header_key, header_value) in &headers {
- req = req.header(header_key.to_owned(), header_value)?;
- }
-
- log::trace!("FOG Ping Uploader sending ping to {}", parsed_url);
- let res = req.send()?;
- Ok(UploadResult::http_status(res.status as i32))
- })();
+ /// * `upload_request` - the ping and its metadata to upload.
+ fn upload(&self, upload_request: PingUploadRequest) -> UploadResult {
+ log::trace!("FOG Ping Uploader uploading to {}", upload_request.url);
+
+ // SAFETY NOTE: Safe because it returns a primitive by value.
+ if unsafe { FOG_TooLateToSend() } {
+ log::trace!("Attempted to send ping too late into shutdown.");
+ return UploadResult::done();
+ }
+
+ let debug_tagged = upload_request
+ .headers
+ .iter()
+ .any(|(name, _)| name == "X-Debug-ID");
+ let localhost_port = static_prefs::pref!("telemetry.fog.test.localhost_port");
+ if localhost_port < 0
+ || (localhost_port == 0 && !debug_tagged && cfg!(feature = "disable_upload"))
+ {
+ log::info!("FOG Ping uploader faking success");
+ return UploadResult::http_status(200);
+ }
+
+ // Localhost-destined pings are sent without OHTTP,
+ // even if configured to use OHTTP.
+ let result = if localhost_port == 0 && should_ohttp_upload(&upload_request) {
+ ohttp_upload(upload_request)
+ } else {
+ viaduct_upload(upload_request)
+ };
+
log::trace!(
- "FOG Ping Uploader completed uploading to {} (Result {:?})",
- url,
+ "FOG Ping Uploader completed uploading (Result {:?})",
result
);
+
match result {
Ok(result) => result,
- Err(NonTlsUrl | UrlError(_)) => UploadResult::unrecoverable_failure(),
- Err(
+ Err(ViaductUploaderError::Viaduct(ve)) => match ve {
+ NonTlsUrl | UrlError(_) => UploadResult::unrecoverable_failure(),
RequestHeaderError(_)
| BackendError(_)
| NetworkError(_)
| BackendNotInitialized
- | SetBackendError,
- ) => UploadResult::recoverable_failure(),
+ | SetBackendError => UploadResult::recoverable_failure(),
+ },
+ Err(
+ ViaductUploaderError::Bhttp(_)
+ | ViaductUploaderError::Ohttp(_)
+ | ViaductUploaderError::Fatal,
+ ) => UploadResult::unrecoverable_failure(),
}
}
}
+
+fn viaduct_upload(upload_request: PingUploadRequest) -> Result<UploadResult, ViaductUploaderError> {
+ let parsed_url = Url::parse(&upload_request.url)?;
+
+ log::info!("FOG viaduct uploader uploading to {:?}", parsed_url);
+
+ let mut req = Request::post(parsed_url.clone()).body(upload_request.body);
+ for (header_key, header_value) in &upload_request.headers {
+ req = req.header(header_key.to_owned(), header_value)?;
+ }
+
+ log::trace!("FOG viaduct uploader sending ping to {:?}", parsed_url);
+ let res = req.send()?;
+ Ok(UploadResult::http_status(res.status as i32))
+}
+
+fn should_ohttp_upload(upload_request: &PingUploadRequest) -> bool {
+ crate::ohttp_pings::uses_ohttp(&upload_request.ping_name)
+ && !upload_request.body_has_info_sections
+}
+
+fn ohttp_upload(upload_request: PingUploadRequest) -> Result<UploadResult, ViaductUploaderError> {
+ static CELL: OnceCell<Vec<u8>> = once_cell::sync::OnceCell::new();
+ let config = CELL.get_or_try_init(|| get_config())?;
+
+ let binary_request = bhttp_encode(upload_request)?;
+
+ static OHTTP_INIT: Once = Once::new();
+ OHTTP_INIT.call_once(|| {
+ ohttp::init();
+ });
+
+ let ohttp_request = ohttp::ClientRequest::new(config)?;
+ let (capsule, ohttp_response) = ohttp_request.encapsulate(&binary_request)?;
+
+ const OHTTP_RELAY_URL: &str = "https://mozilla-ohttp.fastly-edge.com/";
+ let parsed_relay_url = Url::parse(OHTTP_RELAY_URL)?;
+
+ log::trace!("FOG ohttp uploader uploading to {}", parsed_relay_url);
+
+ const OHTTP_MESSAGE_CONTENT_TYPE: &str = "message/ohttp-req";
+ let req = Request::post(parsed_relay_url)
+ .header(
+ viaduct::header_names::CONTENT_TYPE,
+ OHTTP_MESSAGE_CONTENT_TYPE,
+ )?
+ .body(capsule);
+ let res = req.send()?;
+
+ if res.status == 200 {
+ // This just tells us the HTTP went well. Check OHTTP's status.
+ let binary_response = ohttp_response.decapsulate(&res.body)?;
+ let mut cursor = std::io::Cursor::new(binary_response);
+ let bhttp_message = bhttp::Message::read_bhttp(&mut cursor)?;
+ let res = bhttp_message
+ .control()
+ .status()
+ .ok_or(ViaductUploaderError::Fatal)?;
+ Ok(UploadResult::http_status(res as i32))
+ } else {
+ Ok(UploadResult::http_status(res.status as i32))
+ }
+}
+
+fn get_config() -> Result<Vec<u8>, ViaductUploaderError> {
+ const OHTTP_CONFIG_URL: &str =
+ "https://prod.ohttp-gateway.prod.webservices.mozgcp.net/ohttp-configs";
+ log::trace!("Getting OHTTP config from {}", OHTTP_CONFIG_URL);
+ let parsed_config_url = Url::parse(OHTTP_CONFIG_URL)?;
+ Ok(Request::get(parsed_config_url).send()?.body)
+}
+
+/// Encode the ping upload request in binary HTTP.
+/// (draft-ietf-httpbis-binary-message)
+fn bhttp_encode(upload_request: PingUploadRequest) -> Result<Vec<u8>, ViaductUploaderError> {
+ let parsed_url = Url::parse(&upload_request.url)?;
+ let mut message = bhttp::Message::request(
+ "POST".into(),
+ parsed_url.scheme().into(),
+ parsed_url
+ .host_str()
+ .ok_or(ViaductUploaderError::Fatal)?
+ .into(),
+ parsed_url.path().into(),
+ );
+
+ upload_request
+ .headers
+ .into_iter()
+ .for_each(|(k, v)| message.put_header(k, v));
+
+ message.write_content(upload_request.body);
+
+ let mut encoded = vec![];
+ message.write_bhttp(bhttp::Mode::KnownLength, &mut encoded)?;
+
+ Ok(encoded)
+}
+
+/// Unioned error across upload backends.
+#[derive(Debug, thiserror::Error)]
+enum ViaductUploaderError {
+ #[error("bhttp::Error {0}")]
+ Bhttp(#[from] bhttp::Error),
+
+ #[error("ohttp::Error {0}")]
+ Ohttp(#[from] ohttp::Error),
+
+ #[error("viaduct::Error {0}")]
+ Viaduct(#[from] viaduct::Error),
+
+ #[error("Fatal upload error")]
+ Fatal,
+}
+
+impl From<url::ParseError> for ViaductUploaderError {
+ fn from(e: url::ParseError) -> Self {
+ ViaductUploaderError::Viaduct(viaduct::Error::from(e))
+ }
+}
diff --git a/toolkit/components/glean/src/lib.rs b/toolkit/components/glean/src/lib.rs
index 79f3258bb7..b682f43066 100644
--- a/toolkit/components/glean/src/lib.rs
+++ b/toolkit/components/glean/src/lib.rs
@@ -28,6 +28,7 @@ extern crate cstr;
extern crate xpcom;
mod init;
+mod ohttp_pings;
pub use init::fog_init;
@@ -46,6 +47,7 @@ static mut PENDING_BUF: Vec<u8> = Vec::new();
// IPC serialization/deserialization methods
// Crucially important that the first two not be called on multiple threads.
+/// # Safety
/// Only safe if only called on a single thread (the same single thread you call
/// fog_give_ipc_buf on).
#[no_mangle]
@@ -59,6 +61,7 @@ pub unsafe extern "C" fn fog_serialize_ipc_buf() -> usize {
}
}
+/// # Safety
/// Only safe if called on a single thread (the same single thread you call
/// fog_serialize_ipc_buf on), and if buf points to an allocated buffer of at
/// least buf_len bytes.
@@ -73,6 +76,7 @@ pub unsafe extern "C" fn fog_give_ipc_buf(buf: *mut u8, buf_len: usize) -> usize
pending_len
}
+/// # Safety
/// Only safe if buf points to an allocated buffer of at least buf_len bytes.
/// No ownership is transfered to Rust by this method: caller owns the memory at
/// buf before and after this call.
@@ -92,9 +96,9 @@ pub unsafe extern "C" fn fog_use_ipc_buf(buf: *const u8, buf_len: usize) {
pub extern "C" fn fog_set_debug_view_tag(value: &nsACString) -> nsresult {
let result = glean::set_debug_view_tag(&value.to_string());
if result {
- return NS_OK;
+ NS_OK
} else {
- return NS_ERROR_FAILURE;
+ NS_ERROR_FAILURE
}
}
@@ -137,7 +141,7 @@ pub extern "C" fn fog_set_experiment_active(
extra_values.len(),
"Experiment extra keys and values differ in length."
);
- let extra = if extra_keys.len() == 0 {
+ let extra = if extra_keys.is_empty() {
None
} else {
Some(
diff --git a/toolkit/components/glean/src/ohttp_pings.rs b/toolkit/components/glean/src/ohttp_pings.rs
new file mode 100644
index 0000000000..71b9512a8b
--- /dev/null
+++ b/toolkit/components/glean/src/ohttp_pings.rs
@@ -0,0 +1,13 @@
+// This Source Code Form is subject to the terms of 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/.
+
+//! This file contains the generated logic for ohttp pings.
+//!
+//! The contents of this module are generated by
+//! `toolkit/components/glean/build_scripts/glean_parser_ext/run_glean_parser.py`, from
+//! ping definitions files identified by `toolkit/components/glean/metrics_index.py`.
+
+include!(mozbuild::objdir_path!(
+ "toolkit/components/glean/src/ohttp_pings.rs"
+));
diff --git a/toolkit/components/glean/tags.yaml b/toolkit/components/glean/tags.yaml
index 9c6c09bb9c..c935371b28 100644
--- a/toolkit/components/glean/tags.yaml
+++ b/toolkit/components/glean/tags.yaml
@@ -35,6 +35,8 @@ $schema: moz://mozilla.org/schemas/glean/tags/1-0-0
description: The Bugzilla component which applies to this object.
'Core :: Cycle Collector':
description: The Bugzilla component which applies to this object.
+'Core :: DLL Services':
+ description: The Bugzilla component which applies to this object.
'Core :: DOM: Animation':
description: The Bugzilla component which applies to this object.
'Core :: DOM: Bindings (WebIDL)':
@@ -95,8 +97,6 @@ $schema: moz://mozilla.org/schemas/glean/tags/1-0-0
description: The Bugzilla component which applies to this object.
'Core :: Disability Access APIs':
description: The Bugzilla component which applies to this object.
-'Core :: Document Navigation':
- description: The Bugzilla component which applies to this object.
'Core :: Gecko Profiler':
description: The Bugzilla component which applies to this object.
'Core :: General':
@@ -119,6 +119,8 @@ $schema: moz://mozilla.org/schemas/glean/tags/1-0-0
description: The Bugzilla component which applies to this object.
'Core :: IPC':
description: The Bugzilla component which applies to this object.
+'Core :: IPC: MSCOM':
+ description: The Bugzilla component which applies to this object.
'Core :: Internationalization':
description: The Bugzilla component which applies to this object.
'Core :: JavaScript Engine':
@@ -169,6 +171,8 @@ $schema: moz://mozilla.org/schemas/glean/tags/1-0-0
description: The Bugzilla component which applies to this object.
'Core :: MFBT':
description: The Bugzilla component which applies to this object.
+'Core :: Machine Learning':
+ description: The Bugzilla component which applies to this object.
'Core :: MathML':
description: The Bugzilla component which applies to this object.
'Core :: Memory Allocator':
@@ -211,6 +215,8 @@ $schema: moz://mozilla.org/schemas/glean/tags/1-0-0
description: The Bugzilla component which applies to this object.
'Core :: Security: Process Sandboxing':
description: The Bugzilla component which applies to this object.
+'Core :: Security: RLBox':
+ description: The Bugzilla component which applies to this object.
'Core :: Spelling checker':
description: The Bugzilla component which applies to this object.
'Core :: Storage: Cache API':
@@ -337,6 +343,8 @@ $schema: moz://mozilla.org/schemas/glean/tags/1-0-0
description: The Bugzilla component which applies to this object.
'Firefox :: Keyboard Navigation':
description: The Bugzilla component which applies to this object.
+'Firefox :: Launcher Process':
+ description: The Bugzilla component which applies to this object.
'Firefox :: Menus':
description: The Bugzilla component which applies to this object.
'Firefox :: Messaging System':
@@ -357,6 +365,8 @@ $schema: moz://mozilla.org/schemas/glean/tags/1-0-0
description: The Bugzilla component which applies to this object.
'Firefox :: Private Browsing':
description: The Bugzilla component which applies to this object.
+'Firefox :: Profiles':
+ description: The Bugzilla component which applies to this object.
'Firefox :: Protections UI':
description: The Bugzilla component which applies to this object.
'Firefox :: Remote Settings Client':
@@ -497,8 +507,6 @@ $schema: moz://mozilla.org/schemas/glean/tags/1-0-0
description: The Bugzilla component which applies to this object.
'Toolkit :: IOUtils and PathUtils':
description: The Bugzilla component which applies to this object.
-'Toolkit :: NSIS Installer':
- description: The Bugzilla component which applies to this object.
'Toolkit :: Password Manager':
description: The Bugzilla component which applies to this object.
'Toolkit :: Performance Monitoring':
@@ -525,14 +533,14 @@ $schema: moz://mozilla.org/schemas/glean/tags/1-0-0
description: The Bugzilla component which applies to this object.
'Toolkit :: Themes':
description: The Bugzilla component which applies to this object.
+'Toolkit :: UI Widgets':
+ description: The Bugzilla component which applies to this object.
'Toolkit :: UniFFI Bindings':
description: The Bugzilla component which applies to this object.
'Toolkit :: Video/Audio Controls':
description: The Bugzilla component which applies to this object.
'Toolkit :: View Source':
description: The Bugzilla component which applies to this object.
-'Toolkit :: UI Widgets':
- description: The Bugzilla component which applies to this object.
'Toolkit :: about:memory':
description: The Bugzilla component which applies to this object.
'Web Compatibility :: Tooling & Investigations':
diff --git a/toolkit/components/glean/tests/gtest/TestFog.cpp b/toolkit/components/glean/tests/gtest/TestFog.cpp
index 2de8f9ba4d..0c1621911e 100644
--- a/toolkit/components/glean/tests/gtest/TestFog.cpp
+++ b/toolkit/components/glean/tests/gtest/TestFog.cpp
@@ -181,6 +181,7 @@ TEST_F(FOGFixture, TestCppMemoryDistWorks) {
// Sum is in bytes, test_only::do_you_remember is in megabytes. So
// multiplication ahoy!
ASSERT_EQ(data.sum, 24UL * 1024 * 1024);
+ ASSERT_EQ(data.count, 2UL);
for (const auto& entry : data.values) {
const uint64_t bucket = entry.GetKey();
const uint64_t count = entry.GetData();
@@ -196,6 +197,7 @@ TEST_F(FOGFixture, TestCppCustomDistWorks) {
DistributionData data =
test_only_ipc::a_custom_dist.TestGetValue("store1"_ns).unwrap().ref();
ASSERT_EQ(data.sum, 7UL + 268435458);
+ ASSERT_EQ(data.count, 2UL);
for (const auto& entry : data.values) {
const uint64_t bucket = entry.GetKey();
const uint64_t count = entry.GetData();
@@ -253,6 +255,10 @@ TEST_F(FOGFixture, TestCppTimingDistWorks) {
DistributionData data =
test_only::what_time_is_it.TestGetValue().unwrap().ref();
+
+ // Cancelled timers should not increase count.
+ ASSERT_EQ(data.count, 2UL);
+
const uint64_t NANOS_IN_MILLIS = 1e6;
// bug 1701847 - Sleeps don't necessarily round up as you'd expect.
diff --git a/toolkit/components/glean/tests/pytest/gifft_output_Event b/toolkit/components/glean/tests/pytest/gifft_output_Event
index ba80e3300b..203dc19a62 100644
--- a/toolkit/components/glean/tests/pytest/gifft_output_Event
+++ b/toolkit/components/glean/tests/pytest/gifft_output_Event
@@ -26,10 +26,10 @@ using Telemetry::EventID;
static inline Maybe<EventID> EventIdForMetric(uint32_t aId) {
switch(aId) {
- case 17: { // test.nested.event_metric
+ case 18: { // test.nested.event_metric
return Some(EventID::EventMetric_EnumNames_AreStrange);
}
- case 18: { // test.nested.event_metric_with_extra
+ case 19: { // test.nested.event_metric_with_extra
return Some(EventID::EventMetric_EnumName_WithExtra);
}
default: {
diff --git a/toolkit/components/glean/tests/pytest/gifft_output_Scalar b/toolkit/components/glean/tests/pytest/gifft_output_Scalar
index 854455b584..9e1db3e84d 100644
--- a/toolkit/components/glean/tests/pytest/gifft_output_Scalar
+++ b/toolkit/components/glean/tests/pytest/gifft_output_Scalar
@@ -58,13 +58,13 @@ static inline Maybe<ScalarID> ScalarIdForMetric(uint32_t aId) {
case 14: { // test.timespan_metric
return Some(ScalarID::SOME_OTHER_UINT_SCALAR);
}
- case 16: { // test.nested.datetime_metric
+ case 17: { // test.nested.datetime_metric
return Some(ScalarID::SOME_STILL_OTHER_STRING_SCALAR);
}
- case 21: { // test.nested.quantity_metric
+ case 22: { // test.nested.quantity_metric
return Some(ScalarID::TELEMETRY_TEST_MIRROR_FOR_QUANTITY);
}
- case 24: { // test.nested.uuid_metric
+ case 25: { // test.nested.uuid_metric
return Some(ScalarID::SOME_OTHER_STRING_SCALAR);
}
default: {
diff --git a/toolkit/components/glean/tests/pytest/jogfile_output b/toolkit/components/glean/tests/pytest/jogfile_output
index d75c08c8ba..510b4995c5 100644
--- a/toolkit/components/glean/tests/pytest/jogfile_output
+++ b/toolkit/components/glean/tests/pytest/jogfile_output
@@ -189,6 +189,15 @@
],
"test.nested": [
[
+ "object",
+ "an_object",
+ [
+ "metrics"
+ ],
+ "ping",
+ false
+ ],
+ [
"datetime",
"datetime_metric",
[
@@ -303,6 +312,7 @@
true,
false,
true,
+ true,
[
"background",
"dirty_startup",
@@ -314,6 +324,7 @@
true,
true,
true,
+ true,
[]
],
[
@@ -321,6 +332,7 @@
true,
false,
true,
+ true,
[
"background",
"max_capacity",
@@ -332,6 +344,7 @@
true,
false,
true,
+ true,
[
"overdue",
"reschedule",
@@ -339,6 +352,14 @@
"tomorrow",
"upgrade"
]
+ ],
+ [
+ "not-ohttp",
+ false,
+ true,
+ true,
+ false,
+ []
]
]
} \ No newline at end of file
diff --git a/toolkit/components/glean/tests/pytest/metrics_test.yaml b/toolkit/components/glean/tests/pytest/metrics_test.yaml
index cbbb0220b5..6d9a4f8c0e 100644
--- a/toolkit/components/glean/tests/pytest/metrics_test.yaml
+++ b/toolkit/components/glean/tests/pytest/metrics_test.yaml
@@ -396,3 +396,23 @@ test.nested:
- https://bugzilla.mozilla.org/1635260/
data_reviews:
- https://example.com
+
+ an_object:
+ type: object
+ description: An example object
+ bugs:
+ - https://bugzilla.mozilla.org/1839640
+ data_reviews:
+ - http://example.com/reviews
+ notification_emails:
+ - CHANGE-ME@example.com
+ expires: never
+ structure:
+ type: array
+ items:
+ type: object
+ properties:
+ colour:
+ type: string
+ diameter:
+ type: number
diff --git a/toolkit/components/glean/tests/pytest/metrics_test_output b/toolkit/components/glean/tests/pytest/metrics_test_output
index 76808cc984..0f93eb032d 100644
--- a/toolkit/components/glean/tests/pytest/metrics_test_output
+++ b/toolkit/components/glean/tests/pytest/metrics_test_output
@@ -6,6 +6,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
pub enum DynamicLabel { }
pub mod test {
@@ -16,6 +17,9 @@ pub mod test {
use glean::HistogramType;
use once_cell::sync::Lazy;
+ #[allow(unused_imports)]
+ use std::convert::TryFrom;
+
#[allow(non_upper_case_globals)]
/// generated from test.boolean_metric
///
@@ -361,13 +365,40 @@ pub mod test_nested {
use glean::HistogramType;
use once_cell::sync::Lazy;
+ #[allow(unused_imports)]
+ use std::convert::TryFrom;
+
+ pub type AnObjectObject = Vec<AnObjectObjectItem>;
+
+ #[derive(Debug, Hash, Eq, PartialEq, ::glean::traits::__serde::Serialize, ::glean::traits::__serde::Deserialize)]
+ #[serde(deny_unknown_fields)]
+ pub struct AnObjectObjectItem {
+ pub colour: Option<String>,
+ pub diameter: Option<i64>,
+ }
+
+ #[allow(non_upper_case_globals)]
+ /// generated from test.nested.an_object
+ ///
+ /// An example object
+ pub static an_object: Lazy<ObjectMetric<AnObjectObject>> = Lazy::new(|| {
+ ObjectMetric::new(16.into(), CommonMetricData {
+ name: "an_object".into(),
+ category: "test.nested".into(),
+ send_in_pings: vec!["metrics".into()],
+ lifetime: Lifetime::Ping,
+ disabled: false,
+ ..Default::default()
+ })
+ });
+
#[allow(non_upper_case_globals)]
/// generated from test.nested.datetime_metric
///
/// A multi-line
/// description
pub static datetime_metric: Lazy<DatetimeMetric> = Lazy::new(|| {
- DatetimeMetric::new(16.into(), CommonMetricData {
+ DatetimeMetric::new(17.into(), CommonMetricData {
name: "datetime_metric".into(),
category: "test.nested".into(),
send_in_pings: vec!["metrics".into()],
@@ -383,7 +414,7 @@ pub mod test_nested {
/// A multi-line
/// description
pub static event_metric: Lazy<EventMetric<NoExtraKeys>> = Lazy::new(|| {
- EventMetric::new(17.into(), CommonMetricData {
+ EventMetric::new(18.into(), CommonMetricData {
name: "event_metric".into(),
category: "test.nested".into(),
send_in_pings: vec!["events".into()],
@@ -415,7 +446,7 @@ pub mod test_nested {
/// A multi-line
/// description
pub static event_metric_with_extra: Lazy<EventMetric<EventMetricWithExtraExtra>> = Lazy::new(|| {
- EventMetric::new(18.into(), CommonMetricData {
+ EventMetric::new(19.into(), CommonMetricData {
name: "event_metric_with_extra".into(),
category: "test.nested".into(),
send_in_pings: vec!["events".into()],
@@ -431,7 +462,7 @@ pub mod test_nested {
/// A multi-line
/// description
pub static external_denominator: Lazy<DenominatorMetric> = Lazy::new(|| {
- DenominatorMetric::new(19.into(), CommonMetricData {
+ DenominatorMetric::new(20.into(), CommonMetricData {
name: "external_denominator".into(),
category: "test.nested".into(),
send_in_pings: vec!["metrics".into()],
@@ -448,7 +479,7 @@ pub mod test_nested {
/// description
pub static optimizable_counter_metric: Lazy<CounterMetric> = Lazy::new(|| {
CounterMetric::codegen_new(
- 20,
+ 21,
"test.nested",
"optimizable_counter_metric",
"metrics"
@@ -461,7 +492,7 @@ pub mod test_nested {
/// A multi-line
/// description
pub static quantity_metric: Lazy<QuantityMetric> = Lazy::new(|| {
- QuantityMetric::new(21.into(), CommonMetricData {
+ QuantityMetric::new(22.into(), CommonMetricData {
name: "quantity_metric".into(),
category: "test.nested".into(),
send_in_pings: vec!["metrics".into()],
@@ -477,7 +508,7 @@ pub mod test_nested {
/// A multi-line
/// description
pub static rate_metric: Lazy<RateMetric> = Lazy::new(|| {
- RateMetric::new(22.into(), CommonMetricData {
+ RateMetric::new(23.into(), CommonMetricData {
name: "rate_metric".into(),
category: "test.nested".into(),
send_in_pings: vec!["metrics".into()],
@@ -493,7 +524,7 @@ pub mod test_nested {
/// A multi-line
/// description
pub static rate_with_external_denominator: Lazy<NumeratorMetric> = Lazy::new(|| {
- NumeratorMetric::new(23.into(), CommonMetricData {
+ NumeratorMetric::new(24.into(), CommonMetricData {
name: "rate_with_external_denominator".into(),
category: "test.nested".into(),
send_in_pings: vec!["metrics".into()],
@@ -509,7 +540,7 @@ pub mod test_nested {
/// A multi-line
/// description
pub static uuid_metric: Lazy<UuidMetric> = Lazy::new(|| {
- UuidMetric::new(24.into(), CommonMetricData {
+ UuidMetric::new(25.into(), CommonMetricData {
name: "uuid_metric".into(),
category: "test.nested".into(),
send_in_pings: vec!["metrics".into()],
@@ -538,7 +569,7 @@ pub(crate) mod __glean_metric_maps {
pub static COUNTER_MAP: Lazy<HashMap<MetricId, &Lazy<CounterMetric>>> = Lazy::new(|| {
let mut map = HashMap::with_capacity(2);
map.insert(2.into(), &super::test::counter_metric);
- map.insert(20.into(), &super::test_nested::optimizable_counter_metric);
+ map.insert(21.into(), &super::test_nested::optimizable_counter_metric);
map
});
@@ -586,41 +617,100 @@ pub(crate) mod __glean_metric_maps {
pub static DATETIME_MAP: Lazy<HashMap<MetricId, &Lazy<DatetimeMetric>>> = Lazy::new(|| {
let mut map = HashMap::with_capacity(1);
- map.insert(16.into(), &super::test_nested::datetime_metric);
+ map.insert(17.into(), &super::test_nested::datetime_metric);
map
});
pub static DENOMINATOR_MAP: Lazy<HashMap<MetricId, &Lazy<DenominatorMetric>>> = Lazy::new(|| {
let mut map = HashMap::with_capacity(1);
- map.insert(19.into(), &super::test_nested::external_denominator);
+ map.insert(20.into(), &super::test_nested::external_denominator);
map
});
pub static QUANTITY_MAP: Lazy<HashMap<MetricId, &Lazy<QuantityMetric>>> = Lazy::new(|| {
let mut map = HashMap::with_capacity(1);
- map.insert(21.into(), &super::test_nested::quantity_metric);
+ map.insert(22.into(), &super::test_nested::quantity_metric);
map
});
pub static RATE_MAP: Lazy<HashMap<MetricId, &Lazy<RateMetric>>> = Lazy::new(|| {
let mut map = HashMap::with_capacity(1);
- map.insert(22.into(), &super::test_nested::rate_metric);
+ map.insert(23.into(), &super::test_nested::rate_metric);
map
});
pub static NUMERATOR_MAP: Lazy<HashMap<MetricId, &Lazy<NumeratorMetric>>> = Lazy::new(|| {
let mut map = HashMap::with_capacity(1);
- map.insert(23.into(), &super::test_nested::rate_with_external_denominator);
+ map.insert(24.into(), &super::test_nested::rate_with_external_denominator);
map
});
pub static UUID_MAP: Lazy<HashMap<MetricId, &Lazy<UuidMetric>>> = Lazy::new(|| {
let mut map = HashMap::with_capacity(1);
- map.insert(24.into(), &super::test_nested::uuid_metric);
+ map.insert(25.into(), &super::test_nested::uuid_metric);
map
});
+ pub(crate) fn set_object_by_id(metric_id: u32, value: String) -> Result<(), ()> {
+ match metric_id {
+ 16 => {
+ super::test_nested::an_object.set_string(value);
+ Ok(())
+ }
+ _ => Err(()),
+ }
+ }
+
+ /// Wrapper to get the currently stored object for object metric as a string.
+ ///
+ /// # Arguments
+ ///
+ /// * `metric_id` - The metric's ID to look up
+ /// * `ping_name` - (Optional) The ping name to look into.
+ /// Defaults to the first value in `send_in_pings`.
+ ///
+ /// # Returns
+ ///
+ /// Returns the recorded object serialized as a JSON string or `None` if nothing stored.
+ ///
+ /// # Panics
+ ///
+ /// Panics if no object by the given metric ID could be found.
+ pub(crate) fn object_test_get_value(metric_id: u32, ping_name: Option<String>) -> Option<String> {
+ match metric_id {
+ 16 => super::test_nested::an_object.test_get_value_as_str(ping_name.as_deref()),
+ _ => panic!("No object for metric id {}", metric_id),
+ }
+ }
+
+ /// Check the provided object for errors.
+ ///
+ /// # Arguments
+ ///
+ /// * `metric_id` - The metric's ID to look up
+ ///
+ /// # Returns
+ ///
+ /// Returns a string for the recorded error or `None`.
+ ///
+ /// # Panics
+ ///
+ /// Panics if no object by the given metric ID could be found.
+ #[allow(unused_variables)]
+ pub(crate) fn object_test_get_error(metric_id: u32) -> Option<String> {
+ #[cfg(feature = "with_gecko")]
+ match metric_id {
+ 16 => test_get_errors!(super::test_nested::an_object),
+ _ => panic!("No object for metric id {}", metric_id),
+ }
+
+ #[cfg(not(feature = "with_gecko"))]
+ {
+ return None;
+ }
+ }
+
/// Wrapper to record an event based on its metric ID.
///
/// # Arguments
@@ -635,7 +725,7 @@ pub(crate) mod __glean_metric_maps {
/// or an `EventRecordingError::InvalidExtraKey` if the `extra` map could not be deserialized.
pub(crate) fn record_event_by_id(metric_id: u32, extra: HashMap<String, String>) -> Result<(), EventRecordingError> {
match metric_id {
- 17 => {
+ 18 => {
assert!(
extra_keys_len(&super::test_nested::event_metric) != 0 || extra.is_empty(),
"No extra keys allowed, but some were passed"
@@ -644,7 +734,7 @@ pub(crate) mod __glean_metric_maps {
super::test_nested::event_metric.record_raw(extra);
Ok(())
}
- 18 => {
+ 19 => {
assert!(
extra_keys_len(&super::test_nested::event_metric_with_extra) != 0 || extra.is_empty(),
"No extra keys allowed, but some were passed"
@@ -673,7 +763,7 @@ pub(crate) mod __glean_metric_maps {
/// but some are passed in.
pub(crate) fn record_event_by_id_with_time(metric_id: MetricId, timestamp: u64, extra: HashMap<String, String>) -> Result<(), EventRecordingError> {
match metric_id {
- MetricId(17) => {
+ MetricId(18) => {
if extra_keys_len(&super::test_nested::event_metric) == 0 && !extra.is_empty() {
return Err(EventRecordingError::InvalidExtraKey);
}
@@ -681,7 +771,7 @@ pub(crate) mod __glean_metric_maps {
super::test_nested::event_metric.record_with_time(timestamp, extra);
Ok(())
}
- MetricId(18) => {
+ MetricId(19) => {
if extra_keys_len(&super::test_nested::event_metric_with_extra) == 0 && !extra.is_empty() {
return Err(EventRecordingError::InvalidExtraKey);
}
@@ -710,8 +800,8 @@ pub(crate) mod __glean_metric_maps {
/// Panics if no event by the given metric ID could be found.
pub(crate) fn event_test_get_value_wrapper(metric_id: u32, ping_name: Option<String>) -> Option<Vec<RecordedEvent>> {
match metric_id {
- 17 => super::test_nested::event_metric.test_get_value(ping_name.as_deref()),
- 18 => super::test_nested::event_metric_with_extra.test_get_value(ping_name.as_deref()),
+ 18 => super::test_nested::event_metric.test_get_value(ping_name.as_deref()),
+ 19 => super::test_nested::event_metric_with_extra.test_get_value(ping_name.as_deref()),
_ => panic!("No event for metric id {}", metric_id),
}
}
@@ -735,8 +825,8 @@ pub(crate) mod __glean_metric_maps {
pub(crate) fn event_test_get_error(metric_id: u32) -> Option<String> {
#[cfg(feature = "with_gecko")]
match metric_id {
- 17 => test_get_errors!(super::test_nested::event_metric),
- 18 => test_get_errors!(super::test_nested::event_metric_with_extra),
+ 18 => test_get_errors!(super::test_nested::event_metric),
+ 19 => test_get_errors!(super::test_nested::event_metric_with_extra),
_ => panic!("No event for metric id {}", metric_id),
}
diff --git a/toolkit/components/glean/tests/pytest/metrics_test_output_cpp b/toolkit/components/glean/tests/pytest/metrics_test_output_cpp
index 5b9c7bfae4..615e1c857d 100644
--- a/toolkit/components/glean/tests/pytest/metrics_test_output_cpp
+++ b/toolkit/components/glean/tests/pytest/metrics_test_output_cpp
@@ -181,6 +181,7 @@ namespace test {
}
namespace test_nested {
+
/**
* generated from test.nested.datetime_metric
*/
@@ -188,7 +189,7 @@ namespace test_nested {
* A multi-line
* description
*/
- constexpr impl::DatetimeMetric datetime_metric(16);
+ constexpr impl::DatetimeMetric datetime_metric(17);
/**
* generated from test.nested.event_metric
@@ -197,7 +198,7 @@ namespace test_nested {
* A multi-line
* description
*/
- constexpr impl::EventMetric<NoExtraKeys> event_metric(17);
+ constexpr impl::EventMetric<NoExtraKeys> event_metric(18);
/**
* generated from test.nested.event_metric_with_extra
@@ -224,7 +225,7 @@ namespace test_nested {
* A multi-line
* description
*/
- constexpr impl::EventMetric<EventMetricWithExtraExtra> event_metric_with_extra(18);
+ constexpr impl::EventMetric<EventMetricWithExtraExtra> event_metric_with_extra(19);
/**
* generated from test.nested.external_denominator
@@ -233,7 +234,7 @@ namespace test_nested {
* A multi-line
* description
*/
- constexpr impl::DenominatorMetric external_denominator(19);
+ constexpr impl::DenominatorMetric external_denominator(20);
/**
* generated from test.nested.optimizable_counter_metric
@@ -242,7 +243,7 @@ namespace test_nested {
* A multi-line
* description
*/
- constexpr impl::CounterMetric optimizable_counter_metric(20);
+ constexpr impl::CounterMetric optimizable_counter_metric(21);
/**
* generated from test.nested.quantity_metric
@@ -251,7 +252,7 @@ namespace test_nested {
* A multi-line
* description
*/
- constexpr impl::QuantityMetric quantity_metric(21);
+ constexpr impl::QuantityMetric quantity_metric(22);
/**
* generated from test.nested.rate_metric
@@ -260,7 +261,7 @@ namespace test_nested {
* A multi-line
* description
*/
- constexpr impl::RateMetric rate_metric(22);
+ constexpr impl::RateMetric rate_metric(23);
/**
* generated from test.nested.rate_with_external_denominator
@@ -269,7 +270,7 @@ namespace test_nested {
* A multi-line
* description
*/
- constexpr impl::NumeratorMetric rate_with_external_denominator(23);
+ constexpr impl::NumeratorMetric rate_with_external_denominator(24);
/**
* generated from test.nested.uuid_metric
@@ -278,7 +279,7 @@ namespace test_nested {
* A multi-line
* description
*/
- constexpr impl::UuidMetric uuid_metric(24);
+ constexpr impl::UuidMetric uuid_metric(25);
}
diff --git a/toolkit/components/glean/tests/pytest/metrics_test_output_js_cpp b/toolkit/components/glean/tests/pytest/metrics_test_output_js_cpp
index ba4619bb57..7cb5ce7d12 100644
--- a/toolkit/components/glean/tests/pytest/metrics_test_output_js_cpp
+++ b/toolkit/components/glean/tests/pytest/metrics_test_output_js_cpp
@@ -39,8 +39,8 @@ using metric_entry_t = uint64_t;
static_assert(GLEAN_INDEX_BITS + GLEAN_TYPE_BITS + GLEAN_ID_BITS == sizeof(metric_entry_t) * 8, "Index, Type, and ID bits need to fit into a metric_entry_t");
static_assert(GLEAN_TYPE_BITS + GLEAN_ID_BITS <= sizeof(uint32_t) * 8, "Metric Types and IDs need to fit into at most 32 bits");
static_assert(2 < UINT32_MAX, "Too many metric categories generated.");
-static_assert(24 < 33554432, "Too many metrics generated. Need room for 2 signal bits.");
-static_assert(19 < 32, "Too many different metric types.");
+static_assert(25 < 33554432, "Too many metrics generated. Need room for 2 signal bits.");
+static_assert(20 < 32, "Too many different metric types.");
already_AddRefed<GleanMetric> NewMetricFromId(uint32_t id, nsISupports* aParent) {
uint32_t typeId = GLEAN_TYPE_ID(id);
@@ -95,31 +95,35 @@ already_AddRefed<GleanMetric> NewMetricFromId(uint32_t id, nsISupports* aParent)
{
return MakeAndAddRef<GleanTimingDistribution>(metricId, aParent);
}
- case 13: /* datetime */
+ case 13: /* object */
+ {
+ return MakeAndAddRef<GleanObject>(metricId, aParent);
+ }
+ case 14: /* datetime */
{
return MakeAndAddRef<GleanDatetime>(metricId, aParent);
}
- case 14: /* event */
+ case 15: /* event */
{
return MakeAndAddRef<GleanEvent>(metricId, aParent);
}
- case 15: /* denominator */
+ case 16: /* denominator */
{
return MakeAndAddRef<GleanDenominator>(metricId, aParent);
}
- case 16: /* quantity */
+ case 17: /* quantity */
{
return MakeAndAddRef<GleanQuantity>(metricId, aParent);
}
- case 17: /* rate */
+ case 18: /* rate */
{
return MakeAndAddRef<GleanRate>(metricId, aParent);
}
- case 18: /* numerator */
+ case 19: /* numerator */
{
return MakeAndAddRef<GleanNumerator>(metricId, aParent);
}
- case 19: /* uuid */
+ case 20: /* uuid */
{
return MakeAndAddRef<GleanUuid>(metricId, aParent);
}
@@ -289,45 +293,47 @@ constexpr char gMetricStringTable[] = {
/* 310 - "test.textMetric" */ 't', 'e', 's', 't', '.', 't', 'e', 'x', 't', 'M', 'e', 't', 'r', 'i', 'c', '\0',
/* 326 - "test.timespanMetric" */ 't', 'e', 's', 't', '.', 't', 'i', 'm', 'e', 's', 'p', 'a', 'n', 'M', 'e', 't', 'r', 'i', 'c', '\0',
/* 346 - "test.timingDistributionMetric" */ 't', 'e', 's', 't', '.', 't', 'i', 'm', 'i', 'n', 'g', 'D', 'i', 's', 't', 'r', 'i', 'b', 'u', 't', 'i', 'o', 'n', 'M', 'e', 't', 'r', 'i', 'c', '\0',
- /* 376 - "testNested.datetimeMetric" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'd', 'a', 't', 'e', 't', 'i', 'm', 'e', 'M', 'e', 't', 'r', 'i', 'c', '\0',
- /* 402 - "testNested.eventMetric" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'e', 'v', 'e', 'n', 't', 'M', 'e', 't', 'r', 'i', 'c', '\0',
- /* 425 - "testNested.eventMetricWithExtra" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'e', 'v', 'e', 'n', 't', 'M', 'e', 't', 'r', 'i', 'c', 'W', 'i', 't', 'h', 'E', 'x', 't', 'r', 'a', '\0',
- /* 457 - "testNested.externalDenominator" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'e', 'x', 't', 'e', 'r', 'n', 'a', 'l', 'D', 'e', 'n', 'o', 'm', 'i', 'n', 'a', 't', 'o', 'r', '\0',
- /* 488 - "testNested.optimizableCounterMetric" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'o', 'p', 't', 'i', 'm', 'i', 'z', 'a', 'b', 'l', 'e', 'C', 'o', 'u', 'n', 't', 'e', 'r', 'M', 'e', 't', 'r', 'i', 'c', '\0',
- /* 524 - "testNested.quantityMetric" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'q', 'u', 'a', 'n', 't', 'i', 't', 'y', 'M', 'e', 't', 'r', 'i', 'c', '\0',
- /* 550 - "testNested.rateMetric" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'r', 'a', 't', 'e', 'M', 'e', 't', 'r', 'i', 'c', '\0',
- /* 572 - "testNested.rateWithExternalDenominator" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'r', 'a', 't', 'e', 'W', 'i', 't', 'h', 'E', 'x', 't', 'e', 'r', 'n', 'a', 'l', 'D', 'e', 'n', 'o', 'm', 'i', 'n', 'a', 't', 'o', 'r', '\0',
- /* 611 - "testNested.uuidMetric" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'u', 'u', 'i', 'd', 'M', 'e', 't', 'r', 'i', 'c', '\0',
+ /* 376 - "testNested.anObject" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'a', 'n', 'O', 'b', 'j', 'e', 'c', 't', '\0',
+ /* 396 - "testNested.datetimeMetric" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'd', 'a', 't', 'e', 't', 'i', 'm', 'e', 'M', 'e', 't', 'r', 'i', 'c', '\0',
+ /* 422 - "testNested.eventMetric" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'e', 'v', 'e', 'n', 't', 'M', 'e', 't', 'r', 'i', 'c', '\0',
+ /* 445 - "testNested.eventMetricWithExtra" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'e', 'v', 'e', 'n', 't', 'M', 'e', 't', 'r', 'i', 'c', 'W', 'i', 't', 'h', 'E', 'x', 't', 'r', 'a', '\0',
+ /* 477 - "testNested.externalDenominator" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'e', 'x', 't', 'e', 'r', 'n', 'a', 'l', 'D', 'e', 'n', 'o', 'm', 'i', 'n', 'a', 't', 'o', 'r', '\0',
+ /* 508 - "testNested.optimizableCounterMetric" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'o', 'p', 't', 'i', 'm', 'i', 'z', 'a', 'b', 'l', 'e', 'C', 'o', 'u', 'n', 't', 'e', 'r', 'M', 'e', 't', 'r', 'i', 'c', '\0',
+ /* 544 - "testNested.quantityMetric" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'q', 'u', 'a', 'n', 't', 'i', 't', 'y', 'M', 'e', 't', 'r', 'i', 'c', '\0',
+ /* 570 - "testNested.rateMetric" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'r', 'a', 't', 'e', 'M', 'e', 't', 'r', 'i', 'c', '\0',
+ /* 592 - "testNested.rateWithExternalDenominator" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'r', 'a', 't', 'e', 'W', 'i', 't', 'h', 'E', 'x', 't', 'e', 'r', 'n', 'a', 'l', 'D', 'e', 'n', 'o', 'm', 'i', 'n', 'a', 't', 'o', 'r', '\0',
+ /* 631 - "testNested.uuidMetric" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'u', 'u', 'i', 'd', 'M', 'e', 't', 'r', 'i', 'c', '\0',
};
static_assert(sizeof(gMetricStringTable) < 4294967296, "Metric string table is too large.");
const metric_entry_t sMetricByNameLookupEntries[] = {
- 6341068335467200838ull,
- 3458764548180279480ull,
- 1152921590506193384ull,
- 576460756598390784ull,
- 2882303787286921342ull,
- 6917529092065591642ull,
- 9799832883647480358ull,
- 2305843026393563204ull,
- 7493989848663982456ull,
+ 5764607578868810038ull,
1152921513196781587ull,
- 8070450609557340585ull,
- 2305843030688530526ull,
- 5188146822270419236ull,
- 8646911366155731401ull,
1729382269795172390ull,
- 10952754396844261987ull,
+ 3458764552475246801ull,
+ 10952754396844261968ull,
+ 4611686065672028430ull,
+ 2305843026393563204ull,
+ 1152921594801160700ull,
+ 5188146822270419236ull,
+ 3458764548180279480ull,
+ 8646911361860764070ull,
+ 8646911366155731389ull,
+ 8070450605262373260ull,
+ 9799832883647480352ull,
+ 11529215153442652791ull,
+ 2305843030688530526ull,
+ 6917529092065591642ull,
+ 9223372122754122205ull,
+ 10376293640245871162ull,
4035225309073637616ull,
- 10376293640245871164ull,
- 9223372127049089548ull,
- 5764607578868810038ull,
- 8070450605262373266ull,
+ 2882303787286921342ull,
+ 576460756598390784ull,
+ 7493989848663982456ull,
2882303791581888664ull,
- 4611686065672028430ull,
- 3458764552475246801ull
+ 6341068335467200838ull
};
@@ -365,39 +371,39 @@ MetricByNameLookup(const nsACString& aKey)
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 3, 0, 0, 0, 0, 1, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0,
- 0, 0, 0, 3, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 6, 0, 12, 0, 0, 0, 0, 0, 0, 0,
- 11, 0, 0, 0, 3, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 1, 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,
+ 2, 0, 0, 0, 6, 0, 0, 51, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
};
diff --git a/toolkit/components/glean/tests/pytest/metrics_test_output_js_h b/toolkit/components/glean/tests/pytest/metrics_test_output_js_h
index 6ce9833732..99887c5a74 100644
--- a/toolkit/components/glean/tests/pytest/metrics_test_output_js_h
+++ b/toolkit/components/glean/tests/pytest/metrics_test_output_js_h
@@ -69,7 +69,7 @@ Maybe<uint32_t> MetricByNameLookup(const nsACString&);
Maybe<uint32_t> CategoryByNameLookup(const nsACString&);
extern const category_entry_t sCategoryByNameLookupEntries[2];
-extern const metric_entry_t sMetricByNameLookupEntries[24];
+extern const metric_entry_t sMetricByNameLookupEntries[25];
} // namespace mozilla::glean
#endif // mozilla_GleanJSMetricsLookup_h
diff --git a/toolkit/components/glean/tests/pytest/pings_test.yaml b/toolkit/components/glean/tests/pytest/pings_test.yaml
index efa6ba9e0a..558cbb1022 100644
--- a/toolkit/components/glean/tests/pytest/pings_test.yaml
+++ b/toolkit/components/glean/tests/pytest/pings_test.yaml
@@ -7,7 +7,7 @@
# `glean_parser` PyPI package.
---
-$schema: moz://mozilla.org/schemas/glean/pings/1-0-0
+$schema: moz://mozilla.org/schemas/glean/pings/2-0-0
not-baseline:
description: >
@@ -114,3 +114,19 @@ not-deletion-request:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1587095#c6
notification_emails:
- glean-team@mozilla.com
+
+not-ohttp:
+ description: >
+ A fake OHTTP-using ping
+ include_client_id: false
+ metadata:
+ include_info_sections: false
+ use_ohttp: true
+ send_if_empty: true
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1862002
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1862002
+ notification_emails:
+ - chutten@mozilla.com
+ - glean-team@mozilla.com
diff --git a/toolkit/components/glean/tests/pytest/pings_test_output b/toolkit/components/glean/tests/pytest/pings_test_output
index 9e037eb22a..97c0793b1f 100644
--- a/toolkit/components/glean/tests/pytest/pings_test_output
+++ b/toolkit/components/glean/tests/pytest/pings_test_output
@@ -20,6 +20,7 @@ pub static not_baseline: Lazy<Ping> = Lazy::new(|| {
true,
false,
true,
+ true,
vec!["background".into(), "dirty_startup".into(), "foreground".into()],
)
});
@@ -36,6 +37,7 @@ pub static not_deletion_request: Lazy<Ping> = Lazy::new(|| {
true,
true,
true,
+ true,
vec![],
)
});
@@ -50,6 +52,7 @@ pub static not_events: Lazy<Ping> = Lazy::new(|| {
true,
false,
true,
+ true,
vec!["background".into(), "max_capacity".into(), "startup".into()],
)
});
@@ -68,10 +71,24 @@ pub static not_metrics: Lazy<Ping> = Lazy::new(|| {
true,
false,
true,
+ true,
vec!["overdue".into(), "reschedule".into(), "today".into(), "tomorrow".into(), "upgrade".into()],
)
});
+#[allow(non_upper_case_globals)]
+/// A fake OHTTP-using ping
+pub static not_ohttp: Lazy<Ping> = Lazy::new(|| {
+ Ping::new(
+ "not-ohttp",
+ false,
+ true,
+ true,
+ false,
+ vec![],
+ )
+});
+
/// Instantiate custom pings once to trigger registration.
///
@@ -87,6 +104,7 @@ pub fn register_pings(application_id: Option<&str>) {
let _ = &*not_deletion_request;
let _ = &*not_events;
let _ = &*not_metrics;
+ let _ = &*not_ohttp;
}
}
}
@@ -110,6 +128,7 @@ pub(crate) fn submit_ping_by_id(id: u32, reason: Option<&str>) {
2 => not_deletion_request.submit(reason),
3 => not_events.submit(reason),
4 => not_metrics.submit(reason),
+ 5 => not_ohttp.submit(reason),
_ => {
// TODO: instrument this error.
log::error!("Cannot submit unknown ping {} by id.", id);
diff --git a/toolkit/components/glean/tests/pytest/pings_test_output_cpp b/toolkit/components/glean/tests/pytest/pings_test_output_cpp
index 289529b118..8994204634 100644
--- a/toolkit/components/glean/tests/pytest/pings_test_output_cpp
+++ b/toolkit/components/glean/tests/pytest/pings_test_output_cpp
@@ -56,6 +56,13 @@ constexpr glean::impl::Ping NotEvents(3);
*/
constexpr glean::impl::Ping NotMetrics(4);
+/*
+ * Generated from not-ohttp.
+ *
+ * A fake OHTTP-using ping
+ */
+constexpr glean::impl::Ping NotOhttp(5);
+
} // namespace mozilla::glean_pings
diff --git a/toolkit/components/glean/tests/pytest/pings_test_output_js_cpp b/toolkit/components/glean/tests/pytest/pings_test_output_js_cpp
index 139eb29148..bc4f235d1e 100644
--- a/toolkit/components/glean/tests/pytest/pings_test_output_js_cpp
+++ b/toolkit/components/glean/tests/pytest/pings_test_output_js_cpp
@@ -33,15 +33,17 @@ constexpr char gPingStringTable[] = {
/* 12 - "notDeletionRequest" */ 'n', 'o', 't', 'D', 'e', 'l', 'e', 't', 'i', 'o', 'n', 'R', 'e', 'q', 'u', 'e', 's', 't', '\0',
/* 31 - "notEvents" */ 'n', 'o', 't', 'E', 'v', 'e', 'n', 't', 's', '\0',
/* 41 - "notMetrics" */ 'n', 'o', 't', 'M', 'e', 't', 'r', 'i', 'c', 's', '\0',
+ /* 52 - "notOhttp" */ 'n', 'o', 't', 'O', 'h', 't', 't', 'p', '\0',
};
const ping_entry_t sPingByNameLookupEntries[] = {
65536,
+ 327732,
+ 262185,
131084,
- 196639,
- 262185
+ 196639
};
@@ -88,7 +90,7 @@ PingByNameLookup(const nsACString& aKey)
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0,
@@ -108,7 +110,7 @@ PingByNameLookup(const nsACString& aKey)
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
diff --git a/toolkit/components/glean/tests/pytest/pings_test_output_js_h b/toolkit/components/glean/tests/pytest/pings_test_output_js_h
index 0c89de93ae..af21515ccc 100644
--- a/toolkit/components/glean/tests/pytest/pings_test_output_js_h
+++ b/toolkit/components/glean/tests/pytest/pings_test_output_js_h
@@ -28,6 +28,6 @@ const char* GetPingName(ping_entry_t aEntry);
*/
Maybe<uint32_t> PingByNameLookup(const nsACString&);
-extern const ping_entry_t sPingByNameLookupEntries[4];
+extern const ping_entry_t sPingByNameLookupEntries[5];
} // namespace mozilla::glean
#endif // mozilla_GleanJSPingsLookup_h
diff --git a/toolkit/components/glean/tests/test_metrics.yaml b/toolkit/components/glean/tests/test_metrics.yaml
index bc290d470f..47587241b1 100644
--- a/toolkit/components/glean/tests/test_metrics.yaml
+++ b/toolkit/components/glean/tests/test_metrics.yaml
@@ -403,6 +403,25 @@ test_only:
- test-ping
telemetry_mirror: TELEMETRY_TEST_MIRROR_FOR_QUANTITY
+ balloons:
+ type: object
+ description: A collection of balloons
+ bugs:
+ - https://bugzilla.mozilla.org/1839640
+ data_reviews:
+ - http://example.com/reviews
+ notification_emails:
+ - CHANGE-ME@example.com
+ expires: never
+ structure:
+ type: array
+ items:
+ type: object
+ properties:
+ colour:
+ type: string
+ diameter:
+ type: number
test_only.ipc:
a_counter:
diff --git a/toolkit/components/glean/tests/test_pings.yaml b/toolkit/components/glean/tests/test_pings.yaml
index d62f682109..8bae13215e 100644
--- a/toolkit/components/glean/tests/test_pings.yaml
+++ b/toolkit/components/glean/tests/test_pings.yaml
@@ -40,3 +40,22 @@ test-ping:
- glean-team@mozilla.com
no_lint:
- REDUNDANT_PING
+
+test-ohttp-ping:
+ description: |
+ This ping is for tests only.
+ Resembles how OHTTP pings are defined.
+ include_client_id: false
+ metadata:
+ include_info_sections: false
+ use_ohttp: true
+ send_if_empty: true
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1862002
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1862002
+ notification_emails:
+ - chutten@mozilla.com
+ - glean-team@mozilla.com
+ no_lint:
+ - REDUNDANT_PING
diff --git a/toolkit/components/glean/tests/xpcshell/head.js b/toolkit/components/glean/tests/xpcshell/head.js
index f42bd02822..eaf8fa9c61 100644
--- a/toolkit/components/glean/tests/xpcshell/head.js
+++ b/toolkit/components/glean/tests/xpcshell/head.js
@@ -4,3 +4,155 @@
const { XPCOMUtils } = ChromeUtils.importESModule(
"resource://gre/modules/XPCOMUtils.sys.mjs"
);
+
+ChromeUtils.defineESModuleGetters(this, {
+ HttpServer: "resource://testing-common/httpd.sys.mjs",
+ NetUtil: "resource://gre/modules/NetUtil.sys.mjs",
+});
+
+const PingServer = {
+ _httpServer: null,
+ _started: false,
+ _defers: [Promise.withResolvers()],
+ _currentDeferred: 0,
+
+ get port() {
+ return this._httpServer.identity.primaryPort;
+ },
+
+ get host() {
+ return this._httpServer.identity.primaryHost;
+ },
+
+ get started() {
+ return this._started;
+ },
+
+ registerPingHandler(handler) {
+ this._httpServer.registerPrefixHandler("/submit/", handler);
+ },
+
+ resetPingHandler() {
+ this.registerPingHandler(request => {
+ let r = request;
+ console.trace(
+ `defaultPingHandler() - ${r.method} ${r.scheme}://${r.host}:${r.port}${r.path}`
+ );
+ let deferred = this._defers[this._defers.length - 1];
+ this._defers.push(Promise.withResolvers());
+ deferred.resolve(request);
+ });
+ },
+
+ start() {
+ this._httpServer = new HttpServer();
+ this._httpServer.start(-1);
+ this._started = true;
+ this.clearRequests();
+ this.resetPingHandler();
+ },
+
+ stop() {
+ return new Promise(resolve => {
+ this._httpServer.stop(resolve);
+ this._started = false;
+ });
+ },
+
+ clearRequests() {
+ this._defers = [Promise.withResolvers()];
+ this._currentDeferred = 0;
+ },
+
+ promiseNextRequest() {
+ const deferred = this._defers[this._currentDeferred++];
+ // Send the ping to the consumer on the next tick, so that the completion gets
+ // signaled to Telemetry.
+ return new Promise(r =>
+ Services.tm.dispatchToMainThread(() => r(deferred.promise))
+ );
+ },
+
+ promiseNextPing() {
+ return this.promiseNextRequest().then(request =>
+ decodeRequestPayload(request)
+ );
+ },
+
+ async promiseNextRequests(count) {
+ let results = [];
+ for (let i = 0; i < count; ++i) {
+ results.push(await this.promiseNextRequest());
+ }
+
+ return results;
+ },
+
+ promiseNextPings(count) {
+ return this.promiseNextRequests(count).then(requests => {
+ return Array.from(requests, decodeRequestPayload);
+ });
+ },
+};
+
+/**
+ * Decode the payload of an HTTP request into a ping.
+ *
+ * @param {object} request The data representing an HTTP request (nsIHttpRequest).
+ * @returns {object} The decoded ping payload.
+ */
+function decodeRequestPayload(request) {
+ let s = request.bodyInputStream;
+ let payload = null;
+
+ if (
+ request.hasHeader("content-encoding") &&
+ request.getHeader("content-encoding") == "gzip"
+ ) {
+ let observer = {
+ buffer: "",
+ onStreamComplete(loader, context, status, length, result) {
+ // 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 < result.length; offset += chunkSize) {
+ this.buffer += String.fromCharCode.apply(
+ String,
+ result.slice(offset, offset + chunkSize)
+ );
+ }
+ },
+ };
+
+ 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(
+ "gzip",
+ "uncompressed",
+ listener,
+ null
+ );
+ converter.onStartRequest(null, null);
+ converter.onDataAvailable(null, s, 0, s.available());
+ converter.onStopRequest(null, null, null);
+ // TODO: nsIScriptableUnicodeConverter is deprecated
+ // But I can't figure out how else to ungzip bodyInputStream.
+ let unicodeConverter = Cc[
+ "@mozilla.org/intl/scriptableunicodeconverter"
+ ].createInstance(Ci.nsIScriptableUnicodeConverter);
+ unicodeConverter.charset = "UTF-8";
+ let utf8string = unicodeConverter.ConvertToUnicode(observer.buffer);
+ utf8string += unicodeConverter.Finish();
+ payload = JSON.parse(utf8string);
+ } else {
+ let bytes = NetUtil.readInputStream(s, s.available());
+ payload = JSON.parse(new TextDecoder().decode(bytes));
+ }
+
+ return payload;
+}
diff --git a/toolkit/components/glean/tests/xpcshell/test_GIFFT.js b/toolkit/components/glean/tests/xpcshell/test_GIFFT.js
index 015b4d4e38..8fcf755d3a 100644
--- a/toolkit/components/glean/tests/xpcshell/test_GIFFT.js
+++ b/toolkit/components/glean/tests/xpcshell/test_GIFFT.js
@@ -176,10 +176,7 @@ add_task(async function test_gifft_timing_dist() {
// But we can guarantee it's only two samples.
Assert.equal(
2,
- Object.entries(data.values).reduce(
- (acc, [bucket, count]) => acc + count,
- 0
- ),
+ Object.entries(data.values).reduce((acc, [, count]) => acc + count, 0),
"Only two buckets with samples"
);
@@ -188,10 +185,7 @@ add_task(async function test_gifft_timing_dist() {
Assert.greaterOrEqual(data.sum, 13, "Histogram's in milliseconds");
Assert.equal(
2,
- Object.entries(data.values).reduce(
- (acc, [bucket, count]) => acc + count,
- 0
- ),
+ Object.entries(data.values).reduce((acc, [, count]) => acc + count, 0),
"Only two samples"
);
});
diff --git a/toolkit/components/glean/tests/xpcshell/test_GIFFTIPC.js b/toolkit/components/glean/tests/xpcshell/test_GIFFTIPC.js
index a318d57b9c..ae4c48b6e8 100644
--- a/toolkit/components/glean/tests/xpcshell/test_GIFFTIPC.js
+++ b/toolkit/components/glean/tests/xpcshell/test_GIFFTIPC.js
@@ -291,10 +291,7 @@ add_task(
// but we can assert there are only two samples.
Assert.equal(
2,
- Object.entries(times.values).reduce(
- (acc, [bucket, count]) => acc + count,
- 0
- )
+ Object.entries(times.values).reduce((acc, [, count]) => acc + count, 0)
);
const timingHist = histSnapshot.content.TELEMETRY_TEST_EXPONENTIAL;
Assert.greaterOrEqual(timingHist.sum, 13, "Histogram's in milliseconds.");
@@ -303,7 +300,7 @@ add_task(
Assert.equal(
2,
Object.entries(timingHist.values).reduce(
- (acc, [bucket, count]) => acc + count,
+ (acc, [, count]) => acc + count,
0
),
"Only two samples"
diff --git a/toolkit/components/glean/tests/xpcshell/test_Glean.js b/toolkit/components/glean/tests/xpcshell/test_Glean.js
index 8375c22a3e..18b450a69b 100644
--- a/toolkit/components/glean/tests/xpcshell/test_Glean.js
+++ b/toolkit/components/glean/tests/xpcshell/test_Glean.js
@@ -182,6 +182,7 @@ add_task(async function test_fog_memory_distribution_works() {
Glean.testOnly.doYouRemember.accumulate(17);
let data = Glean.testOnly.doYouRemember.testGetValue("test-ping");
+ Assert.equal(2, data.count, "Count of entries is correct");
// `data.sum` is in bytes, but the metric is in MB.
Assert.equal(24 * 1024 * 1024, data.sum, "Sum's correct");
for (let [bucket, count] of Object.entries(data.values)) {
@@ -196,6 +197,7 @@ add_task(async function test_fog_custom_distribution_works() {
Glean.testOnlyIpc.aCustomDist.accumulateSamples([7, 268435458]);
let data = Glean.testOnlyIpc.aCustomDist.testGetValue("store1");
+ Assert.equal(2, data.count, "Count of entries is correct");
Assert.equal(7 + 268435458, data.sum, "Sum's correct");
for (let [bucket, count] of Object.entries(data.values)) {
Assert.ok(
@@ -216,7 +218,7 @@ add_task(function test_fog_custom_pings() {
Assert.ok("onePingOnly" in GleanPings);
let submitted = false;
Glean.testOnly.onePingOneBool.set(false);
- GleanPings.onePingOnly.testBeforeNextSubmit(reason => {
+ GleanPings.onePingOnly.testBeforeNextSubmit(() => {
submitted = true;
Assert.equal(false, Glean.testOnly.onePingOneBool.testGetValue());
});
@@ -227,7 +229,7 @@ add_task(function test_fog_custom_pings() {
add_task(function test_recursive_testBeforeNextSubmit() {
Assert.ok("onePingOnly" in GleanPings);
let submitted = 0;
- let rec = reason => {
+ let rec = () => {
submitted++;
GleanPings.onePingOnly.testBeforeNextSubmit(rec);
};
@@ -255,6 +257,10 @@ add_task(async function test_fog_timing_distribution_works() {
Glean.testOnly.whatTimeIsIt.stopAndAccumulate(t3); // 5ms
let data = Glean.testOnly.whatTimeIsIt.testGetValue();
+
+ // Cancelled timers should not be counted.
+ Assert.equal(2, data.count, "Count of entries is correct");
+
const NANOS_IN_MILLIS = 1e6;
// bug 1701949 - Sleep gets close, but sometimes doesn't wait long enough.
const EPSILON = 40000;
@@ -266,10 +272,7 @@ add_task(async function test_fog_timing_distribution_works() {
// But we can guarantee it's only two samples.
Assert.equal(
2,
- Object.entries(data.values).reduce(
- (acc, [bucket, count]) => acc + count,
- 0
- ),
+ Object.entries(data.values).reduce((acc, [, count]) => acc + count, 0),
"Only two buckets with samples"
);
});
@@ -456,3 +459,106 @@ add_task(async function test_fog_text_works_unusual_character() {
Assert.greater(rslt.length, 100);
});
+
+add_task(async function test_fog_object_works() {
+ if (!Glean.testOnly.balloons) {
+ // FIXME(bug 1883857): object metric type not available, e.g. in artifact builds.
+ // Skipping this test.
+ return;
+ }
+
+ Assert.equal(
+ undefined,
+ Glean.testOnly.balloons.testGetValue(),
+ "No object stored"
+ );
+
+ // Can't store not-objects.
+ let invalidValues = [1, "str", false, undefined, null, NaN, Infinity];
+ for (let value of invalidValues) {
+ Assert.throws(
+ () => Glean.testOnly.balloons.set(value),
+ /is not an object/,
+ "Should throw a type error"
+ );
+ }
+
+ // No invalid value will be stored.
+ Assert.equal(
+ undefined,
+ Glean.testOnly.balloons.testGetValue(),
+ "No object stored"
+ );
+
+ // `JS_Stringify` internally throws
+ // an `TypeError: cyclic object value` exception.
+ // That's cleared and `set` should not throw on it.
+ // This eventually should log a proper error in Glean.
+ let selfref = {};
+ selfref.a = selfref;
+ Glean.testOnly.balloons.set(selfref);
+ Assert.equal(
+ undefined,
+ Glean.testOnly.balloons.testGetValue(),
+ "No object stored"
+ );
+
+ let balloons = [
+ { colour: "red", diameter: 5 },
+ { colour: "blue", diameter: 7 },
+ { colour: "orange" },
+ ];
+ Glean.testOnly.balloons.set(balloons);
+
+ let result = Glean.testOnly.balloons.testGetValue();
+ let expected = [
+ { colour: "red", diameter: 5 },
+ { colour: "blue", diameter: 7 },
+ { colour: "orange", diameter: null },
+ ];
+ Assert.deepEqual(expected, result);
+
+ // These values are coerced to null or removed.
+ balloons = [
+ { colour: "inf", diameter: Infinity },
+ { colour: "negative-inf", diameter: -1 / 0 },
+ { colour: "nan", diameter: NaN },
+ { colour: "undef", diameter: undefined },
+ ];
+ Glean.testOnly.balloons.set(balloons);
+ result = Glean.testOnly.balloons.testGetValue();
+ expected = [
+ { colour: "inf", diameter: null },
+ { colour: "negative-inf", diameter: null },
+ { colour: "nan", diameter: null },
+ { colour: "undef", diameter: null },
+ ];
+ Assert.deepEqual(expected, result);
+
+ // colour != color.
+ let invalid = [{ color: "orange" }, { color: "red", diameter: "small" }];
+ Glean.testOnly.balloons.set(invalid);
+ Assert.throws(
+ () => Glean.testOnly.balloons.testGetValue(),
+ /invalid_value/,
+ "Should throw because last object was invalid."
+ );
+
+ Services.fog.testResetFOG();
+ // set again to ensure it's stored
+ balloons = [
+ { colour: "red", diameter: 5 },
+ { colour: "blue", diameter: 7 },
+ ];
+ Glean.testOnly.balloons.set(balloons);
+ result = Glean.testOnly.balloons.testGetValue();
+ Assert.deepEqual(balloons, result);
+
+ invalid = [{ colour: "red", diameter: 5, extra: "field" }];
+ Glean.testOnly.balloons.set(invalid);
+ Assert.throws(
+ () => Glean.testOnly.balloons.testGetValue(),
+ /invalid_value/,
+ "Should throw because last object was invalid."
+ );
+});
diff --git a/toolkit/components/glean/tests/xpcshell/test_GleanIPC.js b/toolkit/components/glean/tests/xpcshell/test_GleanIPC.js
index 3d665c23b9..2db1c53218 100644
--- a/toolkit/components/glean/tests/xpcshell/test_GleanIPC.js
+++ b/toolkit/components/glean/tests/xpcshell/test_GleanIPC.js
@@ -127,10 +127,7 @@ add_task(
// but we can assert there are only two samples.
Assert.equal(
2,
- Object.entries(times.values).reduce(
- (acc, [bucket, count]) => acc + count,
- 0
- )
+ Object.entries(times.values).reduce((acc, [, count]) => acc + count, 0)
);
const mabelsCounters = Glean.testOnly.mabelsKitchenCounters;
diff --git a/toolkit/components/glean/tests/xpcshell/test_JOG.js b/toolkit/components/glean/tests/xpcshell/test_JOG.js
index 53b1d25962..00f3ded135 100644
--- a/toolkit/components/glean/tests/xpcshell/test_JOG.js
+++ b/toolkit/components/glean/tests/xpcshell/test_JOG.js
@@ -289,11 +289,11 @@ add_task(async function test_jog_custom_pings() {
`"ping"`,
false
);
- Services.fog.testRegisterRuntimePing("jog-ping", true, true, true, []);
+ Services.fog.testRegisterRuntimePing("jog-ping", true, true, true, true, []);
Assert.ok("jogPing" in GleanPings);
let submitted = false;
Glean.jogCat.jogPingBool.set(false);
- GleanPings.jogPing.testBeforeNextSubmit(reason => {
+ GleanPings.jogPing.testBeforeNextSubmit(() => {
submitted = true;
Assert.equal(false, Glean.jogCat.jogPingBool.testGetValue());
});
@@ -338,10 +338,7 @@ add_task(async function test_jog_timing_distribution_works() {
// But we can guarantee it's only two samples.
Assert.equal(
2,
- Object.entries(data.values).reduce(
- (acc, [bucket, count]) => acc + count,
- 0
- ),
+ Object.entries(data.values).reduce((acc, [, count]) => acc + count, 0),
"Only two buckets with samples"
);
});
@@ -642,7 +639,9 @@ add_task(function test_jog_dotted_categories_work() {
add_task(async function test_jog_ping_works() {
const kReason = "reason-1";
- Services.fog.testRegisterRuntimePing("my-ping", true, true, true, [kReason]);
+ Services.fog.testRegisterRuntimePing("my-ping", true, true, true, true, [
+ kReason,
+ ]);
let submitted = false;
GleanPings.myPing.testBeforeNextSubmit(reason => {
submitted = true;
@@ -652,6 +651,20 @@ add_task(async function test_jog_ping_works() {
Assert.ok(submitted, "Ping must have been submitted");
});
+add_task(async function test_jog_noinfo_ping_works() {
+ const kReason = "reason-1";
+ Services.fog.testRegisterRuntimePing("noinfo-ping", true, true, true, false, [
+ kReason,
+ ]);
+ let submitted = false;
+ GleanPings.noinfoPing.testBeforeNextSubmit(reason => {
+ submitted = true;
+ Assert.equal(kReason, reason);
+ });
+ GleanPings.noinfoPing.submit("reason-1");
+ Assert.ok(submitted, "Ping must have been submitted");
+});
+
add_task(function test_jog_name_collision() {
Assert.ok("aCounter" in Glean.testOnlyJog);
Assert.equal(undefined, Glean.testOnlyJog.aCounter.testGetValue());
diff --git a/toolkit/components/glean/tests/xpcshell/test_JOGIPC.js b/toolkit/components/glean/tests/xpcshell/test_JOGIPC.js
index 505dba6825..b43a448c53 100644
--- a/toolkit/components/glean/tests/xpcshell/test_JOGIPC.js
+++ b/toolkit/components/glean/tests/xpcshell/test_JOGIPC.js
@@ -226,10 +226,7 @@ add_task(
// but we can assert there are only two samples.
Assert.equal(
2,
- Object.entries(times.values).reduce(
- (acc, [bucket, count]) => acc + count,
- 0
- )
+ Object.entries(times.values).reduce((acc, [, count]) => acc + count, 0)
);
const labeledCounter = Glean.jogIpc.jogLabeledCounter;
diff --git a/toolkit/components/glean/tests/xpcshell/test_OHTTP.js b/toolkit/components/glean/tests/xpcshell/test_OHTTP.js
new file mode 100644
index 0000000000..76d1d2a67b
--- /dev/null
+++ b/toolkit/components/glean/tests/xpcshell/test_OHTTP.js
@@ -0,0 +1,32 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_setup(async () => {
+ // FOG needs a profile dir to put its data in.
+ do_get_profile();
+
+ PingServer.start();
+
+ registerCleanupFunction(async () => {
+ await PingServer.stop();
+ });
+
+ Services.prefs.setIntPref(
+ "telemetry.fog.test.localhost_port",
+ PingServer.port
+ );
+ // Port pref needs to be set before init, so let's reset to reinit.
+ Services.fog.testResetFOG();
+});
+
+add_task(async () => {
+ PingServer.clearRequests();
+ GleanPings.testOhttpPing.submit();
+
+ let ping = await PingServer.promiseNextPing();
+
+ ok(!("client_info" in ping), "No client_info allowed.");
+ ok(!("ping_info" in ping), "No ping_info allowed.");
+});
diff --git a/toolkit/components/glean/tests/xpcshell/xpcshell.toml b/toolkit/components/glean/tests/xpcshell/xpcshell.toml
index 40b1a22bf4..22806a32e6 100644
--- a/toolkit/components/glean/tests/xpcshell/xpcshell.toml
+++ b/toolkit/components/glean/tests/xpcshell/xpcshell.toml
@@ -30,3 +30,6 @@ skip-if = ["os == 'android'"] # Server Knobs on mobile will be handled by the sp
["test_MillionQ.js"]
skip-if = ["os == 'android'"] # Android inits its own FOG, so the test won't work.
+
+["test_OHTTP.js"]
+skip-if = ["os == 'android'"] # FOG isn't responsible for monitoring prefs and controlling upload on Android
diff --git a/toolkit/components/glean/xpcom/FOG.cpp b/toolkit/components/glean/xpcom/FOG.cpp
index 3673b23707..d4c03a5b9e 100644
--- a/toolkit/components/glean/xpcom/FOG.cpp
+++ b/toolkit/components/glean/xpcom/FOG.cpp
@@ -419,12 +419,13 @@ FOG::TestRegisterRuntimePing(const nsACString& aName,
const bool aIncludeClientId,
const bool aSendIfEmpty,
const bool aPreciseTimestamps,
+ const bool aIncludeInfoSections,
const nsTArray<nsCString>& aReasonCodes,
uint32_t* aPingIdOut) {
*aPingIdOut = 0;
- *aPingIdOut =
- glean::jog::jog_test_register_ping(&aName, aIncludeClientId, aSendIfEmpty,
- aPreciseTimestamps, &aReasonCodes);
+ *aPingIdOut = glean::jog::jog_test_register_ping(
+ &aName, aIncludeClientId, aSendIfEmpty, aPreciseTimestamps,
+ aIncludeInfoSections, &aReasonCodes);
return NS_OK;
}
diff --git a/toolkit/components/glean/xpcom/nsIFOG.idl b/toolkit/components/glean/xpcom/nsIFOG.idl
index 682df85f1a..dd5d0ec21a 100644
--- a/toolkit/components/glean/xpcom/nsIFOG.idl
+++ b/toolkit/components/glean/xpcom/nsIFOG.idl
@@ -180,11 +180,14 @@ interface nsIFOG : nsISupports
* @param aName - The ping's name.
* @param aIncludeClientId - Whether the ping should include the client_id.
* @param aSendIfEmpty - Whether the ping should send even if empty.
+ * @param aIncludeInfoSections - Whether the ping should include
+ * {client|ping}_info sections.
* @param aReasonCodes - The list of valid reasons for ping submission.
*/
uint32_t testRegisterRuntimePing(in ACString aName,
in boolean aIncludeClientId,
in boolean aSendIfEmpty,
in boolean aPreciseTimestamps,
+ in boolean aIncludeInfoSections,
in Array<ACString> aReasonCodes);
};
diff --git a/toolkit/components/kvstore/kvstore.sys.mjs b/toolkit/components/kvstore/kvstore.sys.mjs
index 838f68a5df..9085eed530 100644
--- a/toolkit/components/kvstore/kvstore.sys.mjs
+++ b/toolkit/components/kvstore/kvstore.sys.mjs
@@ -18,7 +18,8 @@ function promisify(fn, ...args) {
* with a database's path and (optionally) its name:
*
* ```
- * ChromeUtils.import("resource://gre/modules/kvstore.jsm");
+ * let { keyValueService } =
+ * ChromeUtils.importESModule("resource://gre/modules/kvstore.sys.mjs");
* let database = await KeyValueService.getOrCreate(path, name);
* ```
*
@@ -27,11 +28,32 @@ function promisify(fn, ...args) {
*/
export class KeyValueService {
+ static RecoveryStrategy = {
+ ERROR: gKeyValueService.ERROR,
+ DISCARD: gKeyValueService.DISCARD,
+ RENAME: gKeyValueService.RENAME,
+ };
+
static async getOrCreate(dir, name) {
return new KeyValueDatabase(
await promisify(gKeyValueService.getOrCreate, dir, name)
);
}
+
+ static async getOrCreateWithOptions(
+ dir,
+ name,
+ { strategy = gKeyValueService.RENAME } = {}
+ ) {
+ return new KeyValueDatabase(
+ await promisify(
+ gKeyValueService.getOrCreateWithOptions,
+ dir,
+ name,
+ strategy
+ )
+ );
+ }
}
/**
diff --git a/toolkit/components/kvstore/nsIKeyValue.idl b/toolkit/components/kvstore/nsIKeyValue.idl
index b90d45fc5a..08cd548af2 100644
--- a/toolkit/components/kvstore/nsIKeyValue.idl
+++ b/toolkit/components/kvstore/nsIKeyValue.idl
@@ -22,7 +22,7 @@ interface nsIKeyValuePair;
* for all use cases. Extension of this API to support transactions is tracked
* by bug 1499238.
*
- * The kvstore.jsm module wraps this API in a more idiomatic, Promise-based
+ * The kvstore.sys.mjs module wraps this API in a more idiomatic, Promise-based
* JS API that supports async/await. In most cases, you're better off using
* that API from JS rather than using this one directly. Bug 1512319 tracks
* native support for Promise in Rust-implemented XPCOM methods.
@@ -33,6 +33,12 @@ interface nsIKeyValuePair;
*/
[scriptable, builtinclass, rust_sync, uuid(46c893dd-4c14-4de0-b33d-a1be18c6d062)]
interface nsIKeyValueService : nsISupports {
+ cenum RecoveryStrategy: 8 {
+ ERROR,
+ DISCARD,
+ RENAME,
+ };
+
/**
* Get a handle to an existing database or a newly-created one
* at the specified path and with the given name.
@@ -46,6 +52,12 @@ interface nsIKeyValueService : nsISupports {
in nsIKeyValueDatabaseCallback callback,
in AUTF8String path,
in AUTF8String name);
+
+ void getOrCreateWithOptions(
+ in nsIKeyValueDatabaseCallback callback,
+ in AUTF8String path,
+ in AUTF8String name,
+ [optional] in nsIKeyValueService_RecoveryStrategy recoveryStrategy);
};
/**
@@ -157,7 +169,7 @@ interface nsIKeyValuePair : nsISupports {
* an nsIKeyValuePair rather than an nsISupports, so consumers don't need
* to QI it to that interface; but this interface doesn't implement the JS
* iteration protocol (because the Rust-XPCOM bindings don't yet support it),
- * which is another reason why you should use the kvstore.jsm module from JS
+ * which is another reason why you should use the kvstore.sys.mjs module from JS
* instead of accessing this API directly.
*/
[scriptable, builtinclass, rust_sync, uuid(b9ba7116-b7ff-4717-9a28-a08e6879b199)]
diff --git a/toolkit/components/kvstore/src/lib.rs b/toolkit/components/kvstore/src/lib.rs
index 5601ecb12a..c110313ad2 100644
--- a/toolkit/components/kvstore/src/lib.rs
+++ b/toolkit/components/kvstore/src/lib.rs
@@ -29,7 +29,7 @@ use moz_task::{create_background_task_queue, DispatchOptions, TaskRunnable};
use nserror::{nsresult, NS_ERROR_FAILURE, NS_OK};
use nsstring::{nsACString, nsCString};
use owned_value::{owned_to_variant, variant_to_owned};
-use rkv::backend::{SafeModeDatabase, SafeModeEnvironment};
+use rkv::backend::{RecoveryStrategy, SafeModeDatabase, SafeModeEnvironment};
use rkv::OwnedValue;
use std::{
ptr,
@@ -37,14 +37,16 @@ use std::{
vec::IntoIter,
};
use task::{
- ClearTask, DeleteTask, EnumerateTask, GetOrCreateTask, GetTask, HasTask, PutTask, WriteManyTask,
+ ClearTask, DeleteTask, EnumerateTask, GetOrCreateWithOptionsTask, GetTask, HasTask, PutTask,
+ WriteManyTask,
};
use thin_vec::ThinVec;
use xpcom::{
getter_addrefs,
interfaces::{
nsIKeyValueDatabaseCallback, nsIKeyValueEnumeratorCallback, nsIKeyValuePair,
- nsIKeyValueVariantCallback, nsIKeyValueVoidCallback, nsISerialEventTarget, nsIVariant,
+ nsIKeyValueService, nsIKeyValueVariantCallback, nsIKeyValueVoidCallback,
+ nsISerialEventTarget, nsIVariant,
},
nsIID, xpcom, xpcom_method, RefPtr,
};
@@ -109,15 +111,49 @@ impl KeyValueService {
path: &nsACString,
name: &nsACString,
) -> Result<(), nsresult> {
- let task = Box::new(GetOrCreateTask::new(
+ let task = Box::new(GetOrCreateWithOptionsTask::new(
RefPtr::new(callback),
nsCString::from(path),
nsCString::from(name),
+ RecoveryStrategy::Error,
));
TaskRunnable::new("KVService::GetOrCreate", task)?
.dispatch_background_task_with_options(DispatchOptions::default().may_block(true))
}
+
+ xpcom_method!(
+ get_or_create_with_options => GetOrCreateWithOptions(
+ callback: *const nsIKeyValueDatabaseCallback,
+ path: *const nsACString,
+ name: *const nsACString,
+ strategy: u8
+ )
+ );
+
+ fn get_or_create_with_options(
+ &self,
+ callback: &nsIKeyValueDatabaseCallback,
+ path: &nsACString,
+ name: &nsACString,
+ xpidl_strategy: u8,
+ ) -> Result<(), nsresult> {
+ let strategy = match xpidl_strategy {
+ nsIKeyValueService::ERROR => RecoveryStrategy::Error,
+ nsIKeyValueService::DISCARD => RecoveryStrategy::Discard,
+ nsIKeyValueService::RENAME => RecoveryStrategy::Rename,
+ _ => return Err(NS_ERROR_FAILURE),
+ };
+ let task = Box::new(GetOrCreateWithOptionsTask::new(
+ RefPtr::new(callback),
+ nsCString::from(path),
+ nsCString::from(name),
+ strategy,
+ ));
+
+ TaskRunnable::new("KVService::GetOrCreateWithOptions", task)?
+ .dispatch_background_task_with_options(DispatchOptions::default().may_block(true))
+ }
}
#[xpcom(implement(nsIKeyValueDatabase), atomic)]
diff --git a/toolkit/components/kvstore/src/task.rs b/toolkit/components/kvstore/src/task.rs
index 3608dc9665..2e0ba02e0b 100644
--- a/toolkit/components/kvstore/src/task.rs
+++ b/toolkit/components/kvstore/src/task.rs
@@ -10,7 +10,10 @@ use moz_task::Task;
use nserror::{nsresult, NS_ERROR_FAILURE};
use nsstring::nsCString;
use owned_value::owned_to_variant;
-use rkv::backend::{BackendInfo, SafeMode, SafeModeDatabase, SafeModeEnvironment};
+use rkv::backend::{
+ BackendEnvironmentBuilder, BackendInfo, RecoveryStrategy, SafeMode, SafeModeDatabase,
+ SafeModeEnvironment,
+};
use rkv::{OwnedValue, StoreError, StoreOptions, Value};
use std::{
path::Path,
@@ -161,23 +164,26 @@ fn passive_resize(env: &Rkv, wanted: usize) -> Result<(), StoreError> {
Ok(())
}
-pub struct GetOrCreateTask {
+pub struct GetOrCreateWithOptionsTask {
callback: AtomicCell<Option<ThreadBoundRefPtr<nsIKeyValueDatabaseCallback>>>,
path: nsCString,
name: nsCString,
+ strategy: RecoveryStrategy,
result: AtomicCell<Option<Result<RkvStoreTuple, KeyValueError>>>,
}
-impl GetOrCreateTask {
+impl GetOrCreateWithOptionsTask {
pub fn new(
callback: RefPtr<nsIKeyValueDatabaseCallback>,
path: nsCString,
name: nsCString,
- ) -> GetOrCreateTask {
- GetOrCreateTask {
+ strategy: RecoveryStrategy,
+ ) -> GetOrCreateWithOptionsTask {
+ GetOrCreateWithOptionsTask {
callback: AtomicCell::new(Some(ThreadBoundRefPtr::new(callback))),
path,
name,
+ strategy,
result: AtomicCell::default(),
}
}
@@ -187,18 +193,24 @@ impl GetOrCreateTask {
}
}
-impl Task for GetOrCreateTask {
+impl Task for GetOrCreateWithOptionsTask {
fn run(&self) {
// We do the work within a closure that returns a Result so we can
// use the ? operator to simplify the implementation.
self.result
.store(Some(|| -> Result<RkvStoreTuple, KeyValueError> {
let store;
+ let mut builder = Rkv::environment_builder::<SafeMode>();
+ builder.set_corruption_recovery_strategy(self.strategy);
let mut manager = Manager::singleton().write()?;
// Note that path canonicalization is diabled to work around crashes on Fennec:
// https://bugzilla.mozilla.org/show_bug.cgi?id=1531887
let path = Path::new(str::from_utf8(&self.path)?);
- let rkv = manager.get_or_create(path, Rkv::new::<SafeMode>)?;
+ let rkv = manager.get_or_create_from_builder(
+ path,
+ builder,
+ Rkv::from_builder::<SafeMode>,
+ )?;
{
let env = rkv.read()?;
let load_ratio = env.load_ratio()?.unwrap_or(0.0);
diff --git a/toolkit/components/kvstore/test/xpcshell/test_kvstore.js b/toolkit/components/kvstore/test/xpcshell/test_kvstore.js
index 363feaa43a..4c591622e1 100644
--- a/toolkit/components/kvstore/test/xpcshell/test_kvstore.js
+++ b/toolkit/components/kvstore/test/xpcshell/test_kvstore.js
@@ -12,9 +12,16 @@ function run_test() {
run_next_test();
}
-async function makeDatabaseDir(name) {
+async function makeDatabaseDir(name, { mockCorrupted = false } = {}) {
const databaseDir = PathUtils.join(PathUtils.profileDir, name);
await IOUtils.makeDirectory(databaseDir);
+ if (mockCorrupted) {
+ // Mock a corrupted db.
+ await IOUtils.write(
+ PathUtils.join(databaseDir, "data.safe.bin"),
+ new Uint8Array([0x00, 0x00, 0x00, 0x00])
+ );
+ }
return databaseDir;
}
@@ -26,6 +33,71 @@ add_task(async function getService() {
Assert.ok(gKeyValueService);
});
+add_task(async function getOrCreate_defaultRecoveryStrategyError() {
+ const databaseDir = await makeDatabaseDir("getOrCreate_Error", {
+ mockCorrupted: true,
+ });
+
+ await Assert.rejects(
+ KeyValueService.getOrCreate(databaseDir, "db"),
+ /FileInvalid/
+ );
+});
+
+add_task(async function getOrCreateWithOptions_RecoveryStrategyError() {
+ const databaseDir = await makeDatabaseDir("getOrCreateWithOptions_Error", {
+ mockCorrupted: true,
+ });
+
+ await Assert.rejects(
+ KeyValueService.getOrCreateWithOptions(databaseDir, "db", {
+ strategy: KeyValueService.RecoveryStrategy.ERROR,
+ }),
+ /FileInvalid/
+ );
+});
+
+add_task(async function getOrCreateWithOptions_RecoveryStrategyRename() {
+ const databaseDir = await makeDatabaseDir("getOrCreateWithOptions_Rename", {
+ mockCorrupted: true,
+ });
+
+ const database = await KeyValueService.getOrCreateWithOptions(
+ databaseDir,
+ "db",
+ {
+ strategy: KeyValueService.RecoveryStrategy.RENAME,
+ }
+ );
+ Assert.ok(database);
+
+ Assert.ok(
+ await IOUtils.exists(PathUtils.join(databaseDir, "data.safe.bin.corrupt")),
+ "Expect corrupt file to be found"
+ );
+});
+
+add_task(async function getOrCreateWithOptions_RecoveryStrategyDiscard() {
+ const databaseDir = await makeDatabaseDir("getOrCreateWithOptions_Discard", {
+ mockCorrupted: true,
+ });
+
+ const database = await KeyValueService.getOrCreateWithOptions(
+ databaseDir,
+ "db",
+ {
+ strategy: KeyValueService.RecoveryStrategy.DISCARD,
+ }
+ );
+ Assert.ok(database);
+
+ Assert.equal(
+ await IOUtils.exists(PathUtils.join(databaseDir, "data.safe.bin.corrupt")),
+ false,
+ "Expect corrupt file to not exist"
+ );
+});
+
add_task(async function getOrCreate() {
const databaseDir = await makeDatabaseDir("getOrCreate");
const database = await KeyValueService.getOrCreate(databaseDir, "db");
diff --git a/toolkit/components/messaging-system/lib/SpecialMessageActions.sys.mjs b/toolkit/components/messaging-system/lib/SpecialMessageActions.sys.mjs
index 7b5151dc23..8ed488ff88 100644
--- a/toolkit/components/messaging-system/lib/SpecialMessageActions.sys.mjs
+++ b/toolkit/components/messaging-system/lib/SpecialMessageActions.sys.mjs
@@ -182,6 +182,7 @@ export const SpecialMessageActions = {
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",
diff --git a/toolkit/components/messaging-system/schemas/TriggerActionSchemas/index.md b/toolkit/components/messaging-system/schemas/TriggerActionSchemas/index.md
index 0d2f6dc89b..85be613392 100644
--- a/toolkit/components/messaging-system/schemas/TriggerActionSchemas/index.md
+++ b/toolkit/components/messaging-system/schemas/TriggerActionSchemas/index.md
@@ -114,7 +114,7 @@ same reason, the trigger only fires after a 10-second delay. The trigger context
includes an `event` and `type` that can be used in targeting. Possible events
include `add`, `update`, and `use`. Possible types are `card` and `address`.
This trigger is especially intended to be used in tandem with the
-`creditCardsSaved` and `addressesSaved` [targeting attributes](../../../../../browser/components/newtab/content-src/asrouter/docs/targeting-attributes.md).
+`creditCardsSaved` and `addressesSaved` [targeting attributes](/browser/components/asrouter/docs/targeting-attributes.md).
```js
{
diff --git a/toolkit/components/messaging-system/schemas/index.rst b/toolkit/components/messaging-system/schemas/index.rst
index f4db543600..957dd06c3a 100644
--- a/toolkit/components/messaging-system/schemas/index.rst
+++ b/toolkit/components/messaging-system/schemas/index.rst
@@ -178,8 +178,8 @@ Triggers and actions
.. _Experimenter: https://experimenter.info
-.. _CFRMessageProvider: https://searchfox.org/mozilla-central/source/browser/components/asrouter/modules/CFRMessageProvider.jsm
-.. _PanelTestProvider: https://searchfox.org/mozilla-central/source/browser/components/asrouter/modules/PanelTestProvider.jsm
+.. _CFRMessageProvider: https://searchfox.org/mozilla-central/source/browser/components/asrouter/modules/CFRMessageProvider.sys.mjs
+.. _PanelTestProvider: https://searchfox.org/mozilla-central/source/browser/components/asrouter/modules/PanelTestProvider.sys.mjs
.. _OnboardingMessageProvider: https://searchfox.org/mozilla-central/source/browser/components/asrouter/modules/OnboardingMessageProvider.sys.mjs
.. _Test_CFRMessageProvider: https://searchfox.org/mozilla-central/source/browser/components/asrouter/tests//xpcshell/test_CFMessageProvider.js
.. _Test_OnboardingMessageProvider: https://searchfox.org/mozilla-central/source/browser/components/asrouter/tests//xpcshell/test_OnboardingMessageProvider.js
diff --git a/toolkit/components/ml/actors/MLEngineChild.sys.mjs b/toolkit/components/ml/actors/MLEngineChild.sys.mjs
new file mode 100644
index 0000000000..925ce59266
--- /dev/null
+++ b/toolkit/components/ml/actors/MLEngineChild.sys.mjs
@@ -0,0 +1,325 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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
+ * @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",
+});
+
+ChromeUtils.defineLazyGetter(lazy, "console", () => {
+ return console.createInstance({
+ maxLogLevelPref: "browser.ml.logLevel",
+ prefix: "ML",
+ });
+});
+
+XPCOMUtils.defineLazyPreferenceGetter(
+ lazy,
+ "loggingLevel",
+ "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": {
+ const { engineName, port, timeoutMS } = data;
+ this.#engineDispatchers.set(
+ engineName,
+ new EngineDispatcher(this, port, engineName, timeoutMS)
+ );
+ break;
+ }
+ case "MLEngine:ForceShutdown": {
+ for (const engineDispatcher of this.#engineDispatchers.values()) {
+ return engineDispatcher.terminate();
+ }
+ this.#engineDispatchers = null;
+ break;
+ }
+ }
+ }
+
+ handleEvent(event) {
+ switch (event.type) {
+ case "DOMContentLoaded":
+ this.sendAsyncMessage("MLEngine:Ready");
+ break;
+ }
+ }
+
+ /**
+ * @returns {ArrayBuffer}
+ */
+ getWasmArrayBuffer() {
+ return this.sendQuery("MLEngine:GetWasmArrayBuffer");
+ }
+
+ /**
+ * @param {string} engineName
+ */
+ removeEngine(engineName) {
+ this.#engineDispatchers.delete(engineName);
+ if (this.#engineDispatchers.size === 0) {
+ this.sendQuery("MLEngine:DestroyEngineProcess");
+ }
+ }
+}
+
+/**
+ * This classes manages the lifecycle of an ML Engine, and handles dispatching messages
+ * to it.
+ */
+class EngineDispatcher {
+ /** @type {Set<MessagePort>} */
+ #ports = new Set();
+
+ /** @type {TimeoutID | null} */
+ #keepAliveTimeout = null;
+
+ /** @type {PromiseWithResolvers} */
+ #modelRequest;
+
+ /** @type {Promise<Engine> | null} */
+ #engine = null;
+
+ /** @type {string} */
+ #engineName;
+
+ /**
+ * @param {MLEngineChild} mlEngineChild
+ * @param {MessagePort} port
+ * @param {string} engineName
+ * @param {number} timeoutMS
+ */
+ constructor(mlEngineChild, port, engineName, timeoutMS) {
+ /** @type {MLEngineChild} */
+ this.mlEngineChild = mlEngineChild;
+
+ /** @type {number} */
+ this.timeoutMS = timeoutMS;
+
+ this.#engineName = engineName;
+
+ this.#engine = Promise.all([
+ this.mlEngineChild.getWasmArrayBuffer(),
+ this.getModel(port),
+ ]).then(([wasm, model]) => FakeEngine.create(wasm, model));
+
+ 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);
+ }
+
+ /**
+ * 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, 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) {
+ port.onmessage = async ({ data }) => {
+ switch (data.type) {
+ case "EnginePort:Discard": {
+ port.close();
+ this.#ports.delete(port);
+ break;
+ }
+ case "EnginePort:Terminate": {
+ this.terminate();
+ 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;
+ let engine;
+ try {
+ engine = await this.#engine;
+ } catch (error) {
+ port.postMessage({
+ type: "EnginePort:RunResponse",
+ requestId,
+ response: null,
+ error,
+ });
+ // The engine failed to load. Terminate the entire dispatcher.
+ this.terminate();
+ 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 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.
+ */
+ async terminate() {
+ if (this.#keepAliveTimeout) {
+ lazy.clearTimeout(this.#keepAliveTimeout);
+ this.#keepAliveTimeout = null;
+ }
+ for (const port of this.#ports) {
+ port.postMessage({ type: "EnginePort:EngineTerminated" });
+ port.close();
+ }
+ this.#ports = new Set();
+ this.mlEngineChild.removeEngine(this.#engineName);
+ try {
+ const engine = await this.#engine;
+ engine.terminate();
+ } catch (error) {
+ console.error("Failed to get the engine", error);
+ }
+ }
+}
+
+/**
+ * Fake the engine by slicing the text in half.
+ */
+class FakeEngine {
+ /** @type {BasePromiseWorker} */
+ #worker;
+
+ /**
+ * Initialize the worker.
+ *
+ * @param {ArrayBuffer} wasm
+ * @param {ArrayBuffer} model
+ * @returns {FakeEngine}
+ */
+ static async create(wasm, model) {
+ /** @type {BasePromiseWorker} */
+ const worker = new lazy.BasePromiseWorker(
+ "chrome://global/content/ml/MLEngine.worker.mjs",
+ { type: "module" }
+ );
+
+ const args = [wasm, model, lazy.loggingLevel];
+ const closure = {};
+ const transferables = [wasm, model];
+ await worker.post("initializeEngine", args, closure, transferables);
+ return new FakeEngine(worker);
+ }
+
+ /**
+ * @param {BasePromiseWorker} worker
+ */
+ constructor(worker) {
+ this.#worker = worker;
+ }
+
+ /**
+ * @param {string} request
+ * @returns {Promise<string>}
+ */
+ run(request) {
+ return this.#worker.post("run", [request]);
+ }
+
+ terminate() {
+ this.#worker.terminate();
+ this.#worker = null;
+ }
+}
diff --git a/toolkit/components/ml/actors/MLEngineParent.sys.mjs b/toolkit/components/ml/actors/MLEngineParent.sys.mjs
new file mode 100644
index 0000000000..10b4eed4fa
--- /dev/null
+++ b/toolkit/components/ml/actors/MLEngineParent.sys.mjs
@@ -0,0 +1,403 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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
+ * @property {typeof console} console
+ * @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",
+ });
+});
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ EngineProcess: "chrome://global/content/ml/EngineProcess.sys.mjs",
+ RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
+ TranslationsParent: "resource://gre/actors/TranslationsParent.sys.mjs",
+});
+
+/**
+ * @typedef {import("../../translations/translations").WasmRecord} WasmRecord
+ */
+
+const DEFAULT_CACHE_TIMEOUT_MS = 15_000;
+
+/**
+ * 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 {RemoteSettingsClient | null}
+ */
+ static #remoteClient = null;
+
+ /** @type {Promise<WasmRecord> | null} */
+ static #wasmRecord = null;
+
+ /**
+ * 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;
+
+ /**
+ * Remote settings isn't available in tests, so provide mocked responses.
+ *
+ * @param {RemoteSettingsClient} remoteClient
+ */
+ static mockRemoteSettings(remoteClient) {
+ lazy.console.log("Mocking remote settings in MLEngineParent.");
+ MLEngineParent.#remoteClient = remoteClient;
+ MLEngineParent.#wasmRecord = null;
+ }
+
+ /**
+ * Remove anything that could have been mocked.
+ */
+ static removeMocks() {
+ lazy.console.log("Removing mocked remote client in MLEngineParent.");
+ MLEngineParent.#remoteClient = null;
+ MLEngineParent.#wasmRecord = null;
+ }
+
+ /**
+ * @param {string} engineName
+ * @param {() => Promise<ArrayBuffer>} getModel
+ * @param {number} cacheTimeoutMS - How long the engine cache remains alive between
+ * uses, in milliseconds. In automation the engine is manually created and destroyed
+ * to avoid timing issues.
+ * @returns {MLEngine}
+ */
+ getEngine(engineName, getModel, cacheTimeoutMS = DEFAULT_CACHE_TIMEOUT_MS) {
+ return new MLEngine(this, engineName, getModel, cacheTimeoutMS);
+ }
+
+ // eslint-disable-next-line consistent-return
+ async receiveMessage({ name, data }) {
+ switch (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:DestroyEngineProcess":
+ lazy.EngineProcess.destroyMLEngine().catch(error =>
+ console.error(error)
+ );
+ break;
+ }
+ }
+
+ /**
+ * @param {RemoteSettingsClient} client
+ */
+ static async #getWasmArrayRecord(client) {
+ // Load the wasm binary from remote settings, if it hasn't been already.
+ lazy.console.log(`Getting remote wasm records.`);
+
+ /** @type {WasmRecord[]} */
+ const wasmRecords = await lazy.TranslationsParent.getMaxVersionRecords(
+ client,
+ {
+ // TODO - This record needs to be created with the engine wasm payload.
+ filters: { name: "inference-engine" },
+ 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.log(
+ `Using ${record.name}@${record.release} release version ${record.version} first released on Fx${record.fx_release}`,
+ record
+ );
+ return record;
+ }
+
+ /**
+ * Download the wasm for the ML inference engine.
+ *
+ * @returns {Promise<ArrayBuffer>}
+ */
+ static async getWasmArrayBuffer() {
+ const client = MLEngineParent.#getRemoteClient();
+
+ 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.
+ *
+ * @returns {RemoteSettingsClient}
+ */
+ static #getRemoteClient() {
+ if (MLEngineParent.#remoteClient) {
+ return MLEngineParent.#remoteClient;
+ }
+
+ /** @type {RemoteSettingsClient} */
+ const client = lazy.RemoteSettings("ml-wasm");
+
+ MLEngineParent.#remoteClient = client;
+
+ client.on("sync", async ({ data: { created, updated, deleted } }) => {
+ lazy.console.log(`"sync" event for ml-wasm`, {
+ 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");
+ }
+}
+
+/**
+ * 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
+ */
+
+/**
+ * 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 {
+ /**
+ * @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";
+
+ /**
+ * @param {MLEngineParent} mlEngineParent
+ * @param {string} engineName
+ * @param {() => Promise<ArrayBuffer>} getModel
+ * @param {number} timeoutMS
+ */
+ constructor(mlEngineParent, engineName, getModel, timeoutMS) {
+ /** @type {MLEngineParent} */
+ this.mlEngineParent = mlEngineParent;
+ /** @type {string} */
+ this.engineName = engineName;
+ /** @type {() => Promise<ArrayBuffer>} */
+ this.getModel = getModel;
+ /** @type {number} */
+ this.timeoutMS = timeoutMS;
+
+ this.#setupPortCommunication();
+ }
+
+ /**
+ * Create a MessageChannel to communicate with the engine directly.
+ */
+ #setupPortCommunication() {
+ const { port1: childPort, port2: parentPort } = new MessageChannel();
+ const transferables = [childPort];
+ this.#port = parentPort;
+
+ this.#port.onmessage = this.handlePortMessage;
+ this.mlEngineParent.sendAsyncMessage(
+ "MLEngine:NewPort",
+ {
+ port: childPort,
+ engineName: this.engineName,
+ timeoutMS: this.timeoutMS,
+ },
+ transferables
+ );
+ }
+
+ handlePortMessage = ({ data }) => {
+ switch (data.type) {
+ 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.engineStatus = "closed";
+ this.discardPort();
+ break;
+ }
+ default:
+ lazy.console.error("Unknown port message from engine", data);
+ break;
+ }
+ };
+
+ discardPort() {
+ if (this.#port) {
+ this.#port.postMessage({ type: "EnginePort:Discard" });
+ this.#port.close();
+ this.#port = null;
+ }
+ }
+
+ terminate() {
+ this.#port.postMessage({ type: "EnginePort:Terminate" });
+ }
+
+ /**
+ * @param {Request} request
+ * @returns {Promise<Response>}
+ */
+ run(request) {
+ const resolvers = Promise.withResolvers();
+ const requestId = this.#nextRequestId++;
+ this.#requests.set(requestId, resolvers);
+ this.#port.postMessage({
+ type: "EnginePort:Run",
+ requestId,
+ request,
+ });
+ return resolvers.promise;
+ }
+}
diff --git a/toolkit/components/ml/actors/moz.build b/toolkit/components/ml/actors/moz.build
new file mode 100644
index 0000000000..de3e27ae2a
--- /dev/null
+++ b/toolkit/components/ml/actors/moz.build
@@ -0,0 +1,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/.
+
+FINAL_TARGET_FILES.actors += [
+ "MLEngineChild.sys.mjs",
+ "MLEngineParent.sys.mjs",
+]
diff --git a/toolkit/components/ml/content/EngineProcess.sys.mjs b/toolkit/components/ml/content/EngineProcess.sys.mjs
new file mode 100644
index 0000000000..36a9381192
--- /dev/null
+++ b/toolkit/components/ml/content/EngineProcess.sys.mjs
@@ -0,0 +1,241 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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",
+});
+
+/**
+ * @typedef {import("../actors/MLEngineParent.sys.mjs").MLEngineParent} MLEngineParent
+ */
+
+/**
+ * @typedef {import("../../translations/actors/TranslationsEngineParent.sys.mjs").TranslationsEngineParent} TranslationsEngineParent
+ */
+
+/**
+ * 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() {
+ 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", "web");
+ 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 #destroyEngine({ id, keyName }) {
+ ChromeUtils.addProfilerMarker(
+ "EngineProcess",
+ {},
+ `Destroying the "${id}" engine`
+ );
+
+ const actorShutdown = this.forceActorShutdown(id, keyName).catch(
+ error => void console.error(error)
+ );
+
+ 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.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 the promise even if there are errors.
+ return Promise.resolve();
+ }
+
+ /**
+ * 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();
+ }
+}
diff --git a/toolkit/components/ml/content/MLEngine.html b/toolkit/components/ml/content/MLEngine.html
new file mode 100644
index 0000000000..8763995102
--- /dev/null
+++ b/toolkit/components/ml/content/MLEngine.html
@@ -0,0 +1,16 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.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>
diff --git a/toolkit/components/ml/content/MLEngine.worker.mjs b/toolkit/components/ml/content/MLEngine.worker.mjs
new file mode 100644
index 0000000000..1013977e07
--- /dev/null
+++ b/toolkit/components/ml/content/MLEngine.worker.mjs
@@ -0,0 +1,91 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import { PromiseWorker } from "resource://gre/modules/workers/PromiseWorker.mjs";
+
+// Respect the preference "browser.ml.logLevel".
+let _loggingLevel = "Error";
+function log(...args) {
+ if (_loggingLevel !== "Error" && _loggingLevel !== "Warn") {
+ console.log("ML:", ...args);
+ }
+}
+function trace(...args) {
+ if (_loggingLevel === "Trace" || _loggingLevel === "All") {
+ console.log("ML:", ...args);
+ }
+}
+
+/**
+ * The actual MLEngine lives here in a worker.
+ */
+class MLEngineWorker {
+ /** @type {ArrayBuffer} */
+ #wasm;
+ /** @type {ArrayBuffer} */
+ #model;
+
+ constructor() {
+ // Connect the provider to the worker.
+ this.#connectToPromiseWorker();
+ }
+
+ /**
+ * @param {ArrayBuffer} wasm
+ * @param {ArrayBuffer} model
+ * @param {string} loggingLevel
+ */
+ initializeEngine(wasm, model, loggingLevel) {
+ this.#wasm = wasm;
+ this.#model = model;
+ _loggingLevel = loggingLevel;
+ // TODO - Initialize the engine for real here.
+ log("MLEngineWorker is initalized");
+ }
+
+ /**
+ * Run the worker.
+ *
+ * @param {string} request
+ */
+ run(request) {
+ if (!this.#wasm) {
+ throw new Error("Expected the wasm to exist.");
+ }
+ if (!this.#model) {
+ throw new Error("Expected the model to exist");
+ }
+ if (request === "throw") {
+ throw new Error(
+ 'Received the message "throw", so intentionally throwing an error.'
+ );
+ }
+ trace("inference run requested with:", request);
+ return request.slice(0, Math.floor(request.length / 2));
+ }
+
+ /**
+ * Glue code to connect the `MLEngineWorker` to the PromiseWorker interface.
+ */
+ #connectToPromiseWorker() {
+ const worker = new 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.addEventListener("message", msg => worker.handleMessage(msg));
+ self.addEventListener("unhandledrejection", function (error) {
+ throw error.reason;
+ });
+ }
+}
+
+new MLEngineWorker();
diff --git a/toolkit/components/ml/content/SummarizerModel.sys.mjs b/toolkit/components/ml/content/SummarizerModel.sys.mjs
new file mode 100644
index 0000000000..7cac55d92f
--- /dev/null
+++ b/toolkit/components/ml/content/SummarizerModel.sys.mjs
@@ -0,0 +1,160 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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} LazyImports
+ * @property {typeof import("../actors/MLEngineParent.sys.mjs").MLEngineParent} MLEngineParent
+ */
+
+/** @type {LazyImports} */
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
+ TranslationsParent: "resource://gre/actors/TranslationsParent.sys.mjs",
+});
+
+ChromeUtils.defineLazyGetter(lazy, "console", () => {
+ return console.createInstance({
+ maxLogLevelPref: "browser.ml.logLevel",
+ prefix: "ML",
+ });
+});
+
+export class SummarizerModel {
+ /**
+ * The RemoteSettingsClient that downloads the summarizer model.
+ *
+ * @type {RemoteSettingsClient | null}
+ */
+ static #remoteClient = null;
+
+ /** @type {Promise<WasmRecord> | null} */
+ static #modelRecord = null;
+
+ /**
+ * 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 MODEL_MAJOR_VERSION = 1;
+
+ /**
+ * Remote settings isn't available in tests, so provide mocked responses.
+ */
+ static mockRemoteSettings(remoteClient) {
+ lazy.console.log("Mocking remote client in SummarizerModel.");
+ SummarizerModel.#remoteClient = remoteClient;
+ SummarizerModel.#modelRecord = null;
+ }
+
+ /**
+ * Remove anything that could have been mocked.
+ */
+ static removeMocks() {
+ lazy.console.log("Removing mocked remote client in SummarizerModel.");
+ SummarizerModel.#remoteClient = null;
+ SummarizerModel.#modelRecord = null;
+ }
+ /**
+ * Download or load the model from remote settings.
+ *
+ * @returns {Promise<ArrayBuffer>}
+ */
+ static async getModel() {
+ const client = SummarizerModel.#getRemoteClient();
+
+ if (!SummarizerModel.#modelRecord) {
+ // Place the records into a promise to prevent any races.
+ SummarizerModel.#modelRecord = (async () => {
+ // Load the wasm binary from remote settings, if it hasn't been already.
+ lazy.console.log(`Getting the summarizer model record.`);
+
+ // TODO - The getMaxVersionRecords should eventually migrated to some kind of
+ // shared utility.
+ const { getMaxVersionRecords } = lazy.TranslationsParent;
+
+ /** @type {WasmRecord[]} */
+ const wasmRecords = await getMaxVersionRecords(client, {
+ // TODO - This record needs to be created with the engine wasm payload.
+ filters: { name: "summarizer-model" },
+ majorVersion: SummarizerModel.MODEL_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 models from Remote Settings.");
+ }
+
+ if (wasmRecords.length > 1) {
+ SummarizerModel.reportError(
+ new Error("Expected the ml engine 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;
+ })();
+ }
+
+ try {
+ /** @type {{buffer: ArrayBuffer}} */
+ const { buffer } = await client.attachments.download(
+ await SummarizerModel.#modelRecord
+ );
+
+ return buffer;
+ } catch (error) {
+ SummarizerModel.#modelRecord = null;
+ throw error;
+ }
+ }
+
+ /**
+ * Lazily initializes the RemoteSettingsClient.
+ *
+ * @returns {RemoteSettingsClient}
+ */
+ static #getRemoteClient() {
+ if (SummarizerModel.#remoteClient) {
+ return SummarizerModel.#remoteClient;
+ }
+
+ /** @type {RemoteSettingsClient} */
+ const client = lazy.RemoteSettings("ml-model");
+
+ SummarizerModel.#remoteClient = client;
+
+ client.on("sync", async ({ data: { created, updated, deleted } }) => {
+ lazy.console.log(`"sync" event for ml-model`, {
+ 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;
+ }
+}
diff --git a/toolkit/components/ml/docs/index.md b/toolkit/components/ml/docs/index.md
new file mode 100644
index 0000000000..1b2015456b
--- /dev/null
+++ b/toolkit/components/ml/docs/index.md
@@ -0,0 +1,44 @@
+# Machine Learning
+
+This component is an experimental machine learning local inference engine. Currently there is no inference engine actually integrated yet.
+
+Here is an example of the API:
+
+```js
+// The engine process manages the life cycle of the engine. It runs in its own process.
+// Models can consume large amounts of memory, and this helps encapsulate it at the
+// operating system level.
+const EngineProcess = ChromeUtils.importESModule("chrome://global/content/ml/EngineProcess.sys.mjs");
+
+// The MLEngineParent is a JSActor that can communicate with the engine process.
+const mlEngineParent = await EngineProcess.getMLEngineParent();
+
+
+/**
+ * When implementing a model, there should be a class that provides a `getModel` function
+ * that is responsible for providing the `ArrayBuffer` of the model. Typically this
+ * download is managed by RemoteSettings.
+ */
+class SummarizerModel {
+ /**
+ * @returns {ArrayBuffer}
+ */
+ static getModel() { ... }
+}
+
+// An engine can be created using a unique name for the engine, and the function
+// to get the model. This class handles the life cycle of the engine.
+const summarizer = mlEngineParent.getEngine(
+ "summarizer",
+ SummarizerModel.getModel
+);
+
+// In order to run the model, use the `run` method. This will initiate the engine if
+// it is needed, and return the result. The messaging to the engine process happens
+// through a MessagePort.
+const result = await summarizer.run("A sentence that can be summarized.")
+
+// The engine can be explicitly terminated, or it will be destroyed through an idle
+// timeout when not in use, as the memory requirements for models can be quite large.
+summarizer.terminate();
+```
diff --git a/toolkit/components/ml/jar.mn b/toolkit/components/ml/jar.mn
new file mode 100644
index 0000000000..56bfb0d469
--- /dev/null
+++ b/toolkit/components/ml/jar.mn
@@ -0,0 +1,9 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+toolkit.jar:
+ content/global/ml/EngineProcess.sys.mjs (content/EngineProcess.sys.mjs)
+ content/global/ml/MLEngine.worker.mjs (content/MLEngine.worker.mjs)
+ content/global/ml/MLEngine.html (content/MLEngine.html)
+ content/global/ml/SummarizerModel.sys.mjs (content/SummarizerModel.sys.mjs)
diff --git a/toolkit/components/ml/moz.build b/toolkit/components/ml/moz.build
new file mode 100644
index 0000000000..3308d8f085
--- /dev/null
+++ b/toolkit/components/ml/moz.build
@@ -0,0 +1,17 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+SPHINX_TREES["/toolkit/components/ml"] = "docs"
+
+JAR_MANIFESTS += ["jar.mn"]
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "Machine Learning")
+
+DIRS += ["actors"]
+
+BROWSER_CHROME_MANIFESTS += ["tests/browser/browser.toml"]
+
+with Files("docs/**"):
+ SCHEDULES.exclusive = ["docs"]
diff --git a/toolkit/components/ml/tests/browser/browser.toml b/toolkit/components/ml/tests/browser/browser.toml
new file mode 100644
index 0000000000..9ccda0beaa
--- /dev/null
+++ b/toolkit/components/ml/tests/browser/browser.toml
@@ -0,0 +1,5 @@
+[DEFAULT]
+support-files = [
+ "head.js",
+]
+["browser_ml_engine.js"]
diff --git a/toolkit/components/ml/tests/browser/browser_ml_engine.js b/toolkit/components/ml/tests/browser/browser_ml_engine.js
new file mode 100644
index 0000000000..6942809d6d
--- /dev/null
+++ b/toolkit/components/ml/tests/browser/browser_ml_engine.js
@@ -0,0 +1,219 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/// <reference path="head.js" />
+
+async function setup({ disabled = false, prefs = [] } = {}) {
+ const { removeMocks, remoteClients } = await createAndMockMLRemoteSettings({
+ autoDownloadFromRemoteSettings: false,
+ });
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ // Enabled by default.
+ ["browser.ml.enable", !disabled],
+ ["browser.ml.logLevel", "All"],
+ ...prefs,
+ ],
+ });
+
+ return {
+ remoteClients,
+ async cleanup() {
+ await removeMocks();
+ await waitForCondition(
+ () => EngineProcess.areAllEnginesTerminated(),
+ "Waiting for all of the engines to be terminated.",
+ 100,
+ 200
+ );
+ },
+ };
+}
+
+add_task(async function test_ml_engine_basics() {
+ const { cleanup, remoteClients } = await setup();
+
+ info("Get the engine process");
+ const mlEngineParent = await EngineProcess.getMLEngineParent();
+
+ info("Get summarizer");
+ const summarizer = mlEngineParent.getEngine(
+ "summarizer",
+ SummarizerModel.getModel
+ );
+
+ info("Run the summarizer");
+ const summarizePromise = summarizer.run("This gets cut in half.");
+
+ info("Wait for the pending downloads.");
+ await remoteClients.models.resolvePendingDownloads(1);
+ await remoteClients.wasm.resolvePendingDownloads(1);
+
+ is(
+ await summarizePromise,
+ "This gets c",
+ "The text gets cut in half simulating summarizing"
+ );
+
+ ok(
+ !EngineProcess.areAllEnginesTerminated(),
+ "The engine process is still active."
+ );
+
+ await EngineProcess.destroyMLEngine();
+
+ await cleanup();
+});
+
+add_task(async function test_ml_engine_model_rejection() {
+ const { cleanup, remoteClients } = await setup();
+
+ info("Get the engine process");
+ const mlEngineParent = await EngineProcess.getMLEngineParent();
+
+ info("Get summarizer");
+ const summarizer = mlEngineParent.getEngine(
+ "summarizer",
+ SummarizerModel.getModel
+ );
+
+ info("Run the summarizer");
+ const summarizePromise = summarizer.run("This gets cut in half.");
+
+ info("Wait for the pending downloads.");
+ await remoteClients.wasm.resolvePendingDownloads(1);
+ await remoteClients.models.rejectPendingDownloads(1);
+
+ let error;
+ try {
+ await summarizePromise;
+ } catch (e) {
+ error = e;
+ }
+ is(
+ error?.message,
+ "Intentionally rejecting downloads.",
+ "The error is correctly surfaced."
+ );
+
+ await cleanup();
+});
+
+add_task(async function test_ml_engine_wasm_rejection() {
+ const { cleanup, remoteClients } = await setup();
+
+ info("Get the engine process");
+ const mlEngineParent = await EngineProcess.getMLEngineParent();
+
+ info("Get summarizer");
+ const summarizer = mlEngineParent.getEngine(
+ "summarizer",
+ SummarizerModel.getModel
+ );
+
+ info("Run the summarizer");
+ const summarizePromise = summarizer.run("This gets cut in half.");
+
+ info("Wait for the pending downloads.");
+ await remoteClients.wasm.rejectPendingDownloads(1);
+ await remoteClients.models.resolvePendingDownloads(1);
+
+ let error;
+ try {
+ await summarizePromise;
+ } catch (e) {
+ error = e;
+ }
+ is(
+ error?.message,
+ "Intentionally rejecting downloads.",
+ "The error is correctly surfaced."
+ );
+
+ await cleanup();
+});
+
+/**
+ * Tests that the SummarizerModel's internal errors are correctly surfaced.
+ */
+add_task(async function test_ml_engine_model_error() {
+ const { cleanup, remoteClients } = await setup();
+
+ info("Get the engine process");
+ const mlEngineParent = await EngineProcess.getMLEngineParent();
+
+ info("Get summarizer");
+ const summarizer = mlEngineParent.getEngine(
+ "summarizer",
+ SummarizerModel.getModel
+ );
+
+ info("Run the summarizer with a throwing example.");
+ const summarizePromise = summarizer.run("throw");
+
+ info("Wait for the pending downloads.");
+ await remoteClients.wasm.resolvePendingDownloads(1);
+ await remoteClients.models.resolvePendingDownloads(1);
+
+ let error;
+ try {
+ await summarizePromise;
+ } catch (e) {
+ error = e;
+ }
+ is(
+ error?.message,
+ 'Error: Received the message "throw", so intentionally throwing an error.',
+ "The error is correctly surfaced."
+ );
+
+ summarizer.terminate();
+
+ await cleanup();
+});
+
+/**
+ * This test is really similar to the "basic" test, but tests manually destroying
+ * the summarizer.
+ */
+add_task(async function test_ml_engine_destruction() {
+ const { cleanup, remoteClients } = await setup();
+
+ info("Get the engine process");
+ const mlEngineParent = await EngineProcess.getMLEngineParent();
+
+ info("Get summarizer");
+ const summarizer = mlEngineParent.getEngine(
+ "summarizer",
+ SummarizerModel.getModel
+ );
+
+ info("Run the summarizer");
+ const summarizePromise = summarizer.run("This gets cut in half.");
+
+ info("Wait for the pending downloads.");
+ await remoteClients.models.resolvePendingDownloads(1);
+ await remoteClients.wasm.resolvePendingDownloads(1);
+
+ is(
+ await summarizePromise,
+ "This gets c",
+ "The text gets cut in half simulating summarizing"
+ );
+
+ ok(
+ !EngineProcess.areAllEnginesTerminated(),
+ "The engine process is still active."
+ );
+
+ summarizer.terminate();
+
+ info(
+ "The summarizer is manually destroyed. The cleanup function should wait for the engine process to be destroyed."
+ );
+
+ await cleanup();
+});
diff --git a/toolkit/components/ml/tests/browser/head.js b/toolkit/components/ml/tests/browser/head.js
new file mode 100644
index 0000000000..99d27ce18a
--- /dev/null
+++ b/toolkit/components/ml/tests/browser/head.js
@@ -0,0 +1,155 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/// <reference path="../../../../../toolkit/components/translations/tests/browser/shared-head.js" />
+
+"use strict";
+
+/**
+ * @type {import("../../content/SummarizerModel.sys.mjs")}
+ */
+const { SummarizerModel } = ChromeUtils.importESModule(
+ "chrome://global/content/ml/SummarizerModel.sys.mjs"
+);
+
+/**
+ * @type {import("../../actors/MLEngineParent.sys.mjs")}
+ */
+const { MLEngineParent } = ChromeUtils.importESModule(
+ "resource://gre/actors/MLEngineParent.sys.mjs"
+);
+
+// This test suite shares some utility functions with translations as they work in a very
+// similar fashion. Eventually, the plan is to unify these two components.
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/toolkit/components/translations/tests/browser/shared-head.js",
+ this
+);
+
+function getDefaultModelRecords() {
+ return [
+ {
+ name: "summarizer-model",
+ version: SummarizerModel.MODEL_MAJOR_VERSION + ".0",
+ },
+ ];
+}
+
+function getDefaultWasmRecords() {
+ return [
+ {
+ name: "inference-engine",
+ version: MLEngineParent.WASM_MAJOR_VERSION + ".0",
+ },
+ ];
+}
+
+/**
+ * Creates a local RemoteSettingsClient for use within tests.
+ *
+ * @param {boolean} autoDownloadFromRemoteSettings
+ * @param {object[]} models
+ * @returns {AttachmentMock}
+ */
+async function createMLModelsRemoteClient(
+ autoDownloadFromRemoteSettings,
+ models = getDefaultModelRecords()
+) {
+ const { RemoteSettings } = ChromeUtils.importESModule(
+ "resource://services-settings/remote-settings.sys.mjs"
+ );
+ const mockedCollectionName = "test-ml-models";
+ const client = RemoteSettings(
+ `${mockedCollectionName}-${_remoteSettingsMockId++}`
+ );
+ const metadata = {};
+ await client.db.clear();
+ await client.db.importChanges(
+ metadata,
+ Date.now(),
+ models.map(({ name, version }) => ({
+ id: crypto.randomUUID(),
+ name,
+ version,
+ last_modified: Date.now(),
+ schema: Date.now(),
+ attachment: {
+ hash: `${crypto.randomUUID()}`,
+ size: `123`,
+ filename: name,
+ location: `main-workspace/ml-models/${crypto.randomUUID()}.bin`,
+ mimetype: "application/octet-stream",
+ },
+ }))
+ );
+
+ return createAttachmentMock(
+ client,
+ mockedCollectionName,
+ autoDownloadFromRemoteSettings
+ );
+}
+
+async function createAndMockMLRemoteSettings({
+ models = getDefaultModelRecords(),
+ autoDownloadFromRemoteSettings = false,
+} = {}) {
+ const remoteClients = {
+ models: await createMLModelsRemoteClient(
+ autoDownloadFromRemoteSettings,
+ models
+ ),
+ wasm: await createMLWasmRemoteClient(autoDownloadFromRemoteSettings),
+ };
+
+ MLEngineParent.mockRemoteSettings(remoteClients.wasm.client);
+ SummarizerModel.mockRemoteSettings(remoteClients.models.client);
+
+ return {
+ async removeMocks() {
+ await remoteClients.models.client.attachments.deleteAll();
+ await remoteClients.models.client.db.clear();
+ await remoteClients.wasm.client.attachments.deleteAll();
+ await remoteClients.wasm.client.db.clear();
+
+ MLEngineParent.removeMocks();
+ SummarizerModel.removeMocks();
+ },
+ remoteClients,
+ };
+}
+
+/**
+ * Creates a local RemoteSettingsClient for use within tests.
+ *
+ * @param {boolean} autoDownloadFromRemoteSettings
+ * @returns {AttachmentMock}
+ */
+async function createMLWasmRemoteClient(autoDownloadFromRemoteSettings) {
+ const { RemoteSettings } = ChromeUtils.importESModule(
+ "resource://services-settings/remote-settings.sys.mjs"
+ );
+ const mockedCollectionName = "test-translation-wasm";
+ const client = RemoteSettings(
+ `${mockedCollectionName}-${_remoteSettingsMockId++}`
+ );
+ const metadata = {};
+ await client.db.clear();
+ await client.db.importChanges(
+ metadata,
+ Date.now(),
+ getDefaultWasmRecords().map(({ name, version }) => ({
+ id: crypto.randomUUID(),
+ name,
+ version,
+ last_modified: Date.now(),
+ schema: Date.now(),
+ }))
+ );
+
+ return createAttachmentMock(
+ client,
+ mockedCollectionName,
+ autoDownloadFromRemoteSettings
+ );
+}
diff --git a/toolkit/components/moz.build b/toolkit/components/moz.build
index 1a0493f1cd..d574f1305b 100644
--- a/toolkit/components/moz.build
+++ b/toolkit/components/moz.build
@@ -47,6 +47,7 @@ DIRS += [
"jsoncpp/src/lib_json",
"kvstore",
"mediasniffer",
+ "ml",
"mozintl",
"mozprotocol",
"parentalcontrols",
diff --git a/toolkit/components/mozintl/mozIntl.sys.mjs b/toolkit/components/mozintl/mozIntl.sys.mjs
index 50633e365c..ad0b4dd5d9 100644
--- a/toolkit/components/mozintl/mozIntl.sys.mjs
+++ b/toolkit/components/mozintl/mozIntl.sys.mjs
@@ -143,7 +143,7 @@ function bestFit(absDiff) {
* Thresholds to use for calculating the best unit for relative time fromatting.
*/
const threshold = {
- month: 2, // at least 2 months before using year.
+ 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.
diff --git a/toolkit/components/mozintl/test/test_mozintl.js b/toolkit/components/mozintl/test/test_mozintl.js
index dc7b8a7afd..2ee1583b8c 100644
--- a/toolkit/components/mozintl/test/test_mozintl.js
+++ b/toolkit/components/mozintl/test/test_mozintl.js
@@ -139,6 +139,10 @@ function test_rtf_formatBestUnit() {
let anchor = new Date("2016-04-10 12:00:00");
testRTFBestUnit(anchor, "2014-04-01 00:00", "2 years ago");
testRTFBestUnit(anchor, "2015-03-01 00:00", "last year");
+ testRTFBestUnit(anchor, "2015-04-01 00:00", "last year");
+ testRTFBestUnit(anchor, "2015-05-01 00:00", "11 months ago");
+ testRTFBestUnit(anchor, "2015-12-01 00:00", "4 months ago");
+ testRTFBestUnit(anchor, "2016-02-01 00:00", "2 months ago");
testRTFBestUnit(anchor, "2017-05-01 00:00", "next year");
testRTFBestUnit(anchor, "2024-12-01 23:59", "in 8 years");
@@ -147,8 +151,10 @@ function test_rtf_formatBestUnit() {
testRTFBestUnit(anchor, "2015-12-29 18:30", "2 years ago");
anchor = new Date("2016-12-29 18:30");
- testRTFBestUnit(anchor, "2017-07-12 18:30", "next year");
testRTFBestUnit(anchor, "2017-02-12 18:30", "in 2 months");
+ testRTFBestUnit(anchor, "2017-07-12 18:30", "in 7 months");
+ testRTFBestUnit(anchor, "2017-11-29 18:30", "in 11 months");
+ testRTFBestUnit(anchor, "2017-12-01 18:30", "next year");
testRTFBestUnit(anchor, "2018-01-02 18:30", "in 2 years");
testRTFBestUnit(anchor, "2098-01-02 18:30", "in 82 years");
diff --git a/toolkit/components/narrate/NarrateControls.sys.mjs b/toolkit/components/narrate/NarrateControls.sys.mjs
index 316d2a0e34..ed2f8ff124 100644
--- a/toolkit/components/narrate/NarrateControls.sys.mjs
+++ b/toolkit/components/narrate/NarrateControls.sys.mjs
@@ -23,9 +23,9 @@ export function NarrateControls(win, languagePromise) {
win.document.head.appendChild(style);
let elemL10nMap = {
- ".narrate-skip-previous": "back",
+ ".narrate-skip-previous": "previous-label",
".narrate-start-stop": "start-label",
- ".narrate-skip-next": "forward",
+ ".narrate-skip-next": "next-label",
".narrate-rate-input": "speed",
};
@@ -72,23 +72,34 @@ export function NarrateControls(win, languagePromise) {
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 narrateSkipNext = win.document.createElement("button");
- narrateSkipNext.className = "narrate-skip-next";
- narrateSkipNext.disabled = true;
- narrateControl.appendChild(narrateSkipNext);
-
let narrateRateInput = win.document.createElement("input");
narrateRateInput.className = "narrate-rate-input";
narrateRateInput.setAttribute("value", "0");
@@ -98,16 +109,44 @@ export function NarrateControls(win, languagePromise) {
narrateRateInput.setAttribute("type", "range");
narrateRate.appendChild(narrateRateInput);
- for (let [selector, stringID] of Object.entries(elemL10nMap)) {
- if (selector === ".narrate-start-stop") {
- let shortcut = gStrings.GetStringFromName("narrate-key-shortcut");
- let label = gStrings.formatStringFromName(stringID, [shortcut]);
-
- dropdown.querySelector(selector).setAttribute("title", label);
+ function setShortcutAttribute(
+ keyShortcut,
+ stringID,
+ selector,
+ isString = false
+ ) {
+ let shortcut;
+ if (isString) {
+ shortcut = keyShortcut;
} else {
- dropdown
- .querySelector(selector)
- .setAttribute("title", gStrings.GetStringFromName(stringID));
+ 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;
}
}
@@ -282,15 +321,26 @@ NarrateControls.prototype = {
dropdown.classList.toggle("speaking", speaking);
let startStopButton = this._doc.querySelector(".narrate-start-stop");
- let shortcutId = gStrings.GetStringFromName("narrate-key-shortcut");
+ 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",
- [shortcutId]
+ [narrateShortcutId]
);
-
- this._doc.querySelector(".narrate-skip-previous").disabled = !speaking;
- this._doc.querySelector(".narrate-skip-next").disabled = !speaking;
+ skipPreviousButton.title = gStrings.formatStringFromName("previous-label", [
+ skipPreviousShortcut,
+ ]);
+ skipNextButton.title = gStrings.formatStringFromName("next-label", [
+ skipNextShortcut,
+ ]);
},
_createVoiceLabel(voice) {
diff --git a/toolkit/components/narrate/test/browser_narrate_toggle.js b/toolkit/components/narrate/test/browser_narrate_toggle.js
index 54de276001..a7713f01b1 100644
--- a/toolkit/components/narrate/test/browser_narrate_toggle.js
+++ b/toolkit/components/narrate/test/browser_narrate_toggle.js
@@ -4,6 +4,8 @@
// This test verifies that the keyboard shortcut "n" will Start/Stop the
// narration of an article in readermode when the article is in focus.
+// This test also verifies that the keyboard shortcut "←" (left arrow) will
+// skip the narration backward, while "→" (right arrow) skips it forward.
registerCleanupFunction(teardown);
@@ -11,18 +13,39 @@ add_task(async function testToggleNarrate() {
setup();
await spawnInNewReaderTab(TEST_ARTICLE, async function () {
+ let TEST_VOICE = "urn:moz-tts:fake:teresa";
let $ = content.document.querySelector.bind(content.document);
+ let prefChanged = NarrateTestUtils.waitForPrefChange("narrate.voice");
+ NarrateTestUtils.selectVoice(content, TEST_VOICE);
+ await prefChanged;
+
await NarrateTestUtils.waitForNarrateToggle(content);
let eventUtils = NarrateTestUtils.getEventUtils(content);
NarrateTestUtils.isStoppedState(content, ok);
+ let promiseEvent = ContentTaskUtils.waitForEvent(content, "paragraphstart");
$(NarrateTestUtils.TOGGLE).focus();
eventUtils.synthesizeKey("n", {}, content);
+ let speechinfo = (await promiseEvent).detail;
+ let paragraph = speechinfo.paragraph;
+
+ NarrateTestUtils.isStartedState(content, ok);
+
+ promiseEvent = ContentTaskUtils.waitForEvent(content, "paragraphstart");
+ eventUtils.synthesizeKey("KEY_ArrowRight", {}, content);
+ speechinfo = (await promiseEvent).detail;
+ isnot(speechinfo.paragraph, paragraph, "next paragraph is being spoken");
+
+ NarrateTestUtils.isStartedState(content, ok);
+
+ promiseEvent = ContentTaskUtils.waitForEvent(content, "paragraphstart");
+ eventUtils.synthesizeKey("KEY_ArrowLeft", {}, content);
+ speechinfo = (await promiseEvent).detail;
+ is(speechinfo.paragraph, paragraph, "first paragraph being spoken");
- await ContentTaskUtils.waitForEvent(content, "paragraphstart");
NarrateTestUtils.isStartedState(content, ok);
$(NarrateTestUtils.TOGGLE).focus();
diff --git a/toolkit/components/nimbus/FeatureManifest.yaml b/toolkit/components/nimbus/FeatureManifest.yaml
index 746ac4738d..190e29d6db 100644
--- a/toolkit/components/nimbus/FeatureManifest.yaml
+++ b/toolkit/components/nimbus/FeatureManifest.yaml
@@ -2,6 +2,8 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+# yaml-language-server: $schema=schemas/ExperimentFeatureManifest.schema.json
+
# Features must be added here to be accessible through the NimbusFeature API.
"no-feature-firefox-desktop":
@@ -51,7 +53,6 @@ nimbus-qa-1:
nimbus-qa-2:
description: A feature for testing pref-setting on the user branch.
owner: barret@mozilla.com
- isEarlyStartup: true
hasExposure: false
variables:
value:
@@ -315,7 +316,7 @@ urlbar:
Whether Firefox Suggest will use the new Rust backend instead of the
original JS backend.
quickSuggestScenario:
- # IMPORTANT: This should not have a fallbackPref. See UrlbarPrefs.jsm.
+ # IMPORTANT: This should not have a fallbackPref. See UrlbarPrefs.sys.mjs.
type: string
description: The Firefox Suggest scenario in which the user is enrolled
enum:
@@ -421,6 +422,11 @@ urlbar:
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
fallbackPref: browser.urlbar.yelp.minKeywordLength
@@ -567,6 +573,13 @@ aboutwelcome:
fallbackPref: browser.aboutwelcome.newtabUrlBarFocus
description: >-
Should the urlbar be focused when the new tab page loads after new user onboarding
+ 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"
@@ -604,7 +617,6 @@ windowsJumpList:
description: "Controls for the Windows Jump List integration."
owner: mconley@mozilla.com
hasExposure: false
- isEarlyStartup: true
variables:
legacyBackend:
type: boolean
@@ -862,10 +874,15 @@ pocketNewtab:
fallbackPref: >-
browser.newtabpage.activity-stream.discoverystream.ctaButtonSponsors
ctaButtonVariant:
- description: Specifies which veriant to use for any sponsors in ctaButtonSponsors
+ 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
@@ -1064,7 +1081,6 @@ fullPageTranslation:
description: This feature opens a popup panel to offer to translate a page.
owner: gtatum@mozilla.com
hasExposure: false
- isEarlyStartup: true
variables:
boolean:
description: Set to true to enable the translations feature
@@ -1077,7 +1093,6 @@ fullPageTranslationAutomaticPopup:
description: Controls whether the popup automatically shows for translations.
owner: gtatum@mozilla.com
hasExposure: false
- isEarlyStartup: true
variables:
boolean:
description: Set to true to automatically popup, and false to only show the button.
@@ -1246,6 +1261,26 @@ fxms-message-11:
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
+
pbNewtab:
description: "A Firefox Messaging System message for the pbNewtab message channel"
owner: omc@mozilla.com
@@ -1325,6 +1360,7 @@ glean:
gleanInternalSdk:
description: "The Glean internal SDK feature intended only for internal Glean Team use"
+ owner: glean-team@mozilla.com
hasExposure: false
# Some variables are used through the C++ API and thus require pref-storage.
# We rely on those values at Glean.init time, which happens at startup.
@@ -1664,7 +1700,6 @@ fxaButtonVisibility:
description: Prefs to control the visibility of the Firefox Accounts toolbar button when not signed in.
owner: mconley@mozilla.com
hasExposure: false
- isEarlyStartup: true
variables:
boolean:
description: True if the Firefox Accounts toolbar button should be visible when not signed in.
@@ -1802,7 +1837,6 @@ migrationWizard:
description: Prefs to control the Migration Wizard UI.
owner: mconley@mozilla.com
hasExposure: false
- isEarlyStartup: true
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.
@@ -1891,36 +1925,205 @@ mixedContentUpgrading:
branch: default
pref: security.mixed_content.upgrade_display_content.video
-jsParallelParsing:
- description: Pref to toggle JS parallel parsing.
- owner: dpalmeiro@mozilla.com, nbp@mozilla.com
- isEarlyStartup: true
+gc:
+ description: Prefs that control gc heuristics.
+ owner: dpalmeiro@mozilla.com
hasExposure: false
variables:
- enabled:
- description: True to enable parallel parsing.
+ 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.parallel_parsing"
+ 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_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_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"
-gcParallelMarking:
- description: Pref to toggle parallel marking in the GC.
- owner: dpalmeiro@mozilla.com, jonco@mozilla.com
- isEarlyStartup: true
+jsParallelParsing:
+ description: Pref to toggle JS parallel parsing.
+ owner: dpalmeiro@mozilla.com, nbp@mozilla.com
hasExposure: false
variables:
enabled:
- description: True to enable parallel marking.
+ description: True to enable parallel parsing.
type: boolean
setPref:
branch: user
- pref: "javascript.options.mem.gc_parallel_marking"
+ pref: "javascript.options.parallel_parsing"
jitThresholds:
description: Prefs that control jit tier thresholds.
owner: dpalmeiro@mozilla.com, jdemooij@mozilla.com
- isEarlyStartup: true
hasExposure: false
variables:
blinterp_threshold:
@@ -1963,7 +2166,6 @@ jitThresholds:
jitHintsCache:
description: Pref to toggle the JIT hints cache.
owner: dpalmeiro@mozilla.com
- isEarlyStartup: true
hasExposure: false
variables:
enabled:
@@ -1997,17 +2199,6 @@ httpSpeculativeParallelLimit:
branch: default
pref: "network.http.speculative-parallel-limit"
-deviceMigration:
- description: Prefs to control aspects of the new device migration experiment
- owner: hjones@mozilla.com
- hasExposure: false
- isEarlyStartup: true
- variables:
- helpMenuHidden:
- description: True if new help menu item should be hidden
- type: boolean
- fallbackPref: browser.device-migration.help-menu.hidden
-
shopping2023:
description: Prefs to control the 2023 shopping experiment.
owner: jhirsch@mozilla.com
@@ -2069,7 +2260,6 @@ shoppingOHTTP:
opaqueResponseBlocking:
description: Prefs to enable Opaque Response Blocking
owner: farre@mozilla.com
- isEarlyStartup: true
hasExposure: true
exposureDescription: Exposure is sent when a response is blocked
variables:
@@ -2124,7 +2314,6 @@ powerSaver:
description: Prefs to control power saving behaviors
owner: florian@mozilla.com
hasExposure: false
- isEarlyStartup: true
variables:
reduceFrameRates:
type: int
@@ -2165,7 +2354,6 @@ backgroundUpdate:
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.
- isEarlyStartup: true
variables:
enableUpdatesForUnelevatedInstallations:
description: >-
@@ -2174,7 +2362,7 @@ backgroundUpdate:
directory can be written.
type: boolean
setPref:
- branch: default
+ branch: user
pref: app.update.background.allowUpdatesForUnelevatedInstallations
defaultAgent:
@@ -2195,7 +2383,6 @@ bookmarks:
description: Prefs to control aspects of the bookmarks system.
owner: omc@mozilla.com
hasExposure: false
- isEarlyStartup: true
variables:
enableBookmarksToolbar:
type: string
@@ -2274,7 +2461,6 @@ backgroundThreads:
description: Prefs to control MacOS thread priorities for power savings.
owner: kwright@mozilla.com
hasExposure: false
- isEarlyStartup: true
variables:
use_low_power:
description: >-
@@ -2293,9 +2479,9 @@ backgroundThreads:
pref: threads.lower_mainthread_priority_in_background.enabled
reportBrokenSite:
- description: the Report Broken Site feature
+ description: The Report Broken Site feature
+ owner: twisniewski@mozilla.com
hasExposure: false
- isEarlyStartup: true
variables:
enabled:
type: boolean
@@ -2348,7 +2534,6 @@ phc:
description: Prefs to control the Probabalistic Heap Checker (PHC)
owner: pbone@mozilla.com
hasExposure: false
- isEarlyStartup: true
variables:
phcEnabled:
description: Whether to enable PHC
@@ -2413,3 +2598,53 @@ nimbusIsReady:
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
diff --git a/toolkit/components/nimbus/generate/generate_feature_manifest.py b/toolkit/components/nimbus/generate/generate_feature_manifest.py
index c17f320184..14057493c5 100644
--- a/toolkit/components/nimbus/generate/generate_feature_manifest.py
+++ b/toolkit/components/nimbus/generate/generate_feature_manifest.py
@@ -14,7 +14,7 @@ HEADER_LINE = (
" DO NOT EDIT.\n"
)
-FEATURE_MANIFEST_SCHEMA = Path("schemas", "ExperimentFeatureManifest.schema.json")
+FEATURE_SCHEMA = Path("schemas", "ExperimentFeature.schema.json")
NIMBUS_FALLBACK_PREFS = (
"constexpr std::pair<nsLiteralCString, nsLiteralCString>"
@@ -26,36 +26,17 @@ NIMBUS_FALLBACK_PREFS = (
ALLOWED_ISEARLYSTARTUP_FEATURE_IDS = {
"abouthomecache",
"aboutwelcome",
- "backgroundThreads",
- "backgroundUpdate",
- "bookmarks",
"dapTelemetry",
- "deviceMigration",
- "frecency",
- "fullPageTranslation",
- "fullPageTranslationAutomaticPopup",
- "fxaButtonVisibility",
- "gcParallelMarking",
"gleanInternalSdk",
- "jitHintsCache",
- "jitThresholds",
- "jsParallelParsing",
"majorRelease2022",
- "migrationWizard",
"newtab",
- "nimbus-qa-2",
- "opaqueResponseBlocking",
- "phc",
"pocketNewtab",
- "powerSaver",
- "reportBrokenSite",
"saveToPocket",
"searchConfiguration",
"shellService",
"testFeature",
"updatePrompt",
"upgradeDialog",
- "windowsJumpList",
}
@@ -91,7 +72,7 @@ def validate_feature_manifest(schema_path, manifest_path, manifest):
f"Feature {feature_id} is not early startup but is in the allow list."
)
print("Please remove it from generate_feature_manifest.py")
- raise Exception("isEarlyStatup is deprecated")
+ raise Exception("isEarlyStartup is deprecated")
for variable, variable_def in feature.get("variables", {}).items():
set_pref = variable_def.get("setPref")
@@ -158,7 +139,7 @@ def generate_feature_manifest(fd, input_file):
manifest = yaml.safe_load(f)
validate_feature_manifest(
- Path(input_file).parent / FEATURE_MANIFEST_SCHEMA, input_file, manifest
+ Path(input_file).parent / FEATURE_SCHEMA, input_file, manifest
)
fd.write(f"export const FeatureManifest = {json.dumps(manifest)};")
diff --git a/toolkit/components/nimbus/lib/ExperimentManager.sys.mjs b/toolkit/components/nimbus/lib/ExperimentManager.sys.mjs
index 8e1acc4803..d0f313a2ae 100644
--- a/toolkit/components/nimbus/lib/ExperimentManager.sys.mjs
+++ b/toolkit/components/nimbus/lib/ExperimentManager.sys.mjs
@@ -32,6 +32,25 @@ 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 [];
@@ -182,6 +201,14 @@ export class _ExperimentManager {
}
this.observe();
+
+ lazy.NimbusFeatures.nimbusTelemetry.onUpdate(() => {
+ const cfg =
+ lazy.NimbusFeatures.nimbusTelemetry.getVariable(
+ "gleanMetricConfiguration"
+ ) ?? {};
+ Services.fog.setMetricsFeatureConfig(JSON.stringify(cfg));
+ });
}
/**
@@ -223,16 +250,22 @@ export class _ExperimentManager {
missingL10nIds
) {
for (const enrollment of enrollments) {
- const { slug, source } = enrollment;
+ 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)) {
@@ -243,12 +276,23 @@ export class _ExperimentManager {
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);
}
}
@@ -685,7 +729,7 @@ export class _ExperimentManager {
/**
* Unenroll from all active studies if user opts out.
*/
- observe(aSubject, aTopic, aPrefName) {
+ observe() {
if (!this.studiesEnabled) {
for (const { slug } of this.store.getAllActiveExperiments()) {
this.unenroll(slug, "studies-opt-out");
@@ -756,6 +800,34 @@ export class _ExperimentManager {
}
/**
+ *
+ * @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
diff --git a/toolkit/components/nimbus/lib/ExperimentStore.sys.mjs b/toolkit/components/nimbus/lib/ExperimentStore.sys.mjs
index 7fd7fd987e..a7a3069fe5 100644
--- a/toolkit/components/nimbus/lib/ExperimentStore.sys.mjs
+++ b/toolkit/components/nimbus/lib/ExperimentStore.sys.mjs
@@ -237,7 +237,7 @@ export class ExperimentStore extends SharedDataMap {
async init() {
await super.init();
- this.getAllActiveExperiments().forEach(({ slug, branch, featureIds }) => {
+ this.getAllActiveExperiments().forEach(({ branch, featureIds }) => {
(featureIds || getAllBranchFeatureIds(branch)).forEach(featureId =>
this._emitFeatureUpdate(featureId, "feature-experiment-loaded")
);
diff --git a/toolkit/components/nimbus/lib/RemoteSettingsExperimentLoader.sys.mjs b/toolkit/components/nimbus/lib/RemoteSettingsExperimentLoader.sys.mjs
index 8e026e5cba..e372cf57a5 100644
--- a/toolkit/components/nimbus/lib/RemoteSettingsExperimentLoader.sys.mjs
+++ b/toolkit/components/nimbus/lib/RemoteSettingsExperimentLoader.sys.mjs
@@ -350,7 +350,7 @@ export class _RemoteSettingsExperimentLoader {
}
}
- observe(aSubect, aTopic, aData) {
+ observe(aSubect, aTopic) {
if (aTopic === STUDIES_ENABLED_CHANGED) {
this.onEnabledPrefChange();
}
diff --git a/toolkit/components/nimbus/metrics.yaml b/toolkit/components/nimbus/metrics.yaml
index 4faecce490..5b5ff4bd3a 100644
--- a/toolkit/components/nimbus/metrics.yaml
+++ b/toolkit/components/nimbus/metrics.yaml
@@ -223,10 +223,46 @@ nimbus_events:
An event sent when Nimbus is ready — sent upon completion of each update of the recipes.
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1875510
- data_reviews: []
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1875510
data_sensitivity:
- technical
notification_emails:
- chumphreys@mozilla.com
- project-nimbus@mozilla.com
expires: 180
+
+ enrollment_status:
+ type: event
+ description: >
+ Recorded for each enrollment status each time the SDK completes application of pending experiments.
+ extra_keys:
+ slug:
+ type: string
+ description: The slug/unique identifier of the experiment
+ status:
+ type: string
+ description: The status of this enrollment
+ reason:
+ type: string
+ description: The reason the client is in the noted status
+ branch:
+ type: string
+ description: The branch slug/identifier that was randomly chosen (if the client is enrolled)
+ error_string:
+ type: string
+ description: If the enrollment resulted in an error, the associated error string
+ conflict_slug:
+ type: string
+ description: If the enrollment hit a feature conflict, the slug of the conflicting experiment/rollout
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1817481
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1817481
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - chumphreys@mozilla.com
+ - project-nimbus@mozilla.com
+ expires: never
+ disabled: true
diff --git a/toolkit/components/nimbus/schemas/ExperimentFeature.schema.json b/toolkit/components/nimbus/schemas/ExperimentFeature.schema.json
new file mode 100644
index 0000000000..977c18e09e
--- /dev/null
+++ b/toolkit/components/nimbus/schemas/ExperimentFeature.schema.json
@@ -0,0 +1,125 @@
+{
+ "$schema": "https://json-schema.org/draft/2019-09/schema",
+ "additionalProperties": false,
+ "type": "object",
+ "properties": {
+ "description": {
+ "type": "string"
+ },
+ "owner": {
+ "type": "string",
+ "description": "The owner of the feature."
+ },
+ "applications": {
+ "description": "The applications that can enroll in experiments for this feature. Defaults to firefox-desktop if not present.",
+ "type": "array",
+ "items": {
+ "type": "string",
+ "enum": ["firefox-desktop", "firefox-desktop-background-task"]
+ },
+ "minItems": 1
+ },
+ "hasExposure": {
+ "type": "boolean",
+ "description": "If the feature sends an exposure event."
+ },
+ "exposureDescription": {
+ "type": "string",
+ "description": "A description of the implementation details of the exposure event, if one is sent."
+ },
+ "isEarlyStartup": {
+ "type": "boolean",
+ "description": "If the feature values should be cached in prefs for fast early startup."
+ },
+ "schema": {
+ "type": "object",
+ "description": "For features with large number of variables we instead point to a JSONSchema file instead of specifying them in the variables field",
+ "properties": {
+ "uri": {
+ "type": "string",
+ "description": "A resource:// URI that can be loaded at runtime from within Firefox.",
+ "format": "uri"
+ },
+ "path": {
+ "type": "string",
+ "description": "The path to the schema file relative to the repository root"
+ }
+ },
+ "required": ["uri", "path"]
+ },
+ "variables": {
+ "additionalProperties": false,
+ "type": "object",
+ "patternProperties": {
+ "[a-zA-Z0-9_]+": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["json", "boolean", "int", "string"]
+ },
+ "fallbackPref": {
+ "type": "string",
+ "description": "A pref that provides the default value for a feature when none is present"
+ },
+ "setPref": {
+ "description": "A pref that should be set to the value of this variable when enrolling in experiments.",
+ "type": "object",
+ "properties": {
+ "branch": {
+ "type": "string",
+ "enum": ["default", "user"],
+ "description": "The branch the pref will be set on."
+ },
+ "pref": {
+ "type": "string",
+ "description": "The name of the pref."
+ }
+ },
+ "required": ["branch", "pref"],
+ "additionalProperties": false
+ },
+ "enum": {
+ "description": "Validate feature value using a list of possible options (for string only values)."
+ },
+ "description": {
+ "type": "string",
+ "description": "Explain how this value is being used"
+ }
+ },
+ "required": ["type", "description"],
+ "additionalProperties": false,
+ "dependentSchemas": {
+ "fallbackPref": {
+ "description": "setPref is mutually exclusive with fallbackPref",
+ "properties": {
+ "setPref": {
+ "const": null
+ }
+ }
+ },
+ "setPref": {
+ "description": "fallbackPref is mutually exclusive with setPref",
+ "properties": {
+ "fallbackPref": {
+ "const": null
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "required": ["description", "hasExposure", "owner", "variables"],
+ "if": {
+ "properties": {
+ "hasExposure": {
+ "const": true
+ }
+ }
+ },
+ "then": {
+ "required": ["exposureDescription"]
+ }
+}
diff --git a/toolkit/components/nimbus/schemas/ExperimentFeatureManifest.schema.json b/toolkit/components/nimbus/schemas/ExperimentFeatureManifest.schema.json
index c25b7dbf69..ee28f7d93e 100644
--- a/toolkit/components/nimbus/schemas/ExperimentFeatureManifest.schema.json
+++ b/toolkit/components/nimbus/schemas/ExperimentFeatureManifest.schema.json
@@ -1,125 +1,8 @@
{
"$schema": "https://json-schema.org/draft/2019-09/schema",
- "additionalProperties": false,
"type": "object",
- "properties": {
- "description": {
- "type": "string"
- },
- "owner": {
- "type": "string",
- "description": "The owner of the feature."
- },
- "applications": {
- "description": "The applications that can enroll in experiments for this feature. Defaults to firefox-desktop if not present.",
- "type": "array",
- "items": {
- "type": "string",
- "enum": ["firefox-desktop", "firefox-desktop-background-task"]
- },
- "minItems": 1
- },
- "hasExposure": {
- "type": "boolean",
- "description": "If the feature sends an exposure event."
- },
- "exposureDescription": {
- "type": "string",
- "description": "A description of the implementation details of the exposure event, if one is sent."
- },
- "isEarlyStartup": {
- "type": "boolean",
- "description": "If the feature values should be cached in prefs for fast early startup."
- },
- "schema": {
- "type": "object",
- "description": "For features with large number of variables we instead point to a JSONSchema file instead of specifying them in the variables field",
- "properties": {
- "uri": {
- "type": "string",
- "description": "A resource:// URI that can be loaded at runtime from within Firefox.",
- "format": "uri"
- },
- "path": {
- "type": "string",
- "description": "The path to the schema file relative to the repository root"
- }
- },
- "required": ["uri", "path"]
- },
- "variables": {
- "additionalProperties": false,
- "type": "object",
- "patternProperties": {
- "[a-zA-Z0-9_]+": {
- "type": "object",
- "properties": {
- "type": {
- "type": "string",
- "enum": ["json", "boolean", "int", "string"]
- },
- "fallbackPref": {
- "type": "string",
- "description": "A pref that provides the default value for a feature when none is present"
- },
- "setPref": {
- "description": "A pref that should be set to the value of this variable when enrolling in experiments.",
- "type": "object",
- "properties": {
- "branch": {
- "type": "string",
- "enum": ["default", "user"],
- "description": "The branch the pref will be set on."
- },
- "pref": {
- "type": "string",
- "description": "The name of the pref."
- }
- },
- "required": ["branch", "pref"],
- "additionalProperties": false
- },
- "enum": {
- "description": "Validate feature value using a list of possible options (for string only values)."
- },
- "description": {
- "type": "string",
- "description": "Explain how this value is being used"
- }
- },
- "required": ["type", "description"],
- "additionalProperties": false,
- "dependentSchemas": {
- "fallbackPref": {
- "description": "setPref is mutually exclusive with fallbackPref",
- "properties": {
- "setPref": {
- "const": null
- }
- }
- },
- "setPref": {
- "description": "fallbackPref is mutually exclusive with setPref",
- "properties": {
- "fallbackPref": {
- "const": null
- }
- }
- }
- }
- }
- }
- }
- },
- "required": ["description", "hasExposure", "variables"],
- "if": {
- "properties": {
- "hasExposure": {
- "const": true
- }
- }
- },
- "then": {
- "required": ["exposureDescription"]
+ "additionalProperties": false,
+ "patternProperties": {
+ "^[A-Za-z0-9_-]*$": { "$ref": "ExperimentFeature.schema.json" }
}
}
diff --git a/toolkit/components/nimbus/test/unit/test_ExperimentManager_unenroll.js b/toolkit/components/nimbus/test/unit/test_ExperimentManager_unenroll.js
index 3c53148c7a..a32de32cd6 100644
--- a/toolkit/components/nimbus/test/unit/test_ExperimentManager_unenroll.js
+++ b/toolkit/components/nimbus/test/unit/test_ExperimentManager_unenroll.js
@@ -6,6 +6,9 @@ const { TelemetryEvents } = ChromeUtils.importESModule(
const { TelemetryEnvironment } = ChromeUtils.importESModule(
"resource://gre/modules/TelemetryEnvironment.sys.mjs"
);
+const { ExperimentAPI } = ChromeUtils.importESModule(
+ "resource://nimbus/ExperimentAPI.sys.mjs"
+);
const STUDIES_OPT_OUT_PREF = "app.shield.optoutstudies.enabled";
const UPLOAD_ENABLED_PREF = "datareporting.healthreport.uploadEnabled";
@@ -487,3 +490,112 @@ add_task(async function test_rollout_telemetry_events() {
);
globalSandbox.restore();
});
+
+add_task(async function test_check_unseen_enrollments_telemetry_events() {
+ globalSandbox.restore();
+ const store = ExperimentFakes.store();
+ const manager = ExperimentFakes.manager(store);
+ const sandbox = sinon.createSandbox();
+ sandbox.stub(manager, "unenroll").returns();
+ sandbox.stub(ExperimentAPI, "_store").get(() => manager.store);
+ sandbox.stub(ExperimentAPI, "_manager").get(() => manager);
+
+ await manager.onStartup();
+ await manager.store.ready();
+
+ const experiment = ExperimentFakes.recipe("foo", {
+ branches: [
+ {
+ slug: "wsup",
+ ratio: 1,
+ features: [
+ {
+ featureId: "nimbusTelemetry",
+ value: {
+ gleanMetricConfiguration: {
+ "nimbus_events.enrollment_status": true,
+ },
+ },
+ },
+ ],
+ },
+ ],
+ bucketConfig: {
+ ...ExperimentFakes.recipe.bucketConfig,
+ count: 1000,
+ },
+ });
+
+ await manager.enroll(experiment, "aaa");
+
+ const source = "test";
+ const slugs = [],
+ experiments = [];
+ for (let i = 0; i < 7; i++) {
+ slugs.push(`slug-${i}`);
+ experiments.push({
+ slug: slugs[i],
+ source,
+ branch: {
+ slug: "control",
+ },
+ });
+ }
+
+ manager.sessions.set(source, new Set([slugs[0]]));
+
+ manager._checkUnseenEnrollments(
+ experiments,
+ source,
+ [slugs[1]],
+ [slugs[2]],
+ new Map([]),
+ new Map([[slugs[3], experiments[3]]]),
+ [slugs[4]],
+ new Map([[slugs[5], experiments[5]]])
+ );
+
+ const events = Glean.nimbusEvents.enrollmentStatus.testGetValue();
+
+ Assert.equal(events?.length, 7);
+
+ Assert.equal(events[0].extra.status, "Enrolled");
+ Assert.equal(events[0].extra.reason, "Qualified");
+ Assert.equal(events[0].extra.branch, "control");
+ Assert.equal(events[0].extra.slug, slugs[0]);
+
+ Assert.equal(events[1].extra.status, "Disqualified");
+ Assert.equal(events[1].extra.reason, "NotTargeted");
+ Assert.equal(events[1].extra.branch, "control");
+ Assert.equal(events[1].extra.slug, slugs[1]);
+
+ Assert.equal(events[2].extra.status, "Disqualified");
+ Assert.equal(events[2].extra.reason, "Error");
+ Assert.equal(events[2].extra.error_string, "invalid-recipe");
+ Assert.equal(events[2].extra.branch, "control");
+ Assert.equal(events[2].extra.slug, slugs[2]);
+
+ Assert.equal(events[3].extra.status, "Disqualified");
+ Assert.equal(events[3].extra.reason, "Error");
+ Assert.equal(events[3].extra.error_string, "invalid-branch");
+ Assert.equal(events[3].extra.branch, "control");
+ Assert.equal(events[3].extra.slug, slugs[3]);
+
+ Assert.equal(events[4].extra.status, "Disqualified");
+ Assert.equal(events[4].extra.reason, "Error");
+ Assert.equal(events[4].extra.error_string, "l10n-missing-locale");
+ Assert.equal(events[4].extra.branch, "control");
+ Assert.equal(events[4].extra.slug, slugs[4]);
+
+ Assert.equal(events[5].extra.status, "Disqualified");
+ Assert.equal(events[5].extra.reason, "Error");
+ Assert.equal(events[5].extra.error_string, "l10n-missing-entry");
+ Assert.equal(events[5].extra.branch, "control");
+ Assert.equal(events[5].extra.slug, slugs[5]);
+
+ Assert.equal(events[6].extra.status, "WasEnrolled");
+ Assert.equal(events[6].extra.branch, "control");
+ Assert.equal(events[6].extra.slug, slugs[6]);
+
+ sandbox.restore();
+});
diff --git a/toolkit/components/nimbus/test/unit/test_SharedDataMap.js b/toolkit/components/nimbus/test/unit/test_SharedDataMap.js
index 6186b41a40..84132ab73d 100644
--- a/toolkit/components/nimbus/test/unit/test_SharedDataMap.js
+++ b/toolkit/components/nimbus/test/unit/test_SharedDataMap.js
@@ -115,7 +115,6 @@ with_sharedDataMap(async function test_childInit({ instance, sandbox }) {
with_sharedDataMap(async function test_parentChildSync_synchronously({
instance: parentInstance,
- sandbox,
}) {
await parentInstance.init();
parentInstance.set("foo", { bar: 1 });
@@ -142,7 +141,6 @@ with_sharedDataMap(async function test_parentChildSync_synchronously({
with_sharedDataMap(async function test_parentChildSync_async({
instance: parentInstance,
- sandbox,
}) {
const childInstance = new SharedDataMap("xpcshell", {
path: PATH,
@@ -169,7 +167,6 @@ with_sharedDataMap(async function test_parentChildSync_async({
with_sharedDataMap(async function test_earlyChildSync({
instance: parentInstance,
- sandbox,
}) {
const childInstance = new SharedDataMap("xpcshell", {
path: PATH,
@@ -193,7 +190,7 @@ with_sharedDataMap(async function test_earlyChildSync({
);
});
-with_sharedDataMap(async function test_updateStoreData({ instance, sandbox }) {
+with_sharedDataMap(async function test_updateStoreData({ instance }) {
await instance.init();
Assert.ok(!instance.get("foo"), "No value initially");
diff --git a/toolkit/components/normandy/Normandy.sys.mjs b/toolkit/components/normandy/Normandy.sys.mjs
index 653461df48..ce30bb8456 100644
--- a/toolkit/components/normandy/Normandy.sys.mjs
+++ b/toolkit/components/normandy/Normandy.sys.mjs
@@ -78,7 +78,7 @@ export var Normandy = {
await this.finishInit();
},
- async observe(subject, topic, data) {
+ async observe(subject, topic) {
if (topic === UI_AVAILABLE_NOTIFICATION) {
Services.obs.removeObserver(this, UI_AVAILABLE_NOTIFICATION);
this.uiAvailableNotificationObserved.resolve();
diff --git a/toolkit/components/normandy/actions/BaseAction.sys.mjs b/toolkit/components/normandy/actions/BaseAction.sys.mjs
index cb9198a55e..25f64c293f 100644
--- a/toolkit/components/normandy/actions/BaseAction.sys.mjs
+++ b/toolkit/components/normandy/actions/BaseAction.sys.mjs
@@ -183,7 +183,7 @@ export class BaseAction {
*
* @param {Recipe} recipe
*/
- async _run(recipe) {
+ async _run() {
throw new Error("Not implemented");
}
diff --git a/toolkit/components/normandy/actions/BranchedAddonStudyAction.sys.mjs b/toolkit/components/normandy/actions/BranchedAddonStudyAction.sys.mjs
index d775429ced..b3b111cdf8 100644
--- a/toolkit/components/normandy/actions/BranchedAddonStudyAction.sys.mjs
+++ b/toolkit/components/normandy/actions/BranchedAddonStudyAction.sys.mjs
@@ -115,7 +115,7 @@ export class BranchedAddonStudyAction extends BaseStudyAction {
this.seenRecipeIds = new Set();
}
- async _run(recipe) {
+ async _run() {
throw new Error("_run should not be called anymore");
}
diff --git a/toolkit/components/normandy/actions/PreferenceExperimentAction.sys.mjs b/toolkit/components/normandy/actions/PreferenceExperimentAction.sys.mjs
index 310d1b08fd..0dc416d789 100644
--- a/toolkit/components/normandy/actions/PreferenceExperimentAction.sys.mjs
+++ b/toolkit/components/normandy/actions/PreferenceExperimentAction.sys.mjs
@@ -160,7 +160,7 @@ export class PreferenceExperimentAction extends BaseStudyAction {
}
}
- async _run(recipe) {
+ async _run() {
throw new Error("_run shouldn't be called anymore");
}
diff --git a/toolkit/components/normandy/actions/ShowHeartbeatAction.sys.mjs b/toolkit/components/normandy/actions/ShowHeartbeatAction.sys.mjs
index db9a4ec5ff..7e6af63266 100644
--- a/toolkit/components/normandy/actions/ShowHeartbeatAction.sys.mjs
+++ b/toolkit/components/normandy/actions/ShowHeartbeatAction.sys.mjs
@@ -72,10 +72,7 @@ export class ShowHeartbeatAction extends BaseAction {
learnMoreUrl,
postAnswerUrl: await this.generatePostAnswerURL(recipe),
flowId: lazy.NormandyUtils.generateUuid(),
- // Recipes coming from Nimbus won't have a revision_id.
- ...(Object.hasOwn(recipe, "revision_id")
- ? { surveyVersion: recipe.revision_id }
- : {}),
+ surveyVersion: recipe.revision_id,
});
heartbeat.eventEmitter.once(
diff --git a/toolkit/components/normandy/lib/AddonStudies.sys.mjs b/toolkit/components/normandy/lib/AddonStudies.sys.mjs
index 202bf8ccb3..e13d470bd7 100644
--- a/toolkit/components/normandy/lib/AddonStudies.sys.mjs
+++ b/toolkit/components/normandy/lib/AddonStudies.sys.mjs
@@ -162,7 +162,7 @@ export var AddonStudies = {
},
/**
- * These migrations should only be called from `NormandyMigrations.jsm` and
+ * These migrations should only be called from `NormandyMigrations.sys.mjs` and
* tests.
*/
migrations: {
diff --git a/toolkit/components/normandy/lib/LegacyHeartbeat.sys.mjs b/toolkit/components/normandy/lib/LegacyHeartbeat.sys.mjs
index 501c9f70af..93c24faf5d 100644
--- a/toolkit/components/normandy/lib/LegacyHeartbeat.sys.mjs
+++ b/toolkit/components/normandy/lib/LegacyHeartbeat.sys.mjs
@@ -43,6 +43,7 @@ export const LegacyHeartbeat = {
capabilities: ["action.show-heartbeat"],
filter_expression: "true",
use_only_baseline_capabilities: true,
+ revision_id: "1", // Required for the Heartbeat telemetry ping.
};
},
};
diff --git a/toolkit/components/normandy/lib/PreferenceExperiments.sys.mjs b/toolkit/components/normandy/lib/PreferenceExperiments.sys.mjs
index c89beff978..92c5e63076 100644
--- a/toolkit/components/normandy/lib/PreferenceExperiments.sys.mjs
+++ b/toolkit/components/normandy/lib/PreferenceExperiments.sys.mjs
@@ -244,7 +244,7 @@ export var PreferenceExperiments = {
const defaultBranchPrefs = allExperiments
.flatMap(exp => Object.entries(exp.preferences))
.filter(
- ([preferenceName, preferenceInfo]) =>
+ ([, preferenceInfo]) =>
preferenceInfo.preferenceBranchType === "default"
);
for (const [preferenceName, { preferenceValue }] of defaultBranchPrefs) {
@@ -906,7 +906,7 @@ export var PreferenceExperiments = {
InvalidPreferenceName: class extends Error {},
/**
- * These migrations should only be called from `NormandyMigrations.jsm` and tests.
+ * These migrations should only be called from `NormandyMigrations.sys.mjs` and tests.
*/
migrations: {
/** Move experiments into a specific key. */
diff --git a/toolkit/components/normandy/lib/RecipeRunner.sys.mjs b/toolkit/components/normandy/lib/RecipeRunner.sys.mjs
index 7e02a3150a..f9578b37d2 100644
--- a/toolkit/components/normandy/lib/RecipeRunner.sys.mjs
+++ b/toolkit/components/normandy/lib/RecipeRunner.sys.mjs
@@ -63,13 +63,13 @@ ChromeUtils.defineLazyGetter(lazy, "gRemoteSettingsClient", () => {
function cacheProxy(target) {
const cache = new Map();
return new Proxy(target, {
- get(target, prop, receiver) {
+ get(target, prop) {
if (!cache.has(prop)) {
cache.set(prop, target[prop]);
}
return cache.get(prop);
},
- set(target, prop, value, receiver) {
+ set(target, prop, value) {
cache.set(prop, value);
return true;
},
diff --git a/toolkit/components/normandy/test/browser/browser_ActionsManager.js b/toolkit/components/normandy/test/browser/browser_ActionsManager.js
index 8b5772fa26..2667a085cf 100644
--- a/toolkit/components/normandy/test/browser/browser_ActionsManager.js
+++ b/toolkit/components/normandy/test/browser/browser_ActionsManager.js
@@ -14,7 +14,7 @@ const { ActionSchemas } = ChromeUtils.importESModule(
);
// Test life cycle methods for actions
-decorate_task(async function (reportActionStub, Stub) {
+decorate_task(async function () {
let manager = new ActionsManager();
const recipe = { id: 1, action: "test-local-action-used" };
diff --git a/toolkit/components/normandy/test/browser/browser_BaseAction.js b/toolkit/components/normandy/test/browser/browser_BaseAction.js
index 240a235346..3a5ebd9d39 100644
--- a/toolkit/components/normandy/test/browser/browser_BaseAction.js
+++ b/toolkit/components/normandy/test/browser/browser_BaseAction.js
@@ -19,7 +19,7 @@ class NoopAction extends BaseAction {
this._testPreExecutionFlag = true;
}
- _run(recipe) {
+ _run() {
this._testRunFlag = true;
}
@@ -37,7 +37,7 @@ class FailPreExecutionAction extends NoopAction {
}
class FailRunAction extends NoopAction {
- _run(recipe) {
+ _run() {
throw NoopAction._errorToThrow;
}
}
diff --git a/toolkit/components/normandy/test/browser/browser_LegacyHeartbeat.js b/toolkit/components/normandy/test/browser/browser_LegacyHeartbeat.js
index 465e5c1040..8d48298da8 100644
--- a/toolkit/components/normandy/test/browser/browser_LegacyHeartbeat.js
+++ b/toolkit/components/normandy/test/browser/browser_LegacyHeartbeat.js
@@ -9,6 +9,9 @@ const { BaseAction } = ChromeUtils.importESModule(
const { ClientEnvironment } = ChromeUtils.importESModule(
"resource://normandy/lib/ClientEnvironment.sys.mjs"
);
+const { EventEmitter } = ChromeUtils.importESModule(
+ "resource://normandy/lib/EventEmitter.sys.mjs"
+);
const { Heartbeat } = ChromeUtils.importESModule(
"resource://normandy/lib/Heartbeat.sys.mjs"
);
@@ -27,6 +30,9 @@ const { RecipeRunner } = ChromeUtils.importESModule(
const { RemoteSettings } = ChromeUtils.importESModule(
"resource://services-settings/remote-settings.sys.mjs"
);
+const { JsonSchema } = ChromeUtils.importESModule(
+ "resource://gre/modules/JsonSchema.sys.mjs"
+);
const SURVEY = {
surveyId: "a survey",
@@ -39,9 +45,80 @@ const SURVEY = {
repeatOption: "once",
};
+// See properties.payload in
+// https://github.com/mozilla-services/mozilla-pipeline-schemas/blob/main/schemas/telemetry/heartbeat/heartbeat.4.schema.json
+
+const PAYLOAD_SCHEMA = {
+ additionalProperties: false,
+ anyOf: [
+ {
+ required: ["closedTS"],
+ },
+ {
+ required: ["windowClosedTS"],
+ },
+ ],
+ properties: {
+ closedTS: {
+ minimum: 0,
+ type: "integer",
+ },
+ engagedTS: {
+ minimum: 0,
+ type: "integer",
+ },
+ expiredTS: {
+ minimum: 0,
+ type: "integer",
+ },
+ flowId: {
+ pattern:
+ "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$",
+ type: "string",
+ },
+ learnMoreTS: {
+ minimum: 0,
+ type: "integer",
+ },
+ offeredTS: {
+ minimum: 0,
+ type: "integer",
+ },
+ score: {
+ minimum: 1,
+ type: "integer",
+ },
+ surveyId: {
+ type: "string",
+ },
+ surveyVersion: {
+ pattern: "^([0-9]+|[a-fA-F0-9]{64})$",
+ type: "string",
+ },
+ testing: {
+ type: "boolean",
+ },
+ version: {
+ maximum: 1,
+ minimum: 1,
+ type: "number",
+ },
+ votedTS: {
+ minimum: 0,
+ type: "integer",
+ },
+ windowClosedTS: {
+ minimum: 0,
+ type: "integer",
+ },
+ },
+ required: ["version", "flowId", "offeredTS", "surveyId", "surveyVersion"],
+ type: "object",
+};
+
function assertSurvey(actual, expected) {
for (const key of Object.keys(actual)) {
- if (["postAnswerUrl", "flowId"].includes(key)) {
+ if (["flowId", "postAnswerUrl", "surveyVersion"].includes(key)) {
continue;
}
@@ -52,6 +129,7 @@ function assertSurvey(actual, expected) {
);
}
+ Assert.equal(actual.surveyVersion, "1");
Assert.ok(actual.postAnswerUrl.startsWith(expected.postAnswerUrl));
}
@@ -86,3 +164,61 @@ decorate_task(
}
}
);
+
+decorate_task(
+ withClearStorage(),
+ async function testLegacyHeartbeatPingPayload() {
+ const sandbox = sinon.createSandbox();
+
+ const cleanupEnrollment = await ExperimentFakes.enrollWithFeatureConfig({
+ featureId: "legacyHeartbeat",
+ value: {
+ survey: SURVEY,
+ },
+ });
+
+ const client = RemoteSettings("normandy-recipes-capabilities");
+ sandbox.stub(client, "get").resolves([]);
+
+ // Override Heartbeat so we can get the instance and manipulate it directly.
+ const heartbeatDeferred = Promise.withResolvers();
+ class TestHeartbeat extends Heartbeat {
+ constructor(...args) {
+ super(...args);
+ heartbeatDeferred.resolve(this);
+ }
+ }
+ ShowHeartbeatAction.overrideHeartbeatForTests(TestHeartbeat);
+
+ try {
+ await RecipeRunner.run();
+ const heartbeat = await heartbeatDeferred.promise;
+ // We are going to simulate the timer timing out, so we do not want it to
+ // *actually* time out.
+ heartbeat.endTimerIfPresent("surveyEndTimer");
+ const notice = await heartbeat.noticePromise;
+ await notice.updateComplete;
+
+ const telemetrySentPromise = new Promise(resolve => {
+ heartbeat.eventEmitter.once("TelemetrySent", payload =>
+ resolve(payload)
+ );
+ });
+
+ // This method would be triggered when the timer timed out. This will
+ // trigger telemetry to be submitted.
+ heartbeat.close();
+
+ const payload = await telemetrySentPromise;
+
+ const result = JsonSchema.validate(payload, PAYLOAD_SCHEMA);
+ Assert.ok(result.valid);
+ Assert.equal(payload.surveyVersion, "1");
+
+ await cleanupEnrollment();
+ } finally {
+ ShowHeartbeatAction.overrideHeartbeatForTests();
+ sandbox.restore();
+ }
+ }
+);
diff --git a/toolkit/components/normandy/test/browser/browser_PreferenceExperiments.js b/toolkit/components/normandy/test/browser/browser_PreferenceExperiments.js
index 4269020975..e50e9bee0e 100644
--- a/toolkit/components/normandy/test/browser/browser_PreferenceExperiments.js
+++ b/toolkit/components/normandy/test/browser/browser_PreferenceExperiments.js
@@ -312,7 +312,7 @@ decorate_task(
// clearAllExperimentStorage
decorate_task(
withMockExperiments([preferenceStudyFactory({ slug: "test" })]),
- async function ({ prefExperiments }) {
+ async function () {
ok(await PreferenceExperiments.has("test"), "Mock experiment is detected.");
await PreferenceExperiments.clearAllExperimentStorage();
ok(
@@ -434,12 +434,7 @@ decorate_task(
withMockPreferences(),
withStub(PreferenceExperiments, "startObserver"),
withSendEventSpy(),
- async function testStart({
- prefExperiments,
- mockPreferences,
- startObserverStub,
- sendEventSpy,
- }) {
+ async function testStart({ mockPreferences, startObserverStub }) {
mockPreferences.set("fake.preference", "oldvalue", "default");
mockPreferences.set("fake.preference", "uservalue", "user");
mockPreferences.set("fake.preferenceinteger", 1, "default");
@@ -1193,7 +1188,7 @@ decorate_task(withMockExperiments(), async function () {
// get
decorate_task(
withMockExperiments([preferenceStudyFactory({ slug: "test" })]),
- async function ({ prefExperiments }) {
+ async function () {
const experiment = await PreferenceExperiments.get("test");
is(experiment.slug, "test", "get fetches the correct experiment");
@@ -1253,9 +1248,7 @@ decorate_task(
}),
]),
withMockPreferences(),
- async function testGetAllActive({
- prefExperiments: [activeExperiment, inactiveExperiment],
- }) {
+ async function testGetAllActive({ prefExperiments: [activeExperiment] }) {
let allActiveExperiments = await PreferenceExperiments.getAllActive();
Assert.deepEqual(
allActiveExperiments,
@@ -1306,11 +1299,7 @@ decorate_task(
withMockPreferences(),
withStub(TelemetryEnvironment, "setExperimentActive"),
withStub(PreferenceExperiments, "startObserver"),
- async function testInit({
- prefExperiments,
- mockPreferences,
- setExperimentActiveStub,
- }) {
+ async function testInit({ mockPreferences, setExperimentActiveStub }) {
mockPreferences.set("fake.pref", "experiment value");
await PreferenceExperiments.init();
ok(
diff --git a/toolkit/components/normandy/test/browser/browser_RecipeRunner.js b/toolkit/components/normandy/test/browser/browser_RecipeRunner.js
index 00f0f81c51..864c237ff9 100644
--- a/toolkit/components/normandy/test/browser/browser_RecipeRunner.js
+++ b/toolkit/components/normandy/test/browser/browser_RecipeRunner.js
@@ -216,7 +216,6 @@ decorate_task(
withMockNormandyApi(),
withStub(ClientEnvironment, "getClientClassification"),
async function testClientClassificationCache({
- mockNormandyApi,
getClientClassificationStub,
}) {
getClientClassificationStub.returns(Promise.resolve(false));
@@ -294,7 +293,6 @@ decorate_task(
async function testReadFromRemoteSettings({
verifyObjectSignatureStub,
processRecipeStub,
- finalizeStub,
reportRecipeStub,
}) {
const matchRecipe = {
@@ -334,7 +332,7 @@ decorate_task(
let recipesFromRS = (
await RecipeRunner._remoteSettingsClientForTesting.get()
- ).map(({ recipe, signature }) => recipe);
+ ).map(({ recipe }) => recipe);
// Sort the records by id so that they match the order in the assertion
recipesFromRS.sort((a, b) => a.id - b.id);
Assert.deepEqual(
@@ -518,11 +516,7 @@ decorate_task(
withStub(RecipeRunner, "run"),
withStub(RecipeRunner, "registerTimer"),
withStub(RecipeRunner, "watchPrefs"),
- async function testInitFirstRun({
- runStub,
- registerTimerStub,
- watchPrefsStub,
- }) {
+ async function testInitFirstRun({ runStub, registerTimerStub }) {
await RecipeRunner.init();
Assert.deepEqual(
runStub.args,
@@ -818,7 +812,7 @@ decorate_task(
withStub(Uptake, "reportRunner"),
withStub(ActionsManager.prototype, "finalize"),
NormandyTestUtils.withMockRecipeCollection([]),
- async function testRunEvents({ reportRunnerStub, finalizeStub }) {
+ async function testRunEvents() {
const observer = sinon.spy();
Services.obs.addObserver(observer, "recipe-runner:start");
diff --git a/toolkit/components/normandy/test/browser/browser_actions_BranchedAddonStudyAction.js b/toolkit/components/normandy/test/browser/browser_actions_BranchedAddonStudyAction.js
index cce82ae89e..b7d4b34754 100644
--- a/toolkit/components/normandy/test/browser/browser_actions_BranchedAddonStudyAction.js
+++ b/toolkit/components/normandy/test/browser/browser_actions_BranchedAddonStudyAction.js
@@ -719,7 +719,7 @@ decorate_task(
ensureAddonCleanup(),
AddonStudies.withStudies([branchedAddonStudyFactory({ active: false })]),
withSendEventSpy(),
- async ({ addonStudies: [study], sendEventSpy }) => {
+ async ({ addonStudies: [study] }) => {
const action = new BranchedAddonStudyAction();
await Assert.rejects(
action.unenroll(study.recipeId),
diff --git a/toolkit/components/normandy/test/browser/browser_actions_PreferenceExperimentAction.js b/toolkit/components/normandy/test/browser/browser_actions_PreferenceExperimentAction.js
index 5359174169..f275487e14 100644
--- a/toolkit/components/normandy/test/browser/browser_actions_PreferenceExperimentAction.js
+++ b/toolkit/components/normandy/test/browser/browser_actions_PreferenceExperimentAction.js
@@ -831,7 +831,6 @@ decorate_task(
withSpy(PreferenceExperiments, "stop"),
withStub(PreferenceExperimentAction.prototype, "_considerTemporaryError"),
async function testNoRecipes({
- stopSpy,
_considerTemporaryErrorStub,
prefExperiments: [experiment],
}) {
diff --git a/toolkit/components/normandy/test/unit/test_NormandyApi.js b/toolkit/components/normandy/test/unit/test_NormandyApi.js
index c0c826b045..5b0ede1701 100644
--- a/toolkit/components/normandy/test/unit/test_NormandyApi.js
+++ b/toolkit/components/normandy/test/unit/test_NormandyApi.js
@@ -199,7 +199,7 @@ decorate_task(
// A normal request should send that cookie
const cookieExpectedDeferred = Promise.withResolvers();
- function cookieExpectedObserver(aSubject, aTopic, aData) {
+ function cookieExpectedObserver(aSubject, aTopic) {
equal(
aTopic,
"http-on-modify-request",
@@ -223,7 +223,7 @@ decorate_task(
// A request through the NormandyApi method should not send that cookie
const cookieNotExpectedDeferred = Promise.withResolvers();
- function cookieNotExpectedObserver(aSubject, aTopic, aData) {
+ function cookieNotExpectedObserver(aSubject, aTopic) {
equal(
aTopic,
"http-on-modify-request",
diff --git a/toolkit/components/passwordmgr/LoginAutoComplete.sys.mjs b/toolkit/components/passwordmgr/LoginAutoComplete.sys.mjs
index 6ff96d999e..411d9249db 100644
--- a/toolkit/components/passwordmgr/LoginAutoComplete.sys.mjs
+++ b/toolkit/components/passwordmgr/LoginAutoComplete.sys.mjs
@@ -503,7 +503,7 @@ export class LoginAutoComplete {
let searchStartTimeMS = Services.telemetry.msSystemNow();
// Show the insecure login warning in the passwords field on null principal documents.
- // Avoid loading InsecurePasswordUtils.jsm in a sandboxed document (e.g. an ad. frame) if we
+ // Avoid loading InsecurePasswordUtils.sys.mjs in a sandboxed document (e.g. an ad. frame) if we
// already know it has a null principal and will therefore get the insecure autocomplete
// treatment.
// InsecurePasswordUtils doesn't handle the null principal case as not secure because we don't
diff --git a/toolkit/components/passwordmgr/LoginHelper.sys.mjs b/toolkit/components/passwordmgr/LoginHelper.sys.mjs
index a63654c8f2..5626312f17 100644
--- a/toolkit/components/passwordmgr/LoginHelper.sys.mjs
+++ b/toolkit/components/passwordmgr/LoginHelper.sys.mjs
@@ -1371,7 +1371,7 @@ export const LoginHelper = {
* @returns {boolean} True if any of the rules matches
*/
isInferredLoginForm(formElement) {
- // This is copied from 'loginFormAttrRegex' in NewPasswordModel.jsm
+ // 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;
diff --git a/toolkit/components/passwordmgr/LoginManager.shared.mjs b/toolkit/components/passwordmgr/LoginManager.shared.mjs
index d50c53cbad..b0122f7126 100644
--- a/toolkit/components/passwordmgr/LoginManager.shared.mjs
+++ b/toolkit/components/passwordmgr/LoginManager.shared.mjs
@@ -34,7 +34,7 @@ class Logic {
/**
* Test whether associated labels of the element have the keyword.
- * This is a simplified rule of hasLabelMatchingRegex in NewPasswordModel.jsm
+ * This is a simplified rule of hasLabelMatchingRegex in NewPasswordModel.sys.mjs
*/
static hasLabelMatchingRegex(element, regex) {
return regex.test(element.labels?.[0]?.textContent);
diff --git a/toolkit/components/passwordmgr/LoginManager.sys.mjs b/toolkit/components/passwordmgr/LoginManager.sys.mjs
index b95f3ada8b..538d7c63e1 100644
--- a/toolkit/components/passwordmgr/LoginManager.sys.mjs
+++ b/toolkit/components/passwordmgr/LoginManager.sys.mjs
@@ -21,7 +21,7 @@ ChromeUtils.defineLazyGetter(lazy, "log", () => {
const MS_PER_DAY = 24 * 60 * 60 * 1000;
if (Services.appinfo.processType !== Services.appinfo.PROCESS_TYPE_DEFAULT) {
- throw new Error("LoginManager.jsm should only run in the parent process");
+ throw new Error("LoginManager.sys.mjs should only run in the parent process");
}
export function LoginManager() {
diff --git a/toolkit/components/passwordmgr/LoginManagerAuthPrompter.sys.mjs b/toolkit/components/passwordmgr/LoginManagerAuthPrompter.sys.mjs
index 8c39cf09b9..f56ec80d5e 100644
--- a/toolkit/components/passwordmgr/LoginManagerAuthPrompter.sys.mjs
+++ b/toolkit/components/passwordmgr/LoginManagerAuthPrompter.sys.mjs
@@ -86,7 +86,7 @@ XPCOMUtils.defineLazyPreferenceGetter(
/**
* Implements nsIPromptFactory
*
- * Invoked by [toolkit/components/prompts/src/Prompter.jsm]
+ * Invoked by [toolkit/components/prompts/src/Prompter.sys.mjs]
*/
export function LoginManagerAuthPromptFactory() {
Services.obs.addObserver(this, "passwordmgr-crypto-login", true);
@@ -114,7 +114,7 @@ LoginManagerAuthPromptFactory.prototype = {
_uiBusyPromise: null,
_uiBusyResolve: null,
- observe(subject, topic, data) {
+ observe(_subject, topic, _data) {
this.log(`Observed topic: ${topic}.`);
if (topic == "passwordmgr-crypto-login") {
// Show the deferred prompters.
@@ -795,7 +795,7 @@ LoginManagerAuthPrompter.prototype = {
.then(ok => (result = ok))
.finally(() => (closed = true));
Services.tm.spinEventLoopUntilOrQuit(
- "LoginManagerAuthPrompter.jsm:promptAuth",
+ "LoginManagerAuthPrompter.sys.mjs:promptAuth",
() => closed
);
return result;
diff --git a/toolkit/components/passwordmgr/LoginManagerChild.sys.mjs b/toolkit/components/passwordmgr/LoginManagerChild.sys.mjs
index 94be604d02..c89852b56c 100644
--- a/toolkit/components/passwordmgr/LoginManagerChild.sys.mjs
+++ b/toolkit/components/passwordmgr/LoginManagerChild.sys.mjs
@@ -16,10 +16,6 @@ const PASSWORD_INPUT_ADDED_COALESCING_THRESHOLD_MS = 1;
const AUTOCOMPLETE_AFTER_RIGHT_CLICK_THRESHOLD_MS = 400;
const AUTOFILL_STATE = "autofill";
-const SUBMIT_FORM_SUBMIT = 1;
-const SUBMIT_PAGE_NAVIGATION = 2;
-const SUBMIT_FORM_IS_REMOVED = 3;
-
const LOG_MESSAGE_FORM_SUBMISSION = "form submission";
const LOG_MESSAGE_FIELD_EDIT = "field edit";
@@ -53,6 +49,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
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",
LoginFormFactory: "resource://gre/modules/LoginFormFactory.sys.mjs",
LoginHelper: "resource://gre/modules/LoginHelper.sys.mjs",
@@ -117,7 +114,7 @@ const observer = {
LoginManagerChild.forWindow(window)._onNavigation(window.document);
},
- onStateChange(aWebProgress, aRequest, aState, aStatus) {
+ onStateChange(aWebProgress, aRequest, aState, _aStatus) {
const window = aWebProgress.DOMWindow;
const loginManagerChild = () => LoginManagerChild.forWindow(window);
@@ -161,7 +158,7 @@ const observer = {
},
// nsIObserver
- observe(subject, topic, data) {
+ observe(subject, topic, _data) {
switch (topic) {
case "autocomplete-did-enter-text": {
let input = subject.QueryInterface(Ci.nsIAutoCompleteInput);
@@ -1602,10 +1599,6 @@ export class LoginManagerChild extends JSWindowActorChild {
this.#onDOMDocFetchSuccess(event);
break;
}
- case "DOMFormBeforeSubmit": {
- this.#onDOMFormBeforeSubmit(event);
- break;
- }
case "DOMFormHasPassword": {
this.#onDOMFormHasPassword(event, this.document.defaultView);
let formLike = lazy.LoginFormFactory.createFromForm(
@@ -1631,6 +1624,14 @@ export class LoginManagerChild extends JSWindowActorChild {
lazy.InsecurePasswordUtils.reportInsecurePasswords(formLike);
break;
}
+ case "form-submission-detected": {
+ if (lazy.LoginHelper.enabled) {
+ const form = event.detail.form;
+ const reason = event.detail.reason;
+ this.#onFormSubmission(form, reason);
+ }
+ break;
+ }
}
}
@@ -1772,7 +1773,10 @@ export class LoginManagerChild extends JSWindowActorChild {
}
lazy.log("Form is removed.");
- this._onFormSubmit(formLike, SUBMIT_FORM_IS_REMOVED);
+ this._onFormSubmit(
+ formLike,
+ lazy.FORM_SUBMISSION_REASON.FORM_REMOVAL_AFTER_FETCH
+ );
docState.formLikeByObservedNode.delete(event.target);
let weakObserveredNodes = ChromeUtils.nondeterministicGetWeakMapKeys(
@@ -1792,15 +1796,17 @@ export class LoginManagerChild extends JSWindowActorChild {
}
}
- #onDOMFormBeforeSubmit(event) {
- if (!event.isTrusted) {
- return;
- }
-
+ /**
+ * Handle form-submission-detected event (dispatched by FormHandlerChild)
+ *
+ * @param {HTMLFormElement} form that is being submitted
+ * @param {String} reason form submission reason (heuristic that detected the form submission)
+ */
+ #onFormSubmission(form, reason) {
// 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(event.target);
- this._onFormSubmit(formLike, SUBMIT_FORM_SUBMIT);
+ let formLike = lazy.LoginFormFactory.createFromForm(form);
+ this._onFormSubmit(formLike, reason);
}
onDocumentVisibilityChange(event) {
@@ -2310,7 +2316,7 @@ export class LoginManagerChild extends JSWindowActorChild {
}
let formLike = lazy.LoginFormFactory.getForRootElement(formRoot);
- this._onFormSubmit(formLike, SUBMIT_PAGE_NAVIGATION);
+ this._onFormSubmit(formLike, lazy.FORM_SUBMISSION_REASON.PAGE_NAVIGATION);
}
}
@@ -2333,7 +2339,8 @@ export class LoginManagerChild extends JSWindowActorChild {
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 == SUBMIT_FORM_IS_REMOVED,
+ ignoreConnect:
+ reason == lazy.FORM_SUBMISSION_REASON.FORM_REMOVAL_AFTER_FETCH,
}
);
}
@@ -2452,7 +2459,6 @@ export class LoginManagerChild extends JSWindowActorChild {
usernameField,
newPasswordField,
oldPasswordField,
- confirmPasswordField,
isSubmission,
triggeredByFillingGenerated,
}
diff --git a/toolkit/components/passwordmgr/LoginManagerContextMenu.sys.mjs b/toolkit/components/passwordmgr/LoginManagerContextMenu.sys.mjs
index fc87f5cf58..473594bafa 100644
--- a/toolkit/components/passwordmgr/LoginManagerContextMenu.sys.mjs
+++ b/toolkit/components/passwordmgr/LoginManagerContextMenu.sys.mjs
@@ -55,7 +55,7 @@ export const LoginManagerContextMenu = {
// login is bound so we can keep the reference to each object.
item.addEventListener(
"command",
- function (login, event) {
+ function (login, _event) {
this._fillTargetField(
login,
inputElementIdentifier,
@@ -87,7 +87,7 @@ export const LoginManagerContextMenu = {
/**
* Show the password autocomplete UI with the generation option forced to appear.
*/
- async useGeneratedPassword(inputElementIdentifier, documentURI, browser) {
+ async useGeneratedPassword(inputElementIdentifier) {
let browsingContextId = inputElementIdentifier.browsingContextId;
let browsingContext = BrowsingContext.get(browsingContextId);
let actor = browsingContext.currentWindowGlobal.getActor("LoginManager");
diff --git a/toolkit/components/passwordmgr/LoginManagerPrompter.sys.mjs b/toolkit/components/passwordmgr/LoginManagerPrompter.sys.mjs
index 2637e8a52f..ddaef0728c 100644
--- a/toolkit/components/passwordmgr/LoginManagerPrompter.sys.mjs
+++ b/toolkit/components/passwordmgr/LoginManagerPrompter.sys.mjs
@@ -73,7 +73,7 @@ const observer = {
QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
// nsIObserver
- observe(subject, topic, data) {
+ observe(subject, topic, _data) {
switch (topic) {
case "autocomplete-did-enter-text": {
const input = subject.QueryInterface(Ci.nsIAutoCompleteInput);
@@ -636,103 +636,104 @@ export class LoginManagerPrompter {
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
+ {
+ 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
);
}
- });
- const toggleBtn = chromeDoc.getElementById(
- "password-notification-visibilityToggle"
- );
+ 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;
+ }
- 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;
- }
+ 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"
+ );
- let popup = chromeDoc.getElementById("PopupAutoComplete");
- popup.onUsernameSelect = onUsernameSelect;
- popup.onPasswordSelect = onPasswordSelect;
+ 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;
+ }
- LoginManagerPrompter._setUsernameAutocomplete(
- login,
- possibleValues?.usernames
- );
+ let popup = chromeDoc.getElementById("PopupAutoComplete");
+ popup.onUsernameSelect = onUsernameSelect;
+ popup.onPasswordSelect = onPasswordSelect;
+ LoginManagerPrompter._setUsernameAutocomplete(
+ login,
+ possibleValues?.usernames
+ );
+ }
break;
case "shown": {
lazy.log.debug("shown");
diff --git a/toolkit/components/passwordmgr/nsILoginInfo.idl b/toolkit/components/passwordmgr/nsILoginInfo.idl
index 93f4e2808d..d1e8526b27 100644
--- a/toolkit/components/passwordmgr/nsILoginInfo.idl
+++ b/toolkit/components/passwordmgr/nsILoginInfo.idl
@@ -152,9 +152,3 @@ interface nsILoginInfo : nsISupports {
*/
nsILoginInfo clone();
};
-
-%{C++
-
-#define NS_LOGININFO_CONTRACTID "@mozilla.org/login-manager/loginInfo;1"
-
-%}
diff --git a/toolkit/components/passwordmgr/nsILoginManagerAuthPrompter.idl b/toolkit/components/passwordmgr/nsILoginManagerAuthPrompter.idl
index 422981a0a3..e92cf4b5b5 100644
--- a/toolkit/components/passwordmgr/nsILoginManagerAuthPrompter.idl
+++ b/toolkit/components/passwordmgr/nsILoginManagerAuthPrompter.idl
@@ -37,8 +37,3 @@ interface nsILoginManagerAuthPrompter : nsISupports {
*/
attribute Element browser;
};
-%{C++
-
-#define NS_LOGINMANAGERAUTHPROMPTER_CONTRACTID "@mozilla.org/login-manager/authprompter/;1"
-
-%}
diff --git a/toolkit/components/passwordmgr/nsILoginManagerPrompter.idl b/toolkit/components/passwordmgr/nsILoginManagerPrompter.idl
index 4f244258c0..9ebdd05e91 100644
--- a/toolkit/components/passwordmgr/nsILoginManagerPrompter.idl
+++ b/toolkit/components/passwordmgr/nsILoginManagerPrompter.idl
@@ -96,8 +96,3 @@ interface nsILoginManagerPrompter : nsISupports {
in Array<nsILoginInfo> logins,
in nsILoginInfo aNewLogin);
};
-%{C++
-
-#define NS_LOGINMANAGERPROMPTER_CONTRACTID "@mozilla.org/login-manager/prompter/;1"
-
-%}
diff --git a/toolkit/components/passwordmgr/storage-geckoview.sys.mjs b/toolkit/components/passwordmgr/storage-geckoview.sys.mjs
index db2ef6d61b..d68faa32e9 100644
--- a/toolkit/components/passwordmgr/storage-geckoview.sys.mjs
+++ b/toolkit/components/passwordmgr/storage-geckoview.sys.mjs
@@ -49,15 +49,15 @@ export class LoginManagerStorage extends LoginManagerStorage_json {
*/
terminate() {}
- async addLoginsAsync(logins, continueOnDuplicates = false) {
+ async addLoginsAsync(_logins, _continueOnDuplicates = false) {
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
}
- removeLogin(login) {
+ removeLogin(_login) {
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
}
- modifyLogin(oldLogin, newLoginData) {
+ modifyLogin(_oldLogin, _newLoginData) {
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
}
@@ -180,7 +180,7 @@ export class LoginManagerStorage extends LoginManagerStorage_json {
/**
* Use `searchLoginsAsync` instead.
*/
- searchLogins(matchData) {
+ searchLogins(_matchData) {
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
}
@@ -191,7 +191,7 @@ export class LoginManagerStorage extends LoginManagerStorage_json {
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
}
- countLogins(origin, formActionOrigin, httpRealm) {
+ countLogins(_origin, _formActionOrigin, _httpRealm) {
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
}
@@ -226,7 +226,7 @@ export class LoginManagerStorage extends LoginManagerStorage_json {
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
}
- async setSyncID(syncID) {
+ async setSyncID(_syncID) {
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
}
@@ -234,7 +234,7 @@ export class LoginManagerStorage extends LoginManagerStorage_json {
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
}
- async setLastSync(timestamp) {
+ async setLastSync(_timestamp) {
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
}
}
diff --git a/toolkit/components/passwordmgr/test/browser/browser.toml b/toolkit/components/passwordmgr/test/browser/browser.toml
index 53066c92d4..77c107c6f3 100644
--- a/toolkit/components/passwordmgr/test/browser/browser.toml
+++ b/toolkit/components/passwordmgr/test/browser/browser.toml
@@ -36,8 +36,9 @@ support-files = [
["browser_DOMInputPasswordAdded.js"]
skip-if = [
- "os == 'linux'", # Bug 1337606
- "os == 'mac'", # Bug 1337606
+ "os == 'linux' && os_version == '18.04'", # Bug 1337606
+ "apple_catalina", # Bug 1337606
+ "apple_silicon", # Bug 1337606
]
["browser_autocomplete_autofocus_with_frame.js"]
@@ -47,15 +48,17 @@ support-files = ["form_autofocus_frame.html"]
support-files = ["form_disabled_readonly_passwordField.html"]
["browser_autocomplete_footer.js"]
-skip-if = ["!debug && os == 'linux' && bits == 64 && os_version == '18.04'"] # Bug 1591126
+skip-if = ["os == 'linux' && os_version == '18.04' && bits == 64 && !debug"] # Bug 1591126
["browser_autocomplete_generated_password_private_window.js"]
["browser_autocomplete_import.js"]
https_first_disabled = true
skip-if = [
- "os == 'mac'", # Bug 1775902
- "os == 'win' && !debug", # Bug 1775902
+ "apple_catalina", # Bug 1775902
+ "apple_silicon", # Bug 1775902
+ "win10_2009 && !debug", # Bug 1775902
+ "win11_2009 && !debug", # Bug 1775902
]
["browser_autocomplete_insecure_warning.js"]
@@ -64,8 +67,7 @@ skip-if = [
["browser_autofill_hidden_document.js"]
skip-if = [
- "os == 'win' && os_version == '10.0' && debug", # bug 1530935
- "apple_catalina && fission && !debug", # high frequency intermittent, Bug 1716486
+ "apple_catalina && !debug", # high frequency intermittent, Bug 1716486
]
["browser_autofill_http.js"]
@@ -81,7 +83,10 @@ skip-if = ["os == 'android'"]
["browser_basicAuth_rateLimit.js"]
["browser_basicAuth_switchTab.js"]
-skip-if = ["debug && os == 'mac'"] # Bug 1530566
+skip-if = [
+ "apple_catalina && debug", # Bug 1530566
+ "apple_silicon && debug", # Bug 1530566
+]
["browser_context_menu.js"]
@@ -121,9 +126,10 @@ support-files = [
"form_password_change.html",
]
skip-if = [
- "os == 'linux'", # Bug 1729196
+ "os == 'linux' && os_version == '18.04'", # Bug 1729196
"win11_2009 && bits == 64", # Bug 1729196
- "os == 'mac' && debug", # Bug 1729196
+ "apple_catalina && debug", # Bug 1729196
+ "apple_silicon && debug", # Bug 1729196
]
["browser_doorhanger_httpsUpgrade.js"]
@@ -149,8 +155,8 @@ skip-if = ["a11y_checks"] # Bugs 1858041, 1854454 and 1824058 for causing interm
fail-if = ["a11y_checks"] # Bug 1854452 clicked dropmaker may not be focusable
skip-if = [
"tsan", # Bug 1661305
- "os == 'linux' && debug", # Bug 1658056
- "os == 'linux' && asan", # Bug 1695395
+ "os == 'linux' && os_version == '18.04' && debug", # Bug 1658056
+ "os == 'linux' && os_version == '18.04' && asan", # Bug 1695395
]
["browser_doorhanger_target_blank.js"]
@@ -165,7 +171,7 @@ support-files = [
"subtst_notifications_11.html",
"subtst_notifications_11_popup.html",
]
-skip-if = ["os == 'linux'"] # Bug 1312981, bug 1313136
+skip-if = ["os == 'linux' && os_version == '18.04'"] # Bug 1312981, bug 1313136
["browser_entry_point_telemetry.js"]
@@ -178,7 +184,7 @@ support-files = ["file_focus_before_DOMContentLoaded.sjs"]
["browser_form_history_fallback.js"]
https_first_disabled = true # TODO remove that line and move test to HTTPS, see Bug 1776350
-skip-if = ["os == 'linux' && debug"] # Bug 1334336
+skip-if = ["os == 'linux' && os_version == '18.04' && debug"] # Bug 1334336
support-files = [
"subtst_notifications_1.html",
"subtst_notifications_2.html",
@@ -208,8 +214,9 @@ support-files = ["form_signup_detection.html"]
["browser_localip_frame.js"]
skip-if = [
- "os == 'mac' && bits == 64", # Bug 1683848
- "os == 'linux' && !debug && bits == 64", # Bug 1683848
+ "apple_catalina", # Bug 1683848
+ "apple_silicon", # Bug 1683848
+ "os == 'linux' && os_version == '18.04' && bits == 64 && !debug", # Bug 1683848
]
["browser_message_onFormSubmit.js"]
@@ -219,8 +226,11 @@ skip-if = [
["browser_preselect_login.js"]
fail-if = ["a11y_checks"] # Bug 1854452 clicked ac-secondary-action may not be labeled
skip-if = [
- "os == 'linux' && (asan || tsan || debug)", # Bug 1840479
- "os == 'win' && (asan || debug)", # Bug 1840479
+ "asan",
+ "tsan",
+ "os == 'linux' && os_version == '18.04' && debug", # Bug 1840479
+ "win10_2009 && debug", # Bug 1840479
+ "win11_2009 && debug", # Bug 1840479
]
["browser_private_window.js"]
@@ -229,8 +239,7 @@ support-files = [
"form_password_change.html",
]
skip-if = [
- "os == 'linux' && bits == 64 && os_version == '18.04' && !debug", # Bug 1744976
- "os == 'win' && os_version == '10.0' && debug", # Bug 1782656
+ "os == 'linux' && os_version == '18.04' && bits == 64 && !debug", # Bug 1744976
]
["browser_proxyAuth_prompt.js"]
diff --git a/toolkit/components/passwordmgr/test/browser/browser_autocomplete_primary_password.js b/toolkit/components/passwordmgr/test/browser/browser_autocomplete_primary_password.js
index c3152740cd..8d6956a018 100644
--- a/toolkit/components/passwordmgr/test/browser/browser_autocomplete_primary_password.js
+++ b/toolkit/components/passwordmgr/test/browser/browser_autocomplete_primary_password.js
@@ -105,7 +105,7 @@ add_task(async function test_mpAutocompleteUIBusy() {
isProbablyANewPasswordField: true,
};
- function dialogObserver(subject, topic, data) {
+ function dialogObserver(_subject, topic, _data) {
Assert.ok(false, "A second dialog shouldn't have been shown");
Services.obs.removeObserver(dialogObserver, topic);
}
diff --git a/toolkit/components/passwordmgr/test/browser/browser_basicAuth_rateLimit.js b/toolkit/components/passwordmgr/test/browser/browser_basicAuth_rateLimit.js
index 1c12a8fec5..b0a5b8b335 100644
--- a/toolkit/components/passwordmgr/test/browser/browser_basicAuth_rateLimit.js
+++ b/toolkit/components/passwordmgr/test/browser/browser_basicAuth_rateLimit.js
@@ -85,13 +85,7 @@ add_task(async function test() {
let iframe = doc.createElement("iframe");
doc.body.appendChild(iframe);
let loaded = new Promise(resolve => {
- iframe.addEventListener(
- "load",
- function (e) {
- resolve();
- },
- { once: true }
- );
+ iframe.addEventListener("load", _e => resolve(), { once: true });
});
iframe.src =
"https://example.com/browser/toolkit/components/passwordmgr/test/browser/authenticate.sjs";
@@ -113,13 +107,7 @@ add_task(async function test() {
let iframe = doc.createElement("iframe");
doc.body.appendChild(iframe);
let loaded = new Promise(resolve => {
- iframe.addEventListener(
- "load",
- function (e) {
- resolve();
- },
- { once: true }
- );
+ iframe.addEventListener("load", () => resolve(), { once: true });
});
iframe.src =
"https://example.org/browser/toolkit/components/passwordmgr/test/browser/authenticate.sjs";
diff --git a/toolkit/components/passwordmgr/test/browser/browser_deleteLoginsBackup.js b/toolkit/components/passwordmgr/test/browser/browser_deleteLoginsBackup.js
index a122f52845..db762ca118 100644
--- a/toolkit/components/passwordmgr/test/browser/browser_deleteLoginsBackup.js
+++ b/toolkit/components/passwordmgr/test/browser/browser_deleteLoginsBackup.js
@@ -50,7 +50,7 @@ const loginBackupPath = PathUtils.join(
async function waitForBackupUpdate() {
return new Promise(resolve => {
- Services.obs.addObserver(function observer(subject, topic) {
+ Services.obs.addObserver(function observer(_subject, _topic, _data) {
Services.obs.removeObserver(observer, "logins-backup-updated");
resolve();
}, "logins-backup-updated");
diff --git a/toolkit/components/passwordmgr/test/browser/browser_doorhanger_crossframe.js b/toolkit/components/passwordmgr/test/browser/browser_doorhanger_crossframe.js
index 8c4770d510..ca50715a1f 100644
--- a/toolkit/components/passwordmgr/test/browser/browser_doorhanger_crossframe.js
+++ b/toolkit/components/passwordmgr/test/browser/browser_doorhanger_crossframe.js
@@ -7,7 +7,7 @@ async function acceptPasswordSave() {
let notif = await getCaptureDoorhangerThatMayOpen("password-save");
let promiseNewSavedPassword = TestUtils.topicObserved(
"LoginStats:NewSavedPassword",
- (subject, data) => subject == gBrowser.selectedBrowser
+ (subject, _topic, _data) => subject == gBrowser.selectedBrowser
);
clickDoorhangerButton(notif, REMEMBER_BUTTON);
await promiseNewSavedPassword;
diff --git a/toolkit/components/passwordmgr/test/browser/browser_doorhanger_generated_password.js b/toolkit/components/passwordmgr/test/browser/browser_doorhanger_generated_password.js
index 3740dad1b9..798337ddda 100644
--- a/toolkit/components/passwordmgr/test/browser/browser_doorhanger_generated_password.js
+++ b/toolkit/components/passwordmgr/test/browser/browser_doorhanger_generated_password.js
@@ -901,8 +901,8 @@ add_task(async function contextmenu_fill_generated_password_and_set_username() {
);
await SpecialPowers.spawn(
browser,
- [[passwordInputSelector, usernameInputSelector]],
- function checkEmptyPasswordField([passwordSelector, usernameSelector]) {
+ [passwordInputSelector],
+ function checkEmptyPasswordField(passwordSelector) {
Assert.equal(
content.document.querySelector(passwordSelector).value,
"",
diff --git a/toolkit/components/passwordmgr/test/browser/browser_doorhanger_remembering.js b/toolkit/components/passwordmgr/test/browser/browser_doorhanger_remembering.js
index 93a16c2e3c..c3fb7656de 100644
--- a/toolkit/components/passwordmgr/test/browser/browser_doorhanger_remembering.js
+++ b/toolkit/components/passwordmgr/test/browser/browser_doorhanger_remembering.js
@@ -68,7 +68,7 @@ add_setup(async function () {
add_task(async function test_remember_opens() {
await testSubmittingLoginFormHTTP(
"subtst_notifications_1.html",
- async function (fieldValues) {
+ async fieldValues => {
Assert.equal(
fieldValues.username,
"notifyu1",
@@ -90,7 +90,7 @@ add_task(async function test_remember_opens() {
add_task(async function test_clickNever() {
await testSubmittingLoginFormHTTP(
"subtst_notifications_1.html",
- async function (fieldValues) {
+ async fieldValues => {
Assert.equal(
fieldValues.username,
"notifyu1",
@@ -162,7 +162,7 @@ add_task(async function test_clickRemember() {
await testSubmittingLoginFormHTTP(
"subtst_notifications_1.html",
- async function (fieldValues) {
+ async fieldValues => {
Assert.equal(
fieldValues.username,
"notifyu1",
@@ -186,7 +186,7 @@ add_task(async function test_clickRemember() {
await checkDoorhangerUsernamePassword("notifyu1", "notifyp1");
let promiseNewSavedPassword = TestUtils.topicObserved(
"LoginStats:NewSavedPassword",
- (subject, data) => subject == gBrowser.selectedBrowser
+ (subject, _topic, _data) => subject == gBrowser.selectedBrowser
);
clickDoorhangerButton(notif, REMEMBER_BUTTON);
await promiseNewSavedPassword;
@@ -283,7 +283,7 @@ add_task(async function test_rememberSignonsTrue() {
await testSubmittingLoginFormHTTP(
"subtst_notifications_1.html",
- async function (fieldValues) {
+ async fieldValues => {
Assert.equal(
fieldValues.username,
"notifyu1",
@@ -317,7 +317,7 @@ add_task(async function test_autocompleteOffUsername() {
await testSubmittingLoginFormHTTP(
"subtst_notifications_2.html",
- async function (fieldValues) {
+ async fieldValues => {
Assert.equal(
fieldValues.username,
"notifyu1",
@@ -349,7 +349,7 @@ add_task(async function test_autocompleteOffPassword() {
await testSubmittingLoginFormHTTP(
"subtst_notifications_3.html",
- async function (fieldValues) {
+ async fieldValues => {
Assert.equal(
fieldValues.username,
"notifyu1",
@@ -379,7 +379,7 @@ add_task(async function test_autocompleteOffForm() {
await testSubmittingLoginFormHTTP(
"subtst_notifications_4.html",
- async function (fieldValues) {
+ async fieldValues => {
Assert.equal(
fieldValues.username,
"notifyu1",
@@ -434,7 +434,7 @@ add_task(async function test_pwOnlyNewLoginMatchesUPForm() {
await testSubmittingLoginFormHTTP(
"subtst_notifications_1.html",
- async function (fieldValues) {
+ async fieldValues => {
Assert.equal(
fieldValues.username,
"notifyu1",
@@ -501,7 +501,7 @@ add_task(async function test_pwOnlyOldLoginMatchesUPForm() {
await testSubmittingLoginFormHTTP(
"subtst_notifications_1.html",
- async function (fieldValues) {
+ async fieldValues => {
Assert.equal(
fieldValues.username,
"notifyu1",
@@ -588,7 +588,7 @@ add_task(async function test_pwOnlyFormDoesntMatchExisting() {
await testSubmittingLoginFormHTTP(
"subtst_notifications_6.html",
- async function (fieldValues) {
+ async fieldValues => {
Assert.equal(fieldValues.username, "null", "Checking submitted username");
Assert.equal(
fieldValues.password,
@@ -618,7 +618,7 @@ add_task(async function test_changeUPLoginOnUPForm_dont() {
await testSubmittingLoginFormHTTP(
"subtst_notifications_8.html",
- async function (fieldValues) {
+ async fieldValues => {
Assert.equal(
fieldValues.username,
"notifyu1",
@@ -704,7 +704,7 @@ add_task(async function test_changeUPLoginOnUPForm_change() {
await testSubmittingLoginFormHTTP(
"subtst_notifications_8.html",
- async function (fieldValues) {
+ async fieldValues => {
Assert.equal(
fieldValues.username,
"notifyu1",
@@ -727,7 +727,7 @@ add_task(async function test_changeUPLoginOnUPForm_change() {
await checkDoorhangerUsernamePassword("notifyu1", "pass2");
let promiseLoginUpdateSaved = TestUtils.topicObserved(
"LoginStats:LoginUpdateSaved",
- (subject, data) => subject == gBrowser.selectedBrowser
+ (subject, _topic, _data) => subject == gBrowser.selectedBrowser
);
clickDoorhangerButton(notif, CHANGE_BUTTON);
await promiseLoginUpdateSaved;
@@ -760,7 +760,7 @@ add_task(async function test_changePLoginOnUPForm() {
await testSubmittingLoginFormHTTP(
"subtst_notifications_9.html",
- async function (fieldValues) {
+ async fieldValues => {
Assert.equal(fieldValues.username, "", "Checking submitted username");
Assert.equal(
fieldValues.password,
@@ -801,7 +801,7 @@ add_task(async function test_changePLoginOnPForm() {
await testSubmittingLoginFormHTTP(
"subtst_notifications_10.html",
- async function (fieldValues) {
+ async fieldValues => {
Assert.equal(fieldValues.username, "null", "Checking submitted username");
Assert.equal(
fieldValues.password,
@@ -842,7 +842,7 @@ add_task(async function test_checkUPSaveText() {
await testSubmittingLoginFormHTTP(
"subtst_notifications_1.html",
- async function (fieldValues) {
+ async fieldValues => {
Assert.equal(
fieldValues.username,
"notifyu1",
@@ -880,7 +880,7 @@ add_task(async function test_checkPSaveText() {
await testSubmittingLoginFormHTTP(
"subtst_notifications_6.html",
- async function (fieldValues) {
+ async fieldValues => {
Assert.equal(fieldValues.username, "null", "Checking submitted username");
Assert.equal(
fieldValues.password,
@@ -917,7 +917,7 @@ add_task(async function test_capture2pw0un() {
await testSubmittingLoginFormHTTP(
"subtst_notifications_2pw_0un.html",
- async function (fieldValues) {
+ async fieldValues => {
Assert.equal(fieldValues.username, "null", "Checking submitted username");
Assert.equal(
fieldValues.password,
@@ -948,7 +948,7 @@ add_task(async function test_change2pw0unExistingDifferentUP() {
await testSubmittingLoginFormHTTP(
"subtst_notifications_2pw_0un.html",
- async function (fieldValues) {
+ async fieldValues => {
Assert.equal(fieldValues.username, "null", "Checking submitted username");
Assert.equal(
fieldValues.password,
@@ -982,7 +982,7 @@ add_task(async function test_change2pw0unExistingDifferentP() {
await testSubmittingLoginFormHTTP(
"subtst_notifications_2pw_0un.html",
- async function (fieldValues) {
+ async fieldValues => {
Assert.equal(fieldValues.username, "null", "Checking submitted username");
Assert.equal(
fieldValues.password,
@@ -1046,7 +1046,7 @@ add_task(async function test_changeUPLoginOnPUpdateForm() {
await testSubmittingLoginFormHTTP(
"subtst_notifications_change_p.html",
- async function (fieldValues) {
+ async fieldValues => {
Assert.equal(fieldValues.username, "null", "Checking submitted username");
Assert.equal(
fieldValues.password,
@@ -1094,7 +1094,7 @@ add_task(async function test_recipeCaptureFields_NewLogin() {
await testSubmittingLoginFormHTTP(
"subtst_notifications_2pw_1un_1text.html",
- async function (fieldValues) {
+ async fieldValues => {
Assert.equal(
fieldValues.username,
"notifyu1",
@@ -1175,7 +1175,7 @@ add_task(async function test_saveUsingEnter() {
info("Waiting for form submit and doorhanger interaction");
await testSubmittingLoginFormHTTP(
"subtst_notifications_1.html",
- async function (fieldValues) {
+ async fieldValues => {
Assert.equal(
fieldValues.username,
"notifyu1",
@@ -1231,7 +1231,7 @@ add_task(async function test_noShowPasswordOnDismissal() {
await testSubmittingLoginFormHTTP(
"subtst_notifications_1.html",
- async function (fieldValues) {
+ async function (_fieldValues) {
info("Opening popup");
let notif = await getCaptureDoorhangerThatMayOpen("password-save");
Assert.ok(!notif.dismissed, "doorhanger is not dismissed");
@@ -1265,7 +1265,7 @@ add_task(async function test_showPasswordOn1stOpenOfDismissedByDefault() {
await testSubmittingLoginFormHTTP(
"subtst_notifications_1.html",
- async function (fieldValues) {
+ async function (_fieldValues) {
info("Opening popup");
let notif = await getCaptureDoorhangerThatMayOpen("password-save");
Assert.ok(!notif.dismissed, "doorhanger is not dismissed");
diff --git a/toolkit/components/passwordmgr/test/browser/browser_doorhanger_window_open.js b/toolkit/components/passwordmgr/test/browser/browser_doorhanger_window_open.js
index ea0437955d..6224f027a4 100644
--- a/toolkit/components/passwordmgr/test/browser/browser_doorhanger_window_open.js
+++ b/toolkit/components/passwordmgr/test/browser/browser_doorhanger_window_open.js
@@ -27,7 +27,7 @@ let login2 = new nsLoginInfo(
);
function withTestTabUntilStorageChange(aPageFile, aTaskFn) {
- function storageChangedObserved(subject, data) {
+ function storageChangedObserved(_subject, data) {
// Watch for actions triggered from a doorhanger (not cleanup tasks with removeLogin)
if (data == "removeLogin") {
return false;
@@ -44,7 +44,7 @@ function withTestTabUntilStorageChange(aPageFile, aTaskFn) {
gBrowser,
url: "http://mochi.test:8888" + DIRECTORY_PATH + aPageFile,
},
- async function (browser) {
+ async function (_browser) {
Assert.ok(true, "loaded " + aPageFile);
info("running test case task");
await aTaskFn();
diff --git a/toolkit/components/passwordmgr/test/browser/browser_entry_point_telemetry.js b/toolkit/components/passwordmgr/test/browser/browser_entry_point_telemetry.js
index e400dc4637..9241f18612 100644
--- a/toolkit/components/passwordmgr/test/browser/browser_entry_point_telemetry.js
+++ b/toolkit/components/passwordmgr/test/browser/browser_entry_point_telemetry.js
@@ -66,7 +66,7 @@ add_task(async function pageInfo_entryPoint() {
gBrowser,
url: TEST_ORIGIN,
},
- async function (browser) {
+ async function (_browser) {
info("pageInfo_entryPoint, opening pageinfo");
let pageInfo = BrowserPageInfo(TEST_ORIGIN, "securityTab", {});
await BrowserTestUtils.waitForEvent(pageInfo, "page-info-init");
diff --git a/toolkit/components/passwordmgr/test/browser/browser_formless_submit_chrome.js b/toolkit/components/passwordmgr/test/browser/browser_formless_submit_chrome.js
index bb7e973db6..8a625fd86f 100644
--- a/toolkit/components/passwordmgr/test/browser/browser_formless_submit_chrome.js
+++ b/toolkit/components/passwordmgr/test/browser/browser_formless_submit_chrome.js
@@ -50,7 +50,7 @@ add_setup(async function () {
});
add_task(async function test_urlbar_new_URL() {
- await withTestPage(async function (aBrowser) {
+ await withTestPage(async aBrowser => {
gURLBar.value = "";
let focusPromise = BrowserTestUtils.waitForEvent(gURLBar, "focus");
gURLBar.focus();
@@ -67,7 +67,7 @@ add_task(async function test_urlbar_new_URL() {
});
add_task(async function test_urlbar_fragment_enter() {
- await withTestPage(function (aBrowser) {
+ await withTestPage(_browser => {
gURLBar.focus();
gURLBar.select();
EventUtils.synthesizeKey("KEY_ArrowRight");
@@ -77,7 +77,7 @@ add_task(async function test_urlbar_fragment_enter() {
});
add_task(async function test_backButton_forwardButton() {
- await withTestPage(async function (aBrowser) {
+ await withTestPage(async aBrowser => {
info("Loading formless_basic.html?second");
// Load a new page in the tab so we can test going back
BrowserTestUtils.startLoadingURIString(
@@ -120,7 +120,7 @@ add_task(async function test_backButton_forwardButton() {
});
add_task(async function test_reloadButton() {
- await withTestPage(async function (aBrowser) {
+ await withTestPage(async aBrowser => {
let reloadButton = document.getElementById("reload-button");
let loadPromise = BrowserTestUtils.browserLoaded(
aBrowser,
@@ -137,7 +137,7 @@ add_task(async function test_reloadButton() {
});
add_task(async function test_back_keyboard_shortcut() {
- await withTestPage(async function (aBrowser) {
+ await withTestPage(async aBrowser => {
// Load a new page in the tab so we can test going back
BrowserTestUtils.startLoadingURIString(
aBrowser,
diff --git a/toolkit/components/passwordmgr/test/browser/browser_message_onFormSubmit.js b/toolkit/components/passwordmgr/test/browser/browser_message_onFormSubmit.js
index 5e611a7384..eca5f6818a 100644
--- a/toolkit/components/passwordmgr/test/browser/browser_message_onFormSubmit.js
+++ b/toolkit/components/passwordmgr/test/browser/browser_message_onFormSubmit.js
@@ -5,7 +5,7 @@
async function waitForFormSubmissionDetected() {
return new Promise(resolve => {
- Services.obs.addObserver(function observer(subject, topic) {
+ Services.obs.addObserver(function observer(_subject, _topic, _data) {
Services.obs.removeObserver(
observer,
"passwordmgr-form-submission-detected"
diff --git a/toolkit/components/passwordmgr/test/browser/browser_preselect_login.js b/toolkit/components/passwordmgr/test/browser/browser_preselect_login.js
index 5847e84282..e8a405cbca 100644
--- a/toolkit/components/passwordmgr/test/browser/browser_preselect_login.js
+++ b/toolkit/components/passwordmgr/test/browser/browser_preselect_login.js
@@ -154,7 +154,7 @@ add_task(
gBrowser,
url: TEST_URL_PATH,
},
- async function (browser) {
+ async function (_browser) {
await waitForAppMenu();
const appMenuPasswordsButton = document.getElementById(
diff --git a/toolkit/components/passwordmgr/test/browser/browser_proxyAuth_prompt.js b/toolkit/components/passwordmgr/test/browser/browser_proxyAuth_prompt.js
index 478f204581..dfa78ff28e 100644
--- a/toolkit/components/passwordmgr/test/browser/browser_proxyAuth_prompt.js
+++ b/toolkit/components/passwordmgr/test/browser/browser_proxyAuth_prompt.js
@@ -13,12 +13,12 @@ function initProxy() {
let proxyCallback = {
QueryInterface: ChromeUtils.generateQI(["nsIProtocolProxyCallback"]),
- onProxyAvailable(req, uri, pi, status) {
+ onProxyAvailable(_req, _uri, _pi, _status) {
class ProxyChannelListener {
- onStartRequest(request) {
+ onStartRequest(_request) {
resolve(proxyChannel);
}
- onStopRequest(request, status) {}
+ onStopRequest(_request, _status) {}
}
// I'm cheating a bit here... We should probably do some magic foo to get
// something implementing nsIProxiedProtocolHandler and then call
@@ -81,7 +81,7 @@ function getAuthPromptCallback() {
callbackResolver = resolve;
});
let callback = {
- onAuthAvailable(context, authInfo) {
+ onAuthAvailable(_context, authInfo) {
callbackResolver(authInfo);
},
};
diff --git a/toolkit/components/passwordmgr/test/browser/browser_relay_telemetry.js b/toolkit/components/passwordmgr/test/browser/browser_relay_telemetry.js
index ea5a25db79..31d294e01d 100644
--- a/toolkit/components/passwordmgr/test/browser/browser_relay_telemetry.js
+++ b/toolkit/components/passwordmgr/test/browser/browser_relay_telemetry.js
@@ -203,7 +203,7 @@ add_task(async function test_pref_toggle() {
gBrowser,
url: "about:preferences#privacy",
},
- async function (browser) {
+ async _browser => {
const relayIntegrationCheckbox = content.document.querySelector(
"checkbox#relayIntegration"
);
diff --git a/toolkit/components/passwordmgr/test/browser/browser_username_only_form_telemetry.js b/toolkit/components/passwordmgr/test/browser/browser_username_only_form_telemetry.js
index 54304c24ac..0824745bfd 100644
--- a/toolkit/components/passwordmgr/test/browser/browser_username_only_form_telemetry.js
+++ b/toolkit/components/passwordmgr/test/browser/browser_username_only_form_telemetry.js
@@ -5,7 +5,7 @@
"use strict";
-async function setupForms(numUsernameOnly, numBasic, numOther) {
+async function setupForms(numUsernameOnly, numBasic) {
const TEST_HOSTNAME = "https://example.com";
let tab = await BrowserTestUtils.openNewForegroundTab(
gBrowser,
@@ -88,9 +88,7 @@ add_task(async function test_oneUsernameOnlyForm() {
const numUsernameOnlyForms = 1;
const numBasicForms = 0;
- // number of "other" forms doesn't change the outcome, set it to 2 here and
- // in the following testcase just to ensure it doesn't affect the result.
- let tab = await setupForms(numUsernameOnlyForms, numBasicForms, 2);
+ let tab = await setupForms(numUsernameOnlyForms, numBasicForms);
await checkChildHistogram(
"PWMGR_IS_USERNAME_ONLY_FORM",
@@ -111,7 +109,7 @@ add_task(async function test_multipleUsernameOnlyForms() {
const numUsernameOnlyForms = 3;
const numBasicForms = 2;
- let tab = await setupForms(numUsernameOnlyForms, numBasicForms, 2);
+ let tab = await setupForms(numUsernameOnlyForms, numBasicForms);
await checkChildHistogram(
"PWMGR_IS_USERNAME_ONLY_FORM",
@@ -133,7 +131,7 @@ add_task(async function test_multipleDocument() {
let numUsernameOnlyForms1 = 2;
let numBasicForms1 = 2;
- let tab1 = await setupForms(numUsernameOnlyForms1, numBasicForms1, 2);
+ let tab1 = await setupForms(numUsernameOnlyForms1, numBasicForms1);
await checkChildHistogram(
"PWMGR_IS_USERNAME_ONLY_FORM",
@@ -150,7 +148,7 @@ add_task(async function test_multipleDocument() {
let numUsernameOnlyForms2 = 15;
let numBasicForms2 = 3;
- let tab2 = await setupForms(numUsernameOnlyForms2, numBasicForms2, 2);
+ let tab2 = await setupForms(numUsernameOnlyForms2, numBasicForms2);
await checkChildHistogram(
"PWMGR_IS_USERNAME_ONLY_FORM",
@@ -180,7 +178,7 @@ add_task(async function test_tooManyUsernameOnlyForms() {
const numUsernameOnlyForms = 25;
const numBasicForms = 2;
- let tab = await setupForms(numUsernameOnlyForms, numBasicForms, 2);
+ let tab = await setupForms(numUsernameOnlyForms, numBasicForms);
await checkChildHistogram(
"PWMGR_IS_USERNAME_ONLY_FORM",
diff --git a/toolkit/components/passwordmgr/test/mochitest/pwmgr_common.js b/toolkit/components/passwordmgr/test/mochitest/pwmgr_common.js
index 8b125897a5..1494c60bbd 100644
--- a/toolkit/components/passwordmgr/test/mochitest/pwmgr_common.js
+++ b/toolkit/components/passwordmgr/test/mochitest/pwmgr_common.js
@@ -30,15 +30,6 @@ let authPromptModalType = SpecialPowers.Services.prefs.getIntPref(
"prompts.modalType.httpAuth"
);
-// Whether the auth prompt is a commonDialog.xhtml or a TabModalPrompt
-let authPromptIsCommonDialog =
- authPromptModalType === SpecialPowers.Services.prompt.MODAL_TYPE_WINDOW ||
- (authPromptModalType === SpecialPowers.Services.prompt.MODAL_TYPE_TAB &&
- SpecialPowers.Services.prefs.getBoolPref(
- "prompts.tabChromePromptSubDialog",
- false
- ));
-
/**
* Recreate a DOM tree using the outerHTML to ensure that any event listeners
* and internal state for the elements are removed.
@@ -145,7 +136,7 @@ function setUserInputValues(parentNode, selectorValues, userInput = true) {
*/
function getSubmitMessage(aFilterFn = undefined) {
info("getSubmitMessage");
- return new Promise((resolve, reject) => {
+ return new Promise(resolve => {
PWMGR_COMMON_PARENT.addMessageListener(
"formSubmissionProcessed",
function processed(...args) {
@@ -170,7 +161,7 @@ function getSubmitMessage(aFilterFn = undefined) {
*/
function getPasswordEditedMessage() {
info("getPasswordEditedMessage");
- return new Promise((resolve, reject) => {
+ return new Promise(resolve => {
PWMGR_COMMON_PARENT.addMessageListener(
"passwordEditedOrGenerated",
function listener(...args) {
@@ -643,8 +634,8 @@ function registerRunTests(existingPasswordFieldsCount = 0, callback) {
let foundForcer = false;
var observer = SpecialPowers.wrapCallback(function (
- subject,
- topic,
+ _subject,
+ _topic,
data
) {
if (data === "observerforcer") {
@@ -714,8 +705,8 @@ function logoutPrimaryPassword() {
*/
function promiseFormsProcessedInSameProcess(expectedCount = 1) {
var processedCount = 0;
- return new Promise((resolve, reject) => {
- function onProcessedForm(subject, topic, data) {
+ return new Promise(resolve => {
+ function onProcessedForm(subject, _topic, data) {
processedCount++;
if (processedCount == expectedCount) {
info(`${processedCount} form(s) processed`);
@@ -1068,7 +1059,7 @@ SimpleTest.registerCleanupFunction(() => {
this.LoginManager = new Proxy(
{},
{
- get(target, prop, receiver) {
+ get(_target, prop, _receiver) {
return (...args) => {
let loginInfoIndices = [];
let cloneableArgs = args.map((val, index) => {
@@ -1159,7 +1150,7 @@ async function formAutofillResult(formId) {
delete gPwmgrCommonCapturedAutofillResults[formId];
return autofillResult;
}
- return new Promise((resolve, reject) => {
+ return new Promise(resolve => {
PWMGR_COMMON_PARENT.addMessageListener(
"formProcessed",
({ formId: id, autofillResult }) => {
diff --git a/toolkit/components/passwordmgr/test/mochitest/pwmgr_common_parent.js b/toolkit/components/passwordmgr/test/mochitest/pwmgr_common_parent.js
index 09ad80f467..e530fddf1c 100644
--- a/toolkit/components/passwordmgr/test/mochitest/pwmgr_common_parent.js
+++ b/toolkit/components/passwordmgr/test/mochitest/pwmgr_common_parent.js
@@ -109,8 +109,8 @@ function dumpLogin(label, login) {
}
addMessageListener("storageChanged", async function ({ expectedChangeTypes }) {
- return new Promise((resolve, reject) => {
- function storageChanged(subject, topic, data) {
+ return new Promise(resolve => {
+ function storageChanged(_subject, _topic, data) {
let changeType = expectedChangeTypes.shift();
if (data != changeType) {
resolve("Unexpected change type " + data + ", expected " + changeType);
@@ -129,7 +129,7 @@ addMessageListener("storageChanged", async function ({ expectedChangeTypes }) {
addMessageListener("promptShown", async function () {
return new Promise(resolve => {
- function promptShown(subject, topic, data) {
+ function promptShown(_subject, topic, _data) {
Services.obs.removeObserver(promptShown, "passwordmgr-prompt-change");
Services.obs.removeObserver(promptShown, "passwordmgr-prompt-save");
resolve(topic);
diff --git a/toolkit/components/passwordmgr/test/mochitest/test_autofill_https_downgrade.html b/toolkit/components/passwordmgr/test/mochitest/test_autofill_https_downgrade.html
index 091f9c8ad6..5d74cef106 100644
--- a/toolkit/components/passwordmgr/test/mochitest/test_autofill_https_downgrade.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_autofill_https_downgrade.html
@@ -30,7 +30,7 @@ let nsLoginInfo = SpecialPowers.wrap(SpecialPowers.Components).Constructor("@moz
let win = window.open("about:blank");
SimpleTest.registerCleanupFunction(() => win.close());
-async function prepareAndProcessForm(url, login) {
+async function prepareAndProcessForm(url) {
let processedPromise = promiseFormsProcessed();
win.location = url;
info("prepareAndProcessForm, assigned window location: " + url);
diff --git a/toolkit/components/passwordmgr/test/mochitest/test_bug_627616.html b/toolkit/components/passwordmgr/test/mochitest/test_bug_627616.html
index 429ec2269c..50d94dfbae 100644
--- a/toolkit/components/passwordmgr/test/mochitest/test_bug_627616.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_bug_627616.html
@@ -56,7 +56,7 @@
function runNextTest() {
is(gExpectedDialogs, 0, "received expected number of auth dialogs");
mm.sendAsyncMessage("prepareForNextTest");
- mm.addMessageListener("prepareForNextTestDone", function prepared(msg) {
+ mm.addMessageListener("prepareForNextTestDone", function prepared(_msg) {
mm.removeMessageListener("prepareForNextTestDone", prepared);
if (pendingTests.length) {
({expectedDialogs: gExpectedDialogs,
@@ -92,7 +92,7 @@
const pps = parentCc["@mozilla.org/network/protocol-proxy-service;1"].
getService(parentCi.nsIProtocolProxyService);
pps.asyncResolve(channel, 0, {
- async onProxyAvailable(req, uri, pi, status) {
+ async onProxyAvailable(req, uri, pi, _status) {
const mozproxy = "moz-proxy://" + pi.host + ":" + pi.port;
const login1 = parentCc["@mozilla.org/login-manager/loginInfo;1"].
createInstance(parentCi.nsILoginInfo);
@@ -110,54 +110,32 @@
QueryInterface: ChromeUtils.generateQI([parentCi.nsIProtocolProxyCallback]),
});
- addMessageListener("prepareForNextTest", message => {
+ addMessageListener("prepareForNextTest", _message => {
parentCc["@mozilla.org/network/http-auth-manager;1"].
getService(parentCi.nsIHttpAuthManager).
clearAll();
sendAsyncMessage("prepareForNextTestDone");
});
- const modalType = Services.prefs.getIntPref(
- "prompts.modalType.httpAuth"
- );
- const authPromptIsCommonDialog =
- modalType === Services.prompt.MODAL_TYPE_WINDOW
- || (modalType === Services.prompt.MODAL_TYPE_TAB
- && Services.prefs.getBoolPref(
- "prompts.tabChromePromptSubDialog",
- false
- ));
-
- const dialogObserverTopic = authPromptIsCommonDialog
- ? "common-dialog-loaded" : "tabmodal-dialog-loaded";
-
- function dialogObserver(subj, topic, data) {
- if (authPromptIsCommonDialog) {
- subj.Dialog.ui.prompt.document
- .getElementById("commonDialog")
- .acceptDialog();
- } else {
- const prompt = subj.ownerGlobal.gBrowser.selectedBrowser
- .tabModalPromptBox.getPrompt(subj);
- prompt.Dialog.ui.button0.click(); // Accept button
- }
+ const dialogObserverTopic = "common-dialog-loaded";
+
+ function dialogObserver(subj, _topic, _data) {
+ subj.Dialog.ui.prompt.document
+ .getElementById("commonDialog")
+ .acceptDialog();
sendAsyncMessage("promptAccepted");
}
Services.obs.addObserver(dialogObserver, dialogObserverTopic);
- addMessageListener("cleanup", message => {
+ addMessageListener("cleanup", _message => {
Services.obs.removeObserver(dialogObserver, dialogObserverTopic);
sendAsyncMessage("cleanupDone");
});
});
- mm.addMessageListener("promptAccepted", msg => {
- gExpectedDialogs--;
- });
- mm.addMessageListener("setupDone", msg => {
- runNextTest();
- });
+ mm.addMessageListener("promptAccepted", _message => gExpectedDialogs--);
+ mm.addMessageListener("setupDone", _message => runNextTest());
</script>
</body>
</html>
diff --git a/toolkit/components/passwordmgr/test/mochitest/test_dismissed_doorhanger_in_shadow_DOM.html b/toolkit/components/passwordmgr/test/mochitest/test_dismissed_doorhanger_in_shadow_DOM.html
index 87638b1132..5d014124a1 100644
--- a/toolkit/components/passwordmgr/test/mochitest/test_dismissed_doorhanger_in_shadow_DOM.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_dismissed_doorhanger_in_shadow_DOM.html
@@ -67,13 +67,7 @@ async function editPasswordFieldInShadowDOM() {
async function testForm(testcase) {
const iframeLoaded = new Promise(resolve => {
- IFRAME.addEventListener(
- "load",
- function(e) {
- resolve(true);
- },
- { once: true }
- );
+ IFRAME.addEventListener("load", _e => resolve(true), { once: true });
});
// This could complete before the page finishes loading.
diff --git a/toolkit/components/passwordmgr/test/mochitest/test_formLike_rootElement_with_Shadow_DOM.html b/toolkit/components/passwordmgr/test/mochitest/test_formLike_rootElement_with_Shadow_DOM.html
index 06458893ea..722a86efc9 100644
--- a/toolkit/components/passwordmgr/test/mochitest/test_formLike_rootElement_with_Shadow_DOM.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_formLike_rootElement_with_Shadow_DOM.html
@@ -94,13 +94,7 @@ const TESTCASES = [
async function testForm(testcase) {
const iframeLoaded = new Promise(resolve => {
- IFRAME.addEventListener(
- "load",
- function(e) {
- resolve(true);
- },
- { once: true }
- );
+ IFRAME.addEventListener("load", _e => resolve(true), { once: true });
});
// This could complete before the page finishes loading.
diff --git a/toolkit/components/passwordmgr/test/mochitest/test_formless_autofill.html b/toolkit/components/passwordmgr/test/mochitest/test_formless_autofill.html
index 4d1b7582a9..05be33e4dd 100644
--- a/toolkit/components/passwordmgr/test/mochitest/test_formless_autofill.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_formless_autofill.html
@@ -12,7 +12,7 @@
gTestDependsOnDeprecatedLogin = true;
let doneSetupPromise = new Promise(resolve => {
document.addEventListener("DOMContentLoaded", () => {
- document.getElementById("loginFrame").addEventListener("load", async evt => {
+ document.getElementById("loginFrame").addEventListener("load", async _e => {
// Tell the parent to setup test logins.
await runChecksAfterCommonInit();
resolve();
diff --git a/toolkit/components/passwordmgr/test/mochitest/test_formless_submit_form_removal.html b/toolkit/components/passwordmgr/test/mochitest/test_formless_submit_form_removal.html
index 5512c57db2..b2ff2d8845 100644
--- a/toolkit/components/passwordmgr/test/mochitest/test_formless_submit_form_removal.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_formless_submit_form_removal.html
@@ -10,10 +10,8 @@
<body>
<script type="application/javascript">
let loadPromise = new Promise(resolve => {
- document.addEventListener("DOMContentLoaded", () => {
- document.getElementById("loginFrame").addEventListener("load", (evt) => {
- resolve();
- });
+ document.addEventListener("DOMContentLoaded", _event => {
+ document.getElementById("loginFrame").addEventListener("load", _e => resolve());
});
});
@@ -187,16 +185,14 @@ const TESTCASES = [
},*/
];
-function filterFormSubmissions({ origin, data }) {
+function filterFormSubmissions({ data }) {
return data.newPasswordField.value != "ignore-form-submission";
}
async function testFormlesSubmitFormRemoval(tc, testDoc, scriptName) {
let loginFrame = document.getElementById("loginFrame");
- let loadedPromise = new Promise((resolve) => {
- loginFrame.addEventListener("load", function() {
- resolve();
- }, {once: true});
+ let loadedPromise = new Promise(resolve => {
+ loginFrame.addEventListener("load", _e => resolve(), {once: true});
});
loginFrame.src = DEFAULT_ORIGIN + "/tests/toolkit/components/passwordmgr/test/mochitest/blank.html";
await loadedPromise;
diff --git a/toolkit/components/passwordmgr/test/mochitest/test_formless_submit_form_removal_negative.html b/toolkit/components/passwordmgr/test/mochitest/test_formless_submit_form_removal_negative.html
index dfd7670a12..845706e9a4 100644
--- a/toolkit/components/passwordmgr/test/mochitest/test_formless_submit_form_removal_negative.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_formless_submit_form_removal_negative.html
@@ -13,9 +13,7 @@ SimpleTest.requestFlakyTimeout("Testing that a message doesn't arrive");
let loadPromise = new Promise(resolve => {
document.addEventListener("DOMContentLoaded", () => {
- document.getElementById("loginFrame").addEventListener("load", (evt) => {
- resolve();
- });
+ document.getElementById("loginFrame").addEventListener("load", _e => resolve());
});
});
diff --git a/toolkit/components/passwordmgr/test/mochitest/test_formless_submit_navigation.html b/toolkit/components/passwordmgr/test/mochitest/test_formless_submit_navigation.html
index 348669a85c..78ddb18e4b 100644
--- a/toolkit/components/passwordmgr/test/mochitest/test_formless_submit_navigation.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_formless_submit_navigation.html
@@ -11,9 +11,7 @@
<script type="application/javascript">
let loadPromise = new Promise(resolve => {
document.addEventListener("DOMContentLoaded", () => {
- document.getElementById("loginFrame").addEventListener("load", (evt) => {
- resolve();
- });
+ document.getElementById("loginFrame").addEventListener("load", _e => resolve());
});
});
@@ -179,7 +177,7 @@ const TESTCASES = [
},
];
-function filterFormSubmissions({ origin, data }) {
+function filterFormSubmissions({ _origin, data }) {
return data.newPasswordField.value != "ignore-form-submission";
}
diff --git a/toolkit/components/passwordmgr/test/mochitest/test_formless_submit_navigation_negative.html b/toolkit/components/passwordmgr/test/mochitest/test_formless_submit_navigation_negative.html
index 338cd5d2c1..f5bb29c14d 100644
--- a/toolkit/components/passwordmgr/test/mochitest/test_formless_submit_navigation_negative.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_formless_submit_navigation_negative.html
@@ -12,10 +12,8 @@
SimpleTest.requestFlakyTimeout("Testing that a message doesn't arrive");
let loadPromise = new Promise(resolve => {
- document.addEventListener("DOMContentLoaded", () => {
- document.getElementById("loginFrame").addEventListener("load", (evt) => {
- resolve();
- });
+ document.addEventListener("DOMContentLoaded", _event => {
+ document.getElementById("loginFrame").addEventListener("load", _e => resolve());
});
});
@@ -97,9 +95,7 @@ async function testFormlesSubmitNavigationNegative(tc, scriptName) {
}
let loadedPromise = new Promise((resolve) => {
- loginFrame.addEventListener("load", function() {
- resolve();
- }, {once: true});
+ loginFrame.addEventListener("load", _e => resolve(), {once: true});
});
loginFrame.src = DEFAULT_ORIGIN + "/tests/toolkit/components/passwordmgr/test/mochitest/blank.html";
await loadedPromise;
diff --git a/toolkit/components/passwordmgr/test/mochitest/test_munged_values.html b/toolkit/components/passwordmgr/test/mochitest/test_munged_values.html
index d2fdf91d4a..8b0c243c4d 100644
--- a/toolkit/components/passwordmgr/test/mochitest/test_munged_values.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_munged_values.html
@@ -113,7 +113,7 @@ add_task(async function test_new_logins() {
<button type="submit" id="submitBtn">Submit</button>
</form>`;
await loadFormIntoWindow(DEFAULT_ORIGIN, html, win);
- await SpecialPowers.spawn(win, [html], function(contentHtml) {
+ await SpecialPowers.spawn(win, [], function() {
const doc = this.content.document;
for (const field of doc.querySelectorAll("input")) {
const actualValue = field.value;
@@ -189,7 +189,7 @@ add_task(async function test_no_save_dialog_when_password_is_fully_munged() {
<button type="submit" id="submitBtn">Submit</button>
</form>`;
await loadFormIntoWindow(DEFAULT_ORIGIN, html, win);
- await SpecialPowers.spawn(win, [html], function(contentHtml) {
+ await SpecialPowers.spawn(win, [], function() {
const doc = this.content.document;
for (const field of doc.querySelectorAll("input")) {
const actualValue = field.value;
@@ -248,7 +248,7 @@ add_task(async function test_no_autofill_munged_username_matching_password() {
<button type="submit" id="submitBtn">Submit</button>
</form>`;
await loadFormIntoWindow(DEFAULT_ORIGIN, html, win);
- await SpecialPowers.spawn(win, [html], function(contentHtml) {
+ await SpecialPowers.spawn(win, [], function() {
const doc = this.content.document;
for (const field of doc.querySelectorAll("input")) {
const actualValue = field.value;
@@ -312,7 +312,7 @@ add_task(async function test_autofill_munged_username_matching_password() {
<button type="submit" id="submitBtn">Submit</button>
</form>`;
await loadFormIntoWindow(DEFAULT_ORIGIN, html, win);
- await SpecialPowers.spawn(win, [html], function(contentHtml) {
+ await SpecialPowers.spawn(win, [], function() {
const doc = this.content.document;
for (const field of doc.querySelectorAll("input")) {
const actualValue = field.value;
diff --git a/toolkit/components/passwordmgr/test/mochitest/test_one_doorhanger_per_un_pw.html b/toolkit/components/passwordmgr/test/mochitest/test_one_doorhanger_per_un_pw.html
index 4d8dfd1fee..47e216ce4a 100644
--- a/toolkit/components/passwordmgr/test/mochitest/test_one_doorhanger_per_un_pw.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_one_doorhanger_per_un_pw.html
@@ -47,9 +47,8 @@
is(password.value, "pass", "Checking for filled password");
let promptShown = false;
- promptShownPromise = promisePromptShown("passwordmgr-prompt-save").then(value => {
- promptShown = true;
- });
+ promptShownPromise = promisePromptShown("passwordmgr-prompt-save")
+ .then(_value => promptShown = true);
submitButton.click();
await new Promise(resolve => setTimeout(resolve, 1000));
ok(!promptShown, "Prompt is not shown for the same login values a second time");
diff --git a/toolkit/components/passwordmgr/test/mochitest/test_password_length.html b/toolkit/components/passwordmgr/test/mochitest/test_password_length.html
index 724caf236f..10a79f2839 100644
--- a/toolkit/components/passwordmgr/test/mochitest/test_password_length.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_password_length.html
@@ -12,19 +12,15 @@
let readyPromise = registerRunTests();
let loadPromise = new Promise(resolve => {
- document.addEventListener("DOMContentLoaded", () => {
- document.getElementById("loginFrame").addEventListener("load", (evt) => {
- resolve();
- });
+ document.addEventListener("DOMContentLoaded", _event => {
+ document.getElementById("loginFrame").addEventListener("load", _e => resolve());
});
});
async function loadFormIntoIframe(origin, html) {
let loginFrame = document.getElementById("loginFrame");
let loadedPromise = new Promise((resolve) => {
- loginFrame.addEventListener("load", function() {
- resolve();
- }, {once: true});
+ loginFrame.addEventListener("load", _e => resolve(), {once: true});
});
let processedPromise = promiseFormsProcessed();
loginFrame.src = origin + "/tests/toolkit/components/passwordmgr/test/mochitest/blank.html";
@@ -80,7 +76,7 @@ const TESTCASES = [
* @return {Promise} resolving when form submission was processed.
*/
function getSubmitMessage() {
- return new Promise((resolve, reject) => {
+ return new Promise((resolve) => {
PWMGR_COMMON_PARENT.addMessageListener("formSubmissionProcessed", function processed(...args) {
info("got formSubmissionProcessed");
PWMGR_COMMON_PARENT.removeMessageListener("formSubmissionProcessed", processed);
diff --git a/toolkit/components/passwordmgr/test/mochitest/test_prompt_async.html b/toolkit/components/passwordmgr/test/mochitest/test_prompt_async.html
index 41a58cb416..1edbf8cf18 100644
--- a/toolkit/components/passwordmgr/test/mochitest/test_prompt_async.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_prompt_async.html
@@ -58,9 +58,7 @@
function promiseLoadedContentDoc(frame) {
return new Promise(resolve => {
- frame.addEventListener("load", function onLoad(evt) {
- resolve(SpecialPowers.wrap(frame).contentDocument);
- }, { once: true });
+ frame.addEventListener("load", _e => resolve(SpecialPowers.wrap(frame).contentDocument), { once: true });
});
}
@@ -98,7 +96,7 @@
return this;
},
- onProxyAvailable(req, uri, pi, status) {
+ onProxyAvailable(req, uri, pi, _status) {
// Add logins using the proxy host and port used by the mochitest harness.
mozproxyOrigin = "moz-proxy://" + SpecialPowers.wrap(pi).host + ":" +
SpecialPowers.wrap(pi).port;
diff --git a/toolkit/components/passwordmgr/test/mochitest/test_prompt_promptAuth_proxy.html b/toolkit/components/passwordmgr/test/mochitest/test_prompt_promptAuth_proxy.html
index 6f78d62f1b..75342cd4cb 100644
--- a/toolkit/components/passwordmgr/test/mochitest/test_prompt_promptAuth_proxy.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_prompt_promptAuth_proxy.html
@@ -86,10 +86,10 @@ let chromeScript = runInParent(() => {
}
class ProxyChannelListener {
- onStartRequest(request) {
+ onStartRequest(_request) {
sendAsyncMessage("initDone");
}
- onStopRequest(request, status) {}
+ onStopRequest(_request, _status) {}
}
async function initLogins(pi) {
@@ -126,7 +126,7 @@ let chromeScript = runInParent(() => {
return this;
},
- async onProxyAvailable(req, uri, pi, status) {
+ async onProxyAvailable(req, uri, pi, _status) {
await initLogins(pi);
// I'm cheating a bit here... We should probably do some magic foo to get
diff --git a/toolkit/components/passwordmgr/test/mochitest/test_submit_without_field_modifications.html b/toolkit/components/passwordmgr/test/mochitest/test_submit_without_field_modifications.html
index c31efe6a98..72740d3289 100644
--- a/toolkit/components/passwordmgr/test/mochitest/test_submit_without_field_modifications.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_submit_without_field_modifications.html
@@ -139,9 +139,7 @@ add_task(async function test_no_message_on_navigation() {
await setup(PREFILLED_FORM_URL);
let submitMessageSent = false;
- getSubmitMessage().then(value => {
- submitMessageSent = true;
- });
+ getSubmitMessage().then(_value => submitMessageSent = true);
await navigateWithoutUserInteraction();
// allow time to pass before concluding no onFormSubmit message was sent
@@ -197,9 +195,7 @@ add_task(async function test_no_message_on_autofill_without_user_interaction() {
);
ok(!(await checkDocumentUserHasInteracted()), "document.userHasInteracted should be initially false");
let submitMessageSent = false;
- getSubmitMessage().then(value => {
- submitMessageSent = true;
- });
+ getSubmitMessage().then(_value => submitMessageSent = true);
info("Navigating the page")
await navigateWithoutUserInteraction();
diff --git a/toolkit/components/passwordmgr/test/mochitest/test_xhr.html b/toolkit/components/passwordmgr/test/mochitest/test_xhr.html
index 811a2e7759..4461195359 100644
--- a/toolkit/components/passwordmgr/test/mochitest/test_xhr.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_xhr.html
@@ -21,7 +21,7 @@ Login Manager test: XHR prompt
/** Test for Login Manager: XHR prompts. **/
function makeRequest(uri) {
- return new Promise((resolve, reject) => {
+ return new Promise((resolve) => {
let request = new XMLHttpRequest();
request.open("GET", uri, true);
request.addEventListener("loadend", function onLoadEnd() {
diff --git a/toolkit/components/passwordmgr/test/unit/test_LoginManagerParent_onPasswordEditedOrGenerated.js b/toolkit/components/passwordmgr/test/unit/test_LoginManagerParent_onPasswordEditedOrGenerated.js
index bf665faaed..4001029c55 100644
--- a/toolkit/components/passwordmgr/test/unit/test_LoginManagerParent_onPasswordEditedOrGenerated.js
+++ b/toolkit/components/passwordmgr/test/unit/test_LoginManagerParent_onPasswordEditedOrGenerated.js
@@ -147,13 +147,10 @@ function checkEditTelemetryRecorded(expectedCount, msg) {
object: "generatedpassword",
});
const results = snapshot.parent.filter(
- ([time, category, method, object]) => {
- return (
- category === telemetryProps.category &&
- method === telemetryProps.method &&
- object === telemetryProps.object
- );
- }
+ ([_time, category, method, object]) =>
+ category === telemetryProps.category &&
+ method === telemetryProps.method &&
+ object === telemetryProps.object
);
resultsCount = results.length;
}
diff --git a/toolkit/components/passwordmgr/test/unit/test_logins_change.js b/toolkit/components/passwordmgr/test/unit/test_logins_change.js
index cc82d6cc0b..96ee38891f 100644
--- a/toolkit/components/passwordmgr/test/unit/test_logins_change.js
+++ b/toolkit/components/passwordmgr/test/unit/test_logins_change.js
@@ -157,7 +157,7 @@ add_task(async function event_data_includes_plaintext_username_and_password() {
"nsIObserver",
"nsISupportsWeakReference",
]),
- observe(subject, topic, data) {
+ observe(subject, _topic, _data) {
Assert.ok(subject instanceof Ci.nsILoginInfo);
Assert.ok(subject instanceof Ci.nsILoginMetaInfo);
Assert.equal(
diff --git a/toolkit/components/pdfjs/content/GeckoViewPdfjsChild.sys.mjs b/toolkit/components/pdfjs/content/GeckoViewPdfjsChild.sys.mjs
index e838c67874..749532053b 100644
--- a/toolkit/components/pdfjs/content/GeckoViewPdfjsChild.sys.mjs
+++ b/toolkit/components/pdfjs/content/GeckoViewPdfjsChild.sys.mjs
@@ -17,9 +17,7 @@ import { GeckoViewActorChild } from "resource://gre/modules/GeckoViewActorChild.
export class GeckoViewPdfjsChild extends GeckoViewActorChild {
init(aSupportsFind) {
- if (aSupportsFind) {
- this.sendAsyncMessage("PDFJS:Parent:addEventListener");
- }
+ this.sendAsyncMessage("PDFJS:Parent:addEventListener", { aSupportsFind });
}
dispatchEvent(aType, aDetail) {
diff --git a/toolkit/components/pdfjs/content/GeckoViewPdfjsParent.sys.mjs b/toolkit/components/pdfjs/content/GeckoViewPdfjsParent.sys.mjs
index b07ed8c7b1..e27fdac51d 100644
--- a/toolkit/components/pdfjs/content/GeckoViewPdfjsParent.sys.mjs
+++ b/toolkit/components/pdfjs/content/GeckoViewPdfjsParent.sys.mjs
@@ -258,7 +258,7 @@ export class GeckoViewPdfjsParent extends GeckoViewActorParent {
case "PDFJS:Parent:updateMatchesCount":
return this.#updateMatchesCount(aMsg);
case "PDFJS:Parent:addEventListener":
- return this.#addEventListener();
+ return this.#addEventListener(aMsg);
case "PDFJS:Parent:saveURL":
return this.#save(aMsg);
case "PDFJS:Parent:getNimbus":
@@ -299,7 +299,16 @@ export class GeckoViewPdfjsParent extends GeckoViewActorParent {
this.#fileSaver = null;
}
- #addEventListener() {
+ #addEventListener({ data: { aSupportsFind } }) {
+ this.#fileSaver = new FileSaver(this.browser, this.eventDispatcher);
+ this.eventDispatcher.registerListener(this.#fileSaver, [
+ "GeckoView:PDFSave",
+ ]);
+
+ if (!aSupportsFind) {
+ return;
+ }
+
if (this.#findHandler) {
this.#findHandler.cleanup();
return;
@@ -311,11 +320,6 @@ export class GeckoViewPdfjsParent extends GeckoViewActorParent {
"GeckoView:DisplayMatches",
"GeckoView:FindInPage",
]);
-
- this.#fileSaver = new FileSaver(this.browser, this.eventDispatcher);
- this.eventDispatcher.registerListener(this.#fileSaver, [
- "GeckoView:PDFSave",
- ]);
}
#updateMatchesCount({ data }) {
diff --git a/toolkit/components/pdfjs/content/PdfJsDefaultPreferences.sys.mjs b/toolkit/components/pdfjs/content/PdfJsDefaultPreferences.sys.mjs
index 02d1095919..ee30631bff 100644
--- a/toolkit/components/pdfjs/content/PdfJsDefaultPreferences.sys.mjs
+++ b/toolkit/components/pdfjs/content/PdfJsDefaultPreferences.sys.mjs
@@ -25,6 +25,7 @@ export const PdfJsDefaultPreferences = Object.freeze({
defaultZoomValue: "",
disablePageLabels: false,
enableHighlightEditor: false,
+ enableHighlightFloatingButton: false,
enableML: false,
enablePermissions: false,
enablePrintAutoRotate: true,
diff --git a/toolkit/components/pdfjs/content/PdfStreamConverter.sys.mjs b/toolkit/components/pdfjs/content/PdfStreamConverter.sys.mjs
index 050d730872..2a56cbb935 100644
--- a/toolkit/components/pdfjs/content/PdfStreamConverter.sys.mjs
+++ b/toolkit/components/pdfjs/content/PdfStreamConverter.sys.mjs
@@ -19,6 +19,9 @@ const PDF_VIEWER_WEB_PAGE = "resource://pdf.js/web/viewer.html";
const MAX_NUMBER_OF_PREFS = 50;
const PDF_CONTENT_TYPE = "application/pdf";
+// Preferences
+const caretBrowsingModePref = "accessibility.browsewithcaret";
+
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
@@ -178,6 +181,45 @@ PdfDataListener.prototype = {
},
};
+class PrefObserver {
+ #domWindow;
+
+ constructor(domWindow) {
+ this.#domWindow = domWindow;
+ this.#init();
+ }
+
+ #init() {
+ Services.prefs.addObserver(
+ caretBrowsingModePref,
+ this,
+ /* aHoldWeak = */ true
+ );
+ }
+
+ observe(_aSubject, aTopic, aPrefName) {
+ if (aTopic != "nsPref:changed") {
+ return;
+ }
+
+ const actor = getActor(this.#domWindow);
+ if (!actor) {
+ return;
+ }
+ const eventName = "updatedPreference";
+ switch (aPrefName) {
+ case caretBrowsingModePref:
+ actor.dispatchEvent(eventName, {
+ name: "supportsCaretBrowsingMode",
+ value: Services.prefs.getBoolPref(caretBrowsingModePref),
+ });
+ break;
+ }
+ }
+
+ QueryInterface = ChromeUtils.generateQI([Ci.nsISupportsWeakReference]);
+}
+
/**
* All the privileged actions.
*/
@@ -187,6 +229,7 @@ class ChromeActions {
this.contentDispositionFilename = contentDispositionFilename;
this.sandbox = null;
this.unloadListener = null;
+ this.observer = new PrefObserver(domWindow);
}
createSandbox(data, sendResponse) {
@@ -300,7 +343,7 @@ class ChromeActions {
Services.prefs.getIntPref("mousewheel.with_meta.action") === 3,
supportsPinchToZoom: Services.prefs.getBoolPref("apz.allow_zooming"),
supportsCaretBrowsingMode: Services.prefs.getBoolPref(
- "accessibility.browsewithcaret"
+ caretBrowsingModePref
),
};
}
@@ -330,9 +373,8 @@ class ChromeActions {
}
reportTelemetry(data) {
- const probeInfo = JSON.parse(data);
const actor = getActor(this.domWindow);
- actor?.sendAsyncMessage("PDFJS:Parent:reportTelemetry", probeInfo);
+ actor?.sendAsyncMessage("PDFJS:Parent:reportTelemetry", data);
}
updateFindControlState(data) {
@@ -434,6 +476,7 @@ class ChromeActions {
hasSomethingToUndo: false,
hasSomethingToRedo: false,
hasSelectedEditor: false,
+ hasSelectedText: false,
};
}
const { editorStates } = doc;
diff --git a/toolkit/components/pdfjs/content/PdfjsChild.sys.mjs b/toolkit/components/pdfjs/content/PdfjsChild.sys.mjs
index 0b5276ec12..b4162966dd 100644
--- a/toolkit/components/pdfjs/content/PdfjsChild.sys.mjs
+++ b/toolkit/components/pdfjs/content/PdfjsChild.sys.mjs
@@ -14,30 +14,27 @@
*/
export class PdfjsChild extends JSWindowActorChild {
- init(supportsFind) {
- if (supportsFind) {
+ init(aSupportsFind) {
+ if (aSupportsFind) {
this.sendAsyncMessage("PDFJS:Parent:addEventListener");
}
}
- dispatchEvent(type, detail) {
+ dispatchEvent(aType, aDetail) {
+ aDetail &&= Cu.cloneInto(aDetail, this.contentWindow);
const contentWindow = this.contentWindow;
const forward = contentWindow.document.createEvent("CustomEvent");
- forward.initCustomEvent(type, true, true, detail);
+ forward.initCustomEvent(aType, true, true, aDetail);
contentWindow.dispatchEvent(forward);
}
receiveMessage(msg) {
switch (msg.name) {
- case "PDFJS:Child:handleEvent": {
- let detail = Cu.cloneInto(msg.data.detail, this.contentWindow);
- this.dispatchEvent(msg.data.type, detail);
+ case "PDFJS:Child:handleEvent":
+ this.dispatchEvent(msg.data.type, msg.data.detail);
break;
- }
-
case "PDFJS:Editing":
- let data = Cu.cloneInto(msg.data, this.contentWindow);
- this.dispatchEvent("editingaction", data);
+ this.dispatchEvent("editingaction", msg.data);
break;
case "PDFJS:ZoomIn":
case "PDFJS:ZoomOut":
diff --git a/toolkit/components/pdfjs/content/build/pdf.mjs b/toolkit/components/pdfjs/content/build/pdf.mjs
index 72dcbae165..4e51efdb78 100644
--- a/toolkit/components/pdfjs/content/build/pdf.mjs
+++ b/toolkit/components/pdfjs/content/build/pdf.mjs
@@ -139,7 +139,8 @@ const AnnotationEditorParamsType = {
HIGHLIGHT_COLOR: 31,
HIGHLIGHT_DEFAULT_COLOR: 32,
HIGHLIGHT_THICKNESS: 33,
- HIGHLIGHT_FREE: 34
+ HIGHLIGHT_FREE: 34,
+ HIGHLIGHT_SHOW_ALL: 35
};
const PermissionFlag = {
PRINT: 0x04,
@@ -1591,9 +1592,173 @@ function setLayerDimensions(div, viewport, mustFlip = false, mustRotate = true)
}
}
+;// CONCATENATED MODULE: ./src/display/editor/toolbar.js
+
+class EditorToolbar {
+ #toolbar = null;
+ #colorPicker = null;
+ #editor;
+ #buttons = null;
+ constructor(editor) {
+ this.#editor = editor;
+ }
+ render() {
+ const editToolbar = this.#toolbar = document.createElement("div");
+ editToolbar.className = "editToolbar";
+ editToolbar.setAttribute("role", "toolbar");
+ editToolbar.addEventListener("contextmenu", noContextMenu);
+ editToolbar.addEventListener("pointerdown", EditorToolbar.#pointerDown);
+ 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) {
+ element.addEventListener("focusin", this.#focusIn.bind(this), {
+ capture: true
+ });
+ element.addEventListener("focusout", this.#focusOut.bind(this), {
+ capture: true
+ });
+ element.addEventListener("contextmenu", noContextMenu);
+ }
+ hide() {
+ this.#toolbar.classList.add("hidden");
+ this.#colorPicker?.hideDropdown();
+ }
+ show() {
+ this.#toolbar.classList.remove("hidden");
+ }
+ #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();
+ });
+ this.#buttons.append(button);
+ }
+ get #divider() {
+ const divider = document.createElement("div");
+ divider.className = "divider";
+ return divider;
+ }
+ addAltTextButton(button) {
+ this.#addListenersToElement(button);
+ this.#buttons.prepend(button, this.#divider);
+ }
+ 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);
+ 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-button`);
+ const span = document.createElement("span");
+ button.append(span);
+ span.className = "visuallyHidden";
+ span.setAttribute("data-l10n-id", "pdfjs-editor-highlight-button-label");
+ button.addEventListener("contextmenu", noContextMenu);
+ button.addEventListener("click", () => {
+ this.#uiManager.highlightSelection("floating_button");
+ });
+ 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));
@@ -1933,10 +2098,12 @@ class AnnotationEditorUIManager {
#draggingEditors = null;
#editorTypes = null;
#editorsToRescale = new Set();
+ #enableHighlightFloatingButton = false;
#filterFactory = null;
#focusMainContainerTimeoutId = null;
#highlightColors = null;
#highlightWhenShiftUp = false;
+ #highlightToolbar = null;
#idManager = new IdManager();
#isEnabled = false;
#isWaiting = false;
@@ -1947,6 +2114,7 @@ class AnnotationEditorUIManager {
#selectedEditors = new Set();
#selectedTextNode = null;
#pageColors = null;
+ #showAllStates = null;
#boundBlur = this.blur.bind(this);
#boundFocus = this.focus.bind(this);
#boundCopy = this.copy.bind(this);
@@ -2031,7 +2199,7 @@ class AnnotationEditorUIManager {
checker: arrowChecker
}]]));
}
- constructor(container, viewer, altTextManager, eventBus, pdfDocument, pageColors, highlightColors, mlManager) {
+ constructor(container, viewer, altTextManager, eventBus, pdfDocument, pageColors, highlightColors, enableHighlightFloatingButton, mlManager) {
this.#container = container;
this.#viewer = viewer;
this.#altTextManager = altTextManager;
@@ -2041,10 +2209,12 @@ class AnnotationEditorUIManager {
this._eventBus._on("scalechanging", this.#boundOnScaleChanging);
this._eventBus._on("rotationchanging", this.#boundOnRotationChanging);
this.#addSelectionListener();
+ this.#addKeyboardManager();
this.#annotationStorage = pdfDocument.annotationStorage;
this.#filterFactory = pdfDocument.filterFactory;
this.#pageColors = pageColors;
this.#highlightColors = highlightColors || null;
+ this.#enableHighlightFloatingButton = enableHighlightFloatingButton;
this.#mlManager = mlManager || null;
this.viewParameters = {
realScale: PixelsPerInch.PDF_TO_CSS_UNITS,
@@ -2069,6 +2239,8 @@ class AnnotationEditorUIManager {
this.#selectedEditors.clear();
this.#commandManager.destroy();
this.#altTextManager?.destroy();
+ this.#highlightToolbar?.hide();
+ this.#highlightToolbar = null;
if (this.#focusMainContainerTimeoutId) {
clearTimeout(this.#focusMainContainerTimeoutId);
this.#focusMainContainerTimeoutId = null;
@@ -2149,6 +2321,11 @@ class AnnotationEditorUIManager {
this.commitOrRemove();
this.viewParameters.rotation = pagesRotation;
}
+ #getAnchorElementForSelection({
+ anchorNode
+ }) {
+ return anchorNode.nodeType === Node.TEXT_NODE ? anchorNode.parentElement : anchorNode;
+ }
highlightSelection(methodOfCreation = "") {
const selection = document.getSelection();
if (!selection || selection.isCollapsed) {
@@ -2160,15 +2337,20 @@ class AnnotationEditorUIManager {
focusNode,
focusOffset
} = selection;
- const anchorElement = anchorNode.nodeType === Node.TEXT_NODE ? anchorNode.parentElement : anchorNode;
+ const text = selection.toString();
+ const anchorElement = this.#getAnchorElementForSelection(selection);
const textLayer = anchorElement.closest(".textLayer");
const boxes = this.getSelectionBoxes(textLayer);
+ if (!boxes) {
+ return;
+ }
selection.empty();
if (this.#mode === AnnotationEditorType.NONE) {
this._eventBus.dispatch("showannotationeditorui", {
source: this,
mode: AnnotationEditorType.HIGHLIGHT
});
+ this.showAllEditors("highlight", true, true);
}
for (const layer of this.#allLayers.values()) {
if (layer.hasTextLayer(textLayer)) {
@@ -2181,12 +2363,27 @@ class AnnotationEditorUIManager {
anchorNode,
anchorOffset,
focusNode,
- focusOffset
+ focusOffset,
+ text
});
break;
}
}
}
+ #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);
@@ -2196,6 +2393,7 @@ class AnnotationEditorUIManager {
const selection = document.getSelection();
if (!selection || selection.isCollapsed) {
if (this.#selectedTextNode) {
+ this.#highlightToolbar?.hide();
this.#selectedTextNode = null;
this.#dispatchUpdateStates({
hasSelectedText: false
@@ -2209,9 +2407,11 @@ class AnnotationEditorUIManager {
if (anchorNode === this.#selectedTextNode) {
return;
}
- const anchorElement = anchorNode.nodeType === Node.TEXT_NODE ? anchorNode.parentElement : anchorNode;
- if (!anchorElement.closest(".textLayer")) {
+ 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
@@ -2219,13 +2419,17 @@ class AnnotationEditorUIManager {
}
return;
}
+ this.#highlightToolbar?.hide();
this.#selectedTextNode = anchorNode;
this.#dispatchUpdateStates({
hasSelectedText: true
});
- if (this.#mode !== AnnotationEditorType.HIGHLIGHT) {
+ 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 pointerup = e => {
@@ -2235,13 +2439,20 @@ class AnnotationEditorUIManager {
window.removeEventListener("pointerup", pointerup);
window.removeEventListener("blur", pointerup);
if (e.type === "pointerup") {
- this.highlightSelection("main_toolbar");
+ this.#onSelectEnd("main_toolbar");
}
};
window.addEventListener("pointerup", pointerup);
window.addEventListener("blur", pointerup);
}
}
+ #onSelectEnd(methodOfCreation = "") {
+ if (this.#mode === AnnotationEditorType.HIGHLIGHT) {
+ this.highlightSelection(methodOfCreation);
+ } else if (this.#enableHighlightFloatingButton) {
+ this.#displayHighlightToolbar();
+ }
+ }
#addSelectionListener() {
document.addEventListener("selectionchange", this.#boundSelectionChange);
}
@@ -2260,7 +2471,7 @@ class AnnotationEditorUIManager {
this.isShiftKeyDown = false;
if (this.#highlightWhenShiftUp) {
this.#highlightWhenShiftUp = false;
- this.highlightSelection("main_toolbar");
+ this.#onSelectEnd("main_toolbar");
}
if (!this.hasSelection) {
return;
@@ -2398,7 +2609,7 @@ class AnnotationEditorUIManager {
if (!this.isShiftKeyDown && event.key === "Shift") {
this.isShiftKeyDown = true;
}
- if (!this.isEditorHandlingKeyboard) {
+ if (this.#mode !== AnnotationEditorType.NONE && !this.isEditorHandlingKeyboard) {
AnnotationEditorUIManager._keyboardManager.exec(this, event);
}
}
@@ -2407,7 +2618,7 @@ class AnnotationEditorUIManager {
this.isShiftKeyDown = false;
if (this.#highlightWhenShiftUp) {
this.#highlightWhenShiftUp = false;
- this.highlightSelection("main_toolbar");
+ this.#onSelectEnd("main_toolbar");
}
}
}
@@ -2447,7 +2658,6 @@ class AnnotationEditorUIManager {
setEditingState(isEditing) {
if (isEditing) {
this.#addFocusManager();
- this.#addKeyboardManager();
this.#addCopyPasteListeners();
this.#dispatchUpdateStates({
isEditing: this.#mode !== AnnotationEditorType.NONE,
@@ -2458,7 +2668,6 @@ class AnnotationEditorUIManager {
});
} else {
this.#removeFocusManager();
- this.#removeKeyboardManager();
this.#removeCopyPasteListeners();
this.#dispatchUpdateStates({
isEditing: false
@@ -2554,6 +2763,20 @@ class AnnotationEditorUIManager {
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);
@@ -2562,6 +2785,17 @@ class AnnotationEditorUIManager {
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;
@@ -2582,6 +2816,9 @@ class AnnotationEditorUIManager {
for (const layer of this.#allLayers.values()) {
layer.enable();
}
+ for (const editor of this.#allEditors.values()) {
+ editor.enable();
+ }
}
}
#disableAll() {
@@ -2591,6 +2828,9 @@ class AnnotationEditorUIManager {
for (const layer of this.#allLayers.values()) {
layer.disable();
}
+ for (const editor of this.#allEditors.values()) {
+ editor.disable();
+ }
}
}
getEditors(pageIndex) {
@@ -2641,6 +2881,7 @@ class AnnotationEditorUIManager {
layer.addOrRebuild(editor);
} else {
this.addEditor(editor);
+ this.addToAnnotationStorage(editor);
}
}
setActiveEditor(editor) {
@@ -3172,98 +3413,6 @@ class AltText {
}
}
-;// CONCATENATED MODULE: ./src/display/editor/toolbar.js
-
-class EditorToolbar {
- #toolbar = null;
- #colorPicker = null;
- #editor;
- #buttons = null;
- constructor(editor) {
- this.#editor = editor;
- }
- render() {
- const editToolbar = this.#toolbar = document.createElement("div");
- editToolbar.className = "editToolbar";
- editToolbar.addEventListener("contextmenu", noContextMenu);
- editToolbar.addEventListener("pointerdown", EditorToolbar.#pointerDown);
- 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) {
- element.addEventListener("focusin", this.#focusIn.bind(this), {
- capture: true
- });
- element.addEventListener("focusout", this.#focusOut.bind(this), {
- capture: true
- });
- element.addEventListener("contextmenu", noContextMenu);
- }
- hide() {
- this.#toolbar.classList.add("hidden");
- this.#colorPicker?.hideDropdown();
- }
- show() {
- this.#toolbar.classList.remove("hidden");
- }
- #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();
- });
- this.#buttons.append(button);
- }
- get #divider() {
- const divider = document.createElement("div");
- divider.className = "divider";
- return divider;
- }
- addAltTextButton(button) {
- this.#addListenersToElement(button);
- this.#buttons.prepend(button, this.#divider);
- }
- 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;
- }
-}
-
;// CONCATENATED MODULE: ./src/display/editor/editor.js
@@ -3273,6 +3422,7 @@ class EditorToolbar {
class AnnotationEditor {
#allResizerDivs = null;
#altText = null;
+ #disabled = false;
#keepAspectRatio = false;
#resizersDiv = null;
#savedDimensions = null;
@@ -3289,6 +3439,7 @@ class AnnotationEditor {
#prevDragY = 0;
#telemetryTimeouts = null;
_initialOptions = Object.create(null);
+ _isVisible = true;
_uiManager = null;
_focusEventsAllowed = true;
_l10nPromise = null;
@@ -3555,6 +3706,9 @@ class AnnotationEditor {
return [-x, -y];
}
}
+ get _mustFixPosition() {
+ return true;
+ }
fixAndSetPosition(rotation = this.rotation) {
const [pageWidth, pageHeight] = this.pageDimensions;
let {
@@ -3567,23 +3721,25 @@ class AnnotationEditor {
height *= pageHeight;
x *= pageWidth;
y *= pageHeight;
- 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;
+ 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;
@@ -3902,7 +4058,10 @@ class AnnotationEditor {
this.div.setAttribute("data-editor-rotation", (360 - this.rotation) % 360);
this.div.className = this.name;
this.div.setAttribute("id", this.id);
- this.div.setAttribute("tabIndex", 0);
+ this.div.tabIndex = this.#disabled ? -1 : 0;
+ if (!this._isVisible) {
+ this.div.classList.add("hidden");
+ }
this.setInForeground();
this.div.addEventListener("focusin", this.#boundFocusin);
this.div.addEventListener("focusout", this.#boundFocusout);
@@ -4100,6 +4259,7 @@ class AnnotationEditor {
}
this.#telemetryTimeouts = null;
}
+ this.parent = null;
}
get isResizable() {
return false;
@@ -4319,6 +4479,22 @@ class AnnotationEditor {
}
});
}
+ 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;
+ }
}
class FakeEditor extends AnnotationEditor {
constructor(params) {
@@ -8373,18 +8549,44 @@ class Metadata {
const INTERNAL = Symbol("INTERNAL");
class OptionalContentGroup {
+ #isDisplay = false;
+ #isPrint = false;
+ #userSet = false;
#visible = true;
- constructor(name, intent) {
+ 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() {
- return this.#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) {
+ _setVisible(internal, visible, userSet = false) {
if (internal !== INTERNAL) {
unreachable("Internal method `_setVisible` called.");
}
+ this.#userSet = userSet;
this.#visible = visible;
}
}
@@ -8393,7 +8595,8 @@ class OptionalContentConfig {
#groups = new Map();
#initialHash = null;
#order = null;
- constructor(data) {
+ constructor(data, renderingIntent = RenderingIntentFlag.DISPLAY) {
+ this.renderingIntent = renderingIntent;
this.name = null;
this.creator = null;
if (data === null) {
@@ -8403,7 +8606,7 @@ class OptionalContentConfig {
this.creator = data.creator;
this.#order = data.order;
for (const group of data.groups) {
- this.#groups.set(group.id, new OptionalContentGroup(group.name, group.intent));
+ this.#groups.set(group.id, new OptionalContentGroup(renderingIntent, group));
}
if (data.baseState === "OFF") {
for (const group of this.#groups.values()) {
@@ -8524,11 +8727,43 @@ class OptionalContentConfig {
return true;
}
setVisibility(id, visible = true) {
- if (!this.#groups.has(id)) {
+ const group = this.#groups.get(id);
+ if (!group) {
warn(`Optional content group not found: ${id}`);
return;
}
- this.#groups.get(id)._setVisible(INTERNAL, !!visible);
+ 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() {
@@ -8970,7 +9205,7 @@ function getDocument(src) {
}
const fetchDocParams = {
docId,
- apiVersion: "4.1.249",
+ apiVersion: "4.1.342",
data,
password,
disableAutoFetch,
@@ -9207,8 +9442,13 @@ class PDFDocumentProxy {
getOutline() {
return this._transport.getOutline();
}
- getOptionalContentConfig() {
- return this._transport.getOptionalContentConfig();
+ getOptionalContentConfig({
+ intent = "display"
+ } = {}) {
+ const {
+ renderingIntent
+ } = this._transport.getRenderingIntent(intent);
+ return this._transport.getOptionalContentConfig(renderingIntent);
}
getPermissions() {
return this._transport.getPermissions();
@@ -9299,8 +9539,10 @@ class PDFPageProxy {
getAnnotations({
intent = "display"
} = {}) {
- const intentArgs = this._transport.getRenderingIntent(intent);
- return this._transport.getAnnotations(this._pageIndex, intentArgs.renderingIntent);
+ const {
+ renderingIntent
+ } = this._transport.getRenderingIntent(intent);
+ return this._transport.getAnnotations(this._pageIndex, renderingIntent);
}
getJSActions() {
return this._transport.getPageJSActions(this._pageIndex);
@@ -9328,21 +9570,23 @@ class PDFPageProxy {
}) {
this._stats?.time("Overall");
const intentArgs = this._transport.getRenderingIntent(intent, annotationMode, printAnnotationStorage);
+ const {
+ renderingIntent,
+ cacheKey
+ } = intentArgs;
this.#pendingCleanup = false;
this.#abortDelayedCleanup();
- if (!optionalContentConfigPromise) {
- optionalContentConfigPromise = this._transport.getOptionalContentConfig();
- }
- let intentState = this._intentStates.get(intentArgs.cacheKey);
+ optionalContentConfigPromise ||= this._transport.getOptionalContentConfig(renderingIntent);
+ let intentState = this._intentStates.get(cacheKey);
if (!intentState) {
intentState = Object.create(null);
- this._intentStates.set(intentArgs.cacheKey, intentState);
+ this._intentStates.set(cacheKey, intentState);
}
if (intentState.streamReaderCancelTimeout) {
clearTimeout(intentState.streamReaderCancelTimeout);
intentState.streamReaderCancelTimeout = null;
}
- const intentPrint = !!(intentArgs.renderingIntent & RenderingIntentFlag.PRINT);
+ const intentPrint = !!(renderingIntent & RenderingIntentFlag.PRINT);
if (!intentState.displayReadyCapability) {
intentState.displayReadyCapability = new PromiseCapability();
intentState.operatorList = {
@@ -9399,6 +9643,9 @@ class PDFPageProxy {
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
@@ -10338,8 +10585,8 @@ class WorkerTransport {
getOutline() {
return this.messageHandler.sendWithPromise("GetOutline", null);
}
- getOptionalContentConfig() {
- return this.messageHandler.sendWithPromise("GetOptionalContentConfig", null).then(results => new OptionalContentConfig(results));
+ getOptionalContentConfig(renderingIntent) {
+ return this.#cacheSimpleMethod("GetOptionalContentConfig").then(data => new OptionalContentConfig(data, renderingIntent));
}
getPermissions() {
return this.messageHandler.sendWithPromise("GetPermissions", null);
@@ -10602,8 +10849,8 @@ class InternalRenderTask {
}
}
}
-const version = "4.1.249";
-const build = "d07f37f44";
+const version = "4.1.342";
+const build = "e384df6f1";
;// CONCATENATED MODULE: ./src/shared/scripting_utils.js
function makeColorComp(n) {
@@ -11002,9 +11249,12 @@ class AnnotationElement {
container.tabIndex = DEFAULT_TAB_INDEX;
}
container.style.zIndex = this.parent.zIndex++;
- if (this.data.popupRef) {
+ if (data.popupRef) {
container.setAttribute("aria-haspopup", "dialog");
}
+ if (data.alternativeText) {
+ container.title = data.alternativeText;
+ }
if (data.noRotate) {
container.classList.add("norotate");
}
@@ -11652,9 +11902,6 @@ class TextAnnotationElement extends AnnotationElement {
}
class WidgetAnnotationElement extends AnnotationElement {
render() {
- if (this.data.alternativeText) {
- this.container.title = this.data.alternativeText;
- }
return this.container;
}
showElementAndHideCanvas(element) {
@@ -12255,9 +12502,6 @@ class PushButtonWidgetAnnotationElement extends LinkAnnotationElement {
render() {
const container = super.render();
container.classList.add("buttonWidgetAnnotation", "pushButton");
- if (this.data.alternativeText) {
- container.title = this.data.alternativeText;
- }
const linkElement = container.lastChild;
if (this.enableScripting && this.hasJSActions && linkElement) {
this._setDefaultPropertiesFromJS(linkElement);
@@ -13271,11 +13515,13 @@ class AnnotationLayer {
+const EOL_PATTERN = /\r\n?|\n/g;
class FreeTextEditor extends AnnotationEditor {
#boundEditorDivBlur = this.editorDivBlur.bind(this);
#boundEditorDivFocus = this.editorDivFocus.bind(this);
#boundEditorDivInput = this.editorDivInput.bind(this);
#boundEditorDivKeydown = this.editorDivKeydown.bind(this);
+ #boundEditorDivPaste = this.editorDivPaste.bind(this);
#color;
#content = "";
#editorDivId = `${this.id}-editor`;
@@ -13428,6 +13674,7 @@ class FreeTextEditor extends AnnotationEditor {
this.editorDiv.addEventListener("focus", this.#boundEditorDivFocus);
this.editorDiv.addEventListener("blur", this.#boundEditorDivBlur);
this.editorDiv.addEventListener("input", this.#boundEditorDivInput);
+ this.editorDiv.addEventListener("paste", this.#boundEditorDivPaste);
}
disableEditMode() {
if (!this.isInEditMode()) {
@@ -13443,6 +13690,7 @@ class FreeTextEditor extends AnnotationEditor {
this.editorDiv.removeEventListener("focus", this.#boundEditorDivFocus);
this.editorDiv.removeEventListener("blur", this.#boundEditorDivBlur);
this.editorDiv.removeEventListener("input", this.#boundEditorDivInput);
+ this.editorDiv.removeEventListener("paste", this.#boundEditorDivPaste);
this.div.focus({
preventScroll: true
});
@@ -13484,10 +13732,8 @@ class FreeTextEditor extends AnnotationEditor {
#extractText() {
const buffer = [];
this.editorDiv.normalize();
- const EOL_PATTERN = /\r\n?|\n/g;
for (const child of this.editorDiv.childNodes) {
- const content = child.nodeType === Node.TEXT_NODE ? child.nodeValue : child.innerText;
- buffer.push(content.replaceAll(EOL_PATTERN, ""));
+ buffer.push(FreeTextEditor.#getNodeContent(child));
}
return buffer.join("\n");
}
@@ -13657,6 +13903,85 @@ class FreeTextEditor extends AnnotationEditor {
}
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) {
@@ -14392,6 +14717,7 @@ class ColorPicker {
#dropdown = null;
#dropdownWasFromKeyboard = false;
#isMainColorPicker = false;
+ #editor = null;
#eventBus;
#uiManager = null;
#type;
@@ -14405,6 +14731,7 @@ class ColorPicker {
if (editor) {
this.#isMainColorPicker = false;
this.#type = AnnotationEditorParamsType.HIGHLIGHT_COLOR;
+ this.#editor = editor;
} else {
this.#isMainColorPicker = true;
this.#type = AnnotationEditorParamsType.HIGHLIGHT_DEFAULT_COLOR;
@@ -14423,6 +14750,7 @@ class ColorPicker {
button.addEventListener("keydown", this.#boundKeyDown);
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;
@@ -14546,7 +14874,11 @@ class ColorPicker {
return this.#dropdown && !this.#dropdown.classList.contains("hidden");
}
_hideDropdownFromKeyboard() {
- if (this.#isMainColorPicker || !this.#isDropdownVisible) {
+ if (this.#isMainColorPicker) {
+ return;
+ }
+ if (!this.#isDropdownVisible) {
+ this.#editor?.unselect();
return;
}
this.hideDropdown();
@@ -14600,6 +14932,7 @@ class HighlightEditor extends AnnotationEditor {
#lastPoint = null;
#opacity;
#outlineId = null;
+ #text = "";
#thickness;
#methodOfCreation = "";
static _defaultColor = null;
@@ -14633,6 +14966,7 @@ class HighlightEditor extends AnnotationEditor {
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;
@@ -14857,11 +15191,11 @@ class HighlightEditor extends AnnotationEditor {
this.div.focus();
}
remove() {
- super.remove();
this.#cleanDrawLayer();
this._reportTelemetry({
action: "deleted"
});
+ super.remove();
}
rebuild() {
if (!this.parent) {
@@ -14885,6 +15219,7 @@ class HighlightEditor extends AnnotationEditor {
mustBeSelected = !this.parent && this.div?.classList.contains("selectedEditor");
}
super.setParent(parent);
+ this.show(this._isVisible);
if (mustBeSelected) {
this.select();
}
@@ -14979,6 +15314,12 @@ class HighlightEditor extends AnnotationEditor {
return this.div;
}
const div = super.render();
+ if (this.#text) {
+ const mark = document.createElement("mark");
+ div.append(mark);
+ mark.append(document.createTextNode(this.#text));
+ mark.className = "visuallyHidden";
+ }
if (this.#isFreeHighlight) {
div.classList.add("free");
} else {
@@ -14986,6 +15327,7 @@ class HighlightEditor extends AnnotationEditor {
}
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;
@@ -15029,16 +15371,32 @@ class HighlightEditor extends AnnotationEditor {
}
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;
}
@@ -15487,7 +15845,7 @@ class InkEditor extends AnnotationEditor {
this.allRawPaths.push(currentPath);
this.paths.push(bezier);
this.bezierPath2D.push(path2D);
- this.rebuild();
+ this._uiManager.rebuild(this);
};
const undo = () => {
this.allRawPaths.pop();
@@ -16114,7 +16472,7 @@ class StampEditor extends AnnotationEditor {
if (this.div === null) {
return;
}
- if (this.#bitmapId) {
+ if (this.#bitmapId && this.#canvas === null) {
this.#getBitmap();
}
if (!this.isAttachedToDOM) {
@@ -16126,7 +16484,7 @@ class StampEditor extends AnnotationEditor {
this.div.focus();
}
isEmpty() {
- return !(this.#bitmapPromise || this.#bitmap || this.#bitmapUrl || this.#bitmapFile);
+ return !(this.#bitmapPromise || this.#bitmap || this.#bitmapUrl || this.#bitmapFile || this.#bitmapId);
}
get isResizable() {
return true;
@@ -16142,6 +16500,7 @@ class StampEditor extends AnnotationEditor {
}
super.render();
this.div.hidden = true;
+ this.addAltTextButton();
if (this.#bitmap) {
this.#createCanvas();
} else {
@@ -16186,7 +16545,6 @@ class StampEditor extends AnnotationEditor {
this._reportTelemetry({
action: "inserted_image"
});
- this.addAltTextButton();
if (this.#bitmapFileName) {
canvas.setAttribute("aria-label", this.#bitmapFileName);
}
@@ -16404,9 +16762,9 @@ class AnnotationEditorLayer {
#accessibilityManager;
#allowClick = false;
#annotationLayer = null;
- #boundPointerup = this.pointerup.bind(this);
- #boundPointerdown = this.pointerdown.bind(this);
- #boundTextLayerPointerDown = this.#textLayerPointerDown.bind(this);
+ #boundPointerup = null;
+ #boundPointerdown = null;
+ #boundTextLayerPointerDown = null;
#editorFocusTimeoutId = null;
#editors = new Map();
#hadPointerDown = false;
@@ -16448,6 +16806,9 @@ class AnnotationEditorLayer {
get isEmpty() {
return this.#editors.size === 0;
}
+ get isInvisible() {
+ return this.isEmpty && this.#uiManager.getMode() === AnnotationEditorType.NONE;
+ }
updateToolbar(mode) {
this.#uiManager.updateToolbar(mode);
}
@@ -16519,6 +16880,7 @@ class AnnotationEditorLayer {
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()) {
@@ -16549,6 +16911,7 @@ class AnnotationEditorLayer {
}
disable() {
this.#isDisabling = true;
+ this.div.tabIndex = -1;
this.togglePointerEvents(false);
const hiddenAnnotationIds = new Set();
for (const editor of this.#editors.values()) {
@@ -16597,14 +16960,18 @@ class AnnotationEditorLayer {
this.#uiManager.setActiveEditor(editor);
}
enableTextSelection() {
- if (this.#textLayer?.div) {
+ this.div.tabIndex = -1;
+ if (this.#textLayer?.div && !this.#boundTextLayerPointerDown) {
+ this.#boundTextLayerPointerDown = this.#textLayerPointerDown.bind(this);
this.#textLayer.div.addEventListener("pointerdown", this.#boundTextLayerPointerDown);
this.#textLayer.div.classList.add("highlighting");
}
}
disableTextSelection() {
- if (this.#textLayer?.div) {
+ this.div.tabIndex = 0;
+ if (this.#textLayer?.div && this.#boundTextLayerPointerDown) {
this.#textLayer.div.removeEventListener("pointerdown", this.#boundTextLayerPointerDown);
+ this.#boundTextLayerPointerDown = null;
this.#textLayer.div.classList.remove("highlighting");
}
}
@@ -16617,6 +16984,7 @@ class AnnotationEditorLayer {
if (event.button !== 0 || event.ctrlKey && isMac) {
return;
}
+ this.#uiManager.showAllEditors("highlight", true, true);
this.#textLayer.div.classList.add("free");
HighlightEditor.startHighlighting(this, this.#uiManager.direction === "ltr", event);
this.#textLayer.div.addEventListener("pointerup", () => {
@@ -16628,12 +16996,22 @@ class AnnotationEditorLayer {
}
}
enableClick() {
+ if (this.#boundPointerdown) {
+ return;
+ }
+ this.#boundPointerdown = this.pointerdown.bind(this);
+ this.#boundPointerup = this.pointerup.bind(this);
this.div.addEventListener("pointerdown", this.#boundPointerdown);
this.div.addEventListener("pointerup", this.#boundPointerup);
}
disableClick() {
+ if (!this.#boundPointerdown) {
+ return;
+ }
this.div.removeEventListener("pointerdown", this.#boundPointerdown);
this.div.removeEventListener("pointerup", this.#boundPointerup);
+ this.#boundPointerdown = null;
+ this.#boundPointerup = null;
}
attach(editor) {
this.#editors.set(editor.id, editor);
@@ -16678,6 +17056,9 @@ class AnnotationEditorLayer {
}
}
add(editor) {
+ if (editor.parent === this && editor.isAttachedToDOM) {
+ return;
+ }
this.changeParent(editor);
this.#uiManager.addEditor(editor);
this.attach(editor);
@@ -16720,6 +17101,7 @@ class AnnotationEditorLayer {
if (editor.needsToBeRebuilt()) {
editor.parent ||= this;
editor.rebuild();
+ editor.show();
} else {
this.add(editor);
}
@@ -16910,6 +17292,7 @@ class AnnotationEditorLayer {
setLayerDimensions(this.div, viewport);
for (const editor of this.#uiManager.getEditors(this.pageIndex)) {
this.add(editor);
+ editor.rebuild();
}
this.updateMode();
}
@@ -16917,6 +17300,7 @@ class AnnotationEditorLayer {
viewport
}) {
this.#uiManager.commitOrRemove();
+ this.#cleanup();
const oldRotation = this.viewport.rotation;
const rotation = viewport.rotation;
this.viewport = viewport;
@@ -16928,7 +17312,7 @@ class AnnotationEditorLayer {
editor.rotate(rotation);
}
}
- this.updateMode();
+ this.addInkEditorIfNeeded(false);
}
get pageDimensions() {
const {
@@ -16990,6 +17374,7 @@ class DrawLayer {
#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;
}
@@ -17102,6 +17487,9 @@ class DrawLayer {
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);
}
@@ -17146,8 +17534,8 @@ class DrawLayer {
-const pdfjsVersion = "4.1.249";
-const pdfjsBuild = "d07f37f44";
+const pdfjsVersion = "4.1.342";
+const pdfjsBuild = "e384df6f1";
var __webpack_exports__AbortException = __webpack_exports__.AbortException;
var __webpack_exports__AnnotationEditorLayer = __webpack_exports__.AnnotationEditorLayer;
diff --git a/toolkit/components/pdfjs/content/build/pdf.scripting.mjs b/toolkit/components/pdfjs/content/build/pdf.scripting.mjs
index d667676f1a..f590e83af3 100644
--- a/toolkit/components/pdfjs/content/build/pdf.scripting.mjs
+++ b/toolkit/components/pdfjs/content/build/pdf.scripting.mjs
@@ -3957,8 +3957,8 @@ function initSandbox(params) {
;// CONCATENATED MODULE: ./src/pdf.scripting.js
-const pdfjsVersion = "4.1.249";
-const pdfjsBuild = "d07f37f44";
+const pdfjsVersion = "4.1.342";
+const pdfjsBuild = "e384df6f1";
globalThis.pdfjsScripting = {
initSandbox: initSandbox
};
diff --git a/toolkit/components/pdfjs/content/build/pdf.worker.mjs b/toolkit/components/pdfjs/content/build/pdf.worker.mjs
index 8929572511..e2cd54cf56 100644
--- a/toolkit/components/pdfjs/content/build/pdf.worker.mjs
+++ b/toolkit/components/pdfjs/content/build/pdf.worker.mjs
@@ -94,7 +94,8 @@ const AnnotationEditorParamsType = {
HIGHLIGHT_COLOR: 31,
HIGHLIGHT_DEFAULT_COLOR: 32,
HIGHLIGHT_THICKNESS: 33,
- HIGHLIGHT_FREE: 34
+ HIGHLIGHT_FREE: 34,
+ HIGHLIGHT_SHOW_ALL: 35
};
const PermissionFlag = {
PRINT: 0x04,
@@ -31442,17 +31443,25 @@ class PartialEvaluator {
}
const SMALL_IMAGE_DIMENSIONS = 200;
if (isInline && !dict.has("SMask") && !dict.has("Mask") && w + h < SMALL_IMAGE_DIMENSIONS) {
- 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);
+ 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()}`,
@@ -38663,14 +38672,9 @@ class Catalog {
continue;
}
groupRefs.put(groupRef);
- const group = this.xref.fetch(groupRef);
- groups.push({
- id: groupRef.toString(),
- name: typeof group.get("Name") === "string" ? stringToPDFString(group.get("Name")) : null,
- intent: typeof group.get("Intent") === "string" ? stringToPDFString(group.get("Intent")) : null
- });
+ groups.push(this.#readOptionalContentGroup(groupRef));
}
- config = this._readOptionalContentConfig(defaultConfig, groupRefs);
+ config = this.#readOptionalContentConfig(defaultConfig, groupRefs);
config.groups = groups;
} catch (ex) {
if (ex instanceof MissingDataException) {
@@ -38680,7 +38684,62 @@ class Catalog {
}
return shadow(this, "optionalContentConfig", config);
}
- _readOptionalContentConfig(config, contentGroupRefs) {
+ #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)) {
@@ -55084,17 +55143,24 @@ class Page {
}));
}
const sortedAnnotations = [];
- let popupAnnotations;
+ 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);
}
@@ -56659,7 +56725,7 @@ class WorkerMessageHandler {
docId,
apiVersion
} = docParams;
- const workerVersion = "4.1.249";
+ const workerVersion = "4.1.342";
if (apiVersion !== workerVersion) {
throw new Error(`The API version "${apiVersion}" does not match ` + `the Worker version "${workerVersion}".`);
}
@@ -57221,8 +57287,8 @@ if (typeof window === "undefined" && !isNodeJS && typeof self !== "undefined" &&
;// CONCATENATED MODULE: ./src/pdf.worker.js
-const pdfjsVersion = "4.1.249";
-const pdfjsBuild = "d07f37f44";
+const pdfjsVersion = "4.1.342";
+const pdfjsBuild = "e384df6f1";
var __webpack_exports__WorkerMessageHandler = __webpack_exports__.WorkerMessageHandler;
export { __webpack_exports__WorkerMessageHandler as WorkerMessageHandler };
diff --git a/toolkit/components/pdfjs/content/web/images/gv-toolbarButton-openinapp.svg b/toolkit/components/pdfjs/content/web/images/gv-toolbarButton-openinapp.svg
deleted file mode 100644
index 80ec891aad..0000000000
--- a/toolkit/components/pdfjs/content/web/images/gv-toolbarButton-openinapp.svg
+++ /dev/null
@@ -1,11 +0,0 @@
-<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M4 4.5H6.5V7H4V4.5Z" fill="black"/>
-<path d="M6.5 10.5H4V13H6.5V10.5Z" fill="black"/>
-<path d="M13.25 10.5H10.75V13H13.25V10.5Z" fill="black"/>
-<path d="M17.5 10.5H20V13H17.5V10.5Z" fill="black"/>
-<path d="M6.5 16.5H4V19H6.5V16.5Z" fill="black"/>
-<path d="M10.75 16.5H13.25V19H10.75V16.5Z" fill="black"/>
-<path d="M20 16.5H17.5V19H20V16.5Z" fill="black"/>
-<path d="M13.25 4.5H10.75V7H13.25V4.5Z" fill="black"/>
-<path d="M17.5 4.5H20V7H17.5V4.5Z" fill="black"/>
-</svg>
diff --git a/toolkit/components/pdfjs/content/web/viewer-geckoview.css b/toolkit/components/pdfjs/content/web/viewer-geckoview.css
index 16e27e4eac..c336b4f7fc 100644
--- a/toolkit/components/pdfjs/content/web/viewer-geckoview.css
+++ b/toolkit/components/pdfjs/content/web/viewer-geckoview.css
@@ -23,7 +23,6 @@
text-size-adjust:none;
forced-color-adjust:none;
transform-origin:0 0;
- z-index:2;
caret-color:CanvasText;
&.highlighting{
@@ -162,7 +161,6 @@
left:0;
pointer-events:none;
transform-origin:0 0;
- z-index:3;
&[data-main-rotation="90"] .norotate{
transform:rotate(270deg) translateX(-100%);
@@ -817,7 +815,6 @@
overflow:hidden;
width:100%;
height:100%;
- z-index:1;
}
.pdfViewer .page{
@@ -947,7 +944,6 @@
--toolbar-fg-color:#15141a;
--toolbarButton-download-icon:url(images/gv-toolbarButton-download.svg);
- --toolbarButton-openinapp-icon:url(images/gv-toolbarButton-openinapp.svg);
}
:root:dir(rtl){
@@ -1140,10 +1136,6 @@ body{
mask-image:var(--toolbarButton-download-icon);
}
-#openInApp::before{
- mask-image:var(--toolbarButton-openinapp-icon);
-}
-
.dialogButton{
width:auto;
margin:3px 4px 2px !important;
diff --git a/toolkit/components/pdfjs/content/web/viewer-geckoview.html b/toolkit/components/pdfjs/content/web/viewer-geckoview.html
index 1c0296a481..ba52e298ed 100644
--- a/toolkit/components/pdfjs/content/web/viewer-geckoview.html
+++ b/toolkit/components/pdfjs/content/web/viewer-geckoview.html
@@ -45,9 +45,6 @@ See https://github.com/adobe-type-tools/cmap-resources
<button id="download" class="toolbarButton" title="Download" tabindex="31" data-l10n-id="pdfjs-download-button">
<span data-l10n-id="pdfjs-download-button-label">Download</span>
</button>
- <button id="openInApp" class="toolbarButton" title="Open in app" tabindex="32" data-l10n-id="pdfjs-open-in-app-button">
- <span data-l10n-id="pdfjs-open-in-app-button-label">Open in app</span>
- </button>
</div>
<div id="viewerContainer" tabindex="0">
diff --git a/toolkit/components/pdfjs/content/web/viewer-geckoview.mjs b/toolkit/components/pdfjs/content/web/viewer-geckoview.mjs
index 8ba8147a9f..866c4a405a 100644
--- a/toolkit/components/pdfjs/content/web/viewer-geckoview.mjs
+++ b/toolkit/components/pdfjs/content/web/viewer-geckoview.mjs
@@ -606,6 +606,10 @@ const defaultOptions = {
value: false,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
+ enableHighlightFloatingButton: {
+ value: false,
+ kind: OptionKind.VIEWER + OptionKind.PREFERENCE
+ },
enableML: {
value: false,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
@@ -651,7 +655,7 @@ const defaultOptions = {
kind: OptionKind.VIEWER
},
maxCanvasPixels: {
- value: 16777216,
+ value: 2 ** 25,
kind: OptionKind.VIEWER
},
forcePageColors: {
@@ -768,28 +772,20 @@ class AppOptions {
constructor() {
throw new Error("Cannot initialize AppOptions.");
}
+ static getCompat(name) {
+ return compatibilityParams[name] ?? undefined;
+ }
static get(name) {
- const userOption = userOptions[name];
- if (userOption !== undefined) {
- return userOption;
- }
- const defaultOption = defaultOptions[name];
- if (defaultOption !== undefined) {
- return compatibilityParams[name] ?? defaultOption.value;
- }
- return undefined;
+ return userOptions[name] ?? compatibilityParams[name] ?? defaultOptions[name]?.value ?? undefined;
}
- static getAll(kind = null) {
+ static getAll(kind = null, defaultOnly = false) {
const options = Object.create(null);
for (const name in defaultOptions) {
const defaultOption = defaultOptions[name];
- if (kind) {
- if (!(kind & defaultOption.kind)) {
- continue;
- }
+ if (kind && !(kind & defaultOption.kind)) {
+ continue;
}
- const userOption = userOptions[name];
- options[name] = userOption !== undefined ? userOption : compatibilityParams[name] ?? defaultOption.value;
+ options[name] = defaultOnly ? defaultOption.value : userOptions[name] ?? compatibilityParams[name] ?? defaultOption.value;
}
return options;
}
@@ -1112,30 +1108,7 @@ class PDFLinkService {
if (pdfDocument !== this.pdfDocument) {
return;
}
- let operator;
- for (const elem of action.state) {
- switch (elem) {
- case "ON":
- case "OFF":
- case "Toggle":
- operator = elem;
- continue;
- }
- switch (operator) {
- case "ON":
- optionalContentConfig.setVisibility(elem, true);
- break;
- case "OFF":
- optionalContentConfig.setVisibility(elem, false);
- break;
- case "Toggle":
- const group = optionalContentConfig.getGroup(elem);
- if (group) {
- optionalContentConfig.setVisibility(elem, !group.visible);
- }
- break;
- }
- }
+ optionalContentConfig.setOCGState(action);
this.pdfViewer.optionalContentConfigPromise = Promise.resolve(optionalContentConfig);
}
cachePageRef(pageNum, pageRef) {
@@ -1423,7 +1396,7 @@ class BaseExternalServices {
}
updateFindControlState(data) {}
updateFindMatchesCount(data) {}
- initPassiveLoading(callbacks) {}
+ initPassiveLoading() {}
reportTelemetry(data) {}
async createL10n() {
throw new Error("Not implemented: createL10n");
@@ -1440,6 +1413,16 @@ class BaseExternalServices {
;// CONCATENATED MODULE: ./web/preferences.js
class BasePreferences {
+ #browserDefaults = Object.freeze({
+ canvasMaxAreaInBytes: -1,
+ isInAutomation: false,
+ supportsCaretBrowsingMode: false,
+ supportsDocumentFonts: true,
+ supportsIntegratedFind: false,
+ supportsMouseWheelZoomCtrlKey: true,
+ supportsMouseWheelZoomMetaKey: true,
+ supportsPinchToZoom: true
+ });
#defaults = Object.freeze({
annotationEditorMode: 0,
annotationMode: 2,
@@ -1448,6 +1431,7 @@ class BasePreferences {
defaultZoomValue: "",
disablePageLabels: false,
enableHighlightEditor: false,
+ enableHighlightFloatingButton: false,
enableML: false,
enablePermissions: false,
enablePrintAutoRotate: true,
@@ -1482,26 +1466,19 @@ class BasePreferences {
browserPrefs,
prefs
}) => {
- const BROWSER_PREFS = {
- canvasMaxAreaInBytes: -1,
- isInAutomation: false,
- supportsCaretBrowsingMode: false,
- supportsDocumentFonts: true,
- supportsIntegratedFind: false,
- supportsMouseWheelZoomCtrlKey: true,
- supportsMouseWheelZoomMetaKey: true,
- supportsPinchToZoom: true
- };
const options = Object.create(null);
- for (const [name, defaultVal] of Object.entries(BROWSER_PREFS)) {
+ for (const [name, val] of Object.entries(this.#browserDefaults)) {
const prefVal = browserPrefs?.[name];
- options[name] = typeof prefVal === typeof defaultVal ? prefVal : defaultVal;
+ options[name] = typeof prefVal === typeof val ? prefVal : val;
}
- for (const [name, defaultVal] of Object.entries(this.#defaults)) {
+ for (const [name, val] of Object.entries(this.#defaults)) {
const prefVal = prefs?.[name];
- options[name] = this.#prefs[name] = typeof prefVal === typeof defaultVal ? prefVal : defaultVal;
+ options[name] = this.#prefs[name] = typeof prefVal === typeof val ? prefVal : val;
}
AppOptions.setAll(options, true);
+ window.addEventListener("updatedPreference", evt => {
+ this.#updatePref(evt.detail);
+ });
});
}
async _writeToStorage(prefObj) {
@@ -1510,6 +1487,24 @@ class BasePreferences {
async _readFromStorage(prefObj) {
throw new Error("Not implemented: _readFromStorage");
}
+ #updatePref({
+ name,
+ value
+ }) {
+ if (name in this.#browserDefaults) {
+ if (typeof value !== typeof this.#browserDefaults[name]) {
+ return;
+ }
+ } else if (name in this.#defaults) {
+ if (typeof value !== typeof this.#defaults[name]) {
+ return;
+ }
+ this.#prefs[name] = value;
+ } else {
+ return;
+ }
+ AppOptions.set(name, value);
+ }
async reset() {
throw new Error("Please use `about:config` to change preferences.");
}
@@ -1517,12 +1512,7 @@ class BasePreferences {
throw new Error("Please use `about:config` to change preferences.");
}
async get(name) {
- await this.#initializedPromise;
- const defaultValue = this.#defaults[name];
- if (defaultValue === undefined) {
- throw new Error(`Get preference: "${name}" is undefined.`);
- }
- return this.#prefs[name] ?? defaultValue;
+ throw new Error("Not implemented: get");
}
get initializedPromise() {
return this.#initializedPromise;
@@ -1805,8 +1795,7 @@ class Preferences extends BasePreferences {
try {
const hasUnchangedAnnotations = pdfDocument.annotationStorage.size === 0;
const hasWillPrint = pdfViewer.enableScripting && !!(await pdfDocument.getJSActions())?.WillPrint;
- const hasUnchangedOptionalContent = (await pdfViewer.optionalContentConfigPromise).hasInitialVisibility;
- result = hasUnchangedAnnotations && !hasWillPrint && hasUnchangedOptionalContent;
+ result = hasUnchangedAnnotations && !hasWillPrint;
} catch {
console.warn("Unable to check if the document can be downloaded.");
}
@@ -1860,7 +1849,7 @@ class ExternalServices extends BaseExternalServices {
updateFindMatchesCount(data) {
FirefoxCom.request("updateFindMatchesCount", data);
}
- initPassiveLoading(callbacks) {
+ initPassiveLoading() {
let pdfDataRangeTransport;
window.addEventListener("message", function windowMessage(e) {
if (e.source !== null) {
@@ -1874,11 +1863,13 @@ class ExternalServices extends BaseExternalServices {
switch (args.pdfjsLoadAction) {
case "supportsRangedLoading":
if (args.done && !args.data) {
- callbacks.onError();
+ viewerApp._documentError(null);
break;
}
pdfDataRangeTransport = new FirefoxComDataRangeTransport(args.length, args.data, args.done, args.filename);
- callbacks.onOpenWithTransport(pdfDataRangeTransport);
+ viewerApp.open({
+ range: pdfDataRangeTransport
+ });
break;
case "range":
pdfDataRangeTransport.onDataRange(args.begin, args.chunk);
@@ -1894,21 +1885,26 @@ class ExternalServices extends BaseExternalServices {
pdfDataRangeTransport?.onDataProgressiveDone();
break;
case "progress":
- callbacks.onProgress(args.loaded, args.total);
+ viewerApp.progress(args.loaded / args.total);
break;
case "complete":
if (!args.data) {
- callbacks.onError(args.errorCode);
+ viewerApp._documentError(null, {
+ message: args.errorCode
+ });
break;
}
- callbacks.onOpenWithData(args.data, args.filename);
+ viewerApp.open({
+ data: args.data,
+ filename: args.filename
+ });
break;
}
});
FirefoxCom.request("initPassiveLoading", null);
}
reportTelemetry(data) {
- FirefoxCom.request("reportTelemetry", JSON.stringify(data));
+ FirefoxCom.request("reportTelemetry", data);
}
updateEditorStates(data) {
FirefoxCom.request("updateEditorStates", data);
@@ -3671,14 +3667,15 @@ class FirefoxPrintService {
pagesOverview,
printContainer,
printResolution,
- optionalContentConfigPromise = null,
printAnnotationStoragePromise = null
}) {
this.pdfDocument = pdfDocument;
this.pagesOverview = pagesOverview;
this.printContainer = printContainer;
this._printResolution = printResolution || 150;
- this._optionalContentConfigPromise = optionalContentConfigPromise || pdfDocument.getOptionalContentConfig();
+ this._optionalContentConfigPromise = pdfDocument.getOptionalContentConfig({
+ intent: "print"
+ });
this._printAnnotationStoragePromise = printAnnotationStoragePromise || Promise.resolve();
}
layout() {
@@ -4206,10 +4203,10 @@ class PDFScriptingManager {
class AnnotationEditorLayerBuilder {
#annotationLayer = null;
#drawLayer = null;
+ #onAppend = null;
#textLayer = null;
#uiManager;
constructor(options) {
- this.pageDiv = options.pageDiv;
this.pdfPage = options.pdfPage;
this.accessibilityManager = options.accessibilityManager;
this.l10n = options.l10n;
@@ -4220,6 +4217,7 @@ class AnnotationEditorLayerBuilder {
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") {
@@ -4240,10 +4238,9 @@ class AnnotationEditorLayerBuilder {
}
const div = this.div = document.createElement("div");
div.className = "annotationEditorLayer";
- div.tabIndex = 0;
div.hidden = true;
div.dir = this.#uiManager.direction;
- this.pageDiv.append(div);
+ this.#onAppend?.(div);
this.annotationEditorLayer = new AnnotationEditorLayer({
uiManager: this.#uiManager,
div,
@@ -4269,9 +4266,7 @@ class AnnotationEditorLayerBuilder {
if (!this.div) {
return;
}
- this.pageDiv = null;
this.annotationEditorLayer.destroy();
- this.div.remove();
}
hide() {
if (!this.div) {
@@ -4280,7 +4275,7 @@ class AnnotationEditorLayerBuilder {
this.div.hidden = true;
}
show() {
- if (!this.div || this.annotationEditorLayer.isEmpty) {
+ if (!this.div || this.annotationEditorLayer.isInvisible) {
return;
}
this.div.hidden = false;
@@ -4291,9 +4286,9 @@ class AnnotationEditorLayerBuilder {
class AnnotationLayerBuilder {
+ #onAppend = null;
#onPresentationModeChanged = null;
constructor({
- pageDiv,
pdfPage,
linkService,
downloadManager,
@@ -4304,9 +4299,9 @@ class AnnotationLayerBuilder {
hasJSActionsPromise = null,
fieldObjectsPromise = null,
annotationCanvasMap = null,
- accessibilityManager = null
+ accessibilityManager = null,
+ onAppend = null
}) {
- this.pageDiv = pageDiv;
this.pdfPage = pdfPage;
this.linkService = linkService;
this.downloadManager = downloadManager;
@@ -4318,6 +4313,7 @@ class AnnotationLayerBuilder {
this._fieldObjectsPromise = fieldObjectsPromise || Promise.resolve(null);
this._annotationCanvasMap = annotationCanvasMap;
this._accessibilityManager = accessibilityManager;
+ this.#onAppend = onAppend;
this.annotationLayer = null;
this.div = null;
this._cancelled = false;
@@ -4343,7 +4339,7 @@ class AnnotationLayerBuilder {
}
const div = this.div = document.createElement("div");
div.className = "annotationLayer";
- this.pageDiv.append(div);
+ this.#onAppend?.(div);
if (annotations.length === 0) {
this.hide();
return;
@@ -4937,13 +4933,15 @@ class TextHighlighter {
class TextLayerBuilder {
#enablePermissions = false;
+ #onAppend = null;
#rotation = 0;
#scale = 0;
#textContentSource = null;
constructor({
highlighter = null,
accessibilityManager = null,
- enablePermissions = false
+ enablePermissions = false,
+ onAppend = null
}) {
this.textContentItemsStr = [];
this.renderingDone = false;
@@ -4953,8 +4951,9 @@ class TextLayerBuilder {
this.highlighter = highlighter;
this.accessibilityManager = accessibilityManager;
this.#enablePermissions = enablePermissions === true;
- this.onAppend = null;
+ this.#onAppend = onAppend;
this.div = document.createElement("div");
+ this.div.tabIndex = 0;
this.div.className = "textLayer";
}
#finishRendering() {
@@ -5009,7 +5008,7 @@ class TextLayerBuilder {
this.#finishRendering();
this.#scale = scale;
this.#rotation = rotation;
- this.onAppend(this.div);
+ this.#onAppend?.(this.div);
this.highlighter?.enable();
this.accessibilityManager?.enable();
}
@@ -5083,8 +5082,8 @@ class TextLayerBuilder {
-const MAX_CANVAS_PIXELS = compatibilityParams.maxCanvasPixels || 16777216;
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;
#hasRestrictedScaling = false;
@@ -5100,6 +5099,7 @@ class PDFPageView {
regularAnnotations: true
};
#viewportMap = new WeakMap();
+ #layers = [null, null, null, null];
constructor(options) {
const container = options.container;
const defaultViewport = options.defaultViewport;
@@ -5116,7 +5116,7 @@ class PDFPageView {
this.#textLayerMode = options.textLayerMode ?? TextLayerMode.ENABLE;
this.#annotationMode = options.annotationMode ?? AnnotationMode.ENABLE_FORMS;
this.imageResourcesPath = options.imageResourcesPath || "";
- this.maxCanvasPixels = options.maxCanvasPixels ?? MAX_CANVAS_PIXELS;
+ this.maxCanvasPixels = options.maxCanvasPixels ?? (AppOptions.getCompat("maxCanvasPixels") || 2 ** 25);
this.pageColors = options.pageColors || null;
this.eventBus = options.eventBus;
this.renderingQueue = options.renderingQueue;
@@ -5143,6 +5143,23 @@ class PDFPageView {
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;
}
@@ -5256,7 +5273,7 @@ class PDFPageView {
} finally {
if (this.xfaLayer?.div) {
this.l10n.pause();
- this.div.append(this.xfaLayer.div);
+ this.#addLayer(this.xfaLayer.div, "xfaLayer");
this.l10n.resume();
}
this.eventBus.dispatch("xfalayerrendered", {
@@ -5368,6 +5385,10 @@ class PDFPageView {
continue;
}
node.remove();
+ const layerIndex = this.#layers.indexOf(node);
+ if (layerIndex >= 0) {
+ this.#layers[layerIndex] = null;
+ }
}
div.removeAttribute("data-loaded");
if (annotationLayerNode) {
@@ -5627,19 +5648,20 @@ class PDFPageView {
this.renderingState = RenderingStates.RUNNING;
const canvasWrapper = document.createElement("div");
canvasWrapper.classList.add("canvasWrapper");
- div.append(canvasWrapper);
+ canvasWrapper.setAttribute("aria-hidden", true);
+ this.#addLayer(canvasWrapper, "canvasWrapper");
if (!this.textLayer && this.#textLayerMode !== TextLayerMode.DISABLE && !pdfPage.isPureXfa) {
this._accessibilityManager ||= new TextAccessibilityManager();
this.textLayer = new TextLayerBuilder({
highlighter: this._textHighlighter,
accessibilityManager: this._accessibilityManager,
- enablePermissions: this.#textLayerMode === TextLayerMode.ENABLE_PERMISSIONS
+ enablePermissions: this.#textLayerMode === TextLayerMode.ENABLE_PERMISSIONS,
+ onAppend: textLayerDiv => {
+ this.l10n.pause();
+ this.#addLayer(textLayerDiv, "textLayer");
+ this.l10n.resume();
+ }
});
- this.textLayer.onAppend = textLayerDiv => {
- this.l10n.pause();
- this.div.append(textLayerDiv);
- this.l10n.resume();
- };
}
if (!this.annotationLayer && this.#annotationMode !== AnnotationMode.DISABLE) {
const {
@@ -5652,7 +5674,6 @@ class PDFPageView {
} = this.#layerProperties;
this._annotationCanvasMap ||= new Map();
this.annotationLayer = new AnnotationLayerBuilder({
- pageDiv: div,
pdfPage,
annotationStorage,
imageResourcesPath: this.imageResourcesPath,
@@ -5663,7 +5684,10 @@ class PDFPageView {
hasJSActionsPromise,
fieldObjectsPromise,
annotationCanvasMap: this._annotationCanvasMap,
- accessibilityManager: this._accessibilityManager
+ accessibilityManager: this._accessibilityManager,
+ onAppend: annotationLayerDiv => {
+ this.#addLayer(annotationLayerDiv, "annotationLayer");
+ }
});
}
const renderContinueCallback = cont => {
@@ -5752,13 +5776,15 @@ class PDFPageView {
if (!this.annotationEditorLayer) {
this.annotationEditorLayer = new AnnotationEditorLayerBuilder({
uiManager: annotationEditorUIManager,
- pageDiv: div,
pdfPage,
l10n,
accessibilityManager: this._accessibilityManager,
annotationLayer: this.annotationLayer?.annotationLayer,
textLayer: this.textLayer,
- drawLayer: this.drawLayer.getDrawLayer()
+ drawLayer: this.drawLayer.getDrawLayer(),
+ onAppend: annotationEditorLayerDiv => {
+ this.#addLayer(annotationEditorLayerDiv, "annotationEditorLayer");
+ }
});
}
this.#renderAnnotationEditorLayer();
@@ -5883,6 +5909,7 @@ class PDFViewer {
#annotationMode = AnnotationMode.ENABLE_FORMS;
#containerTopLeft = null;
#copyCallbackBound = null;
+ #enableHighlightFloatingButton = false;
#enablePermissions = false;
#mlManager = null;
#getAllTextInProgress = false;
@@ -5895,7 +5922,7 @@ class PDFViewer {
#scaleTimeoutId = null;
#textLayerMode = TextLayerMode.ENABLE;
constructor(options) {
- const viewerVersion = "4.1.249";
+ const viewerVersion = "4.1.342";
if (version !== viewerVersion) {
throw new Error(`The API version "${version}" does not match the Viewer version "${viewerVersion}".`);
}
@@ -5915,6 +5942,7 @@ class PDFViewer {
this.#annotationMode = options.annotationMode ?? AnnotationMode.ENABLE_FORMS;
this.#annotationEditorMode = options.annotationEditorMode ?? AnnotationEditorType.NONE;
this.#annotationEditorHighlightColors = options.annotationEditorHighlightColors || null;
+ this.#enableHighlightFloatingButton = options.enableHighlightFloatingButton === true;
this.imageResourcesPath = options.imageResourcesPath || "";
this.enablePrintAutoRotate = options.enablePrintAutoRotate || false;
this.maxCanvasPixels = options.maxCanvasPixels;
@@ -6225,7 +6253,9 @@ class PDFViewer {
}
const pagesCount = pdfDocument.numPages;
const firstPagePromise = pdfDocument.getPage(1);
- const optionalContentConfigPromise = pdfDocument.getOptionalContentConfig();
+ const optionalContentConfigPromise = pdfDocument.getOptionalContentConfig({
+ intent: "display"
+ });
const permissionsPromise = this.#enablePermissions ? pdfDocument.getPermissions() : Promise.resolve();
if (pagesCount > PagesCountLimit.FORCE_SCROLL_MODE_PAGE) {
console.warn("Forcing PAGE-scrolling for performance reasons, given the length of the document.");
@@ -6281,7 +6311,7 @@ class PDFViewer {
if (pdfDocument.isPureXfa) {
console.warn("Warning: XFA-editing is not implemented.");
} else if (isValidAnnotationEditorMode(mode)) {
- this.#annotationEditorUIManager = new AnnotationEditorUIManager(this.container, this.viewer, this.#altTextManager, this.eventBus, pdfDocument, this.pageColors, this.#annotationEditorHighlightColors, this.#mlManager);
+ this.#annotationEditorUIManager = new AnnotationEditorUIManager(this.container, this.viewer, this.#altTextManager, this.eventBus, pdfDocument, this.pageColors, this.#annotationEditorHighlightColors, this.#enableHighlightFloatingButton, this.#mlManager);
this.eventBus.dispatch("annotationeditoruimanager", {
source: this,
uiManager: this.#annotationEditorUIManager
@@ -6942,7 +6972,9 @@ class PDFViewer {
}
if (!this._optionalContentConfigPromise) {
console.error("optionalContentConfigPromise: Not initialized yet.");
- return this.pdfDocument.getOptionalContentConfig();
+ return this.pdfDocument.getOptionalContentConfig({
+ intent: "display"
+ });
}
return this._optionalContentConfigPromise;
}
@@ -7281,10 +7313,6 @@ class Toolbar {
element: options.download,
eventName: "download",
nimbusName: "download-button"
- }, {
- element: options.openInApp,
- eventName: "openinexternalapp",
- nimbusName: "open-in-app-button"
}];
if (nimbusData) {
this.#buttons = [];
@@ -7645,6 +7673,7 @@ const PDFViewerApplication = {
annotationMode: AppOptions.get("annotationMode"),
annotationEditorMode,
annotationEditorHighlightColors: AppOptions.get("highlightEditorColors"),
+ enableHighlightFloatingButton: AppOptions.get("enableHighlightFloatingButton"),
imageResourcesPath: AppOptions.get("imageResourcesPath"),
enablePrintAutoRotate: AppOptions.get("enablePrintAutoRotate"),
maxCanvasPixels: AppOptions.get("maxCanvasPixels"),
@@ -7784,40 +7813,8 @@ const PDFViewerApplication = {
if (this.supportsIntegratedFind) {
appConfig.toolbar?.viewFind?.classList.add("hidden");
}
- this.initPassiveLoading(file);
- const {
- mainContainer
- } = appConfig;
- ({
- scrollTop: this._lastScrollTop,
- scrollLeft: this._lastScrollLeft
- } = mainContainer);
- const scroll = () => {
- if (this._lastScrollTop === mainContainer.scrollTop && this._lastScrollLeft === mainContainer.scrollLeft) {
- return;
- }
- mainContainer.removeEventListener("scroll", scroll, {
- passive: true
- });
- this._isScrolling = true;
- const scrollend = () => {
- ({
- scrollTop: this._lastScrollTop,
- scrollLeft: this._lastScrollLeft
- } = mainContainer);
- this._isScrolling = false;
- mainContainer.addEventListener("scroll", scroll, {
- passive: true
- });
- mainContainer.removeEventListener("scrollend", scrollend);
- mainContainer.removeEventListener("blur", scrollend);
- };
- mainContainer.addEventListener("scrollend", scrollend);
- mainContainer.addEventListener("blur", scrollend);
- };
- mainContainer.addEventListener("scroll", scroll, {
- passive: true
- });
+ this.setTitleUsingUrl(file, file);
+ this.externalServices.initPassiveLoading();
},
get externalServices() {
return shadow(this, "externalServices", new ExternalServices());
@@ -7890,45 +7887,12 @@ const PDFViewerApplication = {
return shadow(this, "supportsMouseWheelZoomMetaKey", AppOptions.get("supportsMouseWheelZoomMetaKey"));
},
get supportsCaretBrowsingMode() {
- return shadow(this, "supportsCaretBrowsingMode", AppOptions.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);
},
- initPassiveLoading(file) {
- this.setTitleUsingUrl(file, file);
- this.externalServices.initPassiveLoading({
- onOpenWithTransport: range => {
- this.open({
- range
- });
- },
- onOpenWithData: (data, contentDispositionFilename) => {
- if (isPdfFile(contentDispositionFilename)) {
- this._contentDispositionFilename = contentDispositionFilename;
- }
- this.open({
- data
- });
- },
- onOpenWithURL: (url, length, originalUrl) => {
- this.open({
- url,
- length,
- originalUrl
- });
- },
- onError: err => {
- this.l10n.get("pdfjs-loading-error").then(msg => {
- this._documentError(msg, err);
- });
- },
- onProgress: (loaded, total) => {
- this.progress(loaded / total);
- }
- });
- },
setTitleUsingUrl(url = "", downloadUrl = null) {
this.url = url;
this.baseUrl = url.split("#", 1)[0];
@@ -8016,6 +7980,9 @@ const PDFViewerApplication = {
}
const workerParams = AppOptions.getAll(OptionKind.WORKER);
Object.assign(GlobalWorkerOptions, workerParams);
+ if (args.data && isPdfFile(args.filename)) {
+ this._contentDispositionFilename = args.filename;
+ }
AppOptions.set("docBaseUrl", this.baseUrl);
const apiParams = AppOptions.getAll(OptionKind.API);
const loadingTask = getDocument({
@@ -8051,10 +8018,9 @@ const PDFViewerApplication = {
} else if (reason instanceof UnexpectedResponseException) {
key = "pdfjs-unexpected-response-error";
}
- return this.l10n.get(key).then(msg => {
- this._documentError(msg, {
- message: reason?.message
- });
+ return this._documentError(key, {
+ message: reason.message
+ }).then(() => {
throw reason;
});
});
@@ -8118,21 +8084,17 @@ const PDFViewerApplication = {
this.download(options);
}
},
- openInExternalApp() {
- this.downloadOrSave({
- openInExternalApp: true
- });
- },
- _documentError(message, moreInfo = null) {
+ async _documentError(key, moreInfo = null) {
this._unblockDocumentLoadEvent();
- this._otherError(message, moreInfo);
+ const message = await this._otherError(key || "pdfjs-loading-error", moreInfo);
this.eventBus.dispatch("documenterror", {
source: this,
message,
reason: moreInfo?.message ?? null
});
},
- _otherError(message, moreInfo = 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}`);
@@ -8148,6 +8110,7 @@ const PDFViewerApplication = {
}
}
console.error(`${message}\n\n${moreInfoText.join("\n")}`);
+ return message;
},
progress(level) {
if (!this.loadingBar || this.downloadComplete) {
@@ -8272,10 +8235,8 @@ const PDFViewerApplication = {
this._unblockDocumentLoadEvent();
this._initializeAutoPrint(pdfDocument, openActionPromise);
}, reason => {
- this.l10n.get("pdfjs-loading-error").then(msg => {
- this._documentError(msg, {
- message: reason?.message
- });
+ this._documentError("pdfjs-loading-error", {
+ message: reason.message
});
});
onePageRendered.then(data => {
@@ -8521,9 +8482,7 @@ const PDFViewerApplication = {
return;
}
if (!this.supportsPrinting) {
- this.l10n.get("pdfjs-printing-not-supported").then(msg => {
- this._otherError(msg);
- });
+ this._otherError("pdfjs-printing-not-supported");
return;
}
if (!this.pdfViewer.pageViewsReady) {
@@ -8537,7 +8496,6 @@ const PDFViewerApplication = {
pagesOverview: this.pdfViewer.getPagesOverview(),
printContainer: this.appConfig.printContainer,
printResolution: AppOptions.get("printResolution"),
- optionalContentConfigPromise: this.pdfViewer.optionalContentConfigPromise,
printAnnotationStoragePromise: this._printAnnotationStoragePromise
});
this.forceRendering();
@@ -8606,7 +8564,6 @@ const PDFViewerApplication = {
eventBus._on("switchannotationeditorparams", webViewerSwitchAnnotationEditorParams);
eventBus._on("print", webViewerPrint);
eventBus._on("download", webViewerDownload);
- eventBus._on("openinexternalapp", webViewerOpenInExternalApp);
eventBus._on("firstpage", webViewerFirstPage);
eventBus._on("lastpage", webViewerLastPage);
eventBus._on("nextpage", webViewerNextPage);
@@ -8638,7 +8595,10 @@ const PDFViewerApplication = {
bindWindowEvents() {
const {
eventBus,
- _boundEvents
+ _boundEvents,
+ appConfig: {
+ mainContainer
+ }
} = this;
function addWindowResolutionChange(evt = null) {
if (evt) {
@@ -8698,12 +8658,79 @@ const PDFViewerApplication = {
window.addEventListener("beforeprint", _boundEvents.windowBeforePrint);
window.addEventListener("afterprint", _boundEvents.windowAfterPrint);
window.addEventListener("updatefromsandbox", _boundEvents.windowUpdateFromSandbox);
+ ({
+ scrollTop: this._lastScrollTop,
+ scrollLeft: this._lastScrollLeft
+ } = mainContainer);
+ const scrollend = _boundEvents.mainContainerScrollend = () => {
+ ({
+ scrollTop: this._lastScrollTop,
+ scrollLeft: this._lastScrollLeft
+ } = mainContainer);
+ this._isScrolling = false;
+ mainContainer.addEventListener("scroll", scroll, {
+ passive: true
+ });
+ mainContainer.removeEventListener("scrollend", scrollend);
+ mainContainer.removeEventListener("blur", scrollend);
+ };
+ const scroll = _boundEvents.mainContainerScroll = () => {
+ if (this._isCtrlKeyDown || this._lastScrollTop === mainContainer.scrollTop && this._lastScrollLeft === mainContainer.scrollLeft) {
+ return;
+ }
+ mainContainer.removeEventListener("scroll", scroll, {
+ passive: true
+ });
+ this._isScrolling = true;
+ mainContainer.addEventListener("scrollend", scrollend);
+ mainContainer.addEventListener("blur", scrollend);
+ };
+ mainContainer.addEventListener("scroll", scroll, {
+ passive: true
+ });
},
unbindEvents() {
throw new Error("Not implemented: unbindEvents");
},
unbindWindowEvents() {
- throw new Error("Not implemented: unbindWindowEvents");
+ const {
+ _boundEvents,
+ appConfig: {
+ mainContainer
+ }
+ } = this;
+ window.removeEventListener("visibilitychange", webViewerVisibilityChange);
+ window.removeEventListener("wheel", webViewerWheel, {
+ passive: false
+ });
+ window.removeEventListener("touchstart", webViewerTouchStart, {
+ passive: false
+ });
+ window.removeEventListener("touchmove", webViewerTouchMove, {
+ passive: false
+ });
+ window.removeEventListener("touchend", webViewerTouchEnd, {
+ passive: false
+ });
+ window.removeEventListener("click", webViewerClick);
+ window.removeEventListener("keydown", webViewerKeyDown);
+ window.removeEventListener("keyup", webViewerKeyUp);
+ window.removeEventListener("resize", _boundEvents.windowResize);
+ window.removeEventListener("hashchange", _boundEvents.windowHashChange);
+ window.removeEventListener("beforeprint", _boundEvents.windowBeforePrint);
+ window.removeEventListener("afterprint", _boundEvents.windowAfterPrint);
+ window.removeEventListener("updatefromsandbox", _boundEvents.windowUpdateFromSandbox);
+ mainContainer.removeEventListener("scroll", _boundEvents.mainContainerScroll);
+ mainContainer.removeEventListener("scrollend", _boundEvents.mainContainerScrollend);
+ mainContainer.removeEventListener("blur", _boundEvents.mainContainerScrollend);
+ _boundEvents.removeWindowResolutionChange?.();
+ _boundEvents.windowResize = null;
+ _boundEvents.windowHashChange = null;
+ _boundEvents.windowBeforePrint = null;
+ _boundEvents.windowAfterPrint = null;
+ _boundEvents.windowUpdateFromSandbox = null;
+ _boundEvents.mainContainerScroll = null;
+ _boundEvents.mainContainerScrollend = null;
},
_accumulateTicks(ticks, prop) {
if (this[prop] > 0 && ticks < 0 || this[prop] < 0 && ticks > 0) {
@@ -8786,9 +8813,7 @@ function webViewerPageRendered({
}
}
if (error) {
- PDFViewerApplication.l10n.get("pdfjs-rendering-error").then(msg => {
- PDFViewerApplication._otherError(msg, error);
- });
+ PDFViewerApplication._otherError("pdfjs-rendering-error", error);
}
}
function webViewerPageMode({
@@ -8918,9 +8943,6 @@ function webViewerPrint() {
function webViewerDownload() {
PDFViewerApplication.downloadOrSave();
}
-function webViewerOpenInExternalApp() {
- PDFViewerApplication.openInExternalApp();
-}
function webViewerFirstPage() {
PDFViewerApplication.page = 1;
}
@@ -9509,8 +9531,8 @@ function webViewerReportTelemetry({
-const pdfjsVersion = "4.1.249";
-const pdfjsBuild = "d07f37f44";
+const pdfjsVersion = "4.1.342";
+const pdfjsBuild = "e384df6f1";
const AppConstants = null;
window.PDFViewerApplication = PDFViewerApplication;
window.PDFViewerApplicationConstants = AppConstants;
@@ -9524,8 +9546,7 @@ function getViewerConfiguration() {
toolbar: {
mainContainer,
container: document.getElementById("floatingToolbar"),
- download: document.getElementById("download"),
- openInApp: document.getElementById("openInApp")
+ download: document.getElementById("download")
},
passwordOverlay: {
dialog: document.getElementById("passwordDialog"),
diff --git a/toolkit/components/pdfjs/content/web/viewer.css b/toolkit/components/pdfjs/content/web/viewer.css
index 238df7ce7f..2999c89f3a 100644
--- a/toolkit/components/pdfjs/content/web/viewer.css
+++ b/toolkit/components/pdfjs/content/web/viewer.css
@@ -23,7 +23,6 @@
text-size-adjust:none;
forced-color-adjust:none;
transform-origin:0 0;
- z-index:2;
caret-color:CanvasText;
&.highlighting{
@@ -162,7 +161,6 @@
left:0;
pointer-events:none;
transform-origin:0 0;
- z-index:3;
&[data-main-rotation="90"] .norotate{
transform:rotate(270deg) translateX(-100%);
@@ -853,6 +851,208 @@
}
}
+.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;
@@ -878,6 +1078,19 @@
--editorHighlight-editing-cursor:url(images/cursor-editorTextHighlight.svg) 24 24, text;
--editorFreeHighlight-editing-cursor:url(images/cursor-editorFreeHighlight.svg) 1 18, pointer;
}
+.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);
@@ -926,7 +1139,6 @@
font-size:calc(100px * var(--scale-factor));
transform-origin:0 0;
cursor:auto;
- z-index:4;
}
.annotationEditorLayer.waiting{
@@ -995,10 +1207,12 @@
}
.annotationEditorLayer
- :is(.freeTextEditor, .inkEditor, .stampEditor, .highlightEditor){
+ :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);
@@ -1084,6 +1298,25 @@
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);
@@ -2003,7 +2236,12 @@
--example-color:CanvasText;
}
- &::before{
+ :is(& > .editorParamsSlider[disabled]){
+ opacity:0.4;
+ }
+
+ &::before,
+ &::after{
content:"";
width:8px;
aspect-ratio:1;
@@ -2011,20 +2249,46 @@
border-radius:100%;
background-color:var(--example-color);
}
+ &::after{
+ width:24px;
+ }
.editorParamsSlider{
width:unset;
height:14px;
}
+ }
+ }
- &::after{
- content:"";
- width:24px;
- aspect-ratio:1;
- display:block;
- border-radius:100%;
- background-color:var(--example-color);
+ #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;
}
}
}
@@ -2084,7 +2348,6 @@
overflow:hidden;
width:100%;
height:100%;
- z-index:1;
}
.pdfViewer .page{
@@ -2655,6 +2918,7 @@ body{
font-style:normal;
}
.loadingInput:has(> &[data-status="pending"])::after{
+ display:block;
visibility:visible;
}
&[data-status="notFound"]{
@@ -3249,6 +3513,7 @@ a:is(.toolbarButton, .secondaryToolbarButton)[href="#"]{
transition-property:none;
.loadingInput:has(> &.loading)::after{
+ display: block;
visibility:visible;
transition-property:visibility;
@@ -3260,6 +3525,7 @@ a:is(.toolbarButton, .secondaryToolbarButton)[href="#"]{
&::after{
position:absolute;
visibility:hidden;
+ display: none;
top:calc(50% - 8px);
width:16px;
height:16px;
diff --git a/toolkit/components/pdfjs/content/web/viewer.html b/toolkit/components/pdfjs/content/web/viewer.html
index 59438b64b4..941e17b58d 100644
--- a/toolkit/components/pdfjs/content/web/viewer.html
+++ b/toolkit/components/pdfjs/content/web/viewer.html
@@ -127,6 +127,13 @@ See https://github.com/adobe-type-tools/cmap-resources
<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" data-l10n-id="pdfjs-editor-highlight-show-all-button" aria-pressed="true" tabindex="102"></button>
+ </div>
+ </div>
</div>
</div>
@@ -134,11 +141,11 @@ See https://github.com/adobe-type-tools/cmap-resources
<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="102">
+ <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="103">
+ <input type="range" id="editorFreeTextFontSize" class="editorParamsSlider" value="10" min="5" max="100" step="1" tabindex="104">
</div>
</div>
</div>
@@ -147,22 +154,22 @@ See https://github.com/adobe-type-tools/cmap-resources
<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="104">
+ <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="105">
+ <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="106">
+ <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="secondaryToolbarButton" title="Add image" tabindex="107" data-l10n-id="pdfjs-editor-stamp-add-image-button">
+ <button id="editorStampAddImage" class="secondaryToolbarButton" 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>
diff --git a/toolkit/components/pdfjs/content/web/viewer.mjs b/toolkit/components/pdfjs/content/web/viewer.mjs
index a047a9f7c1..778ce57e1a 100644
--- a/toolkit/components/pdfjs/content/web/viewer.mjs
+++ b/toolkit/components/pdfjs/content/web/viewer.mjs
@@ -606,6 +606,10 @@ const defaultOptions = {
value: false,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
+ enableHighlightFloatingButton: {
+ value: false,
+ kind: OptionKind.VIEWER + OptionKind.PREFERENCE
+ },
enableML: {
value: false,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
@@ -651,7 +655,7 @@ const defaultOptions = {
kind: OptionKind.VIEWER
},
maxCanvasPixels: {
- value: 16777216,
+ value: 2 ** 25,
kind: OptionKind.VIEWER
},
forcePageColors: {
@@ -768,28 +772,20 @@ class AppOptions {
constructor() {
throw new Error("Cannot initialize AppOptions.");
}
+ static getCompat(name) {
+ return compatibilityParams[name] ?? undefined;
+ }
static get(name) {
- const userOption = userOptions[name];
- if (userOption !== undefined) {
- return userOption;
- }
- const defaultOption = defaultOptions[name];
- if (defaultOption !== undefined) {
- return compatibilityParams[name] ?? defaultOption.value;
- }
- return undefined;
+ return userOptions[name] ?? compatibilityParams[name] ?? defaultOptions[name]?.value ?? undefined;
}
- static getAll(kind = null) {
+ static getAll(kind = null, defaultOnly = false) {
const options = Object.create(null);
for (const name in defaultOptions) {
const defaultOption = defaultOptions[name];
- if (kind) {
- if (!(kind & defaultOption.kind)) {
- continue;
- }
+ if (kind && !(kind & defaultOption.kind)) {
+ continue;
}
- const userOption = userOptions[name];
- options[name] = userOption !== undefined ? userOption : compatibilityParams[name] ?? defaultOption.value;
+ options[name] = defaultOnly ? defaultOption.value : userOptions[name] ?? compatibilityParams[name] ?? defaultOption.value;
}
return options;
}
@@ -1112,30 +1108,7 @@ class PDFLinkService {
if (pdfDocument !== this.pdfDocument) {
return;
}
- let operator;
- for (const elem of action.state) {
- switch (elem) {
- case "ON":
- case "OFF":
- case "Toggle":
- operator = elem;
- continue;
- }
- switch (operator) {
- case "ON":
- optionalContentConfig.setVisibility(elem, true);
- break;
- case "OFF":
- optionalContentConfig.setVisibility(elem, false);
- break;
- case "Toggle":
- const group = optionalContentConfig.getGroup(elem);
- if (group) {
- optionalContentConfig.setVisibility(elem, !group.visible);
- }
- break;
- }
- }
+ optionalContentConfig.setOCGState(action);
this.pdfViewer.optionalContentConfigPromise = Promise.resolve(optionalContentConfig);
}
cachePageRef(pageNum, pageRef) {
@@ -1423,7 +1396,7 @@ class BaseExternalServices {
}
updateFindControlState(data) {}
updateFindMatchesCount(data) {}
- initPassiveLoading(callbacks) {}
+ initPassiveLoading() {}
reportTelemetry(data) {}
async createL10n() {
throw new Error("Not implemented: createL10n");
@@ -1440,6 +1413,16 @@ class BaseExternalServices {
;// CONCATENATED MODULE: ./web/preferences.js
class BasePreferences {
+ #browserDefaults = Object.freeze({
+ canvasMaxAreaInBytes: -1,
+ isInAutomation: false,
+ supportsCaretBrowsingMode: false,
+ supportsDocumentFonts: true,
+ supportsIntegratedFind: false,
+ supportsMouseWheelZoomCtrlKey: true,
+ supportsMouseWheelZoomMetaKey: true,
+ supportsPinchToZoom: true
+ });
#defaults = Object.freeze({
annotationEditorMode: 0,
annotationMode: 2,
@@ -1448,6 +1431,7 @@ class BasePreferences {
defaultZoomValue: "",
disablePageLabels: false,
enableHighlightEditor: false,
+ enableHighlightFloatingButton: false,
enableML: false,
enablePermissions: false,
enablePrintAutoRotate: true,
@@ -1482,26 +1466,19 @@ class BasePreferences {
browserPrefs,
prefs
}) => {
- const BROWSER_PREFS = {
- canvasMaxAreaInBytes: -1,
- isInAutomation: false,
- supportsCaretBrowsingMode: false,
- supportsDocumentFonts: true,
- supportsIntegratedFind: false,
- supportsMouseWheelZoomCtrlKey: true,
- supportsMouseWheelZoomMetaKey: true,
- supportsPinchToZoom: true
- };
const options = Object.create(null);
- for (const [name, defaultVal] of Object.entries(BROWSER_PREFS)) {
+ for (const [name, val] of Object.entries(this.#browserDefaults)) {
const prefVal = browserPrefs?.[name];
- options[name] = typeof prefVal === typeof defaultVal ? prefVal : defaultVal;
+ options[name] = typeof prefVal === typeof val ? prefVal : val;
}
- for (const [name, defaultVal] of Object.entries(this.#defaults)) {
+ for (const [name, val] of Object.entries(this.#defaults)) {
const prefVal = prefs?.[name];
- options[name] = this.#prefs[name] = typeof prefVal === typeof defaultVal ? prefVal : defaultVal;
+ options[name] = this.#prefs[name] = typeof prefVal === typeof val ? prefVal : val;
}
AppOptions.setAll(options, true);
+ window.addEventListener("updatedPreference", evt => {
+ this.#updatePref(evt.detail);
+ });
});
}
async _writeToStorage(prefObj) {
@@ -1510,6 +1487,24 @@ class BasePreferences {
async _readFromStorage(prefObj) {
throw new Error("Not implemented: _readFromStorage");
}
+ #updatePref({
+ name,
+ value
+ }) {
+ if (name in this.#browserDefaults) {
+ if (typeof value !== typeof this.#browserDefaults[name]) {
+ return;
+ }
+ } else if (name in this.#defaults) {
+ if (typeof value !== typeof this.#defaults[name]) {
+ return;
+ }
+ this.#prefs[name] = value;
+ } else {
+ return;
+ }
+ AppOptions.set(name, value);
+ }
async reset() {
throw new Error("Please use `about:config` to change preferences.");
}
@@ -1517,12 +1512,7 @@ class BasePreferences {
throw new Error("Please use `about:config` to change preferences.");
}
async get(name) {
- await this.#initializedPromise;
- const defaultValue = this.#defaults[name];
- if (defaultValue === undefined) {
- throw new Error(`Get preference: "${name}" is undefined.`);
- }
- return this.#prefs[name] ?? defaultValue;
+ throw new Error("Not implemented: get");
}
get initializedPromise() {
return this.#initializedPromise;
@@ -1824,7 +1814,7 @@ class ExternalServices extends BaseExternalServices {
updateFindMatchesCount(data) {
FirefoxCom.request("updateFindMatchesCount", data);
}
- initPassiveLoading(callbacks) {
+ initPassiveLoading() {
let pdfDataRangeTransport;
window.addEventListener("message", function windowMessage(e) {
if (e.source !== null) {
@@ -1838,11 +1828,13 @@ class ExternalServices extends BaseExternalServices {
switch (args.pdfjsLoadAction) {
case "supportsRangedLoading":
if (args.done && !args.data) {
- callbacks.onError();
+ viewerApp._documentError(null);
break;
}
pdfDataRangeTransport = new FirefoxComDataRangeTransport(args.length, args.data, args.done, args.filename);
- callbacks.onOpenWithTransport(pdfDataRangeTransport);
+ viewerApp.open({
+ range: pdfDataRangeTransport
+ });
break;
case "range":
pdfDataRangeTransport.onDataRange(args.begin, args.chunk);
@@ -1858,21 +1850,26 @@ class ExternalServices extends BaseExternalServices {
pdfDataRangeTransport?.onDataProgressiveDone();
break;
case "progress":
- callbacks.onProgress(args.loaded, args.total);
+ viewerApp.progress(args.loaded / args.total);
break;
case "complete":
if (!args.data) {
- callbacks.onError(args.errorCode);
+ viewerApp._documentError(null, {
+ message: args.errorCode
+ });
break;
}
- callbacks.onOpenWithData(args.data, args.filename);
+ viewerApp.open({
+ data: args.data,
+ filename: args.filename
+ });
break;
}
});
FirefoxCom.request("initPassiveLoading", null);
}
reportTelemetry(data) {
- FirefoxCom.request("reportTelemetry", JSON.stringify(data));
+ FirefoxCom.request("reportTelemetry", data);
}
updateEditorStates(data) {
FirefoxCom.request("updateEditorStates", data);
@@ -2150,7 +2147,8 @@ class AnnotationEditorParams {
editorInkThickness,
editorInkOpacity,
editorStampAddImage,
- editorFreeHighlightThickness
+ editorFreeHighlightThickness,
+ editorHighlightShowAll
}) {
const dispatchEvent = (typeStr, value) => {
this.eventBus.dispatch("switchannotationeditorparams", {
@@ -2180,6 +2178,11 @@ class AnnotationEditorParams {
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) {
@@ -2204,6 +2207,9 @@ class AnnotationEditorParams {
case AnnotationEditorParamsType.HIGHLIGHT_FREE:
editorFreeHighlightThickness.disabled = !value;
break;
+ case AnnotationEditorParamsType.HIGHLIGHT_SHOW_ALL:
+ editorHighlightShowAll.setAttribute("aria-pressed", value);
+ break;
}
}
});
@@ -4690,7 +4696,9 @@ class PDFLayerViewer extends BaseTreeViewer {
return;
}
const pdfDocument = this._pdfDocument;
- const optionalContentConfig = await (promise || pdfDocument.getOptionalContentConfig());
+ const optionalContentConfig = await (promise || pdfDocument.getOptionalContentConfig({
+ intent: "display"
+ }));
if (pdfDocument !== this._pdfDocument) {
return;
}
@@ -5429,14 +5437,15 @@ class FirefoxPrintService {
pagesOverview,
printContainer,
printResolution,
- optionalContentConfigPromise = null,
printAnnotationStoragePromise = null
}) {
this.pdfDocument = pdfDocument;
this.pagesOverview = pagesOverview;
this.printContainer = printContainer;
this._printResolution = printResolution || 150;
- this._optionalContentConfigPromise = optionalContentConfigPromise || pdfDocument.getOptionalContentConfig();
+ this._optionalContentConfigPromise = pdfDocument.getOptionalContentConfig({
+ intent: "print"
+ });
this._printAnnotationStoragePromise = printAnnotationStoragePromise || Promise.resolve();
}
layout() {
@@ -6680,7 +6689,9 @@ class PDFThumbnailViewer {
return;
}
const firstPagePromise = pdfDocument.getPage(1);
- const optionalContentConfigPromise = pdfDocument.getOptionalContentConfig();
+ const optionalContentConfigPromise = pdfDocument.getOptionalContentConfig({
+ intent: "display"
+ });
firstPagePromise.then(firstPdfPage => {
const pagesCount = pdfDocument.numPages;
const viewport = firstPdfPage.getViewport({
@@ -6770,10 +6781,10 @@ class PDFThumbnailViewer {
class AnnotationEditorLayerBuilder {
#annotationLayer = null;
#drawLayer = null;
+ #onAppend = null;
#textLayer = null;
#uiManager;
constructor(options) {
- this.pageDiv = options.pageDiv;
this.pdfPage = options.pdfPage;
this.accessibilityManager = options.accessibilityManager;
this.l10n = options.l10n;
@@ -6784,6 +6795,7 @@ class AnnotationEditorLayerBuilder {
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") {
@@ -6804,10 +6816,9 @@ class AnnotationEditorLayerBuilder {
}
const div = this.div = document.createElement("div");
div.className = "annotationEditorLayer";
- div.tabIndex = 0;
div.hidden = true;
div.dir = this.#uiManager.direction;
- this.pageDiv.append(div);
+ this.#onAppend?.(div);
this.annotationEditorLayer = new AnnotationEditorLayer({
uiManager: this.#uiManager,
div,
@@ -6833,9 +6844,7 @@ class AnnotationEditorLayerBuilder {
if (!this.div) {
return;
}
- this.pageDiv = null;
this.annotationEditorLayer.destroy();
- this.div.remove();
}
hide() {
if (!this.div) {
@@ -6844,7 +6853,7 @@ class AnnotationEditorLayerBuilder {
this.div.hidden = true;
}
show() {
- if (!this.div || this.annotationEditorLayer.isEmpty) {
+ if (!this.div || this.annotationEditorLayer.isInvisible) {
return;
}
this.div.hidden = false;
@@ -6855,9 +6864,9 @@ class AnnotationEditorLayerBuilder {
class AnnotationLayerBuilder {
+ #onAppend = null;
#onPresentationModeChanged = null;
constructor({
- pageDiv,
pdfPage,
linkService,
downloadManager,
@@ -6868,9 +6877,9 @@ class AnnotationLayerBuilder {
hasJSActionsPromise = null,
fieldObjectsPromise = null,
annotationCanvasMap = null,
- accessibilityManager = null
+ accessibilityManager = null,
+ onAppend = null
}) {
- this.pageDiv = pageDiv;
this.pdfPage = pdfPage;
this.linkService = linkService;
this.downloadManager = downloadManager;
@@ -6882,6 +6891,7 @@ class AnnotationLayerBuilder {
this._fieldObjectsPromise = fieldObjectsPromise || Promise.resolve(null);
this._annotationCanvasMap = annotationCanvasMap;
this._accessibilityManager = accessibilityManager;
+ this.#onAppend = onAppend;
this.annotationLayer = null;
this.div = null;
this._cancelled = false;
@@ -6907,7 +6917,7 @@ class AnnotationLayerBuilder {
}
const div = this.div = document.createElement("div");
div.className = "annotationLayer";
- this.pageDiv.append(div);
+ this.#onAppend?.(div);
if (annotations.length === 0) {
this.hide();
return;
@@ -7501,13 +7511,15 @@ class TextHighlighter {
class TextLayerBuilder {
#enablePermissions = false;
+ #onAppend = null;
#rotation = 0;
#scale = 0;
#textContentSource = null;
constructor({
highlighter = null,
accessibilityManager = null,
- enablePermissions = false
+ enablePermissions = false,
+ onAppend = null
}) {
this.textContentItemsStr = [];
this.renderingDone = false;
@@ -7517,8 +7529,9 @@ class TextLayerBuilder {
this.highlighter = highlighter;
this.accessibilityManager = accessibilityManager;
this.#enablePermissions = enablePermissions === true;
- this.onAppend = null;
+ this.#onAppend = onAppend;
this.div = document.createElement("div");
+ this.div.tabIndex = 0;
this.div.className = "textLayer";
}
#finishRendering() {
@@ -7573,7 +7586,7 @@ class TextLayerBuilder {
this.#finishRendering();
this.#scale = scale;
this.#rotation = rotation;
- this.onAppend(this.div);
+ this.#onAppend?.(this.div);
this.highlighter?.enable();
this.accessibilityManager?.enable();
}
@@ -7647,8 +7660,8 @@ class TextLayerBuilder {
-const MAX_CANVAS_PIXELS = compatibilityParams.maxCanvasPixels || 16777216;
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;
#hasRestrictedScaling = false;
@@ -7664,6 +7677,7 @@ class PDFPageView {
regularAnnotations: true
};
#viewportMap = new WeakMap();
+ #layers = [null, null, null, null];
constructor(options) {
const container = options.container;
const defaultViewport = options.defaultViewport;
@@ -7680,7 +7694,7 @@ class PDFPageView {
this.#textLayerMode = options.textLayerMode ?? TextLayerMode.ENABLE;
this.#annotationMode = options.annotationMode ?? AnnotationMode.ENABLE_FORMS;
this.imageResourcesPath = options.imageResourcesPath || "";
- this.maxCanvasPixels = options.maxCanvasPixels ?? MAX_CANVAS_PIXELS;
+ this.maxCanvasPixels = options.maxCanvasPixels ?? (AppOptions.getCompat("maxCanvasPixels") || 2 ** 25);
this.pageColors = options.pageColors || null;
this.eventBus = options.eventBus;
this.renderingQueue = options.renderingQueue;
@@ -7707,6 +7721,23 @@ class PDFPageView {
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;
}
@@ -7820,7 +7851,7 @@ class PDFPageView {
} finally {
if (this.xfaLayer?.div) {
this.l10n.pause();
- this.div.append(this.xfaLayer.div);
+ this.#addLayer(this.xfaLayer.div, "xfaLayer");
this.l10n.resume();
}
this.eventBus.dispatch("xfalayerrendered", {
@@ -7932,6 +7963,10 @@ class PDFPageView {
continue;
}
node.remove();
+ const layerIndex = this.#layers.indexOf(node);
+ if (layerIndex >= 0) {
+ this.#layers[layerIndex] = null;
+ }
}
div.removeAttribute("data-loaded");
if (annotationLayerNode) {
@@ -8191,19 +8226,20 @@ class PDFPageView {
this.renderingState = RenderingStates.RUNNING;
const canvasWrapper = document.createElement("div");
canvasWrapper.classList.add("canvasWrapper");
- div.append(canvasWrapper);
+ canvasWrapper.setAttribute("aria-hidden", true);
+ this.#addLayer(canvasWrapper, "canvasWrapper");
if (!this.textLayer && this.#textLayerMode !== TextLayerMode.DISABLE && !pdfPage.isPureXfa) {
this._accessibilityManager ||= new TextAccessibilityManager();
this.textLayer = new TextLayerBuilder({
highlighter: this._textHighlighter,
accessibilityManager: this._accessibilityManager,
- enablePermissions: this.#textLayerMode === TextLayerMode.ENABLE_PERMISSIONS
+ enablePermissions: this.#textLayerMode === TextLayerMode.ENABLE_PERMISSIONS,
+ onAppend: textLayerDiv => {
+ this.l10n.pause();
+ this.#addLayer(textLayerDiv, "textLayer");
+ this.l10n.resume();
+ }
});
- this.textLayer.onAppend = textLayerDiv => {
- this.l10n.pause();
- this.div.append(textLayerDiv);
- this.l10n.resume();
- };
}
if (!this.annotationLayer && this.#annotationMode !== AnnotationMode.DISABLE) {
const {
@@ -8216,7 +8252,6 @@ class PDFPageView {
} = this.#layerProperties;
this._annotationCanvasMap ||= new Map();
this.annotationLayer = new AnnotationLayerBuilder({
- pageDiv: div,
pdfPage,
annotationStorage,
imageResourcesPath: this.imageResourcesPath,
@@ -8227,7 +8262,10 @@ class PDFPageView {
hasJSActionsPromise,
fieldObjectsPromise,
annotationCanvasMap: this._annotationCanvasMap,
- accessibilityManager: this._accessibilityManager
+ accessibilityManager: this._accessibilityManager,
+ onAppend: annotationLayerDiv => {
+ this.#addLayer(annotationLayerDiv, "annotationLayer");
+ }
});
}
const renderContinueCallback = cont => {
@@ -8316,13 +8354,15 @@ class PDFPageView {
if (!this.annotationEditorLayer) {
this.annotationEditorLayer = new AnnotationEditorLayerBuilder({
uiManager: annotationEditorUIManager,
- pageDiv: div,
pdfPage,
l10n,
accessibilityManager: this._accessibilityManager,
annotationLayer: this.annotationLayer?.annotationLayer,
textLayer: this.textLayer,
- drawLayer: this.drawLayer.getDrawLayer()
+ drawLayer: this.drawLayer.getDrawLayer(),
+ onAppend: annotationEditorLayerDiv => {
+ this.#addLayer(annotationEditorLayerDiv, "annotationEditorLayer");
+ }
});
}
this.#renderAnnotationEditorLayer();
@@ -8447,6 +8487,7 @@ class PDFViewer {
#annotationMode = AnnotationMode.ENABLE_FORMS;
#containerTopLeft = null;
#copyCallbackBound = null;
+ #enableHighlightFloatingButton = false;
#enablePermissions = false;
#mlManager = null;
#getAllTextInProgress = false;
@@ -8459,7 +8500,7 @@ class PDFViewer {
#scaleTimeoutId = null;
#textLayerMode = TextLayerMode.ENABLE;
constructor(options) {
- const viewerVersion = "4.1.249";
+ const viewerVersion = "4.1.342";
if (version !== viewerVersion) {
throw new Error(`The API version "${version}" does not match the Viewer version "${viewerVersion}".`);
}
@@ -8479,6 +8520,7 @@ class PDFViewer {
this.#annotationMode = options.annotationMode ?? AnnotationMode.ENABLE_FORMS;
this.#annotationEditorMode = options.annotationEditorMode ?? AnnotationEditorType.NONE;
this.#annotationEditorHighlightColors = options.annotationEditorHighlightColors || null;
+ this.#enableHighlightFloatingButton = options.enableHighlightFloatingButton === true;
this.imageResourcesPath = options.imageResourcesPath || "";
this.enablePrintAutoRotate = options.enablePrintAutoRotate || false;
this.maxCanvasPixels = options.maxCanvasPixels;
@@ -8789,7 +8831,9 @@ class PDFViewer {
}
const pagesCount = pdfDocument.numPages;
const firstPagePromise = pdfDocument.getPage(1);
- const optionalContentConfigPromise = pdfDocument.getOptionalContentConfig();
+ const optionalContentConfigPromise = pdfDocument.getOptionalContentConfig({
+ intent: "display"
+ });
const permissionsPromise = this.#enablePermissions ? pdfDocument.getPermissions() : Promise.resolve();
if (pagesCount > PagesCountLimit.FORCE_SCROLL_MODE_PAGE) {
console.warn("Forcing PAGE-scrolling for performance reasons, given the length of the document.");
@@ -8845,7 +8889,7 @@ class PDFViewer {
if (pdfDocument.isPureXfa) {
console.warn("Warning: XFA-editing is not implemented.");
} else if (isValidAnnotationEditorMode(mode)) {
- this.#annotationEditorUIManager = new AnnotationEditorUIManager(this.container, this.viewer, this.#altTextManager, this.eventBus, pdfDocument, this.pageColors, this.#annotationEditorHighlightColors, this.#mlManager);
+ this.#annotationEditorUIManager = new AnnotationEditorUIManager(this.container, this.viewer, this.#altTextManager, this.eventBus, pdfDocument, this.pageColors, this.#annotationEditorHighlightColors, this.#enableHighlightFloatingButton, this.#mlManager);
this.eventBus.dispatch("annotationeditoruimanager", {
source: this,
uiManager: this.#annotationEditorUIManager
@@ -9506,7 +9550,9 @@ class PDFViewer {
}
if (!this._optionalContentConfigPromise) {
console.error("optionalContentConfigPromise: Not initialized yet.");
- return this.pdfDocument.getOptionalContentConfig();
+ return this.pdfDocument.getOptionalContentConfig({
+ intent: "display"
+ });
}
return this._optionalContentConfigPromise;
}
@@ -10694,6 +10740,7 @@ const PDFViewerApplication = {
annotationMode: AppOptions.get("annotationMode"),
annotationEditorMode,
annotationEditorHighlightColors: AppOptions.get("highlightEditorColors"),
+ enableHighlightFloatingButton: AppOptions.get("enableHighlightFloatingButton"),
imageResourcesPath: AppOptions.get("imageResourcesPath"),
enablePrintAutoRotate: AppOptions.get("enablePrintAutoRotate"),
maxCanvasPixels: AppOptions.get("maxCanvasPixels"),
@@ -10833,40 +10880,8 @@ const PDFViewerApplication = {
if (this.supportsIntegratedFind) {
appConfig.toolbar?.viewFind?.classList.add("hidden");
}
- this.initPassiveLoading(file);
- const {
- mainContainer
- } = appConfig;
- ({
- scrollTop: this._lastScrollTop,
- scrollLeft: this._lastScrollLeft
- } = mainContainer);
- const scroll = () => {
- if (this._lastScrollTop === mainContainer.scrollTop && this._lastScrollLeft === mainContainer.scrollLeft) {
- return;
- }
- mainContainer.removeEventListener("scroll", scroll, {
- passive: true
- });
- this._isScrolling = true;
- const scrollend = () => {
- ({
- scrollTop: this._lastScrollTop,
- scrollLeft: this._lastScrollLeft
- } = mainContainer);
- this._isScrolling = false;
- mainContainer.addEventListener("scroll", scroll, {
- passive: true
- });
- mainContainer.removeEventListener("scrollend", scrollend);
- mainContainer.removeEventListener("blur", scrollend);
- };
- mainContainer.addEventListener("scrollend", scrollend);
- mainContainer.addEventListener("blur", scrollend);
- };
- mainContainer.addEventListener("scroll", scroll, {
- passive: true
- });
+ this.setTitleUsingUrl(file, file);
+ this.externalServices.initPassiveLoading();
},
get externalServices() {
return shadow(this, "externalServices", new ExternalServices());
@@ -10939,45 +10954,12 @@ const PDFViewerApplication = {
return shadow(this, "supportsMouseWheelZoomMetaKey", AppOptions.get("supportsMouseWheelZoomMetaKey"));
},
get supportsCaretBrowsingMode() {
- return shadow(this, "supportsCaretBrowsingMode", AppOptions.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);
},
- initPassiveLoading(file) {
- this.setTitleUsingUrl(file, file);
- this.externalServices.initPassiveLoading({
- onOpenWithTransport: range => {
- this.open({
- range
- });
- },
- onOpenWithData: (data, contentDispositionFilename) => {
- if (isPdfFile(contentDispositionFilename)) {
- this._contentDispositionFilename = contentDispositionFilename;
- }
- this.open({
- data
- });
- },
- onOpenWithURL: (url, length, originalUrl) => {
- this.open({
- url,
- length,
- originalUrl
- });
- },
- onError: err => {
- this.l10n.get("pdfjs-loading-error").then(msg => {
- this._documentError(msg, err);
- });
- },
- onProgress: (loaded, total) => {
- this.progress(loaded / total);
- }
- });
- },
setTitleUsingUrl(url = "", downloadUrl = null) {
this.url = url;
this.baseUrl = url.split("#", 1)[0];
@@ -11065,6 +11047,9 @@ const PDFViewerApplication = {
}
const workerParams = AppOptions.getAll(OptionKind.WORKER);
Object.assign(GlobalWorkerOptions, workerParams);
+ if (args.data && isPdfFile(args.filename)) {
+ this._contentDispositionFilename = args.filename;
+ }
AppOptions.set("docBaseUrl", this.baseUrl);
const apiParams = AppOptions.getAll(OptionKind.API);
const loadingTask = getDocument({
@@ -11100,10 +11085,9 @@ const PDFViewerApplication = {
} else if (reason instanceof UnexpectedResponseException) {
key = "pdfjs-unexpected-response-error";
}
- return this.l10n.get(key).then(msg => {
- this._documentError(msg, {
- message: reason?.message
- });
+ return this._documentError(key, {
+ message: reason.message
+ }).then(() => {
throw reason;
});
});
@@ -11167,21 +11151,17 @@ const PDFViewerApplication = {
this.download(options);
}
},
- openInExternalApp() {
- this.downloadOrSave({
- openInExternalApp: true
- });
- },
- _documentError(message, moreInfo = null) {
+ async _documentError(key, moreInfo = null) {
this._unblockDocumentLoadEvent();
- this._otherError(message, moreInfo);
+ const message = await this._otherError(key || "pdfjs-loading-error", moreInfo);
this.eventBus.dispatch("documenterror", {
source: this,
message,
reason: moreInfo?.message ?? null
});
},
- _otherError(message, moreInfo = 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}`);
@@ -11197,6 +11177,7 @@ const PDFViewerApplication = {
}
}
console.error(`${message}\n\n${moreInfoText.join("\n")}`);
+ return message;
},
progress(level) {
if (!this.loadingBar || this.downloadComplete) {
@@ -11321,10 +11302,8 @@ const PDFViewerApplication = {
this._unblockDocumentLoadEvent();
this._initializeAutoPrint(pdfDocument, openActionPromise);
}, reason => {
- this.l10n.get("pdfjs-loading-error").then(msg => {
- this._documentError(msg, {
- message: reason?.message
- });
+ this._documentError("pdfjs-loading-error", {
+ message: reason.message
});
});
onePageRendered.then(data => {
@@ -11603,9 +11582,7 @@ const PDFViewerApplication = {
return;
}
if (!this.supportsPrinting) {
- this.l10n.get("pdfjs-printing-not-supported").then(msg => {
- this._otherError(msg);
- });
+ this._otherError("pdfjs-printing-not-supported");
return;
}
if (!this.pdfViewer.pageViewsReady) {
@@ -11619,7 +11596,6 @@ const PDFViewerApplication = {
pagesOverview: this.pdfViewer.getPagesOverview(),
printContainer: this.appConfig.printContainer,
printResolution: AppOptions.get("printResolution"),
- optionalContentConfigPromise: this.pdfViewer.optionalContentConfigPromise,
printAnnotationStoragePromise: this._printAnnotationStoragePromise
});
this.forceRendering();
@@ -11688,7 +11664,6 @@ const PDFViewerApplication = {
eventBus._on("switchannotationeditorparams", webViewerSwitchAnnotationEditorParams);
eventBus._on("print", webViewerPrint);
eventBus._on("download", webViewerDownload);
- eventBus._on("openinexternalapp", webViewerOpenInExternalApp);
eventBus._on("firstpage", webViewerFirstPage);
eventBus._on("lastpage", webViewerLastPage);
eventBus._on("nextpage", webViewerNextPage);
@@ -11720,7 +11695,10 @@ const PDFViewerApplication = {
bindWindowEvents() {
const {
eventBus,
- _boundEvents
+ _boundEvents,
+ appConfig: {
+ mainContainer
+ }
} = this;
function addWindowResolutionChange(evt = null) {
if (evt) {
@@ -11780,12 +11758,79 @@ const PDFViewerApplication = {
window.addEventListener("beforeprint", _boundEvents.windowBeforePrint);
window.addEventListener("afterprint", _boundEvents.windowAfterPrint);
window.addEventListener("updatefromsandbox", _boundEvents.windowUpdateFromSandbox);
+ ({
+ scrollTop: this._lastScrollTop,
+ scrollLeft: this._lastScrollLeft
+ } = mainContainer);
+ const scrollend = _boundEvents.mainContainerScrollend = () => {
+ ({
+ scrollTop: this._lastScrollTop,
+ scrollLeft: this._lastScrollLeft
+ } = mainContainer);
+ this._isScrolling = false;
+ mainContainer.addEventListener("scroll", scroll, {
+ passive: true
+ });
+ mainContainer.removeEventListener("scrollend", scrollend);
+ mainContainer.removeEventListener("blur", scrollend);
+ };
+ const scroll = _boundEvents.mainContainerScroll = () => {
+ if (this._isCtrlKeyDown || this._lastScrollTop === mainContainer.scrollTop && this._lastScrollLeft === mainContainer.scrollLeft) {
+ return;
+ }
+ mainContainer.removeEventListener("scroll", scroll, {
+ passive: true
+ });
+ this._isScrolling = true;
+ mainContainer.addEventListener("scrollend", scrollend);
+ mainContainer.addEventListener("blur", scrollend);
+ };
+ mainContainer.addEventListener("scroll", scroll, {
+ passive: true
+ });
},
unbindEvents() {
throw new Error("Not implemented: unbindEvents");
},
unbindWindowEvents() {
- throw new Error("Not implemented: unbindWindowEvents");
+ const {
+ _boundEvents,
+ appConfig: {
+ mainContainer
+ }
+ } = this;
+ window.removeEventListener("visibilitychange", webViewerVisibilityChange);
+ window.removeEventListener("wheel", webViewerWheel, {
+ passive: false
+ });
+ window.removeEventListener("touchstart", webViewerTouchStart, {
+ passive: false
+ });
+ window.removeEventListener("touchmove", webViewerTouchMove, {
+ passive: false
+ });
+ window.removeEventListener("touchend", webViewerTouchEnd, {
+ passive: false
+ });
+ window.removeEventListener("click", webViewerClick);
+ window.removeEventListener("keydown", webViewerKeyDown);
+ window.removeEventListener("keyup", webViewerKeyUp);
+ window.removeEventListener("resize", _boundEvents.windowResize);
+ window.removeEventListener("hashchange", _boundEvents.windowHashChange);
+ window.removeEventListener("beforeprint", _boundEvents.windowBeforePrint);
+ window.removeEventListener("afterprint", _boundEvents.windowAfterPrint);
+ window.removeEventListener("updatefromsandbox", _boundEvents.windowUpdateFromSandbox);
+ mainContainer.removeEventListener("scroll", _boundEvents.mainContainerScroll);
+ mainContainer.removeEventListener("scrollend", _boundEvents.mainContainerScrollend);
+ mainContainer.removeEventListener("blur", _boundEvents.mainContainerScrollend);
+ _boundEvents.removeWindowResolutionChange?.();
+ _boundEvents.windowResize = null;
+ _boundEvents.windowHashChange = null;
+ _boundEvents.windowBeforePrint = null;
+ _boundEvents.windowAfterPrint = null;
+ _boundEvents.windowUpdateFromSandbox = null;
+ _boundEvents.mainContainerScroll = null;
+ _boundEvents.mainContainerScrollend = null;
},
_accumulateTicks(ticks, prop) {
if (this[prop] > 0 && ticks < 0 || this[prop] < 0 && ticks > 0) {
@@ -11868,9 +11913,7 @@ function webViewerPageRendered({
}
}
if (error) {
- PDFViewerApplication.l10n.get("pdfjs-rendering-error").then(msg => {
- PDFViewerApplication._otherError(msg, error);
- });
+ PDFViewerApplication._otherError("pdfjs-rendering-error", error);
}
}
function webViewerPageMode({
@@ -12000,9 +12043,6 @@ function webViewerPrint() {
function webViewerDownload() {
PDFViewerApplication.downloadOrSave();
}
-function webViewerOpenInExternalApp() {
- PDFViewerApplication.openInExternalApp();
-}
function webViewerFirstPage() {
PDFViewerApplication.page = 1;
}
@@ -12591,8 +12631,8 @@ function webViewerReportTelemetry({
-const pdfjsVersion = "4.1.249";
-const pdfjsBuild = "d07f37f44";
+const pdfjsVersion = "4.1.342";
+const pdfjsBuild = "e384df6f1";
const AppConstants = null;
window.PDFViewerApplication = PDFViewerApplication;
window.PDFViewerApplicationConstants = AppConstants;
@@ -12718,7 +12758,8 @@ function getViewerConfiguration() {
editorInkThickness: document.getElementById("editorInkThickness"),
editorInkOpacity: document.getElementById("editorInkOpacity"),
editorStampAddImage: document.getElementById("editorStampAddImage"),
- editorFreeHighlightThickness: document.getElementById("editorFreeHighlightThickness")
+ editorFreeHighlightThickness: document.getElementById("editorFreeHighlightThickness"),
+ editorHighlightShowAll: document.getElementById("editorHighlightShowAll")
},
printContainer: document.getElementById("printContainer")
};
diff --git a/toolkit/components/pdfjs/jar.mn b/toolkit/components/pdfjs/jar.mn
index e41afee7e1..52199085ad 100644
--- a/toolkit/components/pdfjs/jar.mn
+++ b/toolkit/components/pdfjs/jar.mn
@@ -18,7 +18,6 @@ pdfjs.jar:
content/web/viewer.css (content/web/viewer-geckoview.css)
content/web/viewer.mjs (content/web/viewer-geckoview.mjs)
content/web/images/gv-toolbarButton-download.svg (content/web/images/gv-toolbarButton-download.svg)
- content/web/images/gv-toolbarButton-openinapp.svg (content/web/images/gv-toolbarButton-openinapp.svg)
#else
content/PdfjsParent.sys.mjs (content/PdfjsParent.sys.mjs)
content/PdfjsChild.sys.mjs (content/PdfjsChild.sys.mjs)
diff --git a/toolkit/components/pdfjs/moz.yaml b/toolkit/components/pdfjs/moz.yaml
index 4d22740080..bd85f67f5a 100644
--- a/toolkit/components/pdfjs/moz.yaml
+++ b/toolkit/components/pdfjs/moz.yaml
@@ -20,8 +20,8 @@ origin:
# Human-readable identifier for this version/release
# Generally "version NNN", "tag SSS", "bookmark SSS"
- release: 29c493d36bb5a44251804e9204973211172e0412 (2024-03-01T12:08:44Z).
- revision: 29c493d36bb5a44251804e9204973211172e0412
+ release: e384df6f16b9ad1c55d1bc325bcae7e096ef3f9b (2024-03-27T19:54:30Z).
+ revision: e384df6f16b9ad1c55d1bc325bcae7e096ef3f9b
# The package's license, where possible using the mnemonic from
# https://spdx.org/licenses/
diff --git a/toolkit/components/pdfjs/test/browser.toml b/toolkit/components/pdfjs/test/browser.toml
index 4d045fd33e..b60d554c24 100644
--- a/toolkit/components/pdfjs/test/browser.toml
+++ b/toolkit/components/pdfjs/test/browser.toml
@@ -4,6 +4,8 @@ support-files = [
"head.js",
]
+["browser_pdfjs_caret_browsing_mode.js"]
+
["browser_pdfjs_download_button.js"]
["browser_pdfjs_editing_contextmenu.js"]
diff --git a/toolkit/components/pdfjs/test/browser_pdfjs_caret_browsing_mode.js b/toolkit/components/pdfjs/test/browser_pdfjs_caret_browsing_mode.js
new file mode 100644
index 0000000000..a7b2a518d9
--- /dev/null
+++ b/toolkit/components/pdfjs/test/browser_pdfjs_caret_browsing_mode.js
@@ -0,0 +1,82 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const RELATIVE_DIR = "toolkit/components/pdfjs/test/";
+const TESTROOT = "https://example.com/browser/" + RELATIVE_DIR;
+const pdfUrl = TESTROOT + "file_pdfjs_test.pdf";
+const caretBrowsingModePref = "accessibility.browsewithcaret";
+
+// Test telemetry.
+add_task(async function test() {
+ let mimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
+ let handlerInfo = mimeService.getFromTypeAndExtension(
+ "application/pdf",
+ "pdf"
+ );
+
+ // Make sure pdf.js is the default handler.
+ is(
+ handlerInfo.alwaysAskBeforeHandling,
+ false,
+ "pdf handler defaults to always-ask is false"
+ );
+ is(
+ handlerInfo.preferredAction,
+ Ci.nsIHandlerInfo.handleInternally,
+ "pdf handler defaults to internal"
+ );
+
+ info("Pref action: " + handlerInfo.preferredAction);
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:blank" },
+ async function test_caret_browsing_mode(browser) {
+ await waitForPdfJS(browser, pdfUrl);
+
+ let promise = BrowserTestUtils.waitForContentEvent(
+ browser,
+ "updatedPreference",
+ false,
+ null,
+ true
+ );
+ await SpecialPowers.pushPrefEnv({
+ set: [[caretBrowsingModePref, true]],
+ });
+ await promise;
+ await TestUtils.waitForTick();
+
+ await SpecialPowers.spawn(browser, [], async function () {
+ const viewer = content.wrappedJSObject.PDFViewerApplication;
+ Assert.ok(
+ viewer.supportsCaretBrowsingMode,
+ "Caret browsing mode is supported"
+ );
+ });
+
+ promise = BrowserTestUtils.waitForContentEvent(
+ browser,
+ "updatedPreference",
+ false,
+ null,
+ true
+ );
+ await SpecialPowers.popPrefEnv();
+ await promise;
+ await TestUtils.waitForTick();
+
+ await SpecialPowers.spawn(browser, [], async function () {
+ const viewer = content.wrappedJSObject.PDFViewerApplication;
+ Assert.ok(
+ !viewer.supportsCaretBrowsingMode,
+ "Caret browsing mode isn't supported"
+ );
+ });
+
+ await SpecialPowers.spawn(browser, [], async function () {
+ const viewer = content.wrappedJSObject.PDFViewerApplication;
+ await viewer.close();
+ });
+ }
+ );
+});
diff --git a/toolkit/components/pdfjs/test/browser_pdfjs_download_button.js b/toolkit/components/pdfjs/test/browser_pdfjs_download_button.js
index 1e3c00620c..1470d80f0e 100644
--- a/toolkit/components/pdfjs/test/browser_pdfjs_download_button.js
+++ b/toolkit/components/pdfjs/test/browser_pdfjs_download_button.js
@@ -7,7 +7,7 @@ const RELATIVE_DIR = "toolkit/components/pdfjs/test/";
const TESTROOT = "https://example.com/browser/" + RELATIVE_DIR;
var MockFilePicker = SpecialPowers.MockFilePicker;
-MockFilePicker.init(window);
+MockFilePicker.init(window.browsingContext);
MockFilePicker.returnValue = MockFilePicker.returnOK;
var tempDir;
diff --git a/toolkit/components/pdfjs/test/browser_pdfjs_editing_contextmenu.js b/toolkit/components/pdfjs/test/browser_pdfjs_editing_contextmenu.js
index d857bb6aac..779a3a6ad4 100644
--- a/toolkit/components/pdfjs/test/browser_pdfjs_editing_contextmenu.js
+++ b/toolkit/components/pdfjs/test/browser_pdfjs_editing_contextmenu.js
@@ -32,9 +32,9 @@ async function openContextMenuAt(browser, x, y) {
* Open a context menu and get the pdfjs entries
* @param {Object} browser
* @param {Object} box
- * @returns {Map<string,HTMLElement>} the pdfjs menu entries.
+ * @returns {Promise<Map<string,HTMLElement>>} the pdfjs menu entries.
*/
-async function getContextMenuItems(browser, box) {
+function getContextMenuItems(browser, box) {
return new Promise(resolve => {
setTimeout(async () => {
const { x, y, width, height } = box;
@@ -48,6 +48,7 @@ async function getContextMenuItems(browser, box) {
"context-pdfjs-delete",
"context-pdfjs-selectall",
"context-sep-pdfjs-selectall",
+ "context-pdfjs-highlight-selection",
];
await openContextMenuAt(browser, x + width / 2, y + height / 2);
@@ -68,7 +69,7 @@ async function getContextMenuItems(browser, box) {
* and returs the pdfjs menu entries.
* @param {Object} browser
* @param {string} selector
- * @returns {Map<string,HTMLElement>} the pdfjs menu entries.
+ * @returns {Promise<Map<string,HTMLElement>>} the pdfjs menu entries.
*/
async function getContextMenuItemsOn(browser, selector) {
const box = await SpecialPowers.spawn(
@@ -129,35 +130,36 @@ function assertMenuitems(menuitems, expected) {
elmt =>
!elmt.id.includes("-sep-") &&
!elmt.hidden &&
- elmt.getAttribute("disabled") === "false"
+ ["", "false"].includes(elmt.getAttribute("disabled"))
)
.map(elmt => elmt.id),
expected
);
}
-// Text copy, paste, undo, redo, delete and select all in using the context
-// menu.
-add_task(async function test() {
- let mimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
- let handlerInfo = mimeService.getFromTypeAndExtension(
- "application/pdf",
- "pdf"
- );
-
- // Make sure pdf.js is the default handler.
- is(
- handlerInfo.alwaysAskBeforeHandling,
- false,
- "pdf handler defaults to always-ask is false"
- );
- is(
- handlerInfo.preferredAction,
- Ci.nsIHandlerInfo.handleInternally,
- "pdf handler defaults to internal"
+async function waitAndCheckEmptyContextMenu(browser) {
+ // check that PDF is opened with internal viewer
+ await waitForPdfJSAllLayers(browser, TESTROOT + "file_pdfjs_test.pdf", [
+ ["annotationEditorLayer", "annotationLayer", "textLayer", "canvasWrapper"],
+ ["annotationEditorLayer", "textLayer", "canvasWrapper"],
+ ]);
+
+ const spanBox = await getSpanBox(browser, "and found references");
+ const menuitems = await getContextMenuItems(browser, spanBox);
+
+ // Nothing have been edited, hence the context menu doesn't contain any
+ // pdf entries.
+ Assert.ok(
+ [...menuitems.values()].every(elmt => elmt.hidden),
+ "No visible pdf menuitem"
);
+ await hideContextMenu(browser);
+}
- info("Pref action: " + handlerInfo.preferredAction);
+// Text copy, paste, undo, redo, delete and select all in using the context
+// menu.
+add_task(async function test_copy_paste_undo_redo() {
+ makePDFJSHandler();
await BrowserTestUtils.withNewTab(
{ gBrowser, url: "about:blank" },
@@ -168,27 +170,8 @@ add_task(async function test() {
set: [["pdfjs.annotationEditorMode", 0]],
});
- // check that PDF is opened with internal viewer
- await waitForPdfJSAllLayers(browser, TESTROOT + "file_pdfjs_test.pdf", [
- [
- "annotationEditorLayer",
- "annotationLayer",
- "textLayer",
- "canvasWrapper",
- ],
- ["annotationEditorLayer", "textLayer", "canvasWrapper"],
- ]);
-
+ await waitAndCheckEmptyContextMenu(browser);
const spanBox = await getSpanBox(browser, "and found references");
- let menuitems = await getContextMenuItems(browser, spanBox);
-
- // Nothing have been edited, hence the context menu doesn't contain any
- // pdf entries.
- Assert.ok(
- [...menuitems.values()].every(elmt => elmt.hidden),
- "No visible pdf menuitem"
- );
- await hideContextMenu(browser);
await enableEditor(browser, "FreeText");
await addFreeText(browser, "hello", spanBox);
@@ -211,7 +194,7 @@ add_task(async function test() {
Assert.equal(await countElements(browser, ".selectedEditor"), 0);
- menuitems = await getContextMenuItems(browser, spanBox);
+ let menuitems = await getContextMenuItems(browser, spanBox);
assertMenuitems(menuitems, [
"context-pdfjs-undo", // Last created editor is undoable
"context-pdfjs-selectall", // and selectable.
@@ -374,6 +357,73 @@ add_task(async function test() {
await SpecialPowers.spawn(browser, [], async function () {
var viewer = content.wrappedJSObject.PDFViewerApplication;
+ viewer.pdfDocument.annotationStorage.resetModified();
+ await viewer.close();
+ });
+ }
+ );
+});
+
+add_task(async function test_highlight_selection() {
+ makePDFJSHandler();
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:blank" },
+ async function (browser) {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["pdfjs.annotationEditorMode", 0],
+ ["pdfjs.enableHighlightEditor", true],
+ ],
+ });
+
+ await waitAndCheckEmptyContextMenu(browser);
+ const spanBox = await getSpanBox(browser, "and found references");
+
+ const changePromise = BrowserTestUtils.waitForContentEvent(
+ browser,
+ "annotationeditorstateschanged",
+ false,
+ null,
+ true
+ );
+ await clickAt(
+ browser,
+ spanBox.x + spanBox.width / 2,
+ spanBox.y + spanBox.height / 2,
+ 2
+ );
+ await changePromise;
+ await TestUtils.waitForTick();
+
+ const mozBox = await getSpanBox(browser, "Mozilla automated testing");
+ const menuitems = await getContextMenuItems(browser, mozBox);
+
+ assertMenuitems(menuitems, ["context-pdfjs-highlight-selection"]);
+
+ const telemetryPromise = BrowserTestUtils.waitForContentEvent(
+ browser,
+ "reporttelemetry",
+ false,
+ null,
+ true
+ );
+ await clickOnItem(
+ browser,
+ menuitems,
+ "context-pdfjs-highlight-selection"
+ );
+ await telemetryPromise;
+
+ Assert.equal(
+ await countElements(browser, ".highlightEditor"),
+ 1,
+ "An highlight editor must have been added"
+ );
+
+ await SpecialPowers.spawn(browser, [], async function () {
+ var viewer = content.wrappedJSObject.PDFViewerApplication;
+ viewer.pdfDocument.annotationStorage.resetModified();
await viewer.close();
});
}
diff --git a/toolkit/components/pdfjs/test/browser_pdfjs_nonpdf_filename.js b/toolkit/components/pdfjs/test/browser_pdfjs_nonpdf_filename.js
index ee08cd45d1..e1723bef6a 100644
--- a/toolkit/components/pdfjs/test/browser_pdfjs_nonpdf_filename.js
+++ b/toolkit/components/pdfjs/test/browser_pdfjs_nonpdf_filename.js
@@ -13,7 +13,7 @@ const LINK_PAGE_URL = TESTROOT + "file_pdf_download_link.html";
add_task(async function test_filename_nonpdf_extension() {
var MockFilePicker = SpecialPowers.MockFilePicker;
- MockFilePicker.init(window);
+ MockFilePicker.init(window.browsingContext);
let filepickerNamePromise = new Promise(resolve => {
MockFilePicker.showCallback = function (fp) {
resolve(fp.defaultString);
diff --git a/toolkit/components/pdfjs/test/browser_pdfjs_octet_stream.js b/toolkit/components/pdfjs/test/browser_pdfjs_octet_stream.js
index d2b4fe310f..da630f726c 100644
--- a/toolkit/components/pdfjs/test/browser_pdfjs_octet_stream.js
+++ b/toolkit/components/pdfjs/test/browser_pdfjs_octet_stream.js
@@ -5,7 +5,7 @@
const TESTROOT = getRootDirectory(gTestPath).replace(
"chrome://mochitests/content/",
- "http://mochi.test:8888/"
+ "https://example.com/"
);
// Get a ref to the pdf we want to open.
diff --git a/toolkit/components/pdfjs/test/browser_pdfjs_saveas.js b/toolkit/components/pdfjs/test/browser_pdfjs_saveas.js
index a1bfc18a91..dcb77c25fe 100644
--- a/toolkit/components/pdfjs/test/browser_pdfjs_saveas.js
+++ b/toolkit/components/pdfjs/test/browser_pdfjs_saveas.js
@@ -7,7 +7,7 @@ const RELATIVE_DIR = "toolkit/components/pdfjs/test/";
const TESTROOT = "http://example.com/browser/" + RELATIVE_DIR;
var MockFilePicker = SpecialPowers.MockFilePicker;
-MockFilePicker.init(window);
+MockFilePicker.init(window.browsingContext);
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockTransfer.js",
diff --git a/toolkit/components/pdfjs/test/browser_pdfjs_stamp_telemetry.js b/toolkit/components/pdfjs/test/browser_pdfjs_stamp_telemetry.js
index b8955f77e3..2ad0179cdc 100644
--- a/toolkit/components/pdfjs/test/browser_pdfjs_stamp_telemetry.js
+++ b/toolkit/components/pdfjs/test/browser_pdfjs_stamp_telemetry.js
@@ -10,7 +10,7 @@ Services.scriptloader.loadSubScript(
);
const MockFilePicker = SpecialPowers.MockFilePicker;
-MockFilePicker.init(window);
+MockFilePicker.init(window.browsingContext);
MockFilePicker.returnValue = MockFilePicker.returnOK;
const file = new FileUtils.File(getTestFilePath("moz.png"));
MockFilePicker.setFiles([file]);
diff --git a/toolkit/components/pdfjs/test/head.js b/toolkit/components/pdfjs/test/head.js
index 04c9543b5d..99e2bc7fb7 100644
--- a/toolkit/components/pdfjs/test/head.js
+++ b/toolkit/components/pdfjs/test/head.js
@@ -426,3 +426,25 @@ async function cleanupDownloads(listId = Downloads.PUBLIC) {
await download.finalize();
}
}
+
+function makePDFJSHandler() {
+ let mimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
+ let handlerInfo = mimeService.getFromTypeAndExtension(
+ "application/pdf",
+ "pdf"
+ );
+
+ // Make sure pdf.js is the default handler.
+ is(
+ handlerInfo.alwaysAskBeforeHandling,
+ false,
+ "pdf handler defaults to always-ask is false"
+ );
+ is(
+ handlerInfo.preferredAction,
+ Ci.nsIHandlerInfo.handleInternally,
+ "pdf handler defaults to internal"
+ );
+
+ info("Pref action: " + handlerInfo.preferredAction);
+}
diff --git a/toolkit/components/pictureinpicture/content/player.js b/toolkit/components/pictureinpicture/content/player.js
index c60c2ca44e..d306494d92 100644
--- a/toolkit/components/pictureinpicture/content/player.js
+++ b/toolkit/components/pictureinpicture/content/player.js
@@ -45,7 +45,7 @@ const BOTTOM_LEFT_QUADRANT = 3;
const BOTTOM_RIGHT_QUADRANT = 4;
/**
- * Public function to be called from PictureInPicture.jsm. This is the main
+ * Public function to be called from PictureInPicture.sys.mjs. This is the main
* entrypoint for initializing the player window.
*
* @param {Number} id
@@ -61,7 +61,7 @@ function setupPlayer(id, wgp, videoRef) {
}
/**
- * Public function to be called from PictureInPicture.jsm. This update the
+ * 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
@@ -72,7 +72,7 @@ function setIsPlayingState(isPlaying) {
}
/**
- * Public function to be called from PictureInPicture.jsm. This update the
+ * 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
@@ -247,10 +247,10 @@ let Player = {
this.audioScrubbing = true;
this.handleAudioScrubbing(event.target.value);
});
- this.audioScrubber.addEventListener("change", event => {
+ this.audioScrubber.addEventListener("change", () => {
this.audioScrubbing = false;
});
- this.audioScrubber.addEventListener("pointerdown", event => {
+ this.audioScrubber.addEventListener("pointerdown", () => {
if (this.isMuted) {
this.audioScrubber.max = 1;
}
@@ -1140,7 +1140,7 @@ let Player = {
* @param {Event} event
* Event context data object
*/
- onResize(event) {
+ onResize() {
this.toggleSubtitlesSettingsPanel({ forceHide: true });
this.resizeDebouncer.disarm();
this.resizeDebouncer.arm();
@@ -1152,7 +1152,7 @@ let Player = {
* @param {Event} event
* Event context data object
*/
- onCommand(event) {
+ onCommand() {
this.closePipWindow({ reason: "shortcut" });
},
diff --git a/toolkit/components/pictureinpicture/content/player.xhtml b/toolkit/components/pictureinpicture/content/player.xhtml
index 5b6892d649..9982bf35be 100644
--- a/toolkit/components/pictureinpicture/content/player.xhtml
+++ b/toolkit/components/pictureinpicture/content/player.xhtml
@@ -6,6 +6,7 @@
<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"
>
diff --git a/toolkit/components/pictureinpicture/tests/browser_cannotTriggerFromContent.js b/toolkit/components/pictureinpicture/tests/browser_cannotTriggerFromContent.js
index 4c3d32b474..1ba93dbb8c 100644
--- a/toolkit/components/pictureinpicture/tests/browser_cannotTriggerFromContent.js
+++ b/toolkit/components/pictureinpicture/tests/browser_cannotTriggerFromContent.js
@@ -18,7 +18,7 @@ add_task(async () => {
// if we receive the PictureInPicture:Request message.
const MESSAGE = "PictureInPicture:Request";
let sawMessage = false;
- let listener = msg => {
+ let listener = () => {
sawMessage = true;
};
browser.messageManager.addMessageListener(MESSAGE, listener);
diff --git a/toolkit/components/pictureinpicture/tests/browser_closePip_pageNavigationChanges.js b/toolkit/components/pictureinpicture/tests/browser_closePip_pageNavigationChanges.js
index c64b5f2b14..b4615c42a1 100644
--- a/toolkit/components/pictureinpicture/tests/browser_closePip_pageNavigationChanges.js
+++ b/toolkit/components/pictureinpicture/tests/browser_closePip_pageNavigationChanges.js
@@ -65,7 +65,7 @@ add_task(async function test_close_pagehide() {
ok(pipWin, "Got Picture-in-Picture window.");
let pipClosed = BrowserTestUtils.domWindowClosed(pipWin);
- await SpecialPowers.spawn(browser, [{ videoID }], async args => {
+ await SpecialPowers.spawn(browser, [{ videoID }], async () => {
content.location.href = "otherpage.html";
});
@@ -92,7 +92,7 @@ add_task(async function test_open_pip_window_history_nav() {
let pipWin = await triggerPictureInPicture(browser, videoID);
ok(pipWin, "Got Picture-in-Picture window.");
- await SpecialPowers.spawn(browser, [{ videoID }], async args => {
+ await SpecialPowers.spawn(browser, [{ videoID }], async () => {
let popStatePromise = ContentTaskUtils.waitForEvent(
content,
"popstate"
diff --git a/toolkit/components/pictureinpicture/tests/browser_preserveTabPipIconOverlay.js b/toolkit/components/pictureinpicture/tests/browser_preserveTabPipIconOverlay.js
index 55cd003a2c..b1296607f1 100644
--- a/toolkit/components/pictureinpicture/tests/browser_preserveTabPipIconOverlay.js
+++ b/toolkit/components/pictureinpicture/tests/browser_preserveTabPipIconOverlay.js
@@ -12,8 +12,8 @@ var EventUtils = {};
Services.scriptloader.loadSubScript(EVENTUTILS_URL, EventUtils);
async function detachTab(tab) {
- let newWindowPromise = new Promise((resolve, reject) => {
- let observe = (win, topic, data) => {
+ let newWindowPromise = new Promise(resolve => {
+ let observe = win => {
Services.obs.removeObserver(observe, "domwindowopened");
resolve(win);
};
diff --git a/toolkit/components/pictureinpicture/tests/browser_removeVideoElement.js b/toolkit/components/pictureinpicture/tests/browser_removeVideoElement.js
index d9622dc3f0..ece635fd34 100644
--- a/toolkit/components/pictureinpicture/tests/browser_removeVideoElement.js
+++ b/toolkit/components/pictureinpicture/tests/browser_removeVideoElement.js
@@ -28,7 +28,7 @@ add_task(async () => {
let otherVideo = doc.querySelector(`video:not([id="${videoID}"])`);
let eventFired = false;
- let listener = e => {
+ let listener = () => {
eventFired = true;
};
diff --git a/toolkit/components/pictureinpicture/tests/browser_reversePiP.js b/toolkit/components/pictureinpicture/tests/browser_reversePiP.js
index a8ae20166f..3f8b43b02b 100644
--- a/toolkit/components/pictureinpicture/tests/browser_reversePiP.js
+++ b/toolkit/components/pictureinpicture/tests/browser_reversePiP.js
@@ -53,8 +53,11 @@ add_task(async () => {
}
);
- // The "flipped" attribute should be set on the toggle button (when applicable).
- Assert.equal(toggleFlippedAttribute, "true");
+ Assert.equal(
+ toggleFlippedAttribute,
+ "",
+ `The "flipped" attribute should be set on the toggle button (when applicable).`
+ );
}
);
});
@@ -110,12 +113,20 @@ add_task(async () => {
let videoID = "reversed";
let videoFlippedAttribute = await getFlippedAttribute(browser, videoID);
- Assert.equal(videoFlippedAttribute, null); // The "flipped" attribute should not be set initially.
+ Assert.equal(
+ videoFlippedAttribute,
+ null,
+ `The "flipped" attribute should not be set initially.`
+ );
let pipWin = await triggerPictureInPicture(browser, videoID);
videoFlippedAttribute = await getFlippedAttribute(browser, "reversed");
- Assert.equal(videoFlippedAttribute, "true"); // The "flipped" value should be set once the PiP window is opened (when applicable).
+ Assert.equal(
+ videoFlippedAttribute,
+ "true",
+ `The "flipped" value should be set once the PiP window is opened (when applicable).`
+ );
let playerBrowser = pipWin.document.getElementById("browser");
let pipVideoTransform = await getPiPVideoTransform(playerBrowser);
@@ -124,7 +135,11 @@ add_task(async () => {
await ensureMessageAndClosePiP(browser, videoID, pipWin, false);
videoFlippedAttribute = await getFlippedAttribute(browser, "reversed");
- Assert.equal(videoFlippedAttribute, null); // The "flipped" attribute should be removed after closing PiP.
+ Assert.equal(
+ videoFlippedAttribute,
+ null,
+ `The "flipped" attribute should be removed after closing PiP.`
+ );
// Now we want to test that regular (not-reversed) videos are unaffected
videoID = "not-reversed";
diff --git a/toolkit/components/pictureinpicture/tests/browser_touch_toggle_enablepip.js b/toolkit/components/pictureinpicture/tests/browser_touch_toggle_enablepip.js
index e50c430d0c..46eebe1719 100644
--- a/toolkit/components/pictureinpicture/tests/browser_touch_toggle_enablepip.js
+++ b/toolkit/components/pictureinpicture/tests/browser_touch_toggle_enablepip.js
@@ -30,7 +30,7 @@ add_task(async () => {
// Remove other page elements before reading PiP toggle's client rect.
// Otherwise, we will provide the wrong coordinates when simulating the touch event.
- await SpecialPowers.spawn(browser, [], async args => {
+ await SpecialPowers.spawn(browser, [], async () => {
info(
"Removing other elements first to make the PiP toggle more visible"
);
diff --git a/toolkit/components/places/BookmarkHTMLUtils.sys.mjs b/toolkit/components/places/BookmarkHTMLUtils.sys.mjs
index 559d8b9a8e..81bd791730 100644
--- a/toolkit/components/places/BookmarkHTMLUtils.sys.mjs
+++ b/toolkit/components/places/BookmarkHTMLUtils.sys.mjs
@@ -428,7 +428,7 @@ BookmarkImporter.prototype = {
* We also don't import ADD_DATE or LAST_MODIFIED for separators because
* pre-Places bookmarks did not support them.
*/
- _handleSeparator: function handleSeparator(aElt) {
+ _handleSeparator: function handleSeparator() {
let frame = this._curFrame;
let separator = {
diff --git a/toolkit/components/places/BookmarkJSONUtils.sys.mjs b/toolkit/components/places/BookmarkJSONUtils.sys.mjs
index 29967b5395..0a27db3696 100644
--- a/toolkit/components/places/BookmarkJSONUtils.sys.mjs
+++ b/toolkit/components/places/BookmarkJSONUtils.sys.mjs
@@ -21,26 +21,6 @@ const OLD_BOOKMARK_QUERY_TRANSLATIONS = {
MOBILE_BOOKMARKS: PlacesUtils.bookmarks.mobileGuid,
};
-/**
- * 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) {
- let cryptoHash = Cc["@mozilla.org/security/hash;1"].createInstance(
- Ci.nsICryptoHash
- );
- cryptoHash.init(Ci.nsICryptoHash.MD5);
- let stringStream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
- Ci.nsIStringInputStream
- );
- stringStream.setUTF8Data(aString);
- cryptoHash.updateFromStream(stringStream, -1);
- // base64 allows the '/' char, but we can't use it for filenames.
- return cryptoHash.finish(true).replace(/\//g, "-");
-}
-
export var BookmarkJSONUtils = Object.freeze({
/**
* Import bookmarks from a url.
@@ -164,7 +144,8 @@ export var BookmarkJSONUtils = Object.freeze({
console.error("Unable to report telemetry.");
}
- let hash = generateHash(jsonString);
+ // 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");
diff --git a/toolkit/components/places/BookmarkList.sys.mjs b/toolkit/components/places/BookmarkList.sys.mjs
new file mode 100644
index 0000000000..3465948b52
--- /dev/null
+++ b/toolkit/components/places/BookmarkList.sys.mjs
@@ -0,0 +1,262 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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);
+ }
+}
diff --git a/toolkit/components/places/Bookmarks.sys.mjs b/toolkit/components/places/Bookmarks.sys.mjs
index 7f9a397c34..2ca2cb6b5d 100644
--- a/toolkit/components/places/Bookmarks.sys.mjs
+++ b/toolkit/components/places/Bookmarks.sys.mjs
@@ -230,7 +230,7 @@ export var Bookmarks = Object.freeze({
if (addedTime > now) {
modTime = now;
}
- let insertInfo = validateBookmarkObject("Bookmarks.jsm: insert", info, {
+ let insertInfo = validateBookmarkObject("Bookmarks.sys.mjs: insert", info, {
type: { defaultValue: this.TYPE_BOOKMARK },
index: { defaultValue: this.DEFAULT_INDEX },
url: {
@@ -488,7 +488,7 @@ export var Bookmarks = Object.freeze({
}
try {
insertInfo = validateBookmarkObject(
- "Bookmarks.jsm: insertTree",
+ "Bookmarks.sys.mjs: insertTree",
info,
insertInfo
);
@@ -681,7 +681,7 @@ export var Bookmarks = Object.freeze({
// 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.jsm: update", info, {
+ let updateInfo = validateBookmarkObject("Bookmarks.sys.mjs: update", info, {
guid: { required: true },
index: {
requiredIf: b => b.hasOwnProperty("parentGuid"),
@@ -722,23 +722,27 @@ export var Bookmarks = Object.freeze({
Math.max(item.lastModified, updateInfo.dateAdded)
);
}
- updateInfo = validateBookmarkObject("Bookmarks.jsm: 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 },
- });
+ 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.jsm: update",
+ "Bookmarks.sys.mjs: update",
async db => {
let parent;
if (updateInfo.hasOwnProperty("parentGuid")) {
@@ -1031,7 +1035,7 @@ export var Bookmarks = Object.freeze({
lazy.PlacesSyncUtils.bookmarks.determineSyncChangeDelta(source);
await lazy.PlacesUtils.withConnectionWrapper(
- "Bookmarks.jsm: moveToFolder",
+ "Bookmarks.sys.mjs: moveToFolder",
async db => {
const lastModified = new Date();
@@ -1283,7 +1287,10 @@ export var Bookmarks = Object.freeze({
// Even if we ignore any other unneeded property, we still validate any
// known property to reduce likelihood of hidden bugs.
- let removeInfo = validateBookmarkObject("Bookmarks.jsm: remove", info);
+ let removeInfo = validateBookmarkObject(
+ "Bookmarks.sys.mjs: remove",
+ info
+ );
removeInfos.push(removeInfo);
}
@@ -1372,7 +1379,7 @@ export var Bookmarks = Object.freeze({
}
return lazy.PlacesUtils.withConnectionWrapper(
- "Bookmarks.jsm: eraseEverything",
+ "Bookmarks.sys.mjs: eraseEverything",
async function (db) {
let urls;
await db.executeTransaction(async function () {
@@ -1552,7 +1559,7 @@ export var Bookmarks = Object.freeze({
// Even if we ignore any other unneeded property, we still validate any
// known property to reduce likelihood of hidden bugs.
let fetchInfo = validateBookmarkObject(
- "Bookmarks.jsm: fetch",
+ "Bookmarks.sys.mjs: fetch",
info,
behavior
);
@@ -1681,7 +1688,7 @@ export var Bookmarks = Object.freeze({
*/
// TODO must implement these methods yet:
// PlacesUtils.promiseBookmarksTree()
- fetchTree(guid = "", options = {}) {
+ fetchTree() {
throw new Error("Not yet implemented");
},
@@ -1695,7 +1702,7 @@ export var Bookmarks = Object.freeze({
* }
*/
async fetchTags() {
- // TODO: Once the tagging API is implemented in Bookmarks.jsm, we can cache
+ // 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(
@@ -1742,7 +1749,7 @@ export var Bookmarks = Object.freeze({
*/
reorder(parentGuid, orderedChildrenGuids, options = {}) {
let info = { guid: parentGuid };
- info = validateBookmarkObject("Bookmarks.jsm: reorder", info, {
+ info = validateBookmarkObject("Bookmarks.sys.mjs: reorder", info, {
guid: { required: true },
});
@@ -2104,7 +2111,7 @@ async function updateBookmark(
function insertBookmark(item, parent) {
return lazy.PlacesUtils.withConnectionWrapper(
- "Bookmarks.jsm: insertBookmark",
+ "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.
@@ -2202,7 +2209,7 @@ function insertBookmark(item, parent) {
function insertBookmarkTree(items, source, parent, urls, lastAddedForParent) {
return lazy.PlacesUtils.withConnectionWrapper(
- "Bookmarks.jsm: insertBookmarkTree",
+ "Bookmarks.sys.mjs: insertBookmarkTree",
async function (db) {
await db.executeTransaction(async function transaction() {
await lazy.PlacesUtils.maybeInsertManyPlaces(db, urls);
@@ -2353,7 +2360,7 @@ async function queryBookmarks(info) {
}
return lazy.PlacesUtils.withConnectionWrapper(
- "Bookmarks.jsm: queryBookmarks",
+ "Bookmarks.sys.mjs: queryBookmarks",
async function (db) {
// _id, _childCount, _grandParentId and _parentId fields
// are required to be in the result by the converting function
@@ -2422,7 +2429,7 @@ async function fetchBookmark(info, options = {}) {
return query(options.db);
}
return lazy.PlacesUtils.withConnectionWrapper(
- "Bookmarks.jsm: fetchBookmark",
+ "Bookmarks.sys.mjs: fetchBookmark",
query
);
}
@@ -2454,7 +2461,7 @@ async function fetchBookmarkByPosition(info, options = {}) {
return query(db);
}
return lazy.PlacesUtils.withConnectionWrapper(
- "Bookmarks.jsm: fetchBookmarkByPosition",
+ "Bookmarks.sys.mjs: fetchBookmarkByPosition",
query
);
}
@@ -2502,7 +2509,7 @@ async function fetchBookmarksByTags(info, options = {}) {
return query(db);
}
return lazy.PlacesUtils.withConnectionWrapper(
- "Bookmarks.jsm: fetchBookmarksByTags",
+ "Bookmarks.sys.mjs: fetchBookmarksByTags",
query
);
}
@@ -2532,7 +2539,7 @@ async function fetchBookmarksByGUIDPrefix(info, options = {}) {
return query(db);
}
return lazy.PlacesUtils.withConnectionWrapper(
- "Bookmarks.jsm: fetchBookmarksByGUIDPrefix",
+ "Bookmarks.sys.mjs: fetchBookmarksByGUIDPrefix",
query
);
}
@@ -2574,7 +2581,7 @@ async function fetchBookmarksByURL(info, options = {}) {
return query(db);
}
return lazy.PlacesUtils.withConnectionWrapper(
- "Bookmarks.jsm: fetchBookmarksByURL",
+ "Bookmarks.sys.mjs: fetchBookmarksByURL",
query
);
}
@@ -2607,14 +2614,14 @@ async function fetchBookmarksByParentGUID(info, options = {}) {
return query(db);
}
return lazy.PlacesUtils.withConnectionWrapper(
- "Bookmarks.jsm: fetchBookmarksByParentGUID",
+ "Bookmarks.sys.mjs: fetchBookmarksByParentGUID",
query
);
}
function fetchRecentBookmarks(numberOfItems) {
return lazy.PlacesUtils.withConnectionWrapper(
- "Bookmarks.jsm: fetchRecentBookmarks",
+ "Bookmarks.sys.mjs: fetchRecentBookmarks",
async function (db) {
let rows = await db.executeCached(
`SELECT b.guid, IFNULL(p.guid, '') AS parentGuid, b.position AS 'index',
@@ -2667,7 +2674,7 @@ async function fetchBookmarksByParent(db, info) {
function removeBookmarks(items, options) {
return lazy.PlacesUtils.withConnectionWrapper(
- "Bookmarks.jsm: removeBookmarks",
+ "Bookmarks.sys.mjs: removeBookmarks",
async function (db) {
let urls = [];
@@ -2780,7 +2787,7 @@ function removeBookmarks(items, options) {
function reorderChildren(parent, orderedChildrenGuids, options) {
return lazy.PlacesUtils.withConnectionWrapper(
- "Bookmarks.jsm: reorderChildren",
+ "Bookmarks.sys.mjs: reorderChildren",
db =>
db.executeTransaction(async function () {
// Fetch old indices for the notifications.
@@ -3314,7 +3321,7 @@ async function retrieveFullBookmarkPath(guid, options = {}) {
return query(db);
}
return lazy.PlacesUtils.withConnectionWrapper(
- "Bookmarks.jsm: retrieveFullBookmarkPath",
+ "Bookmarks.sys.mjs: retrieveFullBookmarkPath",
query
);
}
@@ -3328,42 +3335,38 @@ async function retrieveFullBookmarkPath(guid, options = {}) {
*/
async function getBookmarkDetailMap(aGuids) {
return lazy.PlacesUtils.withConnectionWrapper(
- "Bookmarks.geBookmarkDetails",
+ "Bookmarks.geBookmarkDetailMap",
async db => {
- const rows = 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(aGuids)})
- `,
- aGuids
- );
-
- return new Map(
- rows.map(row => {
- const lastVisitDate = row.getResultByIndex(6);
-
- return [
- row.getResultByIndex(0),
- {
+ 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),
@@ -3376,10 +3379,11 @@ async function getBookmarkDetailMap(aGuids) {
targetFolderGuid: row.getResultByIndex(8),
targetFolderItemId: row.getResultByIndex(9),
targetFolderTitle: row.getResultByIndex(10),
- },
- ];
- })
- );
+ });
+ }
+ );
+ }
+ return entries;
}
);
}
diff --git a/toolkit/components/places/Database.cpp b/toolkit/components/places/Database.cpp
index 891c53156d..56da4094c7 100644
--- a/toolkit/components/places/Database.cpp
+++ b/toolkit/components/places/Database.cpp
@@ -1260,6 +1260,15 @@ nsresult Database::InitSchema(bool* aDatabaseMigrated) {
// Firefox 118 uses schema version 75
+ // Version 76 was not correctly invoked and thus removed.
+
+ if (currentSchemaVersion < 77) {
+ rv = MigrateV77Up();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Firefox 125 uses schema version 77
+
// Schema Upgrades must add migration code here.
// >>> IMPORTANT! <<<
// NEVER MIX UP SYNC AND ASYNC EXECUTION IN MIGRATORS, YOU MAY LOCK THE
@@ -1615,7 +1624,7 @@ nsresult Database::InitFunctions() {
NS_ENSURE_SUCCESS(rv, rv);
rv = InvalidateDaysOfHistoryFunction::create(mMainConn);
NS_ENSURE_SUCCESS(rv, rv);
- rv = MD5HexFunction::create(mMainConn);
+ rv = SHA256HexFunction::create(mMainConn);
NS_ENSURE_SUCCESS(rv, rv);
rv = SetShouldStartFrecencyRecalculationFunction::create(mMainConn);
NS_ENSURE_SUCCESS(rv, rv);
@@ -2041,6 +2050,15 @@ nsresult Database::MigrateV75Up() {
return NS_OK;
}
+nsresult Database::MigrateV77Up() {
+ // Recalculate origins frecency.
+ nsCOMPtr<mozIStorageStatement> stmt;
+ nsresult rv = mMainConn->ExecuteSimpleSQL(
+ "UPDATE moz_origins SET recalc_frecency = 1"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
int64_t Database::CreateMobileRoot() {
MOZ_ASSERT(NS_IsMainThread());
diff --git a/toolkit/components/places/Database.h b/toolkit/components/places/Database.h
index 1798f73eec..755ef26393 100644
--- a/toolkit/components/places/Database.h
+++ b/toolkit/components/places/Database.h
@@ -308,6 +308,7 @@ class Database final : public nsIObserver, public nsSupportsWeakReference {
nsresult MigrateV73Up();
nsresult MigrateV74Up();
nsresult MigrateV75Up();
+ nsresult MigrateV77Up();
nsresult UpdateBookmarkRootTitles();
diff --git a/toolkit/components/places/Helpers.cpp b/toolkit/components/places/Helpers.cpp
index 90ab263ec1..8cd019b6e1 100644
--- a/toolkit/components/places/Helpers.cpp
+++ b/toolkit/components/places/Helpers.cpp
@@ -10,6 +10,7 @@
#include "nsReadableUtils.h"
#include "nsString.h"
#include "nsNavHistory.h"
+#include "nsNetUtil.h"
#include "mozilla/Base64.h"
#include "mozilla/HashFunctions.h"
#include "mozilla/RandomNum.h"
@@ -378,5 +379,26 @@ nsresult BackupDatabaseFile(nsIFile* aDBFile, const nsAString& aBackupFileName,
return aDBFile->CopyTo(parentDir, fileName);
}
+already_AddRefed<nsIURI> GetExposableURI(nsIURI* aURI) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aURI);
+
+ nsresult rv;
+ nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to get nsIIOService");
+ return nsCOMPtr<nsIURI>(aURI).forget();
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ rv = ioService->CreateExposableURI(aURI, getter_AddRefs(uri));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to create exposable URI");
+ return nsCOMPtr<nsIURI>(aURI).forget();
+ }
+
+ return uri.forget();
+}
+
} // namespace places
} // namespace mozilla
diff --git a/toolkit/components/places/Helpers.h b/toolkit/components/places/Helpers.h
index 6719fdded2..814322e042 100644
--- a/toolkit/components/places/Helpers.h
+++ b/toolkit/components/places/Helpers.h
@@ -170,6 +170,14 @@ PRTime RoundedPRNow();
nsresult HashURL(const nsACString& aSpec, const nsACString& aMode,
uint64_t* _hash);
+/**
+ * Return exposable URL from given URI.
+ *
+ * @param aURI The URI to be converted.
+ * @return already_AddRefed<nsIURI> The converted, exposable URI.
+ */
+already_AddRefed<nsIURI> GetExposableURI(nsIURI* aURI);
+
class QueryKeyValuePair final {
public:
QueryKeyValuePair(const nsACString& aKey, const nsACString& aValue) {
diff --git a/toolkit/components/places/History.cpp b/toolkit/components/places/History.cpp
index 81cccdcb55..6c066f47be 100644
--- a/toolkit/components/places/History.cpp
+++ b/toolkit/components/places/History.cpp
@@ -1434,6 +1434,17 @@ void NotifyEmbedVisit(VisitData& aPlace,
(void)NS_DispatchToMainThread(event);
}
+void NotifyVisitIfHavingUserPass(nsIURI* aURI) {
+ MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread!");
+
+ bool hasUserPass;
+ if (NS_SUCCEEDED(aURI->GetHasUserPass(&hasUserPass)) && hasUserPass) {
+ nsCOMPtr<nsIRunnable> event =
+ new NotifyManyVisitsObservers(VisitData(aURI));
+ (void)NS_DispatchToMainThread(event);
+ }
+}
+
////////////////////////////////////////////////////////////////////////////////
//// History
@@ -1925,13 +1936,6 @@ History::VisitURI(nsIWidget* aWidget, nsIURI* aURI, nsIURI* aLastVisitedURI,
NS_ENSURE_SUCCESS(rv, rv);
}
- nsTArray<VisitData> placeArray(1);
- placeArray.AppendElement(VisitData(aURI, aLastVisitedURI));
- VisitData& place = placeArray.ElementAt(0);
- NS_ENSURE_FALSE(place.spec.IsEmpty(), NS_ERROR_INVALID_ARG);
-
- place.visitTime = PR_Now();
-
// Assigns a type to the edge in the visit linked list. Each type will be
// considered differently when weighting the frecency of a location.
uint32_t recentFlags = navHistory->GetRecentFlags(aURI);
@@ -1966,18 +1970,8 @@ History::VisitURI(nsIWidget* aWidget, nsIURI* aURI, nsIURI* aLastVisitedURI,
transitionType = nsINavHistoryService::TRANSITION_FRAMED_LINK;
}
- place.SetTransitionType(transitionType);
bool isRedirect = aFlags & IHistory::REDIRECT_SOURCE;
- if (isRedirect) {
- place.useFrecencyRedirectBonus =
- (aFlags & (IHistory::REDIRECT_SOURCE_PERMANENT |
- IHistory::REDIRECT_SOURCE_UPGRADED)) ||
- transitionType != nsINavHistoryService::TRANSITION_TYPED;
- }
- place.hidden = GetHiddenState(isRedirect, place.transitionType);
-
- // Error pages should never be autocompleted.
- place.isUnrecoverableError = aFlags & IHistory::UNRECOVERABLE_ERROR;
+ bool isHidden = GetHiddenState(isRedirect, transitionType);
// Do not save a reloaded uri if we have visited the same URI recently.
if (reload) {
@@ -1988,17 +1982,43 @@ History::VisitURI(nsIWidget* aWidget, nsIURI* aURI, nsIURI* aLastVisitedURI,
bool wasHidden = entry->mHidden;
// Regardless of whether we store the visit or not, we must update the
// stored visit time.
- AppendToRecentlyVisitedURIs(aURI, place.hidden);
+ AppendToRecentlyVisitedURIs(aURI, isHidden);
// We always want to store an unhidden visit, if the previous visits were
// hidden, because otherwise the page may not appear in the history UI.
// This can happen for example at a page redirecting to itself.
- if (!wasHidden || place.hidden) {
+ if (!wasHidden || isHidden) {
// We can skip this visit.
return NS_OK;
}
}
}
+ // Never store the URL having userpass to database.
+ nsCOMPtr<nsIURI> visitedURI = GetExposableURI(aURI);
+ nsCOMPtr<nsIURI> lastVisitedURI;
+ if (aLastVisitedURI) {
+ lastVisitedURI = GetExposableURI(aLastVisitedURI);
+ }
+
+ nsTArray<VisitData> placeArray(1);
+ placeArray.AppendElement(VisitData(visitedURI, lastVisitedURI));
+ VisitData& place = placeArray.ElementAt(0);
+ NS_ENSURE_FALSE(place.spec.IsEmpty(), NS_ERROR_INVALID_ARG);
+
+ place.visitTime = PR_Now();
+ place.SetTransitionType(transitionType);
+ place.hidden = isHidden;
+
+ if (isRedirect) {
+ place.useFrecencyRedirectBonus =
+ (aFlags & (IHistory::REDIRECT_SOURCE_PERMANENT |
+ IHistory::REDIRECT_SOURCE_UPGRADED)) ||
+ transitionType != nsINavHistoryService::TRANSITION_TYPED;
+ }
+
+ // Error pages should never be autocompleted.
+ place.isUnrecoverableError = aFlags & IHistory::UNRECOVERABLE_ERROR;
+
nsCOMPtr<nsIBrowserWindowTracker> bwt =
do_ImportESModule("resource:///modules/BrowserWindowTracker.sys.mjs",
"BrowserWindowTracker", &rv);
@@ -2067,6 +2087,15 @@ History::VisitURI(nsIWidget* aWidget, nsIURI* aURI, nsIURI* aLastVisitedURI,
NS_ENSURE_SUCCESS(rv, rv);
}
+ // URIs with a userpass component are not stored in the database for security
+ // reasons, we store the exposable URI version of them instead.
+ // The original link pointing at the URI with userpass must still be marked as
+ // visited, to properly react to the user interaction, so we notify a visit.
+ // The visited status is not going to survive a reload of course, though the
+ // alternative of marking any userpass URI as visited if the exposable URI is
+ // visited also feels wrong from the user point of view.
+ NotifyVisitIfHavingUserPass(aURI);
+
return NS_OK;
}
@@ -2099,8 +2128,9 @@ History::SetURITitle(nsIURI* aURI, const nsAString& aTitle) {
//
NS_ENSURE_TRUE(navHistory, NS_ERROR_FAILURE);
+ nsCOMPtr<nsIURI> uri = GetExposableURI(aURI);
bool canAdd;
- nsresult rv = navHistory->CanAddURI(aURI, &canAdd);
+ nsresult rv = navHistory->CanAddURI(uri, &canAdd);
NS_ENSURE_SUCCESS(rv, rv);
if (!canAdd) {
return NS_OK;
@@ -2109,7 +2139,7 @@ History::SetURITitle(nsIURI* aURI, const nsAString& aTitle) {
mozIStorageConnection* dbConn = GetDBConn();
NS_ENSURE_STATE(dbConn);
- return SetPageTitle::Start(dbConn, aURI, aTitle);
+ return SetPageTitle::Start(dbConn, uri, aTitle);
}
////////////////////////////////////////////////////////////////////////////////
@@ -2135,6 +2165,10 @@ History::UpdatePlaces(JS::Handle<JS::Value> aPlaceInfos,
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIURI> uri = GetURIFromJSObject(aCtx, info, "uri");
+ if (uri) {
+ uri = GetExposableURI(uri);
+ }
+
nsCString guid;
{
nsString fatGUID;
diff --git a/toolkit/components/places/History.sys.mjs b/toolkit/components/places/History.sys.mjs
index 3f43ca5a53..fe13043600 100644
--- a/toolkit/components/places/History.sys.mjs
+++ b/toolkit/components/places/History.sys.mjs
@@ -251,8 +251,9 @@ export var History = Object.freeze({
insert(pageInfo) {
let info = lazy.PlacesUtils.validatePageInfo(pageInfo);
- return lazy.PlacesUtils.withConnectionWrapper("History.jsm: insert", db =>
- insert(db, info)
+ return lazy.PlacesUtils.withConnectionWrapper(
+ "History.sys.mjs: insert",
+ db => insert(db, info)
);
},
@@ -322,7 +323,7 @@ export var History = Object.freeze({
}
return lazy.PlacesUtils.withConnectionWrapper(
- "History.jsm: insertMany",
+ "History.sys.mjs: insertMany",
db => insertMany(db, infos, onResult, onError)
);
},
@@ -400,7 +401,7 @@ export var History = Object.freeze({
let pages = { guids: guidsSlice, urls: urlsSlice };
let result = await lazy.PlacesUtils.withConnectionWrapper(
- "History.jsm: remove",
+ "History.sys.mjs: remove",
db => remove(db, pages, onResult)
);
@@ -499,7 +500,7 @@ export var History = Object.freeze({
}
return lazy.PlacesUtils.withConnectionWrapper(
- "History.jsm: removeVisitsByFilter",
+ "History.sys.mjs: removeVisitsByFilter",
db => removeVisitsByFilter(db, filter, onResult)
);
},
@@ -592,7 +593,7 @@ export var History = Object.freeze({
}
return lazy.PlacesUtils.withConnectionWrapper(
- "History.jsm: removeByFilter",
+ "History.sys.mjs: removeByFilter",
db => removeByFilter(db, filter, onResult)
);
},
@@ -644,7 +645,10 @@ export var History = Object.freeze({
* A promise resolved once the operation is complete.
*/
clear() {
- return lazy.PlacesUtils.withConnectionWrapper("History.jsm: clear", clear);
+ return lazy.PlacesUtils.withConnectionWrapper(
+ "History.sys.mjs: clear",
+ clear
+ );
},
/**
@@ -705,7 +709,7 @@ export var History = Object.freeze({
* 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.jsm.
+ * defined in PlacesUtils.sys.mjs.
*
* If a property `annotations` is provided, the annotations will be
* updated. Note that:
@@ -728,7 +732,7 @@ export var History = Object.freeze({
* 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.jsm.
+ * DB_URL_LENGTH_MAX defined in PlacesUtils.sys.mjs.
*/
update(pageInfo) {
let info = lazy.PlacesUtils.validatePageInfo(pageInfo, false);
@@ -744,8 +748,9 @@ export var History = Object.freeze({
);
}
- return lazy.PlacesUtils.withConnectionWrapper("History.jsm: update", db =>
- update(db, info)
+ return lazy.PlacesUtils.withConnectionWrapper(
+ "History.sys.mjs: update",
+ db => update(db, info)
);
},
diff --git a/toolkit/components/places/PlacesBackups.sys.mjs b/toolkit/components/places/PlacesBackups.sys.mjs
index 066eef4ec9..02b105e054 100644
--- a/toolkit/components/places/PlacesBackups.sys.mjs
+++ b/toolkit/components/places/PlacesBackups.sys.mjs
@@ -15,7 +15,7 @@ ChromeUtils.defineLazyGetter(
lazy,
"filenamesRegex",
() =>
- /^bookmarks-([0-9-]+)(?:_([0-9]+)){0,1}(?:_([a-z0-9=+-]{24})){0,1}\.(json(lz4)?)$/i
+ /^bookmarks-([0-9-]+)(?:_([0-9]+)){0,1}(?:_([a-z0-9=_+-]{24,})){0,1}\.(json(lz4)?)$/i
);
async function limitBackups(aMaxBackups, backupFiles) {
diff --git a/toolkit/components/places/PlacesDBUtils.sys.mjs b/toolkit/components/places/PlacesDBUtils.sys.mjs
index 5cc2bfc631..e9a13ac10c 100644
--- a/toolkit/components/places/PlacesDBUtils.sys.mjs
+++ b/toolkit/components/places/PlacesDBUtils.sys.mjs
@@ -874,7 +874,7 @@ export var PlacesDBUtils = {
);
let returnPromise = new Promise(res => {
- let observer = (subject, topic, data) => {
+ let observer = (subject, topic) => {
Services.obs.removeObserver(observer, topic);
logs.push("Database cleaned up");
res(logs);
@@ -1377,7 +1377,7 @@ async function integrity(dbName) {
export function PlacesDBUtilsIdleMaintenance() {}
PlacesDBUtilsIdleMaintenance.prototype = {
- observe(subject, topic, data) {
+ observe(subject, topic) {
switch (topic) {
case "idle-daily":
// Once a week run places.sqlite maintenance tasks.
diff --git a/toolkit/components/places/PlacesExpiration.sys.mjs b/toolkit/components/places/PlacesExpiration.sys.mjs
index 4db494d63e..2621decdc5 100644
--- a/toolkit/components/places/PlacesExpiration.sys.mjs
+++ b/toolkit/components/places/PlacesExpiration.sys.mjs
@@ -435,7 +435,7 @@ export function nsPlacesExpiration() {
);
this._dbInitializedPromise = lazy.PlacesUtils.withConnectionWrapper(
- "PlacesExpiration.jsm: setup",
+ "PlacesExpiration.sys.mjs: setup",
async db => {
await db.execute(
`CREATE TEMP TABLE expiration_notify (
@@ -758,7 +758,7 @@ nsPlacesExpiration.prototype = {
try {
let notifications = [];
await lazy.PlacesUtils.withConnectionWrapper(
- "PlacesExpiration.jsm: expire",
+ "PlacesExpiration.sys.mjs: expire",
async db => {
await db.executeTransaction(async () => {
for (let queryType in EXPIRATION_QUERIES) {
diff --git a/toolkit/components/places/PlacesFrecencyRecalculator.sys.mjs b/toolkit/components/places/PlacesFrecencyRecalculator.sys.mjs
index 2de558af34..7c52c508ee 100644
--- a/toolkit/components/places/PlacesFrecencyRecalculator.sys.mjs
+++ b/toolkit/components/places/PlacesFrecencyRecalculator.sys.mjs
@@ -51,6 +51,14 @@ XPCOMUtils.defineLazyPreferenceGetter(
90
);
+// For origins frecency calculation only sample pages visited recently.
+XPCOMUtils.defineLazyPreferenceGetter(
+ lazy,
+ "originsFrecencyCutOffDays",
+ "places.frecency.originsCutOffDays",
+ 90
+);
+
// 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.
@@ -218,16 +226,19 @@ export class PlacesFrecencyRecalculator {
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 = CAST(
- (SELECT total(frecency)
- FROM moz_places h
- WHERE origin_id = moz_origins.id AND frecency > 0)
- AS INT
- ),
- recalc_frecency = 0
+ 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
@@ -238,25 +249,19 @@ export class PlacesFrecencyRecalculator {
);
affectedCount += affected.length;
- // Calculate and store the frecency statistics used to calculate a
- // thredhold. Origins above that threshold will be considered meaningful
- // and autofilled.
+ // 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.
- let row = (
- await db.executeCached(`
- SELECT count(*), total(frecency), total(pow(frecency,2))
- FROM moz_origins
- WHERE frecency > 0
- `)
- )[0];
- await lazy.PlacesUtils.metadata.setMany(
- new Map([
- ["origin_frecency_count", row.getResultByIndex(0)],
- ["origin_frecency_sum", row.getResultByIndex(1)],
- ["origin_frecency_sum_of_squares", row.getResultByIndex(2)],
- ])
+ // 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
);
});
@@ -343,7 +348,7 @@ export class PlacesFrecencyRecalculator {
}
}
- observe(subject, topic, data) {
+ observe(subject, topic) {
lazy.logger.trace(`Got ${topic} topic`);
switch (topic) {
case "idle-daily":
@@ -524,7 +529,7 @@ class AlternativeFrecencyHelper {
return affected;
}
- async #recalculateSomePagesAlternativeFrecencies({ chunkSize, variables }) {
+ async #recalculateSomePagesAlternativeFrecencies({ chunkSize }) {
lazy.logger.trace(
`Recalculate ${chunkSize} alternative pages frecency values`
);
diff --git a/toolkit/components/places/PlacesPreviews.sys.mjs b/toolkit/components/places/PlacesPreviews.sys.mjs
index 08ee94664a..e19bf2a55c 100644
--- a/toolkit/components/places/PlacesPreviews.sys.mjs
+++ b/toolkit/components/places/PlacesPreviews.sys.mjs
@@ -14,13 +14,8 @@ ChromeUtils.defineESModuleGetters(lazy, {
setTimeout: "resource://gre/modules/Timer.sys.mjs",
});
-ChromeUtils.defineLazyGetter(lazy, "logConsole", function () {
- return console.createInstance({
- prefix: "PlacesPreviews",
- maxLogLevel: Services.prefs.getBoolPref("places.previews.log", false)
- ? "Debug"
- : "Warn",
- });
+ChromeUtils.defineLazyGetter(lazy, "logger", function () {
+ return lazy.PlacesUtils.getLogger({ prefix: "Previews" });
});
// Toggling Places previews requires a restart, because a database trigger
@@ -90,7 +85,7 @@ class DeletionHandler {
constructor() {
// Clear any pending timeouts on shutdown.
lazy.PlacesUtils.history.shutdownClient.jsclient.addBlocker(
- "PlacesPreviews.jsm::DeletionHandler",
+ "PlacesPreviews.sys.mjs::DeletionHandler",
async () => {
this.#shutdownProgress.shuttingDown = true;
lazy.clearTimeout(this.#timeoutId);
@@ -112,7 +107,7 @@ class DeletionHandler {
this.#timeoutId = null;
ChromeUtils.idleDispatch(() => {
this.#deleteChunk().catch(ex =>
- lazy.logConsole.error("Error during previews deletion:" + ex)
+ lazy.logger.error("Error during previews deletion:" + ex)
);
});
}, this.timeout);
@@ -156,7 +151,7 @@ class DeletionHandler {
if (DOMException.isInstance(ex) && ex.name == "NotFoundError") {
deleted.push(hash);
} else {
- lazy.logConsole.error("Unable to delete file: " + filePath);
+ lazy.logger.error("Unable to delete file: " + filePath);
}
}
if (this.#shutdownProgress.shuttingDown) {
@@ -169,7 +164,7 @@ class DeletionHandler {
return p;
}, {});
await lazy.PlacesUtils.withConnectionWrapper(
- "PlacesPreviews.jsm::ExpirePreviews",
+ "PlacesPreviews.sys.mjs::ExpirePreviews",
async db => {
await db.execute(
`DELETE FROM moz_previews_tombstones WHERE hash in
@@ -189,7 +184,7 @@ class DeletionHandler {
/**
* Handles previews for Places urls.
- * Previews are stored in WebP format, using MD5 hash of the page url in hex
+ * 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.
*/
@@ -260,13 +255,13 @@ export const PlacesPreviews = new (class extends EventEmitter {
getPathForUrl(url) {
return PathUtils.join(
this.getPath(),
- lazy.PlacesUtils.md5(url, { format: "hex" }) + this.fileExtension
+ lazy.PlacesUtils.sha256(url, { format: "hex" }) + this.fileExtension
);
}
/**
* Returns the file path of the preview having the given hash.
- * @param {string} hash md5 hash in hex format.
+ * @param {string} hash SHA256 hash in hex format.
* @returns {string } File path of the preview having the given hash.
*/
getPathForHash(hash) {
@@ -293,7 +288,7 @@ export const PlacesPreviews = new (class extends EventEmitter {
* 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.jsm for
+ * 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
@@ -310,7 +305,7 @@ export const PlacesPreviews = new (class extends EventEmitter {
let filePath = this.getPathForUrl(url);
if (!forceUpdate) {
if (this.#recentlyUpdatedPreviews.has(filePath)) {
- lazy.logConsole.debug("Skipping update because recently updated");
+ lazy.logger.debug("Skipping update because recently updated");
return true;
}
try {
@@ -321,13 +316,13 @@ export const PlacesPreviews = new (class extends EventEmitter {
) {
// File is recent enough.
this.#recentlyUpdatedPreviews.add(filePath);
- lazy.logConsole.debug("Skipping update because file is recent");
+ 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.logConsole.error("Error while trying to stat() preview" + ex);
+ lazy.logger.error("Error while trying to stat() preview" + ex);
return false;
}
}
@@ -350,7 +345,7 @@ export const PlacesPreviews = new (class extends EventEmitter {
});
});
if (!buffer) {
- lazy.logConsole.error("Unable to fetch preview: " + url);
+ lazy.logger.error("Unable to fetch preview: " + url);
return false;
}
try {
@@ -359,9 +354,7 @@ export const PlacesPreviews = new (class extends EventEmitter {
tmpPath: filePath + ".tmp",
});
} catch (ex) {
- lazy.logConsole.error(
- lazy.logConsole.error("Unable to create preview: " + ex)
- );
+ lazy.logger.error("Unable to create preview: " + ex);
return false;
}
this.#recentlyUpdatedPreviews.add(filePath);
@@ -388,11 +381,11 @@ export const PlacesPreviews = new (class extends EventEmitter {
let files = await IOUtils.getChildren(this.getPath());
let hashes = files
.map(f => PathUtils.filename(f))
- .filter(n => /^[a-f0-9]{32}\.webp$/)
+ .filter(() => /^[a-f0-9]{32}\.webp$/)
.map(n => n.substring(0, n.lastIndexOf(".")));
await lazy.PlacesUtils.withConnectionWrapper(
- "PlacesPreviews.jsm::deleteOrphans",
+ "PlacesPreviews.sys.mjs::deleteOrphans",
async db => {
await db.execute(
`
@@ -402,7 +395,7 @@ export const PlacesPreviews = new (class extends EventEmitter {
INSERT OR IGNORE INTO moz_previews_tombstones
SELECT hash FROM files
EXCEPT
- SELECT md5hex(url) FROM moz_places
+ SELECT sha256hex(url) FROM moz_places
`
);
}
diff --git a/toolkit/components/places/PlacesSyncUtils.sys.mjs b/toolkit/components/places/PlacesSyncUtils.sys.mjs
index 6da1b91243..caa5054885 100644
--- a/toolkit/components/places/PlacesSyncUtils.sys.mjs
+++ b/toolkit/components/places/PlacesSyncUtils.sys.mjs
@@ -11,7 +11,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
/**
* This module exports functions for Sync to use when applying remote
- * records. The calls are similar to those in `Bookmarks.jsm` and
+ * records. The calls are similar to those in `Bookmarks.sys.mjs` and
* `nsINavBookmarksService`, with special handling for
* tags, keywords, synced annotations, and missing parents.
*/
@@ -1405,7 +1405,7 @@ function validateChangeRecord(name, changeRecord, behavior) {
}
// Similar to the private `fetchBookmarksByParent` implementation in
-// `Bookmarks.jsm`.
+// `Bookmarks.sys.mjs`.
var fetchChildGuids = async function (db, parentGuid) {
let rows = await db.executeCached(
`
diff --git a/toolkit/components/places/PlacesTransactions.sys.mjs b/toolkit/components/places/PlacesTransactions.sys.mjs
index 0c9accd3ba..bd0b7654a3 100644
--- a/toolkit/components/places/PlacesTransactions.sys.mjs
+++ b/toolkit/components/places/PlacesTransactions.sys.mjs
@@ -177,13 +177,7 @@ function setTimeout(callback, ms) {
const lazy = {};
ChromeUtils.defineLazyGetter(lazy, "logger", function () {
- return console.createInstance({
- prefix: "PlacesTransactions",
- maxLogLevel: Services.prefs.getCharPref(
- "places.transactions.logLevel",
- "Error"
- ),
- });
+ return PlacesUtils.getLogger({ prefix: "Transactions" });
});
class TransactionsHistoryArray extends Array {
diff --git a/toolkit/components/places/PlacesUtils.sys.mjs b/toolkit/components/places/PlacesUtils.sys.mjs
index aeebedd31a..8eeb4f94d8 100644
--- a/toolkit/components/places/PlacesUtils.sys.mjs
+++ b/toolkit/components/places/PlacesUtils.sys.mjs
@@ -12,7 +12,6 @@ const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
Bookmarks: "resource://gre/modules/Bookmarks.sys.mjs",
History: "resource://gre/modules/History.sys.mjs",
- Log: "resource://gre/modules/Log.sys.mjs",
PlacesSyncUtils: "resource://gre/modules/PlacesSyncUtils.sys.mjs",
Sqlite: "resource://gre/modules/Sqlite.sys.mjs",
});
@@ -817,7 +816,7 @@ export var PlacesUtils = {
},
// nsIObserver
- observe: function PU_observe(aSubject, aTopic, aData) {
+ observe: function PU_observe(aSubject, aTopic) {
switch (aTopic) {
case this.TOPIC_SHUTDOWN:
Services.obs.removeObserver(this, this.TOPIC_SHUTDOWN);
@@ -1181,7 +1180,7 @@ export var PlacesUtils = {
{
url: { requiredIf: b => !b.guid },
guid: { requiredIf: b => !b.url },
- visits: { requiredIf: b => validateVisits },
+ visits: { requiredIf: () => validateVisits },
}
);
},
@@ -1830,14 +1829,13 @@ export var PlacesUtils = {
* 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:
- * - "ascii" for ascii format.
+ * - "base64" for ascii format.
* - "hex" for hex format.
- * @returns {string} hash of the input string in the required format.
+ * @returns {string} hash of the input data in the required format.
* @deprecated use sha256 instead.
*/
- md5(data, { format = "ascii" } = {}) {
+ 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);
@@ -1847,7 +1845,7 @@ export var PlacesUtils = {
return Array.from(hash, (c, i) =>
hash.charCodeAt(i).toString(16).padStart(2, "0")
).join("");
- case "ascii":
+ case "base64":
default:
return hasher.finish(true);
}
@@ -1855,25 +1853,35 @@ export var PlacesUtils = {
/**
* Run some text through SHA256 and return the hash.
- * @param {string} data The string to hash.
+ * @param {string|nsIStringInputStream} data The data to hash.
* @param {string} [format] Which format of the hash to return:
- * - "ascii" for ascii format.
+ * - "base64" (default) for ascii format, not safe for URIs or file names.
* - "hex" for hex format.
- * @returns {string} hash of the input string in the required 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 = "ascii" } = {}) {
+ sha256(data, { format = "base64" } = {}) {
let hasher = new lazy.CryptoHash("sha256");
-
- // Convert the data to a byte array for hashing.
- data = new TextEncoder().encode(data);
- hasher.update(data, data.length);
+ 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 "ascii":
+ case "base64url":
+ return hasher.finish(true).replaceAll("+", "-").replaceAll("/", "_");
+ case "base64":
default:
return hasher.finish(true);
}
@@ -1950,31 +1958,26 @@ export var PlacesUtils = {
},
/**
- * Creates a logger.
- * Logging level can be controlled through places.loglevel.
+ * Creates a console logger.
+ * Logging level can be controlled through the `places.loglevel` preference.
*
- * @param {string} [prefix] Prefix to use for the logged messages, "::" will
- * be appended automatically to the prefix.
- * @returns {object} The logger.
+ * @param {object} options
+ * @param {string} [options.prefix] Prefix to use for the logged messages.
+ * @returns {ConsoleInstance} The console logger.
*/
getLogger({ prefix = "" } = {}) {
- if (!this._logger) {
- this._logger = lazy.Log.repository.getLogger("places");
- this._logger.manageLevelFromPref("places.loglevel");
- this._logger.addAppender(
- new lazy.Log.ConsoleAppender(new lazy.Log.BasicFormatter())
- );
- }
- if (prefix) {
- // This is not an early return because it is necessary to invoke getLogger
- // at least once before getLoggerWithMessagePrefix; it replaces a
- // method of the original logger, rather than using an actual Proxy.
- return lazy.Log.repository.getLoggerWithMessagePrefix(
- "places",
- 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 this._logger;
+ return logger;
},
};
@@ -2300,7 +2303,7 @@ PlacesUtils.metadata = {
}
return true;
})
- .map(([key, value]) => key);
+ .map(([key]) => key);
if (keysToDelete.length) {
await this.deleteWithConnection(db, ...keysToDelete);
if (keysToDelete.length == pairs.size) {
diff --git a/toolkit/components/places/SQLFunctions.cpp b/toolkit/components/places/SQLFunctions.cpp
index e625f3fa09..85c5cc8d17 100644
--- a/toolkit/components/places/SQLFunctions.cpp
+++ b/toolkit/components/places/SQLFunctions.cpp
@@ -1135,7 +1135,7 @@ GetQueryParamFunction::OnFunctionCall(mozIStorageValueArray* aArguments,
RefPtr<nsVariant> result = new nsVariant();
if (!queryString.IsEmpty() && !paramName.IsEmpty()) {
URLParams::Parse(
- queryString,
+ queryString, true,
[&paramName, &result](const nsAString& aName, const nsAString& aValue) {
NS_ConvertUTF16toUTF8 name(aName);
if (!paramName.Equals(name)) {
@@ -1191,19 +1191,19 @@ HashFunction::OnFunctionCall(mozIStorageValueArray* aArguments,
}
////////////////////////////////////////////////////////////////////////////////
-//// MD5 Function
+//// SHA256Hex Function
/* static */
-nsresult MD5HexFunction::create(mozIStorageConnection* aDBConn) {
- RefPtr<MD5HexFunction> function = new MD5HexFunction();
- return aDBConn->CreateFunction("md5hex"_ns, -1, function);
+nsresult SHA256HexFunction::create(mozIStorageConnection* aDBConn) {
+ RefPtr<SHA256HexFunction> function = new SHA256HexFunction();
+ return aDBConn->CreateFunction("sha256hex"_ns, -1, function);
}
-NS_IMPL_ISUPPORTS(MD5HexFunction, mozIStorageFunction)
+NS_IMPL_ISUPPORTS(SHA256HexFunction, mozIStorageFunction)
NS_IMETHODIMP
-MD5HexFunction::OnFunctionCall(mozIStorageValueArray* aArguments,
- nsIVariant** _result) {
+SHA256HexFunction::OnFunctionCall(mozIStorageValueArray* aArguments,
+ nsIVariant** _result) {
// Must have non-null function arguments.
MOZ_ASSERT(aArguments);
@@ -1217,8 +1217,8 @@ MD5HexFunction::OnFunctionCall(mozIStorageValueArray* aArguments,
nsCOMPtr<nsICryptoHash> hasher =
do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
- // MD5 is not a secure hash function, but it's ok for this use.
- rv = hasher->Init(nsICryptoHash::MD5);
+ // SHA256 is not super strong but fine for our mapping needs.
+ rv = hasher->Init(nsICryptoHash::SHA256);
NS_ENSURE_SUCCESS(rv, rv);
rv = hasher->Update(reinterpret_cast<const uint8_t*>(str.BeginReading()),
diff --git a/toolkit/components/places/SQLFunctions.h b/toolkit/components/places/SQLFunctions.h
index 0b0dbca970..923172a2d7 100644
--- a/toolkit/components/places/SQLFunctions.h
+++ b/toolkit/components/places/SQLFunctions.h
@@ -412,17 +412,17 @@ class HashFunction final : public mozIStorageFunction {
};
////////////////////////////////////////////////////////////////////////////////
-//// MD5 Function
+//// SHA256Hex Function
/**
- * Calculates md5 hash for a given string.
+ * Calculates SHA256 hash for a given string in hex format.
*
* @param string
* A string.
* @return
* The hash for the string.
*/
-class MD5HexFunction final : public mozIStorageFunction {
+class SHA256HexFunction final : public mozIStorageFunction {
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_MOZISTORAGEFUNCTION
@@ -436,7 +436,7 @@ class MD5HexFunction final : public mozIStorageFunction {
static nsresult create(mozIStorageConnection* aDBConn);
private:
- ~MD5HexFunction() = default;
+ ~SHA256HexFunction() = default;
};
////////////////////////////////////////////////////////////////////////////////
diff --git a/toolkit/components/places/Shutdown.h b/toolkit/components/places/Shutdown.h
index 3e5612a454..0a9674bf06 100644
--- a/toolkit/components/places/Shutdown.h
+++ b/toolkit/components/places/Shutdown.h
@@ -28,8 +28,8 @@ class Database;
* PHASE 2 (Modern clients shutdown)
* Modern clients should instead register as a blocker by passing a promise to
- * nsINavHistoryService::shutdownClient (for example see Sanitizer.jsm), so they
- * block Places shutdown until the promise is resolved.
+ * nsINavHistoryService::shutdownClient (for example see Sanitizer.sys.mjs), so
+ * they block Places shutdown until the promise is resolved.
* When profile-change-teardown is observed by async shutdown, it calls
* ClientsShutdownBlocker::BlockShutdown. This class is registered as a teardown
* phase blocker in Database::Init (see Database::mClientsShutdown).
diff --git a/toolkit/components/places/SyncedBookmarksMirror.sys.mjs b/toolkit/components/places/SyncedBookmarksMirror.sys.mjs
index 31688928b3..a09e1b7eb3 100644
--- a/toolkit/components/places/SyncedBookmarksMirror.sys.mjs
+++ b/toolkit/components/places/SyncedBookmarksMirror.sys.mjs
@@ -666,7 +666,7 @@ export class SyncedBookmarksMirror {
"mozISyncedBookmarksMirrorCallback",
]),
// `mozISyncedBookmarksMirrorProgressListener` methods.
- onFetchLocalTree: (took, itemCount, deleteCount, problemsBag) => {
+ onFetchLocalTree: (took, itemCount, deleteCount) => {
let counts = [
{
name: "items",
diff --git a/toolkit/components/places/TaggingService.sys.mjs b/toolkit/components/places/TaggingService.sys.mjs
index e27c84f844..0c038e6b0a 100644
--- a/toolkit/components/places/TaggingService.sys.mjs
+++ b/toolkit/components/places/TaggingService.sys.mjs
@@ -321,7 +321,7 @@ TaggingService.prototype = {
},
// nsIObserver
- observe: function TS_observe(aSubject, aTopic, aData) {
+ observe: function TS_observe(aSubject, aTopic) {
if (aTopic == TOPIC_SHUTDOWN) {
PlacesUtils.observers.removeListener(
[
diff --git a/toolkit/components/places/moz.build b/toolkit/components/places/moz.build
index a0513ec341..28a7421fba 100644
--- a/toolkit/components/places/moz.build
+++ b/toolkit/components/places/moz.build
@@ -60,6 +60,7 @@ if CONFIG["MOZ_PLACES"]:
EXTRA_JS_MODULES += [
"BookmarkHTMLUtils.sys.mjs",
"BookmarkJSONUtils.sys.mjs",
+ "BookmarkList.sys.mjs",
"Bookmarks.sys.mjs",
"ExtensionSearchHandler.sys.mjs",
"History.sys.mjs",
diff --git a/toolkit/components/places/mozIAsyncHistory.idl b/toolkit/components/places/mozIAsyncHistory.idl
index 90c32b1090..88690b4798 100644
--- a/toolkit/components/places/mozIAsyncHistory.idl
+++ b/toolkit/components/places/mozIAsyncHistory.idl
@@ -128,11 +128,11 @@ interface mozIVisitedStatusCallback : nsISupports
/**
* This interface contains APIs for cpp consumers.
- * Javascript consumers should look at History.jsm instead,
+ * Javascript consumers should look at History.sys.mjs instead,
* that is exposed through PlacesUtils.history.
*
* If you're evaluating adding a new history API, it should
- * usually go to History.jsm, unless it needs to do long and
+ * usually go to History.sys.mjs, unless it needs to do long and
* expensive work in a batch, then it could be worth doing
* that in History.cpp.
*/
diff --git a/toolkit/components/places/nsFaviconService.cpp b/toolkit/components/places/nsFaviconService.cpp
index 51d20236c3..c236d7a680 100644
--- a/toolkit/components/places/nsFaviconService.cpp
+++ b/toolkit/components/places/nsFaviconService.cpp
@@ -269,6 +269,9 @@ nsFaviconService::SetAndFetchFaviconForPage(
NS_ENSURE_ARG(aFaviconURI);
NS_ENSURE_ARG_POINTER(_canceler);
+ nsCOMPtr<nsIURI> pageURI = GetExposableURI(aPageURI);
+ nsCOMPtr<nsIURI> faviconURI = GetExposableURI(aFaviconURI);
+
nsCOMPtr<nsIPrincipal> loadingPrincipal = aLoadingPrincipal;
MOZ_ASSERT(loadingPrincipal,
"please provide aLoadingPrincipal for this favicon");
@@ -291,33 +294,33 @@ nsFaviconService::SetAndFetchFaviconForPage(
// Build page data.
PageData page;
- nsresult rv = aPageURI->GetSpec(page.spec);
+ nsresult rv = pageURI->GetSpec(page.spec);
NS_ENSURE_SUCCESS(rv, rv);
// URIs can arguably lack a host.
- Unused << aPageURI->GetHost(page.host);
+ Unused << pageURI->GetHost(page.host);
if (StringBeginsWith(page.host, "www."_ns)) {
page.host.Cut(0, 4);
}
bool canAddToHistory;
nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
NS_ENSURE_TRUE(navHistory, NS_ERROR_OUT_OF_MEMORY);
- rv = navHistory->CanAddURI(aPageURI, &canAddToHistory);
+ rv = navHistory->CanAddURI(pageURI, &canAddToHistory);
NS_ENSURE_SUCCESS(rv, rv);
page.canAddToHistory = !!canAddToHistory && !loadPrivate;
// Build icon data.
IconData icon;
// If we have an in-memory icon payload, it overwrites the actual request.
- UnassociatedIconHashKey* iconKey = mUnassociatedIcons.GetEntry(aFaviconURI);
+ UnassociatedIconHashKey* iconKey = mUnassociatedIcons.GetEntry(faviconURI);
if (iconKey) {
icon = iconKey->iconData;
mUnassociatedIcons.RemoveEntry(iconKey);
} else {
icon.fetchMode = aForceReload ? FETCH_ALWAYS : FETCH_IF_MISSING;
- rv = aFaviconURI->GetSpec(icon.spec);
+ rv = faviconURI->GetSpec(icon.spec);
NS_ENSURE_SUCCESS(rv, rv);
// URIs can arguably lack a host.
- Unused << aFaviconURI->GetHost(icon.host);
+ Unused << faviconURI->GetHost(icon.host);
if (StringBeginsWith(icon.host, "www."_ns)) {
icon.host.Cut(0, 4);
}
@@ -327,9 +330,8 @@ nsFaviconService::SetAndFetchFaviconForPage(
// is just /favicon.ico. These icons are considered valid for the whole
// origin and expired with the origin through a trigger.
nsAutoCString path;
- if (NS_SUCCEEDED(aFaviconURI->GetPathQueryRef(path)) &&
- !icon.host.IsEmpty() && icon.host.Equals(page.host) &&
- path.EqualsLiteral("/favicon.ico")) {
+ if (NS_SUCCEEDED(faviconURI->GetPathQueryRef(path)) && !icon.host.IsEmpty() &&
+ icon.host.Equals(page.host) && path.EqualsLiteral("/favicon.ico")) {
icon.rootIcon = 1;
}
@@ -370,13 +372,15 @@ nsFaviconService::ReplaceFaviconData(nsIURI* aFaviconURI,
NS_ENSURE_ARG(imgLoader::SupportImageWithMimeType(
aMimeType, AcceptedMimeTypes::IMAGES_AND_DOCUMENTS));
+ nsCOMPtr<nsIURI> faviconURI = GetExposableURI(aFaviconURI);
+
PRTime now = PR_Now();
if (aExpiration < now + MIN_FAVICON_EXPIRATION) {
// Invalid input, just use the default.
aExpiration = now + MAX_FAVICON_EXPIRATION;
}
- UnassociatedIconHashKey* iconKey = mUnassociatedIcons.PutEntry(aFaviconURI);
+ UnassociatedIconHashKey* iconKey = mUnassociatedIcons.PutEntry(faviconURI);
if (!iconKey) {
return NS_ERROR_OUT_OF_MEMORY;
}
@@ -397,10 +401,10 @@ nsFaviconService::ReplaceFaviconData(nsIURI* aFaviconURI,
iconData->expiration = aExpiration;
iconData->status = ICON_STATUS_CACHED;
iconData->fetchMode = FETCH_NEVER;
- nsresult rv = aFaviconURI->GetSpec(iconData->spec);
+ nsresult rv = faviconURI->GetSpec(iconData->spec);
NS_ENSURE_SUCCESS(rv, rv);
// URIs can arguably lack a host.
- Unused << aFaviconURI->GetHost(iconData->host);
+ Unused << faviconURI->GetHost(iconData->host);
if (StringBeginsWith(iconData->host, "www."_ns)) {
iconData->host.Cut(0, 4);
}
@@ -428,7 +432,7 @@ nsFaviconService::ReplaceFaviconData(nsIURI* aFaviconURI,
if ((*iconData).payloads.Length() == 0) {
// We cannot optimize this favicon size and we are over the maximum size
// allowed, so we will not save data to the db to avoid bloating it.
- mUnassociatedIcons.RemoveEntry(aFaviconURI);
+ mUnassociatedIcons.RemoveEntry(faviconURI);
return NS_ERROR_FAILURE;
}
@@ -544,8 +548,10 @@ nsFaviconService::GetFaviconURLForPage(nsIURI* aPageURI,
aPreferredWidth = mDefaultIconURIPreferredSize;
}
+ nsCOMPtr<nsIURI> pageURI = GetExposableURI(aPageURI);
+
nsAutoCString pageSpec;
- nsresult rv = aPageURI->GetSpec(pageSpec);
+ nsresult rv = pageURI->GetSpec(pageSpec);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString pageHost;
// It's expected that some domains may not have a host.
@@ -573,12 +579,14 @@ nsFaviconService::GetFaviconDataForPage(nsIURI* aPageURI,
aPreferredWidth = mDefaultIconURIPreferredSize;
}
+ nsCOMPtr<nsIURI> pageURI = GetExposableURI(aPageURI);
+
nsAutoCString pageSpec;
- nsresult rv = aPageURI->GetSpec(pageSpec);
+ nsresult rv = pageURI->GetSpec(pageSpec);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString pageHost;
// It's expected that some domains may not have a host.
- Unused << aPageURI->GetHost(pageHost);
+ Unused << pageURI->GetHost(pageHost);
RefPtr<AsyncGetFaviconDataForPage> event = new AsyncGetFaviconDataForPage(
pageSpec, pageHost, aPreferredWidth, aCallback);
@@ -601,17 +609,20 @@ nsFaviconService::CopyFavicons(nsIURI* aFromPageURI, nsIURI* aToPageURI,
aFaviconLoadType <= nsIFaviconService::FAVICON_LOAD_NON_PRIVATE,
NS_ERROR_INVALID_ARG);
+ nsCOMPtr<nsIURI> fromPageURI = GetExposableURI(aFromPageURI);
+ nsCOMPtr<nsIURI> toPageURI = GetExposableURI(aToPageURI);
+
PageData fromPage;
- nsresult rv = aFromPageURI->GetSpec(fromPage.spec);
+ nsresult rv = fromPageURI->GetSpec(fromPage.spec);
NS_ENSURE_SUCCESS(rv, rv);
PageData toPage;
- rv = aToPageURI->GetSpec(toPage.spec);
+ rv = toPageURI->GetSpec(toPage.spec);
NS_ENSURE_SUCCESS(rv, rv);
bool canAddToHistory;
nsNavHistory* navHistory = nsNavHistory::GetHistoryService();
NS_ENSURE_TRUE(navHistory, NS_ERROR_OUT_OF_MEMORY);
- rv = navHistory->CanAddURI(aToPageURI, &canAddToHistory);
+ rv = navHistory->CanAddURI(toPageURI, &canAddToHistory);
NS_ENSURE_SUCCESS(rv, rv);
toPage.canAddToHistory =
!!canAddToHistory &&
@@ -636,6 +647,8 @@ nsresult nsFaviconService::GetFaviconLinkForIcon(nsIURI* aFaviconURI,
nsAutoCString spec;
if (aFaviconURI) {
+ nsCOMPtr<nsIURI> faviconURI = GetExposableURI(aFaviconURI);
+
// List of protocols for which it doesn't make sense to generate a favicon
// uri since they can be directly loaded from disk or memory.
static constexpr nsLiteralCString sDirectRequestProtocols[] = {
@@ -651,15 +664,15 @@ nsresult nsFaviconService::GetFaviconLinkForIcon(nsIURI* aFaviconURI,
// clang-format on
};
nsAutoCString iconURIScheme;
- if (NS_SUCCEEDED(aFaviconURI->GetScheme(iconURIScheme)) &&
+ if (NS_SUCCEEDED(faviconURI->GetScheme(iconURIScheme)) &&
std::find(std::begin(sDirectRequestProtocols),
std::end(sDirectRequestProtocols),
iconURIScheme) != std::end(sDirectRequestProtocols)) {
// Just return the input URL.
- *_retval = do_AddRef(aFaviconURI).take();
+ *_retval = do_AddRef(faviconURI).take();
return NS_OK;
}
- nsresult rv = aFaviconURI->GetSpec(spec);
+ nsresult rv = faviconURI->GetSpec(spec);
NS_ENSURE_SUCCESS(rv, rv);
}
return GetFaviconLinkForIconString(spec, _retval);
@@ -787,6 +800,11 @@ nsresult nsFaviconService::GetFaviconDataAsync(
const nsCString& aFaviconSpec, mozIStorageStatementCallback* aCallback) {
MOZ_ASSERT(aCallback, "Doesn't make sense to call this without a callback");
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), aFaviconSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ uri = GetExposableURI(uri);
+
nsCOMPtr<mozIStorageAsyncStatement> stmt = mDB->GetAsyncStatement(
"/*Do not warn (bug no: not worth adding an index */ "
"SELECT data, width FROM moz_icons "
@@ -794,7 +812,7 @@ nsresult nsFaviconService::GetFaviconDataAsync(
"ORDER BY width DESC");
NS_ENSURE_STATE(stmt);
- nsresult rv = URIBinder::Bind(stmt, "url"_ns, aFaviconSpec);
+ rv = URIBinder::Bind(stmt, "url"_ns, uri);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<mozIStoragePendingStatement> pendingStatement;
diff --git a/toolkit/components/places/nsINavHistoryService.idl b/toolkit/components/places/nsINavHistoryService.idl
index 5b118911a1..cb9b301410 100644
--- a/toolkit/components/places/nsINavHistoryService.idl
+++ b/toolkit/components/places/nsINavHistoryService.idl
@@ -903,7 +903,7 @@ interface nsINavHistoryService : nsISupports
// The current database schema version.
// To migrate to a new version bump this, add a MigrateVXXUp function to
// Database.cpp/h, and a test into tests/migration/
- const unsigned long DATABASE_SCHEMA_VERSION = 75;
+ const unsigned long DATABASE_SCHEMA_VERSION = 77;
/**
* System Notifications:
diff --git a/toolkit/components/places/nsITaggingService.idl b/toolkit/components/places/nsITaggingService.idl
index acda63b583..93ee82314a 100644
--- a/toolkit/components/places/nsITaggingService.idl
+++ b/toolkit/components/places/nsITaggingService.idl
@@ -58,9 +58,3 @@ interface nsITaggingService : nsISupports
Array<AString> getTagsForURI(in nsIURI aURI);
};
-
-%{C++
-
-#define TAGGING_SERVICE_CID "@mozilla.org/browser/tagging-service;1"
-
-%}
diff --git a/toolkit/components/places/nsPlacesTriggers.h b/toolkit/components/places/nsPlacesTriggers.h
index 0281054c16..c78ca3232c 100644
--- a/toolkit/components/places/nsPlacesTriggers.h
+++ b/toolkit/components/places/nsPlacesTriggers.h
@@ -112,7 +112,7 @@
"UPDATE moz_origins SET recalc_frecency = 1, recalc_alt_frecency = 1 " \
"WHERE id = OLD.origin_id; " \
"INSERT OR IGNORE INTO moz_previews_tombstones VALUES " \
- "(md5hex(OLD.url));" \
+ "(sha256hex(OLD.url));" \
"END ")
// This is the supporting table for the "AFTER DELETE ON moz_places" triggers.
diff --git a/toolkit/components/places/tests/PlacesTestUtils.sys.mjs b/toolkit/components/places/tests/PlacesTestUtils.sys.mjs
index acd1152b44..4e459d2e32 100644
--- a/toolkit/components/places/tests/PlacesTestUtils.sys.mjs
+++ b/toolkit/components/places/tests/PlacesTestUtils.sys.mjs
@@ -64,6 +64,9 @@ export var PlacesTestUtils = Object.freeze({
let info = { url: place.uri || place.url };
let spec =
info.url instanceof Ci.nsIURI ? info.url.spec : new URL(info.url).href;
+ info.exposableURI = Services.io.createExposableURI(
+ Services.io.newURI(spec)
+ );
info.title = "title" in place ? place.title : "test visit for " + spec;
let visitDate = place.visitDate;
if (visitDate) {
@@ -107,7 +110,7 @@ export var PlacesTestUtils = Object.freeze({
}
if (lastStoredVisit) {
await lazy.TestUtils.waitForCondition(
- () => lazy.PlacesUtils.history.fetch(lastStoredVisit.url),
+ () => lazy.PlacesUtils.history.fetch(lastStoredVisit.exposableURI),
"Ensure history has been updated and is visible to read-only connections"
);
}
@@ -556,7 +559,9 @@ export var PlacesTestUtils = Object.freeze({
* @param {string} field - The name of the field to retrieve a value from.
* @param {Object} [conditions] - An object containing the conditions to
* filter the query results. The keys represent the names of the columns to
- * filter by, and the values represent the filter values.
+ * filter by, and the values represent the filter values. It's possible to
+ * pass an array as value where the first element is an operator
+ * (e.g. "<", ">") and the second element is the actual value.
* @return {Promise} A Promise that resolves to the value of the specified
* field from the database table, or null if the query returns no results.
* @throws If more than one result is found for the given conditions.
@@ -579,9 +584,11 @@ export var PlacesTestUtils = Object.freeze({
* conditions.
* @param {string} table - The name of the database table to add to.
* @param {string} fields - an object with field, value pairs
- * @param {Object} [conditions] - An object containing the conditions to filter
- * the query results. The keys represent the names of the columns to filter
- * by, and the values represent the filter values.
+ * @param {Object} [conditions] - An object containing the conditions to
+ * filter the query results. The keys represent the names of the columns to
+ * filter by, and the values represent the filter values. It's possible to
+ * pass an array as value where the first element is an operator
+ * (e.g. "<", ">") and the second element is the actual value.
* @return {Promise} A Promise that resolves to the number of affected rows.
* @throws If no rows were affected.
*/
@@ -636,6 +643,11 @@ export var PlacesTestUtils = Object.freeze({
}
if (column == "url" && table == "moz_places") {
fragments.push("url_hash = hash(:url) AND url = :url");
+ } else if (Array.isArray(value)) {
+ // First element is the operator, second element is the value.
+ let [op, actualValue] = value;
+ fragments.push(`${column} ${op} :${column}`);
+ value = actualValue;
} else {
fragments.push(`${column} = :${column}`);
}
diff --git a/toolkit/components/places/tests/bookmarks/test_818584-discard-duplicate-backups.js b/toolkit/components/places/tests/bookmarks/test_818584-discard-duplicate-backups.js
index be5d53f8c6..71065425fd 100644
--- a/toolkit/components/places/tests/bookmarks/test_818584-discard-duplicate-backups.js
+++ b/toolkit/components/places/tests/bookmarks/test_818584-discard-duplicate-backups.js
@@ -17,7 +17,7 @@ add_task(async function () {
oldBackup
);
Assert.ok(count > 0);
- Assert.equal(hash.length, 24);
+ Assert.equal(hash.length, 44);
oldBackupName = oldBackupName.replace(
/\.json/,
"_" + count + "_" + hash + ".json"
diff --git a/toolkit/components/places/tests/bookmarks/test_818593-store-backup-metadata.js b/toolkit/components/places/tests/bookmarks/test_818593-store-backup-metadata.js
index 6d280e8cad..265cc5621d 100644
--- a/toolkit/components/places/tests/bookmarks/test_818593-store-backup-metadata.js
+++ b/toolkit/components/places/tests/bookmarks/test_818593-store-backup-metadata.js
@@ -28,7 +28,7 @@ add_task(async function test_saveBookmarksToJSONFile_and_create() {
PlacesBackups.filenamesRegex
);
Assert.equal(matches[2], nodeCount);
- Assert.equal(matches[3].length, 24);
+ Assert.equal(matches[3].length, 44);
// Clear all backups in our backups folder.
await PlacesBackups.create(0);
@@ -44,7 +44,7 @@ add_task(async function test_saveBookmarksToJSONFile_and_create() {
PlacesBackups.filenamesRegex
);
Assert.equal(matches[2], nodeCount);
- Assert.equal(matches[3].length, 24);
+ Assert.equal(matches[3].length, 44);
// Cleanup
await IOUtils.remove(backupFile);
diff --git a/toolkit/components/places/tests/bookmarks/test_insert_thousands_bookmarks.js b/toolkit/components/places/tests/bookmarks/test_insert_thousands_bookmarks.js
new file mode 100644
index 0000000000..9d1823449f
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_insert_thousands_bookmarks.js
@@ -0,0 +1,24 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test `insertTree()` with more bookmarks than the Sqlite variables limit.
+
+add_task(async function () {
+ const NUM_BOOKMARKS = 1000;
+ await PlacesUtils.withConnectionWrapper("test", async db => {
+ db.variableLimit = NUM_BOOKMARKS - 100;
+ Assert.greater(
+ NUM_BOOKMARKS,
+ db.variableLimit,
+ "Insert more bookmarks than the Sqlite variables limit."
+ );
+ });
+ let children = [];
+ for (let i = 0; i < NUM_BOOKMARKS; ++i) {
+ children.push({ url: "http://www.mozilla.org/" + i });
+ }
+ await PlacesUtils.bookmarks.insertTree({
+ guid: PlacesUtils.bookmarks.toolbarGuid,
+ children,
+ });
+});
diff --git a/toolkit/components/places/tests/bookmarks/xpcshell.toml b/toolkit/components/places/tests/bookmarks/xpcshell.toml
index 0b989e5fbf..c2e6d7ff09 100644
--- a/toolkit/components/places/tests/bookmarks/xpcshell.toml
+++ b/toolkit/components/places/tests/bookmarks/xpcshell.toml
@@ -70,6 +70,8 @@ support-files = ["bookmarks_long_tag.json"]
["test_bookmarks_update.js"]
+["test_insert_thousands_bookmarks.js"]
+
["test_insertTree_fixupOrSkipInvalidEntries.js"]
["test_keywords.js"]
diff --git a/toolkit/components/places/tests/browser/browser.toml b/toolkit/components/places/tests/browser/browser.toml
index 022b929240..b7d688c980 100644
--- a/toolkit/components/places/tests/browser/browser.toml
+++ b/toolkit/components/places/tests/browser/browser.toml
@@ -44,6 +44,8 @@ support-files = [
"redirect_twice.sjs",
]
+["browser_favicon.js"]
+
["browser_favicon_privatebrowsing_perwindowpb.js"]
["browser_history_post.js"]
@@ -94,9 +96,11 @@ support-files = [
https_first_disabled = true
support-files = [
"begin.html",
+ "favicon.html",
"final.html",
"redirect_once.sjs",
"redirect_twice.sjs",
+ "userpass.html",
]
["browser_visituri_nohistory.js"]
diff --git a/toolkit/components/places/tests/browser/browser_double_redirect.js b/toolkit/components/places/tests/browser/browser_double_redirect.js
index 435bd86f19..d5b2fca1fe 100644
--- a/toolkit/components/places/tests/browser/browser_double_redirect.js
+++ b/toolkit/components/places/tests/browser/browser_double_redirect.js
@@ -16,7 +16,7 @@ add_task(async function () {
let promiseVisits = new Promise(resolve => {
let observer = {
_notified: [],
- onVisit(uri, id, time, referrerId, transition) {
+ onVisit(uri) {
info("Received onVisit: " + uri);
this._notified.push(uri);
diff --git a/toolkit/components/places/tests/browser/browser_favicon.js b/toolkit/components/places/tests/browser/browser_favicon.js
new file mode 100644
index 0000000000..9b4a1b97fe
--- /dev/null
+++ b/toolkit/components/places/tests/browser/browser_favicon.js
@@ -0,0 +1,74 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const lazy = {};
+ChromeUtils.defineESModuleGetters(lazy, {
+ PlacesTestUtils: "resource://testing-common/PlacesTestUtils.sys.mjs",
+});
+
+const { MockRegistrar } = ChromeUtils.importESModule(
+ "resource://testing-common/MockRegistrar.sys.mjs"
+);
+
+add_task(async function test_userpass() {
+ // Setup the prompt to avoid showing it.
+ let mockPromptService = {
+ firstTimeCalled: false,
+ confirmExBC() {
+ if (!this.firstTimeCalled) {
+ this.firstTimeCalled = true;
+ return 0;
+ }
+
+ return 1;
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsIPromptService"]),
+ };
+ let mockPromptServiceCID = MockRegistrar.register(
+ "@mozilla.org/prompter;1",
+ mockPromptService
+ );
+ registerCleanupFunction(() => {
+ MockRegistrar.unregister(mockPromptServiceCID);
+ });
+
+ const pageUrl =
+ "https://user:pass@example.org/tests/toolkit/components/places/tests/browser/favicon.html";
+ const faviconUrl =
+ "https://user:pass@example.org/tests/toolkit/components/places/tests/browser/favicon-normal32.png";
+ const exposableFaviconUrl =
+ "https://example.org/tests/toolkit/components/places/tests/browser/favicon-normal32.png";
+
+ let faviconPromise = lazy.PlacesTestUtils.waitForNotification(
+ "favicon-changed",
+ async () => {
+ let faviconForExposable = await lazy.PlacesTestUtils.getDatabaseValue(
+ "moz_icons",
+ "icon_url",
+ {
+ icon_url: exposableFaviconUrl,
+ }
+ );
+ Assert.ok(faviconForExposable, "Found the icon for exposable URL");
+
+ let faviconForOriginal = await lazy.PlacesTestUtils.getDatabaseValue(
+ "moz_icons",
+ "icon_url",
+ {
+ icon_url: faviconUrl,
+ }
+ );
+ Assert.ok(!faviconForOriginal, "Not found the icon for the original URL");
+ return true;
+ }
+ );
+
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
+ await faviconPromise;
+
+ // Clean up.
+ await PlacesUtils.history.clear();
+ gBrowser.removeCurrentTab();
+});
diff --git a/toolkit/components/places/tests/browser/browser_favicon_privatebrowsing_perwindowpb.js b/toolkit/components/places/tests/browser/browser_favicon_privatebrowsing_perwindowpb.js
index ab3e0a1ef1..090fe802ab 100644
--- a/toolkit/components/places/tests/browser/browser_favicon_privatebrowsing_perwindowpb.js
+++ b/toolkit/components/places/tests/browser/browser_favicon_privatebrowsing_perwindowpb.js
@@ -36,7 +36,7 @@ function test() {
waitForTabLoad(win, function () {
PlacesUtils.favicons.getFaviconURLForPage(
NetUtil.newURI(pageURI),
- function (uri, dataLen, data, mimeType) {
+ function (uri) {
is(uri, null, "No result should be found");
finish();
}
diff --git a/toolkit/components/places/tests/browser/browser_history_post.js b/toolkit/components/places/tests/browser/browser_history_post.js
index a62592516f..dad988a624 100644
--- a/toolkit/components/places/tests/browser/browser_history_post.js
+++ b/toolkit/components/places/tests/browser/browser_history_post.js
@@ -12,7 +12,7 @@ add_task(async function () {
let doc = content.document;
let submit = doc.getElementById("submit");
let iframe = doc.getElementById("post_iframe");
- let p = new Promise((resolve, reject) => {
+ let p = new Promise(resolve => {
iframe.addEventListener(
"load",
function () {
diff --git a/toolkit/components/places/tests/browser/browser_notfound.js b/toolkit/components/places/tests/browser/browser_notfound.js
index 22ac67de0a..84e4f2ab9b 100644
--- a/toolkit/components/places/tests/browser/browser_notfound.js
+++ b/toolkit/components/places/tests/browser/browser_notfound.js
@@ -23,7 +23,7 @@ add_task(async function () {
gBrowser,
url,
},
- async browser => {
+ async () => {
info("awaiting for the visit");
await promiseVisited;
diff --git a/toolkit/components/places/tests/browser/browser_redirect_self.js b/toolkit/components/places/tests/browser/browser_redirect_self.js
index 7ed7ee0af0..3f14cf4f7c 100644
--- a/toolkit/components/places/tests/browser/browser_redirect_self.js
+++ b/toolkit/components/places/tests/browser/browser_redirect_self.js
@@ -37,7 +37,7 @@ add_task(async function () {
gBrowser,
url,
},
- async browser => {
+ async () => {
await TestUtils.waitForCondition(() => visitCount == 2);
// Check that the visit is not hidden in the database.
Assert.ok(
diff --git a/toolkit/components/places/tests/browser/browser_visited_notfound.js b/toolkit/components/places/tests/browser/browser_visited_notfound.js
index 36d1764361..ca0638481b 100644
--- a/toolkit/components/places/tests/browser/browser_visited_notfound.js
+++ b/toolkit/components/places/tests/browser/browser_visited_notfound.js
@@ -29,7 +29,7 @@ add_task(async function test() {
gBrowser,
url,
},
- async browser => {
+ async () => {
info("awaiting for the visit");
Assert.equal(
diff --git a/toolkit/components/places/tests/browser/browser_visituri.js b/toolkit/components/places/tests/browser/browser_visituri.js
index 6633ac188b..529c010e63 100644
--- a/toolkit/components/places/tests/browser/browser_visituri.js
+++ b/toolkit/components/places/tests/browser/browser_visituri.js
@@ -1,86 +1,78 @@
-/**
- * One-time observer callback.
- */
-function promiseObserve(name, checkFn) {
- return new Promise(resolve => {
- Services.obs.addObserver(function observer(subject) {
- if (checkFn(subject)) {
- Services.obs.removeObserver(observer, name);
- resolve();
- }
- }, name);
- });
-}
-
-var conn = PlacesUtils.history.DBConnection;
-
-/**
- * Gets a single column value from either the places or historyvisits table.
- */
-function getColumn(table, column, fromColumnName, fromColumnValue) {
- let sql = `SELECT ${column}
- FROM ${table}
- WHERE ${fromColumnName} = :val
- ${fromColumnName == "url" ? "AND url_hash = hash(:val)" : ""}
- LIMIT 1`;
- let stmt = conn.createStatement(sql);
- try {
- stmt.params.val = fromColumnValue;
- ok(stmt.executeStep(), "Expect to get a row");
- return stmt.row[column];
- } finally {
- stmt.reset();
- }
-}
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
-add_task(async function () {
+const lazy = {};
+ChromeUtils.defineESModuleGetters(lazy, {
+ PlacesTestUtils: "resource://testing-common/PlacesTestUtils.sys.mjs",
+ TestUtils: "resource://testing-common/TestUtils.sys.mjs",
+});
+
+add_task(async function test_basic() {
// Make sure places visit chains are saved correctly with a redirect
// transitions.
// Part 1: observe history events that fire when a visit occurs.
// Make sure visits appear in order, and that the visit chain is correct.
- var expectedUrls = [
+ const expectedUrls = [
"http://example.com/tests/toolkit/components/places/tests/browser/begin.html",
"http://example.com/tests/toolkit/components/places/tests/browser/redirect_twice.sjs",
"http://example.com/tests/toolkit/components/places/tests/browser/redirect_once.sjs",
"http://test1.example.com/tests/toolkit/components/places/tests/browser/final.html",
];
- var currentIndex = 0;
-
- function checkObserver(subject) {
- var uri = subject.QueryInterface(Ci.nsIURI);
- var expected = expectedUrls[currentIndex];
- is(uri.spec, expected, "Saved URL visit " + uri.spec);
-
- var placeId = getColumn("moz_places", "id", "url", uri.spec);
- var fromVisitId = getColumn(
- "moz_historyvisits",
- "from_visit",
- "place_id",
- placeId
- );
- if (currentIndex == 0) {
- is(fromVisitId, 0, "First visit has no from visit");
- } else {
- var lastVisitId = getColumn(
- "moz_historyvisits",
- "place_id",
+ let currentIndex = 0;
+ let visitUriPromise = lazy.TestUtils.topicObserved(
+ "uri-visit-saved",
+ async subject => {
+ let uri = subject.QueryInterface(Ci.nsIURI);
+ let expected = expectedUrls[currentIndex];
+ is(uri.spec, expected, "Saved URL visit " + uri.spec);
+
+ let placeId = await lazy.PlacesTestUtils.getDatabaseValue(
+ "moz_places",
"id",
- fromVisitId
+ {
+ url: uri.spec,
+ }
);
- var fromVisitUrl = getColumn("moz_places", "url", "id", lastVisitId);
- is(
- fromVisitUrl,
- expectedUrls[currentIndex - 1],
- "From visit was " + expectedUrls[currentIndex - 1]
+ let fromVisitId = await lazy.PlacesTestUtils.getDatabaseValue(
+ "moz_historyvisits",
+ "from_visit",
+ {
+ place_id: placeId,
+ }
);
- }
- currentIndex++;
- return currentIndex >= expectedUrls.length;
- }
- let visitUriPromise = promiseObserve("uri-visit-saved", checkObserver);
+ if (currentIndex == 0) {
+ is(fromVisitId, 0, "First visit has no from visit");
+ } else {
+ let lastVisitId = await lazy.PlacesTestUtils.getDatabaseValue(
+ "moz_historyvisits",
+ "place_id",
+ {
+ id: fromVisitId,
+ }
+ );
+ let fromVisitUrl = await lazy.PlacesTestUtils.getDatabaseValue(
+ "moz_places",
+ "url",
+ {
+ id: lastVisitId,
+ }
+ );
+ is(
+ fromVisitUrl,
+ expectedUrls[currentIndex - 1],
+ "From visit was " + expectedUrls[currentIndex - 1]
+ );
+ }
+
+ currentIndex++;
+ return currentIndex >= expectedUrls.length;
+ }
+ );
const testUrl =
"http://example.com/tests/toolkit/components/places/tests/browser/begin.html";
@@ -94,7 +86,119 @@ add_task(async function () {
);
await visitUriPromise;
+ // Clean up.
await PlacesUtils.history.clear();
+ gBrowser.removeCurrentTab();
+});
+
+add_task(async function test_userpass() {
+ // Avoid showing the auth prompt.
+ await SpecialPowers.pushPrefEnv({
+ set: [["network.auth.confirmAuth.enabled", false]],
+ });
+
+ // Open a html having test links.
+ await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "https://example.org/tests/toolkit/components/places/tests/browser/userpass.html"
+ );
+
+ const clickedUrl =
+ "https://user:pass@example.org/tests/toolkit/components/places/tests/browser/favicon.html";
+ const exposablePageUrl =
+ "https://example.org/tests/toolkit/components/places/tests/browser/favicon.html";
+
+ let visitUriPromise = lazy.TestUtils.topicObserved(
+ "uri-visit-saved",
+ async subject => {
+ let uri = subject.QueryInterface(Ci.nsIURI);
+ if (uri.spec !== exposablePageUrl) {
+ return false;
+ }
+ let placeForExposable = await lazy.PlacesTestUtils.getDatabaseValue(
+ "moz_places",
+ "id",
+ {
+ url: exposablePageUrl,
+ }
+ );
+ Assert.ok(placeForExposable, "Found the place for exposable URL");
+
+ let placeForOriginal = await lazy.PlacesTestUtils.getDatabaseValue(
+ "moz_places",
+ "id",
+ {
+ url: clickedUrl,
+ }
+ );
+ Assert.ok(!placeForOriginal, "Not found the place for the original URL");
+
+ return true;
+ }
+ );
+
+ // Open the target link as background.
+ await ContentTask.spawn(gBrowser.selectedBrowser, null, async args => {
+ let link = content.document.getElementById("target-userpass");
+ EventUtils.synthesizeMouseAtCenter(
+ link,
+ {
+ ctrlKey: true,
+ metaKey: true,
+ },
+ content
+ );
+ return link.href;
+ });
+
+ // Wait for fireing visited event.
+ await visitUriPromise;
+
+ // Check the title.
+ await BrowserTestUtils.waitForCondition(async () => {
+ let titleForExposable = await lazy.PlacesTestUtils.getDatabaseValue(
+ "moz_places",
+ "title",
+ {
+ url: exposablePageUrl,
+ }
+ );
+ return titleForExposable == "favicon page";
+ }, "Wait for the proper title is updated");
+
+ // Check the link status.
+ const expectedResults = {
+ "target-userpass": true,
+ "no-userpass": true,
+ "another-userpass": false,
+ "another-url": false,
+ };
+
+ for (const [key, value] of Object.entries(expectedResults)) {
+ await ContentTask.spawn(
+ gBrowser.selectedBrowser,
+ [key, value],
+ async ([k, v]) => {
+ // ElementState::VISITED
+ const VISITED_STATE = 1 << 18;
+ await ContentTaskUtils.waitForCondition(() => {
+ const isVisited = !!(
+ content.InspectorUtils.getContentState(
+ content.document.getElementById(k)
+ ) & VISITED_STATE
+ );
+ return isVisited == v;
+ });
+ }
+ );
+ Assert.ok(true, `The status of ${key} is correct`);
+ }
+
+ // Clean up.
+ await PlacesUtils.history.clear();
+ // Remove the tab for userpass.html
+ gBrowser.removeCurrentTab();
+ // Remove the tab for favicon.html
gBrowser.removeCurrentTab();
});
diff --git a/toolkit/components/places/tests/browser/browser_visituri_privatebrowsing_perwindowpb.js b/toolkit/components/places/tests/browser/browser_visituri_privatebrowsing_perwindowpb.js
index 746611d2ad..2d170115f6 100644
--- a/toolkit/components/places/tests/browser/browser_visituri_privatebrowsing_perwindowpb.js
+++ b/toolkit/components/places/tests/browser/browser_visituri_privatebrowsing_perwindowpb.js
@@ -13,7 +13,7 @@ var visitSavedPromise;
add_setup(async function () {
visitSavedPromise = new Promise(resolve => {
observer = {
- observe(subject, topic, data) {
+ observe(subject, topic) {
// The uri-visit-saved topic should only work when on normal mode.
if (topic == "uri-visit-saved") {
Services.obs.removeObserver(observer, "uri-visit-saved");
diff --git a/toolkit/components/places/tests/browser/favicon.html b/toolkit/components/places/tests/browser/favicon.html
index a0f5ea9594..789d0b89b8 100644
--- a/toolkit/components/places/tests/browser/favicon.html
+++ b/toolkit/components/places/tests/browser/favicon.html
@@ -5,9 +5,10 @@
<html>
<head>
- <link rel="shortcut icon" href="http://example.org/tests/toolkit/components/places/tests/browser/favicon-normal32.png">
+ <link rel="shortcut icon" href="/tests/toolkit/components/places/tests/browser/favicon-normal32.png">
+ <title>favicon page</title>
</head>
<body>
- OK we're done!
+ <label>OK we're done!</label>
</body>
</html>
diff --git a/toolkit/components/places/tests/browser/previews/browser.toml b/toolkit/components/places/tests/browser/previews/browser.toml
index 10758a4803..f7f2c9b583 100644
--- a/toolkit/components/places/tests/browser/previews/browser.toml
+++ b/toolkit/components/places/tests/browser/previews/browser.toml
@@ -2,7 +2,7 @@
prefs = [
"browser.pagethumbnails.capturing_disabled=false",
"places.previews.enabled=true",
- "places.previews.log=true",
+ "places.loglevel='All'",
]
["browser_thumbnails.js"]
diff --git a/toolkit/components/places/tests/browser/userpass.html b/toolkit/components/places/tests/browser/userpass.html
new file mode 100644
index 0000000000..ceff388b79
--- /dev/null
+++ b/toolkit/components/places/tests/browser/userpass.html
@@ -0,0 +1,13 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+ -->
+
+<html>
+ <body>
+ <a href="https://user:pass@example.org/tests/toolkit/components/places/tests/browser/favicon.html" id="target-userpass">target userpass</a>
+ <a href="https://user2:pass2@example.org/tests/toolkit/components/places/tests/browser/favicon.html" id="another-userpass">another userpass</a>
+ <a href="https://example.org/tests/toolkit/components/places/tests/browser/favicon.html" id="no-userpass">no userpass</a>
+ <a href="https://example.org/" id="another-url">another url</a>
+ </body>
+</html>
diff --git a/toolkit/components/places/tests/chrome/test_371798.xhtml b/toolkit/components/places/tests/chrome/test_371798.xhtml
index 33e866e51e..f66ac74aae 100644
--- a/toolkit/components/places/tests/chrome/test_371798.xhtml
+++ b/toolkit/components/places/tests/chrome/test_371798.xhtml
@@ -57,7 +57,7 @@ const TEST_URI = Services.io.newURI("http://foo.com");
let node = rootNode.getChild(i);
// test that bm1 does not have new title
if (node.bookmarkGuid == bm1.guid)
- ok(node.title != "foo",
+ isnot(node.title, "foo",
"Changing a bookmark's title did not affect the title of other bookmarks with the same URI");
}
rootNode.containerOpen = false;
diff --git a/toolkit/components/places/tests/expiration/test_notifications.js b/toolkit/components/places/tests/expiration/test_notifications.js
index d52319a9c9..7ac7d769d6 100644
--- a/toolkit/components/places/tests/expiration/test_notifications.js
+++ b/toolkit/components/places/tests/expiration/test_notifications.js
@@ -14,7 +14,7 @@
var gObserver = {
notifications: 0,
- observe(aSubject, aTopic, aData) {
+ observe() {
this.notifications++;
},
};
diff --git a/toolkit/components/places/tests/favicons/head_favicons.js b/toolkit/components/places/tests/favicons/head_favicons.js
index d8109c66e0..afd2c4924f 100644
--- a/toolkit/components/places/tests/favicons/head_favicons.js
+++ b/toolkit/components/places/tests/favicons/head_favicons.js
@@ -53,13 +53,10 @@ function checkFaviconDataForPage(
* This function is called after the check finished.
*/
function checkFaviconMissingForPage(aPageURI, aCallback) {
- PlacesUtils.favicons.getFaviconURLForPage(
- aPageURI,
- function (aURI, aDataLen, aData, aMimeType) {
- Assert.ok(aURI === null);
- aCallback();
- }
- );
+ PlacesUtils.favicons.getFaviconURLForPage(aPageURI, function (aURI) {
+ Assert.ok(aURI === null);
+ aCallback();
+ });
}
function promiseFaviconMissingForPage(aPageURI) {
diff --git a/toolkit/components/places/tests/favicons/test_cached-favicon_mime_type.js b/toolkit/components/places/tests/favicons/test_cached-favicon_mime_type.js
index 1c95d63f94..c1f6689a70 100644
--- a/toolkit/components/places/tests/favicons/test_cached-favicon_mime_type.js
+++ b/toolkit/components/places/tests/favicons/test_cached-favicon_mime_type.js
@@ -19,7 +19,7 @@ function streamListener(aExpectedContentType) {
}
streamListener.prototype = {
onStartRequest() {},
- onStopRequest(aRequest, aContext, aStatusCode) {
+ onStopRequest(aRequest) {
let channel = aRequest.QueryInterface(Ci.nsIChannel);
Assert.equal(
channel.contentType,
@@ -28,7 +28,7 @@ streamListener.prototype = {
);
this.done.resolve();
},
- onDataAvailable(aRequest, aInputStream, aOffset, aCount) {
+ onDataAvailable(aRequest) {
aRequest.cancel(Cr.NS_ERROR_ABORT);
throw Components.Exception("", Cr.NS_ERROR_ABORT);
},
@@ -85,4 +85,60 @@ add_task(async function () {
let listener = new streamListener("image/png");
channel.asyncOpen(listener);
await listener.done.promise;
+
+ await PlacesUtils.history.clear();
+});
+
+add_task(async function test_userpass() {
+ info("Test whether can get favicon content regardless of user pass");
+
+ const PAGE_NORMAL = uri("http://mozilla.org/");
+ const PAGE_USERPASS = uri("http://user:pass@mozilla.org/");
+ const ICON_NORMAL = uri("http://mozilla.org/favicon.png");
+ const ICON_USERPASS = uri("http://user:pass@mozilla.org/favicon.png");
+ const CACHED_ICON_NORMAL = "cached-favicon:http://mozilla.org/favicon.png";
+ const CACHED_ICON_USERPASS =
+ "cached-favicon:http://user:pass@mozilla.org/favicon.png";
+
+ const testData = [
+ {
+ pageURI: PAGE_USERPASS,
+ iconURI: ICON_NORMAL,
+ },
+ {
+ pageURI: PAGE_NORMAL,
+ iconURI: ICON_USERPASS,
+ },
+ {
+ pageURI: PAGE_USERPASS,
+ iconURI: ICON_USERPASS,
+ },
+ ];
+
+ for (const { pageURI, iconURI } of testData) {
+ for (const loadingIconURISpec of [
+ CACHED_ICON_NORMAL,
+ CACHED_ICON_USERPASS,
+ ]) {
+ PlacesUtils.favicons.replaceFaviconDataFromDataURL(
+ iconURI,
+ testFaviconData,
+ 0,
+ systemPrincipal
+ );
+ await PlacesTestUtils.addVisits(pageURI);
+ await setFaviconForPage(pageURI, iconURI);
+
+ // Open the channel
+ let channel = NetUtil.newChannel({
+ uri: loadingIconURISpec,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_INTERNAL_IMAGE_FAVICON,
+ });
+ let listener = new streamListener("image/png");
+ channel.asyncOpen(listener);
+ await listener.done.promise;
+ await PlacesUtils.history.clear();
+ }
+ }
});
diff --git a/toolkit/components/places/tests/favicons/test_page-icon_protocol.js b/toolkit/components/places/tests/favicons/test_page-icon_protocol.js
index 932040bafb..287484868f 100644
--- a/toolkit/components/places/tests/favicons/test_page-icon_protocol.js
+++ b/toolkit/components/places/tests/favicons/test_page-icon_protocol.js
@@ -185,11 +185,11 @@ add_task(async function page_content_process() {
let img = content.document.createElement("img");
img.src = url;
let imgPromise = new Promise((resolve, reject) => {
- img.addEventListener("error", e => {
+ img.addEventListener("error", () => {
Assert.ok(true, "Got expected load error.");
resolve();
});
- img.addEventListener("load", e => {
+ img.addEventListener("load", () => {
Assert.ok(false, "Did not expect a successful load.");
reject();
});
@@ -225,11 +225,11 @@ add_task(async function page_privileged_about_content_process() {
let img = content.document.createElement("img");
img.src = url;
let imgPromise = new Promise((resolve, reject) => {
- img.addEventListener("error", e => {
+ img.addEventListener("error", () => {
Assert.ok(false, "Did not expect an error. ");
reject();
});
- img.addEventListener("load", e => {
+ img.addEventListener("load", () => {
Assert.ok(true, "Got expected load event.");
resolve();
});
@@ -241,3 +241,57 @@ add_task(async function page_privileged_about_content_process() {
await contentPage.close();
});
+
+add_task(async function test_with_user_pass() {
+ info("Test whether can get favicon content regardless of user pass");
+ await PlacesUtils.history.clear();
+
+ const PAGE_NORMAL = uri("http://mozilla.org/");
+ const PAGE_USERPASS = uri("http://user:pass@mozilla.org/");
+ const ICON_NORMAL = uri("http://mozilla.org/favicon.png");
+ const ICON_USERPASS = uri("http://user:pass@mozilla.org/favicon.png");
+ const PAGE_ICON_NORMAL = "page-icon:http://mozilla.org/";
+ const PAGE_ICON_USERPASS = "page-icon:http://user:pass@mozilla.org/";
+
+ const testData = [
+ {
+ pageURI: PAGE_USERPASS,
+ iconURI: ICON_NORMAL,
+ },
+ {
+ pageURI: PAGE_NORMAL,
+ iconURI: ICON_USERPASS,
+ },
+ {
+ pageURI: PAGE_USERPASS,
+ iconURI: ICON_USERPASS,
+ },
+ ];
+
+ for (const { pageURI, iconURI } of testData) {
+ for (const loadingIconURISpec of [PAGE_ICON_NORMAL, PAGE_ICON_USERPASS]) {
+ PlacesUtils.favicons.replaceFaviconDataFromDataURL(
+ iconURI,
+ ICON_DATAURL,
+ 0,
+ systemPrincipal
+ );
+ await PlacesTestUtils.addVisits(pageURI);
+ await new Promise(resolve => {
+ PlacesUtils.favicons.setAndFetchFaviconForPage(
+ pageURI,
+ iconURI,
+ false,
+ PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
+ resolve,
+ Services.scriptSecurityManager.getSystemPrincipal()
+ );
+ });
+
+ let { data, contentType } = await fetchIconForSpec(loadingIconURISpec);
+ Assert.equal(contentType, gFavicon.contentType);
+ Assert.deepEqual(data, gFavicon.data, "Got the favicon data");
+ await PlacesUtils.history.clear();
+ }
+ }
+});
diff --git a/toolkit/components/places/tests/favicons/test_setAndFetchFaviconForPage.js b/toolkit/components/places/tests/favicons/test_setAndFetchFaviconForPage.js
index 1b4ea87ec0..b2d82e65b0 100644
--- a/toolkit/components/places/tests/favicons/test_setAndFetchFaviconForPage.js
+++ b/toolkit/components/places/tests/favicons/test_setAndFetchFaviconForPage.js
@@ -53,6 +53,17 @@ let gTests = [
Services.prefs.setBoolPref("places.history.enabled", true);
},
},
+ {
+ desc: "Visit URL with login info",
+ href: "http://user:pass@example.com/with_login_info",
+ loadType: PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
+ async setup() {
+ await PlacesTestUtils.addVisits({
+ uri: this.href,
+ transition: TRANSITION_TYPED,
+ });
+ },
+ },
];
add_task(async function () {
@@ -67,6 +78,7 @@ add_task(async function () {
for (let test of gTests) {
info(test.desc);
let pageURI = PlacesUtils.toURI(test.href);
+ let exposableURI = Services.io.createExposableURI(pageURI);
await test.setup();
@@ -75,7 +87,7 @@ add_task(async function () {
"favicon-changed",
events =>
events.some(e => {
- if (e.url == pageURI.spec && e.faviconUrl == faviconURI.spec) {
+ if (e.url == exposableURI.spec && e.faviconUrl == faviconURI.spec) {
pageGuid = e.pageGuid;
return true;
}
@@ -97,7 +109,7 @@ add_task(async function () {
Assert.equal(
pageGuid,
await PlacesTestUtils.getDatabaseValue("moz_places", "guid", {
- url: pageURI,
+ url: exposableURI,
}),
"Page guid is correct"
);
diff --git a/toolkit/components/places/tests/history/test_async_history_api.js b/toolkit/components/places/tests/history/test_async_history_api.js
index ce0d96b306..cc645baaec 100644
--- a/toolkit/components/places/tests/history/test_async_history_api.js
+++ b/toolkit/components/places/tests/history/test_async_history_api.js
@@ -32,7 +32,7 @@ function VisitInfo(aTransitionType, aVisitTime) {
}
function promiseUpdatePlaces(aPlaces, aOptions = {}) {
- return new Promise((resolve, reject) => {
+ return new Promise(resolve => {
asyncHistory.updatePlaces(
aPlaces,
Object.assign(
@@ -974,7 +974,7 @@ add_task(async function test_title_change_notifies() {
place.title = "title 1";
let expectedNotification = false;
let titleChangeObserver;
- let titleChangePromise = new Promise((resolve, reject) => {
+ let titleChangePromise = new Promise(resolve => {
titleChangeObserver = new TitleChangedObserver(
place.uri,
place.title,
@@ -1032,8 +1032,8 @@ add_task(async function test_visit_notifies() {
};
Assert.equal(false, await PlacesUtils.history.hasVisits(place.uri));
- function promiseVisitObserver(aPlace) {
- return new Promise((resolve, reject) => {
+ function promiseVisitObserver() {
+ return new Promise(resolve => {
let callbackCount = 0;
let finisher = function () {
if (++callbackCount == 2) {
@@ -1137,7 +1137,7 @@ add_task(async function test_omit_frecency_notifications() {
// we won't get a ranking changed notification until recalculation happens.
await PlacesUtils.history.clear();
let notified = false;
- let listener = events => {
+ let listener = () => {
notified = true;
PlacesUtils.observers.removeListener(["pages-rank-changed"], listener);
};
diff --git a/toolkit/components/places/tests/history/test_insertMany.js b/toolkit/components/places/tests/history/test_insertMany.js
index b2cf60ed91..3d0774cbf2 100644
--- a/toolkit/components/places/tests/history/test_insertMany.js
+++ b/toolkit/components/places/tests/history/test_insertMany.js
@@ -189,7 +189,7 @@ add_task(async function test_transitions() {
await PlacesUtils.history.insertMany(places);
// Check callbacks.
let count = 0;
- await PlacesUtils.history.insertMany(places, pageInfo => {
+ await PlacesUtils.history.insertMany(places, () => {
++count;
});
Assert.equal(count, Object.keys(PlacesUtils.history.TRANSITIONS).length);
@@ -246,3 +246,22 @@ add_task(async function test_guid() {
"Record C is fetchable after insertMany"
);
});
+
+add_task(async function test_withUserPass() {
+ await PlacesUtils.history.insertMany([
+ {
+ url: "http://user:pass@example.com/userpass",
+ visits: [{ date: new Date() }],
+ },
+ ]);
+
+ Assert.ok(
+ !(await PlacesUtils.history.fetch("http://user:pass@example.com/userpass")),
+ "The url with user and pass is not stored"
+ );
+
+ Assert.ok(
+ await PlacesUtils.history.fetch("http://example.com/userpass"),
+ "The url without user and pass is stored"
+ );
+});
diff --git a/toolkit/components/places/tests/history/test_removeByFilter.js b/toolkit/components/places/tests/history/test_removeByFilter.js
index fb18bf8e74..fe90977bfd 100644
--- a/toolkit/components/places/tests/history/test_removeByFilter.js
+++ b/toolkit/components/places/tests/history/test_removeByFilter.js
@@ -174,13 +174,13 @@ add_task(async function test_removeByFilter() {
for (let callbackUse of [true, false]) {
// Case A Positives
for (let bookmarkUse of [true, false]) {
- let bookmarkedUri = arr => undefined;
+ let bookmarkedUri = () => undefined;
let checkableArray = arr => arr;
let checkClosure = assertNotInDB;
if (bookmarkUse) {
bookmarkedUri = arr => arr[0];
checkableArray = arr => arr.slice(1);
- checkClosure = function (aUri) {};
+ checkClosure = function () {};
}
// Case A 1: Dates
await removeByFilterTester(
diff --git a/toolkit/components/places/tests/history/test_removeVisitsByFilter.js b/toolkit/components/places/tests/history/test_removeVisitsByFilter.js
index 5681ab22bc..be01fcb901 100644
--- a/toolkit/components/places/tests/history/test_removeVisitsByFilter.js
+++ b/toolkit/components/places/tests/history/test_removeVisitsByFilter.js
@@ -117,7 +117,7 @@ add_task(async function test_removeVisitsByFilter() {
}
endIndex = Math.min(
endIndex,
- removedItems.findIndex((v, index) => v.uri.spec != rawURL) - 1
+ removedItems.findIndex(v => v.uri.spec != rawURL) - 1
);
}
removedItems.splice(endIndex + 1);
diff --git a/toolkit/components/places/tests/history/test_updatePlaces_embed.js b/toolkit/components/places/tests/history/test_updatePlaces_embed.js
index a2831f2f58..84efd11b2d 100644
--- a/toolkit/components/places/tests/history/test_updatePlaces_embed.js
+++ b/toolkit/components/places/tests/history/test_updatePlaces_embed.js
@@ -28,10 +28,10 @@ add_task(async function test_embed_visit() {
asyncHistory.updatePlaces(place, {
ignoreErrors: true,
ignoreResults: true,
- handleError(aResultCode, aPlace) {
+ handleError() {
errors++;
},
- handleResult(aPlace) {
+ handleResult() {
results++;
},
handleCompletion(resultCount) {
@@ -64,10 +64,10 @@ add_task(async function test_misc_visits() {
asyncHistory.updatePlaces(place, {
ignoreErrors: true,
ignoreResults: true,
- handleError(aResultCode, aPlace) {
+ handleError() {
errors++;
},
- handleResult(aPlace) {
+ handleResult() {
results++;
},
handleCompletion(resultCount) {
diff --git a/toolkit/components/places/tests/migration/places_v75.sqlite b/toolkit/components/places/tests/migration/places_v77.sqlite
index 2dd624b945..d77d8233b0 100644
--- a/toolkit/components/places/tests/migration/places_v75.sqlite
+++ b/toolkit/components/places/tests/migration/places_v77.sqlite
Binary files differ
diff --git a/toolkit/components/places/tests/migration/test_current_from_v74.js b/toolkit/components/places/tests/migration/test_current_from_v74.js
index 82c535f78f..eeb862daf5 100644
--- a/toolkit/components/places/tests/migration/test_current_from_v74.js
+++ b/toolkit/components/places/tests/migration/test_current_from_v74.js
@@ -4,7 +4,16 @@
"use strict";
add_task(async function setup() {
- await setupPlacesDatabase("places_v74.sqlite");
+ let path = await setupPlacesDatabase("places_v74.sqlite");
+ let db = await Sqlite.openConnection({ path });
+ await db.execute(`
+ INSERT INTO moz_origins (id, prefix, host, frecency, recalc_frecency)
+ VALUES
+ (100, 'https://', 'test1.com', 0, 0),
+ (101, 'https://', 'test2.com', 0, 0),
+ (102, 'https://', 'test3.com', 0, 0)
+ `);
+ await db.close();
});
add_task(async function database_is_valid() {
@@ -20,3 +29,18 @@ add_task(async function database_is_valid() {
await db.execute("SELECT * FROM moz_places_extra");
await db.execute("SELECT * from moz_historyvisits_extra");
});
+
+add_task(async function recalc_origins_frecency() {
+ const db = await PlacesUtils.promiseDBConnection();
+ Assert.equal(await db.getSchemaVersion(), CURRENT_SCHEMA_VERSION);
+
+ Assert.equal(
+ (
+ await db.execute(
+ "SELECT count(*) FROM moz_origins WHERE recalc_frecency = 0"
+ )
+ )[0].getResultByIndex(0),
+ 0,
+ "All entries should be set for recalculation"
+ );
+});
diff --git a/toolkit/components/places/tests/migration/xpcshell.toml b/toolkit/components/places/tests/migration/xpcshell.toml
index b127fa501f..06dbb67fce 100644
--- a/toolkit/components/places/tests/migration/xpcshell.toml
+++ b/toolkit/components/places/tests/migration/xpcshell.toml
@@ -14,7 +14,7 @@ support-files = [
"places_v70.sqlite",
"places_v72.sqlite",
"places_v74.sqlite",
- "places_v75.sqlite",
+ "places_v77.sqlite",
]
["test_current_from_downgraded.js"]
diff --git a/toolkit/components/places/tests/moz.build b/toolkit/components/places/tests/moz.build
index 60c57f53b8..a93b86a134 100644
--- a/toolkit/components/places/tests/moz.build
+++ b/toolkit/components/places/tests/moz.build
@@ -65,6 +65,7 @@ TEST_HARNESS_FILES.testing.mochitest.tests.toolkit.components.places.tests.brows
"browser/redirect_twice_perma.sjs",
"browser/title1.html",
"browser/title2.html",
+ "browser/userpass.html",
]
TEST_HARNESS_FILES.testing.mochitest.tests.toolkit.components.places.tests.chrome += [
diff --git a/toolkit/components/places/tests/queries/test_async.js b/toolkit/components/places/tests/queries/test_async.js
index 8e895748ab..32a5c1a691 100644
--- a/toolkit/components/places/tests/queries/test_async.js
+++ b/toolkit/components/places/tests/queries/test_async.js
@@ -85,7 +85,7 @@ var tests = [
node.containerOpen = false;
},
- opened(node, newState, oldState) {
+ opened() {
do_throw("opened should not be called");
},
diff --git a/toolkit/components/places/tests/queries/test_containersQueries_sorting.js b/toolkit/components/places/tests/queries/test_containersQueries_sorting.js
index 9cdc0f2a52..ff4bbe67bf 100644
--- a/toolkit/components/places/tests/queries/test_containersQueries_sorting.js
+++ b/toolkit/components/places/tests/queries/test_containersQueries_sorting.js
@@ -113,7 +113,7 @@ function cartProd(aSequences, aCallback) {
// For each sequence in aSequences, we maintain a pointer (an array index,
// really) to the element we're currently enumerating in that sequence
- var seqEltPtrs = aSequences.map(i => 0);
+ var seqEltPtrs = aSequences.map(() => 0);
var numProds = 0;
var done = false;
@@ -407,7 +407,7 @@ function check_children_sorting(aRootNode, aExpectedSortingMode) {
var comparator;
switch (aExpectedSortingMode) {
case Ci.nsINavHistoryQueryOptions.SORT_BY_NONE:
- comparator = function (a, b) {
+ comparator = function () {
return 0;
};
break;
diff --git a/toolkit/components/places/tests/queries/test_querySerialization.js b/toolkit/components/places/tests/queries/test_querySerialization.js
index 4c33854718..64a4c5a6e7 100644
--- a/toolkit/components/places/tests/queries/test_querySerialization.js
+++ b/toolkit/components/places/tests/queries/test_querySerialization.js
@@ -86,11 +86,11 @@ const querySwitches = [
desc: "nsINavHistoryQuery.hasBeginTime",
matches: flagSwitchMatches,
runs: [
- function (aQuery, aQueryOptions) {
+ function (aQuery) {
aQuery.beginTime = Date.now() * 1000;
aQuery.beginTimeReference = Ci.nsINavHistoryQuery.TIME_RELATIVE_EPOCH;
},
- function (aQuery, aQueryOptions) {
+ function (aQuery) {
aQuery.beginTime = Date.now() * 1000;
aQuery.beginTimeReference = Ci.nsINavHistoryQuery.TIME_RELATIVE_TODAY;
},
@@ -103,11 +103,11 @@ const querySwitches = [
desc: "nsINavHistoryQuery.hasEndTime",
matches: flagSwitchMatches,
runs: [
- function (aQuery, aQueryOptions) {
+ function (aQuery) {
aQuery.endTime = Date.now() * 1000;
aQuery.endTimeReference = Ci.nsINavHistoryQuery.TIME_RELATIVE_EPOCH;
},
- function (aQuery, aQueryOptions) {
+ function (aQuery) {
aQuery.endTime = Date.now() * 1000;
aQuery.endTimeReference = Ci.nsINavHistoryQuery.TIME_RELATIVE_TODAY;
},
@@ -120,10 +120,10 @@ const querySwitches = [
desc: "nsINavHistoryQuery.hasSearchTerms",
matches: flagSwitchMatches,
runs: [
- function (aQuery, aQueryOptions) {
+ function (aQuery) {
aQuery.searchTerms = "shrimp and white wine";
},
- function (aQuery, aQueryOptions) {
+ function (aQuery) {
aQuery.searchTerms = "";
},
],
@@ -135,15 +135,15 @@ const querySwitches = [
desc: "nsINavHistoryQuery.hasDomain",
matches: flagSwitchMatches,
runs: [
- function (aQuery, aQueryOptions) {
+ function (aQuery) {
aQuery.domain = "mozilla.com";
aQuery.domainIsHost = false;
},
- function (aQuery, aQueryOptions) {
+ function (aQuery) {
aQuery.domain = "www.mozilla.com";
aQuery.domainIsHost = true;
},
- function (aQuery, aQueryOptions) {
+ function (aQuery) {
aQuery.domain = "";
},
],
@@ -155,7 +155,7 @@ const querySwitches = [
desc: "nsINavHistoryQuery.hasUri",
matches: flagSwitchMatches,
runs: [
- function (aQuery, aQueryOptions) {
+ function (aQuery) {
aQuery.uri = uri("http://mozilla.com");
},
],
@@ -167,7 +167,7 @@ const querySwitches = [
desc: "nsINavHistoryQuery.minVisits",
matches: simplePropertyMatches,
runs: [
- function (aQuery, aQueryOptions) {
+ function (aQuery) {
aQuery.minVisits = 0x7fffffff; // 2^31 - 1
},
],
@@ -178,7 +178,7 @@ const querySwitches = [
desc: "nsINavHistoryQuery.maxVisits",
matches: simplePropertyMatches,
runs: [
- function (aQuery, aQueryOptions) {
+ function (aQuery) {
aQuery.maxVisits = 0x7fffffff; // 2^31 - 1
},
],
@@ -205,13 +205,13 @@ const querySwitches = [
return true;
},
runs: [
- function (aQuery, aQueryOptions) {
+ function (aQuery) {
aQuery.setParents([]);
},
- function (aQuery, aQueryOptions) {
+ function (aQuery) {
aQuery.setParents([PlacesUtils.bookmarks.rootGuid]);
},
- function (aQuery, aQueryOptions) {
+ function (aQuery) {
aQuery.setParents([
PlacesUtils.bookmarks.rootGuid,
PlacesUtils.bookmarks.tagsGuid,
@@ -244,13 +244,13 @@ const querySwitches = [
return true;
},
runs: [
- function (aQuery, aQueryOptions) {
+ function (aQuery) {
aQuery.tags = [];
},
- function (aQuery, aQueryOptions) {
+ function (aQuery) {
aQuery.tags = [""];
},
- function (aQuery, aQueryOptions) {
+ function (aQuery) {
aQuery.tags = [
"foo",
"七難",
@@ -263,7 +263,7 @@ const querySwitches = [
"あいうえお",
];
},
- function (aQuery, aQueryOptions) {
+ function (aQuery) {
aQuery.tags = [
"foo",
"七難",
@@ -301,13 +301,13 @@ const querySwitches = [
return true;
},
runs: [
- function (aQuery, aQueryOptions) {
+ function (aQuery) {
aQuery.setTransitions([]);
},
- function (aQuery, aQueryOptions) {
+ function (aQuery) {
aQuery.setTransitions([Ci.nsINavHistoryService.TRANSITION_DOWNLOAD]);
},
- function (aQuery, aQueryOptions) {
+ function (aQuery) {
aQuery.setTransitions([
Ci.nsINavHistoryService.TRANSITION_TYPED,
Ci.nsINavHistoryService.TRANSITION_BOOKMARK,
@@ -455,7 +455,7 @@ function cartProd(aSequences, aCallback) {
// For each sequence in aSequences, we maintain a pointer (an array index,
// really) to the element we're currently enumerating in that sequence
- var seqEltPtrs = aSequences.map(i => 0);
+ var seqEltPtrs = aSequences.map(() => 0);
var numProds = 0;
var done = false;
diff --git a/toolkit/components/places/tests/queries/test_redirects.js b/toolkit/components/places/tests/queries/test_redirects.js
index b0e7c9b421..6b122e3180 100644
--- a/toolkit/components/places/tests/queries/test_redirects.js
+++ b/toolkit/components/places/tests/queries/test_redirects.js
@@ -42,7 +42,7 @@ function check_results_callback(aSequence) {
}
// Build expectedData array.
- let expectedData = visits.filter(function (aVisit, aIndex, aArray) {
+ let expectedData = visits.filter(function (aVisit) {
// Embed visits never appear in results.
if (aVisit.transType == Ci.nsINavHistoryService.TRANSITION_EMBED) {
return false;
@@ -154,7 +154,7 @@ function cartProd(aSequences, aCallback) {
// For each sequence in aSequences, we maintain a pointer (an array index,
// really) to the element we're currently enumerating in that sequence
- let seqEltPtrs = aSequences.map(i => 0);
+ let seqEltPtrs = aSequences.map(() => 0);
let numProds = 0;
let done = false;
diff --git a/toolkit/components/places/tests/queries/test_tags.js b/toolkit/components/places/tests/queries/test_tags.js
index 17ad3478ce..23a332f20b 100644
--- a/toolkit/components/places/tests/queries/test_tags.js
+++ b/toolkit/components/places/tests/queries/test_tags.js
@@ -190,7 +190,7 @@ add_task(async function many_tags_no_bookmark() {
"Querying on many tags associated with a URI and tags not associated " +
"with that URI should not return that URI"
);
- await task_doWithBookmark(["foo", "bar", "baz"], function (aURI) {
+ await task_doWithBookmark(["foo", "bar", "baz"], function () {
var [query, opts] = makeQuery(["foo", "bogus"]);
executeAndCheckQueryResults(query, opts, []);
[query, opts] = makeQuery(["foo", "bar", "bogus"]);
@@ -202,7 +202,7 @@ add_task(async function many_tags_no_bookmark() {
add_task(async function nonexistent_tags() {
info("Querying on nonexistent tag should return no results");
- await task_doWithBookmark(["foo", "bar", "baz"], function (aURI) {
+ await task_doWithBookmark(["foo", "bar", "baz"], function () {
var [query, opts] = makeQuery(["bogus"]);
executeAndCheckQueryResults(query, opts, []);
[query, opts] = makeQuery(["bogus", "gnarly"]);
@@ -449,7 +449,7 @@ function addBookmark(aURI) {
/**
* Asynchronous task that removes all pages from history and bookmarks.
*/
-async function task_cleanDatabase(aCallback) {
+async function task_cleanDatabase() {
await PlacesUtils.bookmarks.eraseEverything();
await PlacesUtils.history.clear();
}
diff --git a/toolkit/components/places/tests/sync/head_sync.js b/toolkit/components/places/tests/sync/head_sync.js
index 7dd69e275b..68221ce93f 100644
--- a/toolkit/components/places/tests/sync/head_sync.js
+++ b/toolkit/components/places/tests/sync/head_sync.js
@@ -116,7 +116,7 @@ function makeRecord(cleartext) {
return new Proxy(
{ cleartext },
{
- get(target, property, receiver) {
+ get(target, property) {
if (property == "cleartext") {
return target.cleartext;
}
@@ -125,7 +125,7 @@ function makeRecord(cleartext) {
}
return target.cleartext[property];
},
- set(target, property, value, receiver) {
+ set(target, property, value) {
if (property == "cleartext") {
target.cleartext = value;
} else if (property != "cleartextToString") {
@@ -135,7 +135,7 @@ function makeRecord(cleartext) {
has(target, property) {
return property == "cleartext" || property in target.cleartext;
},
- deleteProperty(target, property) {},
+ deleteProperty() {},
ownKeys(target) {
return ["cleartext", ...Reflect.ownKeys(target)];
},
diff --git a/toolkit/components/places/tests/sync/test_bookmark_abort_merging.js b/toolkit/components/places/tests/sync/test_bookmark_abort_merging.js
index 877feb99f4..f7a871e3cb 100644
--- a/toolkit/components/places/tests/sync/test_bookmark_abort_merging.js
+++ b/toolkit/components/places/tests/sync/test_bookmark_abort_merging.js
@@ -95,8 +95,8 @@ add_task(async function test_blocker_state() {
let buf = await SyncedBookmarksMirror.open({
path: "blocker_state_buf.sqlite",
finalizeAt: barrier.client,
- recordStepTelemetry(...args) {},
- recordValidationTelemetry(...args) {},
+ recordStepTelemetry() {},
+ recordValidationTelemetry() {},
});
await storeRecords(buf, [
{
diff --git a/toolkit/components/places/tests/sync/test_bookmark_observer_recorder.js b/toolkit/components/places/tests/sync/test_bookmark_observer_recorder.js
index 16d8ed746c..b4f3d4bd0b 100644
--- a/toolkit/components/places/tests/sync/test_bookmark_observer_recorder.js
+++ b/toolkit/components/places/tests/sync/test_bookmark_observer_recorder.js
@@ -185,7 +185,7 @@ add_task(async function test_update_frecencies() {
let frecencies = await promiseAllURLFrecencies();
let urlsWithFrecency = mapFilterIterator(
frecencies.entries(),
- ([href, { frecency, recalc }]) => (recalc == 0 ? href : null)
+ ([href, { recalc }]) => (recalc == 0 ? href : null)
);
// A is unchanged, and we should recalculate frecency for three more
@@ -236,7 +236,7 @@ add_task(async function test_update_frecencies() {
let frecencies = await promiseAllURLFrecencies();
let urlsWithoutFrecency = mapFilterIterator(
frecencies.entries(),
- ([href, { frecency, recalc }]) => (recalc == 1 ? href : null)
+ ([href, { recalc }]) => (recalc == 1 ? href : null)
);
deepEqual(
urlsWithoutFrecency,
diff --git a/toolkit/components/places/tests/unit/test_asyncExecuteLegacyQueries.js b/toolkit/components/places/tests/unit/test_asyncExecuteLegacyQueries.js
index 084415fb37..02577f159e 100644
--- a/toolkit/components/places/tests/unit/test_asyncExecuteLegacyQueries.js
+++ b/toolkit/components/places/tests/unit/test_asyncExecuteLegacyQueries.js
@@ -30,7 +30,7 @@ add_task(async function test_history_query() {
"Async execution error (" + aError.result + "): " + aError.message
);
},
- handleCompletion(aReason) {
+ handleCompletion() {
cleanupTest().then(resolve);
},
});
@@ -69,7 +69,7 @@ add_task(async function test_bookmarks_query() {
"Async execution error (" + aError.result + "): " + aError.message
);
},
- handleCompletion(aReason) {
+ handleCompletion() {
cleanupTest().then(resolve);
},
});
diff --git a/toolkit/components/places/tests/unit/test_async_transactions.js b/toolkit/components/places/tests/unit/test_async_transactions.js
index b0e9b292f3..9f96a9c040 100644
--- a/toolkit/components/places/tests/unit/test_async_transactions.js
+++ b/toolkit/components/places/tests/unit/test_async_transactions.js
@@ -1485,7 +1485,7 @@ add_task(async function test_edit_specific_keyword() {
url: "http://test.edit.keyword/",
};
bm_info.guid = await PT.NewBookmark(bm_info).transact();
- function ensureKeywordChange(aCurrentKeyword = "", aPreviousKeyword = "") {
+ function ensureKeywordChange(aCurrentKeyword = "") {
ensureItemsKeywordChanged({
guid: bm_info.guid,
keyword: aCurrentKeyword,
diff --git a/toolkit/components/places/tests/unit/test_bookmark_list.js b/toolkit/components/places/tests/unit/test_bookmark_list.js
new file mode 100644
index 0000000000..b743a1281d
--- /dev/null
+++ b/toolkit/components/places/tests/unit/test_bookmark_list.js
@@ -0,0 +1,115 @@
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { sinon } = ChromeUtils.importESModule(
+ "resource://testing-common/Sinon.sys.mjs"
+);
+const { BookmarkList } = ChromeUtils.importESModule(
+ "resource://gre/modules/BookmarkList.sys.mjs"
+);
+
+registerCleanupFunction(
+ async () => await PlacesUtils.bookmarks.eraseEverything()
+);
+
+add_task(async function test_url_tracking() {
+ const firstBookmark = await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ url: "https://www.example.com/",
+ });
+ const secondBookmark = await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ url: "https://www.reddit.com/",
+ });
+
+ let deferredUpdate;
+ const bookmarkList = new BookmarkList(
+ [
+ "https://www.example.com/",
+ "https://www.reddit.com/",
+ "https://www.youtube.com/",
+ ],
+ () => deferredUpdate?.resolve()
+ );
+
+ async function waitForUpdateBookmarksTask(updateTask) {
+ deferredUpdate = Promise.withResolvers();
+ await updateTask();
+ return deferredUpdate.promise;
+ }
+
+ info("Check bookmark status of tracked URLs.");
+ equal(await bookmarkList.isBookmark("https://www.example.com/"), true);
+ equal(await bookmarkList.isBookmark("https://www.reddit.com/"), true);
+ equal(await bookmarkList.isBookmark("https://www.youtube.com/"), false);
+
+ info("Add a bookmark.");
+ await waitForUpdateBookmarksTask(() =>
+ PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ url: "https://www.youtube.com/",
+ })
+ );
+ equal(await bookmarkList.isBookmark("https://www.youtube.com/"), true);
+
+ info("Remove a bookmark.");
+ await waitForUpdateBookmarksTask(() =>
+ PlacesUtils.bookmarks.remove(firstBookmark.guid)
+ );
+ equal(await bookmarkList.isBookmark("https://www.example.com/"), false);
+
+ info("Update a bookmark's URL.");
+ await waitForUpdateBookmarksTask(() =>
+ PlacesUtils.bookmarks.update({
+ guid: secondBookmark.guid,
+ url: "https://www.wikipedia.org/",
+ })
+ );
+ equal(await bookmarkList.isBookmark("https://www.reddit.com/"), false);
+
+ info("Add a bookmark after removing listeners.");
+ bookmarkList.removeListeners();
+ await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ url: "https://www.example.com/",
+ });
+
+ info("Reinitialize the list and validate bookmark status.");
+ bookmarkList.setTrackedUrls(["https://www.example.com/"]);
+ bookmarkList.addListeners();
+ equal(await bookmarkList.isBookmark("https://www.example.com/"), true);
+
+ info("Cleanup.");
+ bookmarkList.removeListeners();
+ await PlacesUtils.bookmarks.eraseEverything();
+});
+
+add_task(async function test_no_unnecessary_observer_notifications() {
+ const spy = sinon.spy();
+ const bookmarkList = new BookmarkList(
+ ["https://www.example.com/"],
+ spy,
+ 0,
+ 0
+ );
+
+ info("Add a bookmark with an untracked URL.");
+ await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ url: "https://www.reddit.com/",
+ });
+ await new Promise(resolve => ChromeUtils.idleDispatch(resolve));
+ ok(spy.notCalled, "Observer was not notified.");
+ equal(await bookmarkList.isBookmark("https://www.reddit.com"), undefined);
+
+ info("Add a bookmark after removing listeners.");
+ bookmarkList.removeListeners();
+ await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ url: "https://www.example.com/",
+ });
+ await new Promise(resolve => ChromeUtils.idleDispatch(resolve));
+ ok(spy.notCalled, "Observer was not notified.");
+});
diff --git a/toolkit/components/places/tests/unit/test_bookmarks_html.js b/toolkit/components/places/tests/unit/test_bookmarks_html.js
index 4b3f04b444..f1f8caa354 100644
--- a/toolkit/components/places/tests/unit/test_bookmarks_html.js
+++ b/toolkit/components/places/tests/unit/test_bookmarks_html.js
@@ -246,7 +246,7 @@ add_task(async function test_import_chromefavicon() {
let data = await new Promise(resolve => {
PlacesUtils.favicons.getFaviconDataForPage(
PAGE_URI,
- (uri, dataLen, faviconData, mimeType) => resolve(faviconData)
+ (uri, dataLen, faviconData) => resolve(faviconData)
);
});
diff --git a/toolkit/components/places/tests/unit/test_bookmarks_html_corrupt.js b/toolkit/components/places/tests/unit/test_bookmarks_html_corrupt.js
index 061c8c0c5f..f7b366b309 100644
--- a/toolkit/components/places/tests/unit/test_bookmarks_html_corrupt.js
+++ b/toolkit/components/places/tests/unit/test_bookmarks_html_corrupt.js
@@ -112,7 +112,7 @@ var database_check = async function () {
await new Promise(resolve => {
PlacesUtils.favicons.getFaviconDataForPage(
uri(TEST_FAVICON_PAGE_URL),
- (aURI, aDataLen, aData, aMimeType) => {
+ (aURI, aDataLen) => {
// aURI should never be null when aDataLen > 0.
Assert.notEqual(aURI, null);
// Favicon data is stored in the bookmarks file as a "data:" URI. For
diff --git a/toolkit/components/places/tests/unit/test_bookmarks_restore_notification.js b/toolkit/components/places/tests/unit/test_bookmarks_restore_notification.js
index 892b2d1d04..75b52aafc7 100644
--- a/toolkit/components/places/tests/unit/test_bookmarks_restore_notification.js
+++ b/toolkit/components/places/tests/unit/test_bookmarks_restore_notification.js
@@ -115,7 +115,7 @@ async function checkObservers(expectPromises, expectedData) {
/**
* Run after every test cases.
*/
-async function teardown(file, begin, success, fail) {
+async function teardown(file) {
// On restore failed, file may not exist, so wrap in try-catch.
await IOUtils.remove(file, { ignoreAbsent: true });
diff --git a/toolkit/components/places/tests/unit/test_frecency_decay.js b/toolkit/components/places/tests/unit/test_frecency_decay.js
index 8fbb08aecc..a9762209c1 100644
--- a/toolkit/components/places/tests/unit/test_frecency_decay.js
+++ b/toolkit/components/places/tests/unit/test_frecency_decay.js
@@ -78,5 +78,5 @@ add_task(async function test_frecency_decay() {
);
let snapshot = histogram.snapshot();
- Assert.greater(snapshot.sum, 0);
+ Assert.greater(Object.values(snapshot.values).length, 0);
});
diff --git a/toolkit/components/places/tests/unit/test_nsINavHistoryViewer.js b/toolkit/components/places/tests/unit/test_nsINavHistoryViewer.js
index e98cdbac79..2cdcba60aa 100644
--- a/toolkit/components/places/tests/unit/test_nsINavHistoryViewer.js
+++ b/toolkit/components/places/tests/unit/test_nsINavHistoryViewer.js
@@ -6,11 +6,11 @@
var resultObserver = {
insertedNode: null,
- nodeInserted(parent, node, newIndex) {
+ nodeInserted(parent, node) {
this.insertedNode = node;
},
removedNode: null,
- nodeRemoved(parent, node, oldIndex) {
+ nodeRemoved(parent, node) {
this.removedNode = node;
},
@@ -24,14 +24,14 @@ var resultObserver = {
newAccessCount: 0,
newTime: 0,
nodeChangedByHistoryDetails: null,
- nodeHistoryDetailsChanged(node, oldVisitDate, oldVisitCount) {
+ nodeHistoryDetailsChanged(node) {
this.nodeChangedByHistoryDetails = node;
this.newTime = node.time;
this.newAccessCount = node.accessCount;
},
movedNode: null,
- nodeMoved(node, oldParent, oldIndex, newParent, newIndex) {
+ nodeMoved(node) {
this.movedNode = node;
},
openedContainer: null,
diff --git a/toolkit/components/places/tests/unit/test_origins.js b/toolkit/components/places/tests/unit/test_origins.js
index 67b6d59c7d..f74313a125 100644
--- a/toolkit/components/places/tests/unit/test_origins.js
+++ b/toolkit/components/places/tests/unit/test_origins.js
@@ -1005,6 +1005,33 @@ add_task(async function moreOriginFrecencyStats() {
await cleanUp();
});
+add_task(async function test_cutoff() {
+ // Add first page with visit.
+ await PlacesTestUtils.addVisits([{ uri: "http://example.com/0" }]);
+ // Add a second page last visited before the cutoff, it should be ignored.
+ let visitDate = PlacesUtils.toPRTime(
+ new Date(
+ new Date().setDate(
+ -Services.prefs.getIntPref("places.frecency.originsCutOffDays", 90)
+ )
+ )
+ );
+ await PlacesTestUtils.addVisits([{ uri: "http://example.com/1", visitDate }]);
+ // Add a third page with visit both before and after the cutoff, should count.
+ await PlacesTestUtils.addVisits([
+ { uri: "http://example.com/2" },
+ { uri: "http://example.com/2", visitDate },
+ ]);
+ await checkDB([
+ [
+ "http://",
+ "example.com",
+ ["http://example.com/0", "http://example.com/2"],
+ ],
+ ]);
+ await cleanUp();
+});
+
/**
* Returns the expected frecency of the origin of the given URLs, i.e., the sum
* of their frecencies. Each URL is expected to have the same origin.
@@ -1017,12 +1044,15 @@ async function expectedOriginFrecency(urls) {
let value = 0;
for (let url of urls) {
let v = Math.max(
- await PlacesTestUtils.getDatabaseValue("moz_places", "frecency", { url }),
+ (await PlacesTestUtils.getDatabaseValue("moz_places", "frecency", {
+ url,
+ last_visit_date: [">", 0],
+ })) ?? 0,
0
);
value += v;
}
- return value;
+ return value || 1.0;
}
/**
@@ -1064,49 +1094,34 @@ async function checkDB(expectedOrigins) {
}
Assert.deepEqual(actualOrigins, expected);
if (checkFrecencies) {
- await checkStats(expected.map(o => o[2]).filter(o => o > 0));
+ info("Checking threshold");
+ await PlacesTestUtils.dumpTable({ db, table: "moz_origins" });
+ await checkThreshold(expected.map(o => o[2]));
}
}
/**
- * Asserts that the origin frecency stats are correct.
+ * Asserts that the origin frecency threshold is correct.
*
* @param expectedOriginFrecencies
* An array of expected origin frecencies.
*/
-async function checkStats(expectedOriginFrecencies) {
- let stats = await promiseStats();
- Assert.equal(stats.count, expectedOriginFrecencies.length);
- Assert.equal(
- stats.sum,
- expectedOriginFrecencies.reduce((sum, f) => sum + f, 0)
+async function checkThreshold(expectedOriginFrecencies) {
+ const DEFAULT_THRESHOLD = 2.0;
+ let threshold = await PlacesUtils.metadata.get(
+ "origin_frecency_threshold",
+ DEFAULT_THRESHOLD
);
+
Assert.equal(
- stats.squares,
- expectedOriginFrecencies.reduce((squares, f) => squares + f * f, 0)
+ threshold,
+ expectedOriginFrecencies.length
+ ? expectedOriginFrecencies.reduce((a, b) => a + b, 0) /
+ expectedOriginFrecencies.length
+ : DEFAULT_THRESHOLD
);
}
-/**
- * Returns the origin frecency stats.
- *
- * @return An object: { count, sum, squares }
- */
-async function promiseStats() {
- let db = await PlacesUtils.promiseDBConnection();
- let rows = await db.execute(`
- SELECT
- IFNULL((SELECT value FROM moz_meta WHERE key = 'origin_frecency_count'), 0),
- IFNULL((SELECT value FROM moz_meta WHERE key = 'origin_frecency_sum'), 0),
- IFNULL((SELECT value FROM moz_meta WHERE key = 'origin_frecency_sum_of_squares'), 0)
- `);
- return {
- count: rows[0].getResultByIndex(0),
- sum: rows[0].getResultByIndex(1),
- squares: rows[0].getResultByIndex(2),
- };
-}
-
async function cleanUp() {
await PlacesUtils.bookmarks.eraseEverything();
await PlacesUtils.history.clear();
diff --git a/toolkit/components/places/tests/unit/test_origins_parsing.js b/toolkit/components/places/tests/unit/test_origins_parsing.js
index 35ba8bdd0d..bdeabce271 100644
--- a/toolkit/components/places/tests/unit/test_origins_parsing.js
+++ b/toolkit/components/places/tests/unit/test_origins_parsing.js
@@ -65,7 +65,16 @@ add_task(async function parsing() {
// in the database are correct.
for (let i = 0; i < uris.length; i++) {
await PlacesUtils.history.remove(uris[i]);
- await checkDB(expectedOrigins.slice(i + 1, expectedOrigins.length));
+
+ let uri = Services.io.newURI(uris[i]);
+ if (uri.hasUserPass) {
+ // The history cannot be deleted at a URL with a user path.
+ } else {
+ expectedOrigins = expectedOrigins.filter(
+ ([prefix, hostPort]) => !prefix.startsWith(uri.scheme + ":")
+ );
+ }
+ await checkDB(expectedOrigins);
}
await cleanUp();
}
diff --git a/toolkit/components/places/tests/unit/test_tag_autocomplete_search.js b/toolkit/components/places/tests/unit/test_tag_autocomplete_search.js
index 43f899c237..06182b6dd3 100644
--- a/toolkit/components/places/tests/unit/test_tag_autocomplete_search.js
+++ b/toolkit/components/places/tests/unit/test_tag_autocomplete_search.js
@@ -33,7 +33,7 @@ AutoCompleteInput.prototype = {
popupOpen: false,
popup: {
- setSelectedIndex(aIndex) {},
+ setSelectedIndex() {},
invalidate() {},
// nsISupports implementation
diff --git a/toolkit/components/places/tests/unit/xpcshell.toml b/toolkit/components/places/tests/unit/xpcshell.toml
index 750e8ad9ea..8a56fcc370 100644
--- a/toolkit/components/places/tests/unit/xpcshell.toml
+++ b/toolkit/components/places/tests/unit/xpcshell.toml
@@ -78,6 +78,8 @@ skip-if = ["os == 'linux'"] # Bug 821781
["test_bookmark-tags-changed_frequency.js"]
+["test_bookmark_list.js"]
+
["test_bookmarks_html.js"]
["test_bookmarks_html_corrupt.js"]
diff --git a/toolkit/components/printing/content/print.js b/toolkit/components/printing/content/print.js
index d08d790522..3398b28ace 100644
--- a/toolkit/components/printing/content/print.js
+++ b/toolkit/components/printing/content/print.js
@@ -2811,7 +2811,7 @@ async function pickFileName(contentTitle, currentURI) {
filename = DownloadPaths.sanitize(filename);
picker.init(
- window.docShell.chromeEventHandler.ownerGlobal,
+ window.docShell.chromeEventHandler.ownerGlobal.browsingContext,
title,
Ci.nsIFilePicker.modeSave
);
diff --git a/toolkit/components/printing/tests/browser_preview_navigation.js b/toolkit/components/printing/tests/browser_preview_navigation.js
index b4c0920185..53e54eddb0 100644
--- a/toolkit/components/printing/tests/browser_preview_navigation.js
+++ b/toolkit/components/printing/tests/browser_preview_navigation.js
@@ -412,7 +412,7 @@ add_task(async function testPaginatorAfterSettingsUpdate() {
});
add_task(async function testTooltips() {
- await SpecialPowers.pushPrefEnv({ set: [["ui.tooltipDelay", 0]] });
+ await SpecialPowers.pushPrefEnv({ set: [["ui.tooltip.delay_ms", 0]] });
const mockPrinterName = "Fake Printer";
await PrintHelper.withTestPage(async helper => {
helper.addMockPrinter(mockPrinterName);
diff --git a/toolkit/components/printing/tests/head.js b/toolkit/components/printing/tests/head.js
index a69faa436e..4c8c0b26b6 100644
--- a/toolkit/components/printing/tests/head.js
+++ b/toolkit/components/printing/tests/head.js
@@ -521,7 +521,7 @@ class PrintHelper {
mockFilePickerCancel() {
if (!pickerMocked) {
pickerMocked = true;
- MockFilePicker.init(window);
+ MockFilePicker.init(window.browsingContext);
registerCleanupFunction(() => MockFilePicker.cleanup());
}
MockFilePicker.returnValue = MockFilePicker.returnCancel;
@@ -530,7 +530,7 @@ class PrintHelper {
mockFilePicker(filename) {
if (!pickerMocked) {
pickerMocked = true;
- MockFilePicker.init(window);
+ MockFilePicker.init(window.browsingContext);
registerCleanupFunction(() => MockFilePicker.cleanup());
}
MockFilePicker.returnValue = MockFilePicker.returnOK;
diff --git a/toolkit/components/processtools/ProcInfo.h b/toolkit/components/processtools/ProcInfo.h
index d7e557b42e..7fd2d7aae8 100644
--- a/toolkit/components/processtools/ProcInfo.h
+++ b/toolkit/components/processtools/ProcInfo.h
@@ -171,10 +171,10 @@ struct ProcInfoRequest {
ProcInfoRequest(base::ProcessId aPid, ProcType aProcessType,
const nsACString& aOrigin, nsTArray<WindowInfo>&& aWindowInfo,
nsTArray<UtilityInfo>&& aUtilityInfo, uint32_t aChildId = 0
-#ifdef XP_MACOSX
+#ifdef XP_DARWIN
,
mach_port_t aChildTask = 0
-#endif // XP_MACOSX
+#endif // XP_DARWIN
)
: pid(aPid),
processType(aProcessType),
@@ -182,10 +182,10 @@ struct ProcInfoRequest {
windowInfo(std::move(aWindowInfo)),
utilityInfo(std::move(aUtilityInfo)),
childId(aChildId)
-#ifdef XP_MACOSX
+#ifdef XP_DARWIN
,
childTask(aChildTask)
-#endif // XP_MACOSX
+#endif // XP_DARWIN
{
}
const base::ProcessId pid;
@@ -195,9 +195,9 @@ struct ProcInfoRequest {
const nsTArray<UtilityInfo> utilityInfo;
// If the process is a child, its child id, otherwise `0`.
const int32_t childId;
-#ifdef XP_MACOSX
+#ifdef XP_DARWIN
const mach_port_t childTask;
-#endif // XP_MACOSX
+#endif // XP_DARWIN
};
/**
diff --git a/toolkit/components/processtools/ProcInfo.mm b/toolkit/components/processtools/ProcInfo.mm
index 6c98ce81f5..68edeef81b 100644
--- a/toolkit/components/processtools/ProcInfo.mm
+++ b/toolkit/components/processtools/ProcInfo.mm
@@ -14,7 +14,6 @@
#include <cstring>
#include <unistd.h>
-#include <libproc.h>
#include <sys/sysctl.h>
#include <mach/mach.h>
#include <mach/mach_time.h>
@@ -30,18 +29,19 @@ static void GetTimeBase(mach_timebase_info_data_t* timebase) {
namespace mozilla {
nsresult GetCpuTimeSinceProcessStartInMs(uint64_t* aResult) {
- struct proc_taskinfo pti;
- if ((unsigned long)proc_pidinfo(getpid(), PROC_PIDTASKINFO, 0, &pti,
- PROC_PIDTASKINFO_SIZE) <
- PROC_PIDTASKINFO_SIZE) {
+ task_power_info_data_t task_power_info;
+ mach_msg_type_number_t count = TASK_POWER_INFO_COUNT;
+ kern_return_t kr = task_info(mach_task_self(), TASK_POWER_INFO,
+ (task_info_t)&task_power_info, &count);
+ if (kr != KERN_SUCCESS) {
return NS_ERROR_FAILURE;
}
mach_timebase_info_data_t timebase;
GetTimeBase(&timebase);
- *aResult = (pti.pti_total_user + pti.pti_total_system) * timebase.numer /
- timebase.denom / PR_NSEC_PER_MSEC;
+ *aResult = (task_power_info.total_user + task_power_info.total_system) *
+ timebase.numer / timebase.denom / PR_NSEC_PER_MSEC;
return NS_OK;
}
@@ -82,18 +82,6 @@ ProcInfoPromise::ResolveOrRejectValue GetProcInfoSync(
info.windows = std::move(request.windowInfo);
info.utilityActors = std::move(request.utilityInfo);
- struct proc_taskinfo pti;
- if ((unsigned long)proc_pidinfo(request.pid, PROC_PIDTASKINFO, 0, &pti,
- PROC_PIDTASKINFO_SIZE) <
- PROC_PIDTASKINFO_SIZE) {
- // Can't read data for this process.
- // Probably either a sandboxing issue or a race condition, e.g.
- // the process has been just been killed. Regardless, skip process.
- continue;
- }
- info.cpuTime = (pti.pti_total_user + pti.pti_total_system) *
- timebase.numer / timebase.denom;
-
mach_port_t selectedTask;
// If we did not get a task from a child process, we use mach_task_self()
if (request.childTask == MACH_PORT_NULL) {
@@ -102,12 +90,25 @@ ProcInfoPromise::ResolveOrRejectValue GetProcInfoSync(
selectedTask = request.childTask;
}
+ task_power_info_data_t task_power_info;
+ mach_msg_type_number_t count = TASK_POWER_INFO_COUNT;
+ kern_return_t kr = task_info(selectedTask, TASK_POWER_INFO,
+ (task_info_t)&task_power_info, &count);
+ if (kr != KERN_SUCCESS) {
+ // Can't read data for this process.
+ // Probably either a sandboxing issue or a race condition, e.g.
+ // the process has been just been killed. Regardless, skip process.
+ continue;
+ }
+ info.cpuTime = (task_power_info.total_user + task_power_info.total_system) *
+ timebase.numer / timebase.denom;
+
// The phys_footprint value (introduced in 10.11) of the TASK_VM_INFO data
// matches the value in the 'Memory' column of the Activity Monitor.
task_vm_info_data_t task_vm_info;
- mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
- kern_return_t kr = task_info(selectedTask, TASK_VM_INFO,
- (task_info_t)&task_vm_info, &count);
+ count = TASK_VM_INFO_COUNT;
+ kr = task_info(selectedTask, TASK_VM_INFO, (task_info_t)&task_vm_info,
+ &count);
info.memory = kr == KERN_SUCCESS ? task_vm_info.phys_footprint : 0;
// Now getting threads info
diff --git a/toolkit/components/processtools/moz.build b/toolkit/components/processtools/moz.build
index d45fe87237..ee9d07cd6d 100644
--- a/toolkit/components/processtools/moz.build
+++ b/toolkit/components/processtools/moz.build
@@ -47,7 +47,7 @@ if toolkit == "gtk" or toolkit == "android":
UNIFIED_SOURCES += ["ProcInfo_linux.cpp"]
elif toolkit == "windows":
UNIFIED_SOURCES += ["ProcInfo_win.cpp"]
-elif toolkit == "cocoa":
+elif toolkit in ("cocoa", "uikit"):
UNIFIED_SOURCES += ["ProcInfo.mm"]
include("/ipc/chromium/chromium-config.mozbuild")
diff --git a/toolkit/components/promiseworker/PromiseWorker.sys.mjs b/toolkit/components/promiseworker/PromiseWorker.sys.mjs
index 59721e5663..bf5ae179cb 100644
--- a/toolkit/components/promiseworker/PromiseWorker.sys.mjs
+++ b/toolkit/components/promiseworker/PromiseWorker.sys.mjs
@@ -16,27 +16,6 @@
*/
/**
- * An implementation of queues (FIFO).
- *
- * The current implementation uses one array, runs in O(n ^ 2), and is optimized
- * for the case in which queues are generally short.
- */
-function Queue() {
- this._array = [];
-}
-Queue.prototype = {
- pop: function pop() {
- return this._array.shift();
- },
- push: function push(x) {
- return this._array.push(x);
- },
- isEmpty: function isEmpty() {
- return !this._array.length;
- },
-};
-
-/**
* Constructors for decoding standard exceptions received from the
* worker.
*/
@@ -114,14 +93,31 @@ const EXCEPTION_CONSTRUCTORS = {
*
* @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 = {}) {
+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
@@ -137,24 +133,24 @@ export var BasePromiseWorker = function (url, options = {}) {
this.ExceptionHandlers = Object.create(EXCEPTION_CONSTRUCTORS);
/**
- * The queue of deferred, waiting for the completion of their
+ * The map of deferred, waiting for the completion of their
* respective job by the worker.
*
- * Each item in the list may contain an additional field |closure|,
+ * 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 {Queue<{deferred:deferred, closure:*=}>}
+ * @type {Map<string, {deferred, closure, id}>}
*/
- this._queue = new Queue();
+ this._deferredJobs = new Map();
/**
* The number of the current message.
*
* Used for debugging purposes.
*/
- this._id = 0;
+ this._deferredJobId = 0;
/**
* The instant at which the worker was launched.
@@ -172,6 +168,11 @@ BasePromiseWorker.prototype = {
// By Default, ignore all logs.
},
+ _generateDeferredJobId() {
+ this._deferredJobId += 1;
+ return "ThreadToWorker-" + this._deferredJobId;
+ },
+
/**
* Instantiate the worker lazily.
*/
@@ -205,9 +206,15 @@ BasePromiseWorker.prototype = {
error.filename,
error.lineno
);
+
error.preventDefault();
- let { deferred } = this._queue.pop();
- deferred.reject(error);
+
+ if (this._deferredJobs.size > 0) {
+ this._deferredJobs.forEach(job => {
+ job.deferred.reject(error);
+ });
+ this._deferredJobs.clear();
+ }
};
/**
@@ -218,41 +225,74 @@ BasePromiseWorker.prototype = {
* - {fail: some_error} in case of error, where
* some_error is an instance of |PromiseWorker.WorkerError|
*
- * Messages may also contain a field |id| to help
- * with debugging.
- *
- * Messages may also optionally contain a field |durationMs|, holding
- * the duration of the function call in milliseconds.
+ * 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 => {
- this.log("Received message from worker", msg.data);
- let handler = this._queue.pop();
- let deferred = handler.deferred;
let data = msg.data;
- if (data.id != handler.id) {
- throw new Error(
- "Internal error: expecting msg " +
- handler.id +
- ", " +
- " got " +
- data.id +
- ": " +
- JSON.stringify(msg.data)
- );
- }
+ let messageId = data.id;
+
+ this.log(`Received message ${messageId} from worker`);
+
if ("timeStamps" in data) {
this.workerTimeStamps = data.timeStamps;
}
- 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));
+
+ // 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;
},
@@ -303,9 +343,9 @@ BasePromiseWorker.prototype = {
});
}
- let id = ++this._id;
+ let id = this._generateDeferredJobId();
let message = { fun, args, id };
- this.log("Posting message", message);
+ this.log("Posting message", JSON.stringify(message));
try {
this._worker.postMessage(message, ...[transfers]);
} catch (ex) {
@@ -320,15 +360,14 @@ BasePromiseWorker.prototype = {
}
let deferred = Promise.withResolvers();
- this._queue.push({ deferred, closure, id });
- this.log("Message posted");
+ this._deferredJobs.set(id, { deferred, closure, id });
let reply;
try {
this.log("Expecting reply");
reply = await deferred.promise;
} catch (error) {
- this.log("Got error", error);
+ this.log("Got error", JSON.stringify(error));
reply = error;
if (error instanceof WorkerError) {
@@ -389,7 +428,7 @@ BasePromiseWorker.prototype = {
/**
* 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 queue, we'll reject them and clear it.
+ * If there are pending Promises in the jobs, we'll reject them and clear it.
*/
terminate() {
if (!this.__worker) {
@@ -404,14 +443,12 @@ BasePromiseWorker.prototype = {
this.log("Error whilst terminating ChromeWorker: " + ex.message);
}
- let error;
- while (!this._queue.isEmpty()) {
- if (!error) {
- // We create this lazily, because error objects are not cheap.
- error = new Error("Internal error: worker terminated");
- }
- let { deferred } = this._queue.pop();
- deferred.reject(error);
+ if (this._deferredJobs.size) {
+ let error = new Error("Internal error: worker terminated");
+ this._deferredJobs.forEach(job => {
+ job.deferred.reject(error);
+ });
+ this._deferredJobs.clear();
}
},
};
diff --git a/toolkit/components/promiseworker/tests/xpcshell/data/worker.js b/toolkit/components/promiseworker/tests/xpcshell/data/worker.js
index 30087bdc4a..94b6dd17ad 100644
--- a/toolkit/components/promiseworker/tests/xpcshell/data/worker.js
+++ b/toolkit/components/promiseworker/tests/xpcshell/data/worker.js
@@ -12,8 +12,9 @@ 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.dispatch = async function (method, args = []) {
+ return await Agent[method](...args);
};
worker.postMessage = function (...args) {
self.postMessage(...args);
@@ -34,6 +35,14 @@ var Agent = {
return args;
},
+ async bounceWithExtraCalls(...args) {
+ let result = await worker.callMainThread("echo", [
+ "Posting something unrelated",
+ ]);
+ args.push(result.ok);
+ return args;
+ },
+
throwError(msg, ...args) {
throw new Error(msg);
},
diff --git a/toolkit/components/promiseworker/tests/xpcshell/test_Promise.js b/toolkit/components/promiseworker/tests/xpcshell/test_Promise.js
index f7581b664f..f9091a2b85 100644
--- a/toolkit/components/promiseworker/tests/xpcshell/test_Promise.js
+++ b/toolkit/components/promiseworker/tests/xpcshell/test_Promise.js
@@ -16,7 +16,22 @@ const { setTimeout } = ChromeUtils.importESModule(
var WORKER_SOURCE_URI = "chrome://promiseworker/content/worker.js";
do_load_manifest("data/chrome.manifest");
-var worker = new BasePromiseWorker(WORKER_SOURCE_URI);
+
+const UUID = crypto.randomUUID();
+
+const SOME_ARRAY = new Uint8Array(4);
+for (let i = 0; i < 4; ++i) {
+ SOME_ARRAY[i] = i;
+}
+
+async function echo(message) {
+ return new BasePromiseWorker.Meta([message, UUID, SOME_ARRAY.buffer], {
+ transfers: [SOME_ARRAY.buffer],
+ });
+}
+
+var worker = new BasePromiseWorker(WORKER_SOURCE_URI, {}, { echo });
+
worker.log = function (...args) {
info("Controller: " + args.join(" "));
};
@@ -166,3 +181,34 @@ add_task(async function test_terminate() {
"ChromeWorker instances should differ"
);
});
+
+function cloneArrayBuffer(original) {
+ const clone = new ArrayBuffer(original.byteLength);
+ const originalView = new Uint8Array(original);
+ const cloneView = new Uint8Array(clone);
+ cloneView.set(originalView);
+ return clone;
+}
+
+add_task(async function test_bidirectional() {
+ // Before we transfer the array, we clone it
+ const arrayCopy = cloneArrayBuffer(SOME_ARRAY.buffer);
+
+ let message = ["test_simple_args", Math.random()];
+
+ // Checking the array buffer size
+ Assert.equal(
+ SOME_ARRAY.buffer.byteLength,
+ 4,
+ "The buffer is not detached yet"
+ );
+ let result = await worker.post("bounceWithExtraCalls", message);
+
+ // After the post call, the array was transferred and SOME_ARRAY should be empty
+ Assert.equal(SOME_ARRAY.buffer.byteLength, 0, "The buffer has been detached");
+
+ // The echo() function in the worker adds to the message a string, an uuid and has the transferred array
+ message.push(["Posting something unrelated", UUID, arrayCopy]);
+
+ Assert.deepEqual(result, message);
+});
diff --git a/toolkit/components/promiseworker/worker/PromiseWorker.template.worker.js b/toolkit/components/promiseworker/worker/PromiseWorker.template.worker.js
index c8203db48f..9ea26df9f5 100644
--- a/toolkit/components/promiseworker/worker/PromiseWorker.template.worker.js
+++ b/toolkit/components/promiseworker/worker/PromiseWorker.template.worker.js
@@ -102,19 +102,57 @@ function Meta(data, meta) {
*/
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;
- this.log("Received message", 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) {
diff --git a/toolkit/components/prompts/content/commonDialog.css b/toolkit/components/prompts/content/commonDialog.css
index ac01353aae..3521af13c6 100644
--- a/toolkit/components/prompts/content/commonDialog.css
+++ b/toolkit/components/prompts/content/commonDialog.css
@@ -58,15 +58,6 @@ dialog[insecureauth] {
flex: 1;
}
-#spinnerContainer {
- align-items: center;
-}
-
-#spinnerContainer > img {
- width: 16px;
- height: 16px;
-}
-
#loginLabel, #password1Label {
text-align: start;
}
diff --git a/toolkit/components/prompts/content/commonDialog.js b/toolkit/components/prompts/content/commonDialog.js
index d9b39f696a..1c9ba6a9a8 100644
--- a/toolkit/components/prompts/content/commonDialog.js
+++ b/toolkit/components/prompts/content/commonDialog.js
@@ -95,7 +95,6 @@ function commonDialogOnLoad() {
infoBody: document.getElementById("infoBody"),
infoTitle: document.getElementById("infoTitle"),
infoIcon: document.getElementById("infoIcon"),
- spinnerContainer: document.getElementById("spinnerContainer"),
checkbox: document.getElementById("checkbox"),
checkboxContainer: document.getElementById("checkboxContainer"),
button3: dialog.getButton("extra2"),
diff --git a/toolkit/components/prompts/content/commonDialog.xhtml b/toolkit/components/prompts/content/commonDialog.xhtml
index def3b93956..83cc37d9ed 100644
--- a/toolkit/components/prompts/content/commonDialog.xhtml
+++ b/toolkit/components/prompts/content/commonDialog.xhtml
@@ -83,16 +83,6 @@
/>
</div>
</div>
- <div id="spinnerContainer" class="dialogRow" hidden="hidden">
- <img
- src="chrome://global/skin/icons/loading.png"
- data-l10n-id="common-dialog-spinner"
- srcset="
- chrome://global/skin/icons/loading.png,
- chrome://global/skin/icons/loading@2x.png 1.25x
- "
- />
- </div>
<div id="loginContainer" class="dialogRow" hidden="hidden">
<xul:label
id="loginLabel"
diff --git a/toolkit/components/prompts/docs/nsIPromptService-reference.rst b/toolkit/components/prompts/docs/nsIPromptService-reference.rst
index 9879cd753a..c1412a30f8 100644
--- a/toolkit/components/prompts/docs/nsIPromptService-reference.rst
+++ b/toolkit/components/prompts/docs/nsIPromptService-reference.rst
@@ -2,7 +2,7 @@
Prompt Service Reference
========================
-This is the JSDoc from the Prompter.jsm implementation. You can find the full
+This is the JSDoc from the Prompter.sys.mjs implementation. You can find the full
interface definition in
`nsIPromptService.idl <https://searchfox.org/mozilla-central/source/toolkit/components/windowwatcher/nsIPromptService.idl>`_.
diff --git a/toolkit/components/prompts/src/CommonDialog.sys.mjs b/toolkit/components/prompts/src/CommonDialog.sys.mjs
index 22b6921917..a0812aa8ec 100644
--- a/toolkit/components/prompts/src/CommonDialog.sys.mjs
+++ b/toolkit/components/prompts/src/CommonDialog.sys.mjs
@@ -137,10 +137,6 @@ CommonDialog.prototype = {
commonDialogEl.ownerDocument.title = title;
}
- if (this.ui.spinnerContainer && this.args.showSpinner) {
- this.ui.spinnerContainer.hidden = false;
- }
-
// Set button labels and visibility
//
// This assumes that button0 defaults to a visible "ok" button, and
diff --git a/toolkit/components/prompts/src/Prompter.sys.mjs b/toolkit/components/prompts/src/Prompter.sys.mjs
index ce17f1b457..a0b5edca2f 100644
--- a/toolkit/components/prompts/src/Prompter.sys.mjs
+++ b/toolkit/components/prompts/src/Prompter.sys.mjs
@@ -1058,7 +1058,7 @@ class ModalPrompter {
closed = true;
});
Services.tm.spinEventLoopUntilOrQuit(
- "prompts/Prompter.jsm:openPromptSync",
+ "prompts/Prompter.sys.mjs:openPromptSync",
() => closed
);
}
@@ -1474,7 +1474,8 @@ class ModalPrompter {
}
if (flags & Ci.nsIPrompt.SHOW_SPINNER) {
- args.showSpinner = true;
+ // When bug 1879550 is fixed, add a higher-res version here
+ args.headerIconURL = "chrome://global/skin/icons/loading.png";
}
if (this.async) {
diff --git a/toolkit/components/prompts/test/PromptTestUtils.sys.mjs b/toolkit/components/prompts/test/PromptTestUtils.sys.mjs
index 138c61b189..051763cfa8 100644
--- a/toolkit/components/prompts/test/PromptTestUtils.sys.mjs
+++ b/toolkit/components/prompts/test/PromptTestUtils.sys.mjs
@@ -6,40 +6,9 @@
* nsIPromptService.
*/
-import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
import { BrowserTestUtils } from "resource://testing-common/BrowserTestUtils.sys.mjs";
import { TestUtils } from "resource://testing-common/TestUtils.sys.mjs";
-const kPrefs = {};
-
-// Whether prompts with modal type TAB are shown as SubDialog (true) or
-// TabModalPrompt (false).
-XPCOMUtils.defineLazyPreferenceGetter(
- kPrefs,
- "tabPromptSubDialogEnabled",
- "prompts.tabChromePromptSubDialog",
- false
-);
-
-// Whether web content prompts (alert etc.) are shown as SubDialog (true)
-// or TabModalPrompt (false)
-XPCOMUtils.defineLazyPreferenceGetter(
- kPrefs,
- "contentPromptSubDialogEnabled",
- "prompts.contentPromptSubDialog",
- false
-);
-
-function isCommonDialog(modalType) {
- return (
- modalType === Services.prompt.MODAL_TYPE_WINDOW ||
- (kPrefs.tabPromptSubDialogEnabled &&
- modalType === Services.prompt.MODAL_TYPE_TAB) ||
- (kPrefs.contentPromptSubDialogEnabled &&
- modalType === Services.prompt.MODAL_TYPE_CONTENT)
- );
-}
-
export let PromptTestUtils = {
/**
* Wait for a prompt from nsIPrompt or nsIPromptsService, interact with it and
@@ -85,13 +54,7 @@ export let PromptTestUtils = {
let promptClosePromise;
// Get parent window to listen for prompt close event
- let win;
- if (isCommonDialog(dialog.args.modalType)) {
- win = dialog.ui.prompt?.opener;
- } else {
- // Tab prompts should always have a parent window
- win = dialog.ui.prompt.win;
- }
+ let win = dialog.ui.prompt?.opener;
if (win) {
promptClosePromise = BrowserTestUtils.waitForEvent(
@@ -161,65 +124,50 @@ export let PromptTestUtils = {
}
}
- let topic = isCommonDialog(modalType)
- ? "common-dialog-loaded"
- : "tabmodal-dialog-loaded";
+ let topic = "common-dialog-loaded";
let dialog;
await TestUtils.topicObserved(topic, subject => {
// If we are not given a browser, use the currently selected browser of the window
let browser =
parentBrowser || subject.ownerGlobal.gBrowser?.selectedBrowser;
- if (isCommonDialog(modalType)) {
- // Is not associated with given parent window, skip
- if (parentWindow && subject.opener !== parentWindow) {
- return false;
- }
-
- // For tab prompts, ensure that the associated browser matches.
- if (browser && modalType == Services.prompt.MODAL_TYPE_TAB) {
- let dialogBox = parentWindow.gBrowser.getTabDialogBox(browser);
- let hasMatchingDialog = dialogBox
- .getTabDialogManager()
- ._dialogs.some(
- d => d._frame?.browsingContext == subject.browsingContext
- );
- if (!hasMatchingDialog) {
- return false;
- }
- }
+ // Is not associated with given parent window, skip
+ if (parentWindow && subject.opener !== parentWindow) {
+ return false;
+ }
- if (browser && modalType == Services.prompt.MODAL_TYPE_CONTENT) {
- let dialogBox = parentWindow.gBrowser.getTabDialogBox(browser);
- let hasMatchingDialog = dialogBox
- .getContentDialogManager()
- ._dialogs.some(
- d => d._frame?.browsingContext == subject.browsingContext
- );
- if (!hasMatchingDialog) {
- return false;
- }
+ // For tab prompts, ensure that the associated browser matches.
+ if (browser && modalType == Services.prompt.MODAL_TYPE_TAB) {
+ let dialogBox = parentWindow.gBrowser.getTabDialogBox(browser);
+ let hasMatchingDialog = dialogBox
+ .getTabDialogManager()
+ ._dialogs.some(
+ d => d._frame?.browsingContext == subject.browsingContext
+ );
+ if (!hasMatchingDialog) {
+ return false;
}
+ }
- // subject is the window object of the prompt which has a Dialog object
- // attached.
- dialog = subject.Dialog;
- } else {
- // subject is the tabprompt dom node
- // Get the full prompt object which has the dialog object
- let prompt = browser.tabModalPromptBox.getPrompt(subject);
-
- // Is not associated with given parent browser, skip.
- if (!prompt) {
+ if (browser && modalType == Services.prompt.MODAL_TYPE_CONTENT) {
+ let dialogBox = parentWindow.gBrowser.getTabDialogBox(browser);
+ let hasMatchingDialog = dialogBox
+ .getContentDialogManager()
+ ._dialogs.some(
+ d => d._frame?.browsingContext == subject.browsingContext
+ );
+ if (!hasMatchingDialog) {
return false;
}
-
- dialog = prompt.Dialog;
}
+ // subject is the window object of the prompt which has a Dialog object
+ // attached.
+ dialog = subject.Dialog;
+
// Not the modalType we're looking for.
// For window prompts dialog.args.modalType is undefined.
- if (isCommonDialog(modalType) && dialog.args.modalType !== modalType) {
+ if (dialog.args.modalType !== modalType) {
return false;
}
diff --git a/toolkit/components/prompts/test/chromeScript.js b/toolkit/components/prompts/test/chromeScript.js
index 1e71224f6b..9a6bacc8a4 100644
--- a/toolkit/components/prompts/test/chromeScript.js
+++ b/toolkit/components/prompts/test/chromeScript.js
@@ -8,16 +8,6 @@ const { BrowserTestUtils } = ChromeUtils.importESModule(
"resource://testing-common/BrowserTestUtils.sys.mjs"
);
-var tabSubDialogsEnabled = Services.prefs.getBoolPref(
- "prompts.tabChromePromptSubDialog",
- false
-);
-
-var contentPromptSubdialogsEnabled = Services.prefs.getBoolPref(
- "prompts.contentPromptSubDialog",
- false
-);
-
// Define these to make EventUtils happy.
let window = this;
let parent = {};
@@ -91,31 +81,15 @@ async function handlePrompt(action, modalType, isSelect) {
let ui;
let browserWin = Services.wm.getMostRecentWindow("navigator:browser");
- if (
- (!contentPromptSubdialogsEnabled &&
- modalType === Services.prompt.MODAL_TYPE_CONTENT) ||
- (!tabSubDialogsEnabled && modalType === Services.prompt.MODAL_TYPE_TAB)
- ) {
- let gBrowser = browserWin.gBrowser;
- let promptManager = gBrowser.getTabModalPromptBox(gBrowser.selectedBrowser);
- let prompts = promptManager.listPrompts();
- if (!prompts.length) {
- return false; // try again in a bit
- }
+ let doc = getDialogDoc();
+ if (!doc) {
+ return false; // try again in a bit
+ }
- ui = prompts[0].Dialog.ui;
- checkTabModal(prompts[0], gBrowser.selectedBrowser);
+ if (isSelect) {
+ ui = doc;
} else {
- let doc = getDialogDoc();
- if (!doc) {
- return false; // try again in a bit
- }
-
- if (isSelect) {
- ui = doc;
- } else {
- ui = doc.defaultView.Dialog.ui;
- }
+ ui = doc.defaultView.Dialog.ui;
}
let dialogClosed = BrowserTestUtils.waitForEvent(
diff --git a/toolkit/components/prompts/test/prompt_common.js b/toolkit/components/prompts/test/prompt_common.js
index 4b3a2262aa..8d583060da 100644
--- a/toolkit/components/prompts/test/prompt_common.js
+++ b/toolkit/components/prompts/test/prompt_common.js
@@ -21,10 +21,6 @@ var tabSubDialogsEnabled = SpecialPowers.Services.prefs.getBoolPref(
"prompts.tabChromePromptSubDialog",
false
);
-var contentSubDialogsEnabled = SpecialPowers.Services.prefs.getBoolPref(
- "prompts.contentPromptSubDialog",
- false
-);
var isSelectDialog = false;
var isOSX = "nsILocalFileMac" in SpecialPowers.Ci;
var isE10S = SpecialPowers.Services.appinfo.processType == 2;
@@ -200,20 +196,7 @@ function checkPromptState(promptState, expectedState) {
// XXX check title? OS X has title in content
is(promptState.msg, expectedState.msg, "Checking expected message");
- let isOldContentPrompt =
- !promptState.isSubDialogPrompt &&
- modalType === Ci.nsIPrompt.MODAL_TYPE_CONTENT;
-
- if (isOldContentPrompt && !promptState.showCallerOrigin) {
- ok(
- promptState.titleHidden,
- "The title should be hidden for content prompts opened with tab modal prompt."
- );
- } else if (
- isOSX ||
- promptState.isSubDialogPrompt ||
- promptState.showCallerOrigin
- ) {
+ if (isOSX || promptState.isSubDialogPrompt || promptState.showCallerOrigin) {
ok(
!promptState.titleHidden,
"Checking title always visible on OS X or when opened with common dialog"
diff --git a/toolkit/components/remote/nsDBusRemoteServer.cpp b/toolkit/components/remote/nsDBusRemoteServer.cpp
index 030d00dc25..48665f367b 100644
--- a/toolkit/components/remote/nsDBusRemoteServer.cpp
+++ b/toolkit/components/remote/nsDBusRemoteServer.cpp
@@ -37,7 +37,7 @@ static const char* introspect_template =
bool nsDBusRemoteServer::HandleOpenURL(const gchar* aInterfaceName,
const gchar* aMethodName,
- const nsACString& aParam) {
+ const gchar* aParam) {
nsPrintfCString ourInterfaceName("org.mozilla.%s", mAppName.get());
if ((strcmp("OpenURL", aMethodName) != 0) ||
@@ -50,7 +50,7 @@ bool nsDBusRemoteServer::HandleOpenURL(const gchar* aInterfaceName,
if (timestamp == GDK_CURRENT_TIME) {
timestamp = guint32(g_get_monotonic_time() / 1000);
}
- HandleCommandLine(PromiseFlatCString(aParam).get(), timestamp);
+ HandleCommandLine(aParam, timestamp);
return true;
}
@@ -75,9 +75,9 @@ static void HandleMethodCall(GDBusConnection* aConnection, const gchar* aSender,
}
gsize len;
- const auto* url = (const char*)g_variant_get_fixed_array(
+ const auto* commandLine = (const char*)g_variant_get_fixed_array(
g_variant_get_child_value(aParameters, 0), &len, sizeof(char));
- if (!url) {
+ if (!commandLine || !len) {
g_warning(
"nsDBusRemoteServer: HandleMethodCall: failed to get url string!");
g_dbus_method_invocation_return_error(
@@ -88,7 +88,7 @@ static void HandleMethodCall(GDBusConnection* aConnection, const gchar* aSender,
}
int ret = static_cast<nsDBusRemoteServer*>(aUserData)->HandleOpenURL(
- aInterfaceName, aMethodName, nsDependentCString(url, len));
+ aInterfaceName, aMethodName, commandLine);
if (!ret) {
g_dbus_method_invocation_return_error(
aInvocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED,
diff --git a/toolkit/components/remote/nsDBusRemoteServer.h b/toolkit/components/remote/nsDBusRemoteServer.h
index 4212acb4c3..7f248e3824 100644
--- a/toolkit/components/remote/nsDBusRemoteServer.h
+++ b/toolkit/components/remote/nsDBusRemoteServer.h
@@ -29,7 +29,7 @@ class nsDBusRemoteServer final : public nsRemoteServer,
void OnNameLost(GDBusConnection* aConnection);
bool HandleOpenURL(const gchar* aInterfaceName, const gchar* aMethodName,
- const nsACString& aParam);
+ const gchar* aParam);
private:
uint mDBusID = 0;
diff --git a/toolkit/components/reportbrokensite/ReportBrokenSiteChild.sys.mjs b/toolkit/components/reportbrokensite/ReportBrokenSiteChild.sys.mjs
index 8220f5b4b6..291b1defc8 100644
--- a/toolkit/components/reportbrokensite/ReportBrokenSiteChild.sys.mjs
+++ b/toolkit/components/reportbrokensite/ReportBrokenSiteChild.sys.mjs
@@ -2,8 +2,6 @@
* License, v. 2.0. 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 SCREENSHOT_FORMAT = { format: "jpeg", quality: 75 };
function RunScriptInFrame(win, script) {
@@ -206,95 +204,21 @@ const FrameworkDetector = {
},
};
-function getSysinfoProperty(propertyName, defaultValue) {
- try {
- return Services.sysinfo.getProperty(propertyName);
- } catch (e) {}
- return defaultValue;
-}
-
-const BrowserInfo = {
- getAppInfo() {
- const { userAgent } = Cc[
- "@mozilla.org/network/protocol;1?name=http"
- ].getService(Ci.nsIHttpProtocolHandler);
- return {
- applicationName: Services.appinfo.name,
- buildId: Services.appinfo.appBuildID,
- defaultUserAgent: userAgent,
- updateChannel: AppConstants.MOZ_UPDATE_CHANNEL,
- version: AppConstants.MOZ_APP_VERSION_DISPLAY,
- };
- },
-
- getPrefs() {
- const prefs = {};
- for (const [name, dflt] of Object.entries({
- "layers.acceleration.force-enabled": undefined,
- "gfx.webrender.software": undefined,
- "browser.opaqueResponseBlocking": undefined,
- "extensions.InstallTrigger.enabled": undefined,
- "privacy.resistFingerprinting": undefined,
- "privacy.globalprivacycontrol.enabled": undefined,
- })) {
- prefs[name] = Services.prefs.getBoolPref(name, dflt);
- }
- const cookieBehavior = "network.cookie.cookieBehavior";
- prefs[cookieBehavior] = Services.prefs.getIntPref(cookieBehavior);
- return prefs;
- },
-
- getPlatformInfo() {
- let memoryMB = getSysinfoProperty("memsize", null);
- if (memoryMB) {
- memoryMB = Math.round(memoryMB / 1024 / 1024);
- }
-
- const info = {
- fissionEnabled: Services.appinfo.fissionAutostart,
- memoryMB,
- osArchitecture: getSysinfoProperty("arch", null),
- osName: getSysinfoProperty("name", null),
- osVersion: getSysinfoProperty("version", null),
- os: AppConstants.platform,
- };
- if (AppConstants.platform === "android") {
- info.device = getSysinfoProperty("device", null);
- info.isTablet = getSysinfoProperty("tablet", false);
- }
- return info;
- },
-
- getAllData() {
- return {
- app: BrowserInfo.getAppInfo(),
- prefs: BrowserInfo.getPrefs(),
- platform: BrowserInfo.getPlatformInfo(),
- };
- },
-};
-
export class ReportBrokenSiteChild extends JSWindowActorChild {
#getWebCompatInfo(docShell) {
return Promise.all([
this.#getConsoleLogs(docShell),
- this.sendQuery(
- "GetWebcompatInfoOnlyAvailableInParentProcess",
- SCREENSHOT_FORMAT
- ),
+ this.sendQuery("GetWebcompatInfoFromParentProcess", SCREENSHOT_FORMAT),
]).then(([consoleLog, infoFromParent]) => {
- const { antitracking, graphics, locales, screenshot, security } =
- infoFromParent;
-
- const browser = BrowserInfo.getAllData();
- browser.graphics = graphics;
- browser.locales = locales;
- browser.security = security;
+ 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.os !== "linux") {
+ if (browser.platform.name !== "linux") {
delete browser.prefs["layers.acceleration.force-enabled"];
}
@@ -302,12 +226,12 @@ export class ReportBrokenSiteChild extends JSWindowActorChild {
antitracking,
browser,
consoleLog,
- devicePixelRatio: win.devicePixelRatio,
+ devicePixelRatio,
frameworks,
- languages: win.navigator.languages,
+ languages,
screenshot,
url: win.location.href,
- userAgent: win.navigator.userAgent,
+ userAgent,
};
});
}
@@ -369,13 +293,19 @@ export class ReportBrokenSiteChild extends JSWindowActorChild {
message.blockList = blockList;
- const { app, graphics, prefs, platform, security } = browser;
+ const { app, graphics, locales, prefs, platform, security } = browser;
- const { applicationName, version, updateChannel, defaultUserAgent } = app;
+ const {
+ applicationName,
+ buildId,
+ defaultUserAgent,
+ updateChannel,
+ version,
+ } = app;
const {
fissionEnabled,
- memoryMb,
+ memoryMB,
osArchitecture,
osName,
osVersion,
@@ -386,6 +316,7 @@ export class ReportBrokenSiteChild extends JSWindowActorChild {
const additionalData = {
applicationName,
blockList,
+ buildId,
devicePixelRatio,
finalUserAgent: userAgent,
fissionEnabled,
@@ -395,16 +326,15 @@ export class ReportBrokenSiteChild extends JSWindowActorChild {
hasTrackingContentBlocked,
isPB: isPrivateBrowsing,
languages,
- memoryMb,
+ locales,
+ memoryMB,
osArchitecture,
osName,
osVersion,
prefs,
- updateChannel,
- userAgent: defaultUserAgent,
version,
};
- if (security !== undefined) {
+ if (security !== undefined && Object.keys(security).length) {
additionalData.sec = security;
}
if (device !== undefined) {
@@ -424,9 +354,9 @@ export class ReportBrokenSiteChild extends JSWindowActorChild {
const details = Object.assign(message.details, specialPrefs, {
additionalData,
- buildId: browser.buildId,
blockList,
- channel: browser.updateChannel,
+ channel: updateChannel,
+ defaultUserAgent,
hasTouchScreen: browser.graphics.hasTouchScreen,
});
diff --git a/toolkit/components/reportbrokensite/ReportBrokenSiteParent.sys.mjs b/toolkit/components/reportbrokensite/ReportBrokenSiteParent.sys.mjs
index d6363a4ee1..c9b38b233f 100644
--- a/toolkit/components/reportbrokensite/ReportBrokenSiteParent.sys.mjs
+++ b/toolkit/components/reportbrokensite/ReportBrokenSiteParent.sys.mjs
@@ -3,118 +3,9 @@
* License, v. 2.0. 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";
-
-function getSysinfoProperty(propertyName, defaultValue) {
- try {
- return Services.sysinfo.getProperty(propertyName);
- } catch (e) {}
- return defaultValue;
-}
-
-function getSecurityInfo() {
- const keys = [
- ["registeredAntiVirus", "antivirus"],
- ["registeredAntiSpyware", "antispyware"],
- ["registeredFirewall", "firewall"],
- ];
-
- let result = {};
-
- for (let [inKey, outKey] of keys) {
- const str = getSysinfoProperty(inKey, null);
- result[outKey] = !str ? null : str.split(";");
- }
-
- // 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;
-}
-
-class DriverInfo {
- constructor(gl, ext) {
- try {
- this.extensions = ext.getParameter(ext.EXTENSIONS);
- } catch (e) {}
-
- try {
- this.renderer = ext.getParameter(gl.RENDERER);
- } catch (e) {}
-
- try {
- this.vendor = ext.getParameter(gl.VENDOR);
- } catch (e) {}
-
- try {
- this.version = ext.getParameter(gl.VERSION);
- } catch (e) {}
-
- try {
- this.wsiInfo = ext.getParameter(ext.WSI_INFO);
- } catch (e) {}
- }
-
- equals(info2) {
- return this.renderer == info2.renderer && this.version == info2.version;
- }
-
- static getByType(driver) {
- const doc = new DOMParser().parseFromString("<html/>", "text/html");
- const canvas = doc.createElement("canvas");
- canvas.width = 1;
- canvas.height = 1;
- let error;
- canvas.addEventListener("webglcontextcreationerror", function (e) {
- error = true;
- });
- let gl = null;
- try {
- gl = canvas.getContext(driver);
- } catch (e) {
- error = true;
- }
- if (error || !gl?.getExtension) {
- return undefined;
- }
-
- let ext = null;
- try {
- ext = gl.getExtension("MOZ_debug");
- } catch (e) {}
- if (!ext) {
- return undefined;
- }
-
- const data = new DriverInfo(gl, ext);
-
- try {
- gl.getExtension("WEBGL_lose_context").loseContext();
- } catch (e) {}
-
- return data;
- }
-
- static getAll() {
- const drivers = [];
-
- function tryDriver(type) {
- const driver = DriverInfo.getByType(type);
- if (driver) {
- drivers.push(driver);
- }
- }
-
- tryDriver("webgl");
- tryDriver("webgl2");
- tryDriver("webgpu");
+import { Troubleshoot } from "resource://gre/modules/Troubleshoot.sys.mjs";
- return drivers;
- }
-}
+import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
export class ReportBrokenSiteParent extends JSWindowActorParent {
#getAntitrackingBlockList() {
@@ -145,10 +36,10 @@ export class ReportBrokenSiteParent extends JSWindowActorParent {
};
}
- #getBasicGraphicsInfo(gfxInfo) {
+ #parseGfxInfo(info) {
const get = name => {
try {
- return gfxInfo[name];
+ return info[name];
} catch (e) {}
return undefined;
};
@@ -183,63 +74,165 @@ export class ReportBrokenSiteParent extends JSWindowActorParent {
direct2DEnabled: get("direct2DEnabled"),
directWriteEnabled: get("directWriteEnabled"),
directWriteVersion: get("directWriteVersion"),
- hasTouchScreen: gfxInfo.getInfo().ApzTouchInput == 1,
- cleartypeParameters: get("clearTypeParameters"),
+ hasTouchScreen: info.ApzTouchInput == 1,
+ clearTypeParameters: get("clearTypeParameters"),
targetFrameRate: get("targetFrameRate"),
devices,
});
}
- #getGraphicsInfo() {
- const gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);
+ #parseCodecSupportInfo(codecSupportInfo) {
+ if (!codecSupportInfo) {
+ return undefined;
+ }
- const data = this.#getBasicGraphicsInfo(gfxInfo);
+ 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;
+ }
- data.drivers = DriverInfo.getAll().map(({ renderer, vendor, version }) => {
- return { renderer: `${vendor} -- ${renderer}`, version };
- });
+ #parseFeatureLog(featureLog = {}) {
+ const { features } = featureLog;
+ if (!features) {
+ return undefined;
+ }
- try {
- const info = gfxInfo.CodecSupportInfo;
- if (info) {
- const codecs = {};
- for (const item of gfxInfo.CodecSupportInfo.split("\n")) {
- const [codec, ...types] = item.split(" ");
- if (!codecs[codec]) {
- codecs[codec] = { software: false, hardware: false };
- }
- if (types.includes("SW")) {
- codecs[codec].software = true;
- }
- if (types.includes("HW")) {
- codecs[codec].hardware = true;
- }
+ const parsedFeatures = {};
+ for (let { name, log, status } of features) {
+ for (const item of log.reverse()) {
+ if (!item.failureId || item.status != status) {
+ continue;
}
- data.codecSupport = codecs;
+ status = `${status} (${item.message || item.failureId})`;
}
- } catch (e) {}
+ parsedFeatures[name] = status;
+ }
+ return parsedFeatures;
+ }
- try {
- const { features } = gfxInfo.getFeatureLog();
- data.features = {};
- 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})`;
- }
- data.features[name] = status;
- }
- } catch (e) {}
+ #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 {
- if (AppConstants.platform !== "android") {
- data.monitors = gfxInfo.getMonitors();
- }
+ return Services.sysinfo.getProperty(propertyName);
} catch (e) {}
+ return defaultValue;
+ }
- return data;
+ #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) {
@@ -268,7 +261,7 @@ export class ReportBrokenSiteParent extends JSWindowActorParent {
async receiveMessage(msg) {
switch (msg.name) {
- case "GetWebcompatInfoOnlyAvailableInParentProcess": {
+ case "GetWebcompatInfoFromParentProcess": {
const { format, quality } = msg.data;
const screenshot = await this.#getScreenshot(
msg.target.browsingContext,
@@ -278,12 +271,11 @@ export class ReportBrokenSiteParent extends JSWindowActorParent {
console.error("Report Broken Site: getting a screenshot failed", e);
return Promise.resolve(undefined);
});
+
return {
antitracking: this.#getAntitrackingInfo(msg.target.browsingContext),
- graphics: this.#getGraphicsInfo(),
- locales: Services.locale.availableLocales,
+ browser: await this.#getBrowserInfo(),
screenshot,
- security: getSecurityInfo(),
};
}
}
diff --git a/toolkit/components/reputationservice/ApplicationReputation.cpp b/toolkit/components/reputationservice/ApplicationReputation.cpp
index cc4045d5ad..8973fc85bb 100644
--- a/toolkit/components/reputationservice/ApplicationReputation.cpp
+++ b/toolkit/components/reputationservice/ApplicationReputation.cpp
@@ -519,20 +519,20 @@ const char* const ApplicationReputationService::kBinaryFileExtensions[] = {
".xlam", // MS Excel
".xldm", // MS Excel
//".xll", exec // MS Excel
- ".xlm", // MS Excel
- ".xls", // MS Excel
- ".xlsb", // MS Excel
- ".xlsm", // MS Excel
- ".xlsx", // MS Excel
- ".xlt", // MS Excel
- ".xltm", // MS Excel
- ".xltx", // MS Excel
- ".xlw", // MS Excel
- ".xml", // MS Excel
- ".xnk", // MS Exchange
- ".xrm-ms", // Windows
- ".xsd", // XML schema definition
- ".xsl", // XML Stylesheet
+ ".xlm", // MS Excel
+ ".xls", // MS Excel
+ ".xlsb", // MS Excel
+ ".xlsm", // MS Excel
+ ".xlsx", // MS Excel
+ ".xlt", // MS Excel
+ ".xltm", // MS Excel
+ ".xltx", // MS Excel
+ ".xlw", // MS Excel
+ ".xml", // MS Excel
+ ".xnk", // MS Exchange
+ //".xrm-ms", exec // Windows
+ ".xsd", // XML schema definition
+ ".xsl", // XML Stylesheet
//".xxe",
".xz", // Linux archive (xz)
".z", // InstallShield
diff --git a/toolkit/components/reputationservice/ApplicationReputation.h b/toolkit/components/reputationservice/ApplicationReputation.h
index f708c6eb71..5ea5b825f7 100644
--- a/toolkit/components/reputationservice/ApplicationReputation.h
+++ b/toolkit/components/reputationservice/ApplicationReputation.h
@@ -27,9 +27,9 @@ class ApplicationReputationService final
public:
static const char* const kNonBinaryExecutables[5];
#ifdef XP_WIN
- static const char* const kBinaryFileExtensions[185];
-#else
static const char* const kBinaryFileExtensions[184];
+#else
+ static const char* const kBinaryFileExtensions[183];
#endif
static already_AddRefed<ApplicationReputationService> GetSingleton();
diff --git a/toolkit/components/resistfingerprinting/RFPHelper.sys.mjs b/toolkit/components/resistfingerprinting/RFPHelper.sys.mjs
index a085aa492a..223c0259b7 100644
--- a/toolkit/components/resistfingerprinting/RFPHelper.sys.mjs
+++ b/toolkit/components/resistfingerprinting/RFPHelper.sys.mjs
@@ -20,7 +20,7 @@ var logConsole;
function log(msg) {
if (!logConsole) {
logConsole = console.createInstance({
- prefix: "RFPHelper.jsm",
+ prefix: "RFPHelper",
maxLogLevelPref: "privacy.resistFingerprinting.jsmloglevel",
});
}
diff --git a/toolkit/components/resistfingerprinting/metrics.yaml b/toolkit/components/resistfingerprinting/metrics.yaml
index 916c8c7871..3c706d20fa 100644
--- a/toolkit/components/resistfingerprinting/metrics.yaml
+++ b/toolkit/components/resistfingerprinting/metrics.yaml
@@ -25,3 +25,387 @@ fingerprinting.protection:
- tschuster@mozilla.com
expires: never
telemetry_mirror: FINGERPRINTING_PROTECTION_CANVAS_NOISE_CALCULATE_TIME_MS
+
+
+characteristics:
+ client_identifier:
+ type: uuid
+ description: >
+ A unique identifier for a user, not the same as the normal Telemetry
+ client_id, but needed so we can deduplicate reports and only take the most
+ recent one per user.
+ lifetime: application
+ send_in_pings:
+ - user-characteristics
+ - deletion-request
+ notification_emails:
+ - tom@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879151
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879154
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879154#c8
+ expires: never
+ data_sensitivity:
+ - technical
+
+ submission_schema:
+ type: quantity
+ unit: versions
+ description: >
+ An incrementing constant that represents the current schema/source of the
+ data present in a ping. By referring to this value in a ping, one can know
+ for certain the provenance of other data present in the ping, and what
+ data may or may not be present.
+ lifetime: application
+ send_in_pings:
+ - user-characteristics
+ notification_emails:
+ - tom@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879151
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879154
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879154#c8
+ expires: never
+
+ max_touch_points:
+ type: quantity
+ unit: Fingers
+ description: >
+ The number of touch points we will report to the web. On Android, this is
+ based on Android's FEATURE_TOUCHSCREEN* constants - Mozilla caps this at 5
+ as Android stops distinguishing between numbers greater than 5. On
+ Windows this comes from the SM_MAXIMUMTOUCHES System Metric.
+ lifetime: application
+ send_in_pings:
+ - user-characteristics
+ notification_emails:
+ - tom@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879151
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879156
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879156#c4
+ expires: never
+ data_sensitivity:
+ - technical
+
+ video_dynamic_range:
+ type: boolean
+ description: >
+ What LookAndFeel(VideoDynamicRange) reports. Note that CSSVideoDynamicRange
+ has an additional dependency on Color Depth.
+ lifetime: application
+ send_in_pings:
+ - user-characteristics
+ notification_emails:
+ - tom@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879151
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879624
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879624#c4
+ expires: never
+ data_sensitivity:
+ - technical
+
+ prefers_reduced_transparency:
+ type: boolean
+ description: >
+ What LookAndFeel(PrefersReducedTransparency) reports.
+ lifetime: application
+ send_in_pings:
+ - user-characteristics
+ notification_emails:
+ - tom@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879151
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879624
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879624#c4
+ expires: never
+ data_sensitivity:
+ - technical
+
+ prefers_reduced_motion:
+ type: boolean
+ description: >
+ What LookAndFeel(PrefersReducedMotion) reports.
+ lifetime: application
+ send_in_pings:
+ - user-characteristics
+ notification_emails:
+ - tom@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879151
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879624
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879624#c4
+ expires: never
+ data_sensitivity:
+ - technical
+
+ prefers_contrast:
+ type: quantity
+ unit: enum StylePrefersContrast value
+ description: >
+ What Gecko_MediaFeatures_PrefersContrast reports for a ContentDocument
+ lifetime: application
+ send_in_pings:
+ - user-characteristics
+ notification_emails:
+ - tom@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879151
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879624
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879624#c4
+ expires: never
+ data_sensitivity:
+ - technical
+
+ inverted_colors:
+ type: boolean
+ description: >
+ What LookAndFeel(InvertedColors) reports.
+ lifetime: application
+ send_in_pings:
+ - user-characteristics
+ notification_emails:
+ - tom@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879151
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879624
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879624#c4
+ expires: never
+ data_sensitivity:
+ - technical
+
+ color_scheme:
+ type: quantity
+ unit: enum mozilla::ColorScheme value
+ description: >
+ The Color Scheme used for Content, from ContentPrefs() Preference Sheet.
+ lifetime: application
+ send_in_pings:
+ - user-characteristics
+ notification_emails:
+ - tom@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879151
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879624
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879624#c4
+ expires: never
+ data_sensitivity:
+ - technical
+
+ color_gamut:
+ type: quantity
+ unit: enum dom::ScreenColorGamut value
+ description: >
+ The Color Gamut reported by CSS
+ lifetime: application
+ send_in_pings:
+ - user-characteristics
+ notification_emails:
+ - tom@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879151
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879624
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879624#c4
+ expires: never
+ data_sensitivity:
+ - technical
+
+ color_depth:
+ type: quantity
+ unit: bits
+ description: >
+ The Color Depth reported by CSS
+ lifetime: application
+ send_in_pings:
+ - user-characteristics
+ notification_emails:
+ - tom@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879151
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879624
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879624#c4
+ expires: never
+ data_sensitivity:
+ - technical
+
+ missing_fonts:
+ type: text
+ description: >
+ If a Font List is available for the user's platform, this
+ string_list contains the fonts that are missing from the user's
+ computer.
+ lifetime: application
+ send_in_pings:
+ - user-characteristics
+ notification_emails:
+ - tom@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879151
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1880561
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1880561#c6
+ expires: never
+ data_sensitivity:
+ # Text metrics are _required_ to be web_activity or highly_sensitive, so even though this
+ # is more like 'technical' (per the Data Review), I'm marking highly sensitive.
+ - highly_sensitive
+
+ screen_width:
+ type: quantity
+ unit: pixels
+ description: >
+ Width of the primary screen in pixels.
+ lifetime: application
+ send_in_pings:
+ - user-characteristics
+ notification_emails:
+ - tom@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879151
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1881749
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1881749#c5
+ expires: never
+ data_sensitivity:
+ - technical
+
+ screen_height:
+ type: quantity
+ unit: pixels
+ description: >
+ Height of the primary screen in pixels.
+ lifetime: application
+ send_in_pings:
+ - user-characteristics
+ notification_emails:
+ - tom@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879151
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1881749
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1881749#c5
+ expires: never
+ data_sensitivity:
+ - technical
+
+ processor_count:
+ type: quantity
+ unit: int
+ description: >
+ Number of processors.
+ lifetime: application
+ send_in_pings:
+ - user-characteristics
+ notification_emails:
+ - tom@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879151
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1881759
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1881759#c4
+ expires: never
+ data_sensitivity:
+ - technical
+
+ timezone:
+ type: string
+ description: >
+ The the current timezone of the system
+ lifetime: application
+ send_in_pings:
+ - user-characteristics
+ notification_emails:
+ - tom@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879151
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1881773
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1881773#c4
+ expires: never
+ data_sensitivity:
+ - interaction
+
+ target_frame_rate:
+ type: quantity
+ unit: int
+ description: >
+ The target frame rate in frames-per-second.
+ lifetime: application
+ send_in_pings:
+ - user-characteristics
+ notification_emails:
+ - tom@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879151
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1882054
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1882054#c3
+ expires: never
+ data_sensitivity:
+ - technical
+
+ prefs_intl_accept_languages:
+ type: string
+ description: >
+ Value of the intl.accept_languages pref.
+ lifetime: application
+ send_in_pings:
+ - user-characteristics
+ notification_emails:
+ - tom@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879151
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1882482
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1882482#c6
+ expires: never
+ data_sensitivity:
+ - interaction
+
+ prefs_media_eme_enabled:
+ type: boolean
+ description: >
+ Value of the media.eme.enabled pref.
+ lifetime: application
+ send_in_pings:
+ - user-characteristics
+ notification_emails:
+ - tom@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879151
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1882482
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1882482#c6
+ expires: never
+ data_sensitivity:
+ - interaction
+
+ prefs_zoom_text_only:
+ type: boolean
+ description: >
+ Text-only zoom enabled (vs. full-zoom)
+ lifetime: application
+ send_in_pings:
+ - user-characteristics
+ notification_emails:
+ - tom@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879151
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1882482
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1882482#c6
+ expires: never
+ data_sensitivity:
+ - interaction
diff --git a/toolkit/components/resistfingerprinting/moz.build b/toolkit/components/resistfingerprinting/moz.build
index 6ac25d3b76..9b4e9cc8ed 100644
--- a/toolkit/components/resistfingerprinting/moz.build
+++ b/toolkit/components/resistfingerprinting/moz.build
@@ -13,6 +13,17 @@ UNIFIED_SOURCES += [
"nsRFPService.cpp",
"RelativeTimeline.cpp",
]
+# Because nsUserCharacteristics doesn't `use namespace mozilla` (because we're going to wind
+# up with a million includes on this file and pollution will get confusing), unified build
+# will mask a lot of errors that would cause backouts. This exposes them locally.
+SOURCES += [
+ "nsUserCharacteristics.cpp",
+]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa":
+ LOCAL_INCLUDES += [
+ "/xpcom/base",
+ ]
SPHINX_TREES["resistfingerprinting"] = "docs"
@@ -23,6 +34,7 @@ EXPORTS.mozilla += [
"RelativeTimeline.h",
"RFPTargetIPCUtils.h",
]
+EXPORTS.mozilla.gtest += ["nsUserCharacteristics.h"]
EXTRA_JS_MODULES += [
"FingerprintingWebCompatService.sys.mjs",
diff --git a/toolkit/components/resistfingerprinting/nsRFPService.cpp b/toolkit/components/resistfingerprinting/nsRFPService.cpp
index 8579fe2a3b..643cc2cb7a 100644
--- a/toolkit/components/resistfingerprinting/nsRFPService.cpp
+++ b/toolkit/components/resistfingerprinting/nsRFPService.cpp
@@ -71,6 +71,7 @@
#include "nsTLiteralString.h"
#include "nsTPromiseFlatString.h"
#include "nsTStringRepr.h"
+#include "nsUserCharacteristics.h"
#include "nsXPCOM.h"
#include "nsICookieJarSettings.h"
@@ -100,8 +101,14 @@ static mozilla::LazyLogModule gFingerprinterDetection("FingerprinterDetection");
#define RESIST_FINGERPRINTINGPROTECTION_OVERRIDE_PREF \
"privacy.fingerprintingProtection.overrides"
+#define GLEAN_DATA_SUBMISSION_PREF "datareporting.healthreport.uploadEnabled"
+#define USER_CHARACTERISTICS_UUID_PREF \
+ "toolkit.telemetry.user_characteristics_ping.uuid"
+
#define RFP_TIMER_UNCONDITIONAL_VALUE 20
#define LAST_PB_SESSION_EXITED_TOPIC "last-pb-context-exited"
+#define IDLE_TOPIC "browser-idle-startup-tasks-finished"
+#define GFX_FEATURES "gfx-features-ready"
static constexpr uint32_t kVideoFramesPerSec = 30;
static constexpr uint32_t kVideoDroppedRatio = 5;
@@ -156,6 +163,7 @@ already_AddRefed<nsRFPService> nsRFPService::GetOrCreate() {
static const char* gCallbackPrefs[] = {
RESIST_FINGERPRINTINGPROTECTION_OVERRIDE_PREF,
+ GLEAN_DATA_SUBMISSION_PREF,
nullptr,
};
@@ -176,6 +184,12 @@ nsresult nsRFPService::Init() {
rv = obs->AddObserver(this, OBSERVER_TOPIC_IDLE_DAILY, false);
NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = obs->AddObserver(this, IDLE_TOPIC, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = obs->AddObserver(this, GFX_FEATURES, false);
+ NS_ENSURE_SUCCESS(rv, rv);
}
Preferences::RegisterCallbacks(nsRFPService::PrefChanged, gCallbackPrefs,
@@ -273,6 +287,8 @@ void nsRFPService::StartShutdown() {
if (XRE_IsParentProcess()) {
obs->RemoveObserver(this, LAST_PB_SESSION_EXITED_TOPIC);
obs->RemoveObserver(this, OBSERVER_TOPIC_IDLE_DAILY);
+ obs->RemoveObserver(this, IDLE_TOPIC);
+ obs->RemoveObserver(this, GFX_FEATURES);
}
}
@@ -290,16 +306,30 @@ void nsRFPService::PrefChanged(const char* aPref, void* aSelf) {
}
void nsRFPService::PrefChanged(const char* aPref) {
+ MOZ_LOG(gResistFingerprintingLog, LogLevel::Info,
+ ("Pref Changed: %s", aPref));
nsDependentCString pref(aPref);
if (pref.EqualsLiteral(RESIST_FINGERPRINTINGPROTECTION_OVERRIDE_PREF)) {
UpdateFPPOverrideList();
+ } else if (pref.EqualsLiteral(GLEAN_DATA_SUBMISSION_PREF)) {
+ if (XRE_IsParentProcess() &&
+ !Preferences::GetBool(GLEAN_DATA_SUBMISSION_PREF, false)) {
+ MOZ_LOG(gResistFingerprintingLog, LogLevel::Info, ("Clearing UUID"));
+ // If the user has unset the telemetry pref, wipe out the UUID pref value
+ // (The data will also be erased server-side via the "deletion-request"
+ // ping)
+ Preferences::SetCString(USER_CHARACTERISTICS_UUID_PREF, ""_ns);
+ }
}
}
NS_IMETHODIMP
nsRFPService::Observe(nsISupports* aObject, const char* aTopic,
const char16_t* aMessage) {
+ const int kNumTopicsForUserCharacteristics = 2;
+ static int seenTopicsForUserCharacteristics = 0;
+
if (strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, aTopic) == 0) {
StartShutdown();
}
@@ -312,6 +342,14 @@ nsRFPService::Observe(nsISupports* aObject, const char* aTopic,
ClearBrowsingSessionKey(pattern);
}
+ if (!strcmp(IDLE_TOPIC, aTopic) || !strcmp(GFX_FEATURES, aTopic)) {
+ seenTopicsForUserCharacteristics++;
+
+ if (seenTopicsForUserCharacteristics == kNumTopicsForUserCharacteristics) {
+ nsUserCharacteristics::MaybeSubmitPing();
+ }
+ }
+
if (!strcmp(OBSERVER_TOPIC_IDLE_DAILY, aTopic)) {
if (StaticPrefs::
privacy_resistFingerprinting_randomization_daily_reset_enabled()) {
diff --git a/toolkit/components/resistfingerprinting/nsUserCharacteristics.cpp b/toolkit/components/resistfingerprinting/nsUserCharacteristics.cpp
new file mode 100644
index 0000000000..9bce616f81
--- /dev/null
+++ b/toolkit/components/resistfingerprinting/nsUserCharacteristics.cpp
@@ -0,0 +1,297 @@
+/* -*- 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/. */
+
+#include "nsUserCharacteristics.h"
+
+#include "nsID.h"
+#include "nsIUUIDGenerator.h"
+#include "nsServiceManagerUtils.h"
+
+#include "mozilla/Logging.h"
+#include "mozilla/glean/GleanPings.h"
+#include "mozilla/glean/GleanMetrics.h"
+
+#include "mozilla/StaticPrefs_media.h"
+
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/PreferenceSheet.h"
+#include "mozilla/RelativeLuminanceUtils.h"
+#include "mozilla/ServoStyleConsts.h"
+#include "mozilla/dom/ScreenBinding.h"
+#include "mozilla/intl/TimeZone.h"
+#include "mozilla/widget/ScreenManager.h"
+
+#include "gfxPlatformFontList.h"
+#include "prsystem.h"
+#if defined(XP_WIN)
+# include "WinUtils.h"
+#elif defined(MOZ_WIDGET_ANDROID)
+# include "mozilla/java/GeckoAppShellWrappers.h"
+#elif defined(XP_MACOSX)
+# include "nsMacUtilsImpl.h"
+#endif
+
+static mozilla::LazyLogModule gUserCharacteristicsLog("UserCharacteristics");
+
+// ==================================================================
+namespace testing {
+extern "C" {
+
+int MaxTouchPoints() {
+#if defined(XP_WIN)
+ return mozilla::widget::WinUtils::GetMaxTouchPoints();
+#elif defined(MOZ_WIDGET_ANDROID)
+ return mozilla::java::GeckoAppShell::GetMaxTouchPoints();
+#else
+ return 0;
+#endif
+}
+
+} // extern "C"
+}; // namespace testing
+
+// ==================================================================
+void PopulateCSSProperties() {
+ mozilla::glean::characteristics::video_dynamic_range.Set(
+ mozilla::LookAndFeel::GetInt(
+ mozilla::LookAndFeel::IntID::VideoDynamicRange));
+ mozilla::glean::characteristics::prefers_reduced_transparency.Set(
+ mozilla::LookAndFeel::GetInt(
+ mozilla::LookAndFeel::IntID::PrefersReducedTransparency));
+ mozilla::glean::characteristics::prefers_reduced_motion.Set(
+ mozilla::LookAndFeel::GetInt(
+ mozilla::LookAndFeel::IntID::PrefersReducedMotion));
+ mozilla::glean::characteristics::inverted_colors.Set(
+ mozilla::LookAndFeel::GetInt(
+ mozilla::LookAndFeel::IntID::InvertedColors));
+ mozilla::glean::characteristics::color_scheme.Set(
+ (int)mozilla::PreferenceSheet::ContentPrefs().mColorScheme);
+
+ mozilla::StylePrefersContrast prefersContrast = [] {
+ // Replicates Gecko_MediaFeatures_PrefersContrast but without a Document
+ if (!mozilla::PreferenceSheet::ContentPrefs().mUseAccessibilityTheme &&
+ mozilla::PreferenceSheet::ContentPrefs().mUseDocumentColors) {
+ return mozilla::StylePrefersContrast::NoPreference;
+ }
+
+ const auto& colors = mozilla::PreferenceSheet::ContentPrefs().ColorsFor(
+ mozilla::ColorScheme::Light);
+ float ratio = mozilla::RelativeLuminanceUtils::ContrastRatio(
+ colors.mDefaultBackground, colors.mDefault);
+ // https://www.w3.org/TR/WCAG21/#contrast-minimum
+ if (ratio < 4.5f) {
+ return mozilla::StylePrefersContrast::Less;
+ }
+ // https://www.w3.org/TR/WCAG21/#contrast-enhanced
+ if (ratio >= 7.0f) {
+ return mozilla::StylePrefersContrast::More;
+ }
+ return mozilla::StylePrefersContrast::Custom;
+ }();
+ mozilla::glean::characteristics::prefers_contrast.Set((int)prefersContrast);
+}
+
+void PopulateScreenProperties() {
+ auto& screenManager = mozilla::widget::ScreenManager::GetSingleton();
+ RefPtr<mozilla::widget::Screen> screen = screenManager.GetPrimaryScreen();
+ MOZ_ASSERT(screen);
+
+ mozilla::dom::ScreenColorGamut colorGamut;
+ screen->GetColorGamut(&colorGamut);
+ mozilla::glean::characteristics::color_gamut.Set((int)colorGamut);
+
+ int32_t colorDepth;
+ screen->GetColorDepth(&colorDepth);
+ mozilla::glean::characteristics::color_depth.Set(colorDepth);
+
+ mozilla::glean::characteristics::color_gamut.Set((int)colorGamut);
+ mozilla::glean::characteristics::color_depth.Set(colorDepth);
+ const mozilla::LayoutDeviceIntRect rect = screen->GetRect();
+ mozilla::glean::characteristics::screen_height.Set(rect.Height());
+ mozilla::glean::characteristics::screen_width.Set(rect.Width());
+}
+
+void PopulateMissingFonts() {
+ nsCString aMissingFonts;
+ gfxPlatformFontList::PlatformFontList()->GetMissingFonts(aMissingFonts);
+
+ mozilla::glean::characteristics::missing_fonts.Set(aMissingFonts);
+}
+
+void PopulatePrefs() {
+ nsAutoCString acceptLang;
+ mozilla::Preferences::GetLocalizedCString("intl.accept_languages",
+ acceptLang);
+ mozilla::glean::characteristics::prefs_intl_accept_languages.Set(acceptLang);
+
+ mozilla::glean::characteristics::prefs_media_eme_enabled.Set(
+ mozilla::StaticPrefs::media_eme_enabled());
+
+ mozilla::glean::characteristics::prefs_zoom_text_only.Set(
+ !mozilla::Preferences::GetBool("browser.zoom.full"));
+}
+
+// ==================================================================
+// The current schema of the data. Anytime you add a metric, or change how a
+// metric is set, this variable should be incremented. It'll be a lot. It's
+// okay. We're going to need it to know (including during development) what is
+// the source of the data we are looking at.
+const int kSubmissionSchema = 0;
+
+/* static */
+void nsUserCharacteristics::MaybeSubmitPing() {
+ MOZ_LOG(gUserCharacteristicsLog, mozilla::LogLevel::Debug,
+ ("In MaybeSubmitPing()"));
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ /**
+ * There are two preferences at play here:
+ * - Last Version Sent - preference containing the last version sent by the
+ * user to Mozilla
+ * - Current Version - preference containing the version Mozilla would like
+ * the user to send
+ *
+ * A point of complexity arises in that these two values _may_ be changed
+ * by the user, even though neither is intended to be.
+ *
+ * When Current Version > Last Version Sent, we intend for the user to submit
+ * a new ping, which will include the schema version. Then update Last Version
+ * Sent = Current Version.
+ *
+ */
+ const auto* const kLastVersionPref =
+ "toolkit.telemetry.user_characteristics_ping.last_version_sent";
+ const auto* const kCurrentVersionPref =
+ "toolkit.telemetry.user_characteristics_ping.current_version";
+
+ auto lastSubmissionVersion =
+ mozilla::Preferences::GetInt(kLastVersionPref, 0);
+ auto currentVersion = mozilla::Preferences::GetInt(kCurrentVersionPref, 0);
+
+ MOZ_ASSERT(currentVersion == -1 || lastSubmissionVersion <= currentVersion,
+ "lastSubmissionVersion is somehow greater than currentVersion "
+ "- did you edit prefs improperly?");
+
+ if (lastSubmissionVersion < 0) {
+ // This is a way for users to opt out of this ping specifically.
+ MOZ_LOG(gUserCharacteristicsLog, mozilla::LogLevel::Debug,
+ ("Returning, User Opt-out"));
+ return;
+ }
+ if (currentVersion == 0) {
+ // Do nothing. We do not want any pings.
+ MOZ_LOG(gUserCharacteristicsLog, mozilla::LogLevel::Debug,
+ ("Returning, currentVersion == 0"));
+ return;
+ }
+ if (currentVersion == -1) {
+ // currentVersion = -1 is a development value to force a ping submission
+ MOZ_LOG(gUserCharacteristicsLog, mozilla::LogLevel::Debug,
+ ("Force-Submitting Ping"));
+ if (NS_SUCCEEDED(PopulateData())) {
+ SubmitPing();
+ }
+ return;
+ }
+ if (lastSubmissionVersion > currentVersion) {
+ // This is an unexpected scneario that indicates something is wrong. We
+ // asserted against it (in debug, above) We will try to sanity-correct
+ // ourselves by setting it to the current version.
+ mozilla::Preferences::SetInt(kLastVersionPref, currentVersion);
+ MOZ_LOG(gUserCharacteristicsLog, mozilla::LogLevel::Warning,
+ ("Returning, lastSubmissionVersion > currentVersion"));
+ return;
+ }
+ if (lastSubmissionVersion == currentVersion) {
+ // We are okay, we've already submitted the most recent ping
+ MOZ_LOG(gUserCharacteristicsLog, mozilla::LogLevel::Warning,
+ ("Returning, lastSubmissionVersion == currentVersion"));
+ return;
+ }
+ if (lastSubmissionVersion < currentVersion) {
+ if (NS_SUCCEEDED(PopulateData())) {
+ if (NS_SUCCEEDED(SubmitPing())) {
+ mozilla::Preferences::SetInt(kLastVersionPref, currentVersion);
+ }
+ }
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Should never reach here");
+ }
+}
+
+const auto* const kUUIDPref =
+ "toolkit.telemetry.user_characteristics_ping.uuid";
+
+/* static */
+nsresult nsUserCharacteristics::PopulateData(bool aTesting /* = false */) {
+ MOZ_LOG(gUserCharacteristicsLog, mozilla::LogLevel::Warning,
+ ("Populating Data"));
+ MOZ_ASSERT(XRE_IsParentProcess());
+ mozilla::glean::characteristics::submission_schema.Set(kSubmissionSchema);
+
+ nsAutoCString uuidString;
+ nsresult rv = mozilla::Preferences::GetCString(kUUIDPref, uuidString);
+ if (NS_FAILED(rv) || uuidString.Length() == 0) {
+ nsCOMPtr<nsIUUIDGenerator> uuidgen =
+ do_GetService("@mozilla.org/uuid-generator;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsIDToCString id(nsID::GenerateUUID());
+ uuidString = id.get();
+ mozilla::Preferences::SetCString(kUUIDPref, uuidString);
+ }
+ mozilla::glean::characteristics::client_identifier.Set(uuidString);
+
+ mozilla::glean::characteristics::max_touch_points.Set(
+ testing::MaxTouchPoints());
+
+ if (aTesting) {
+ // Many of the later peices of data do not work in a gtest
+ // so just populate something, and return
+ return NS_OK;
+ }
+
+ PopulateMissingFonts();
+ PopulateCSSProperties();
+ PopulateScreenProperties();
+ PopulatePrefs();
+
+ mozilla::glean::characteristics::target_frame_rate.Set(
+ gfxPlatform::TargetFrameRate());
+
+ int32_t processorCount = 0;
+#if defined(XP_MACOSX)
+ if (nsMacUtilsImpl::IsTCSMAvailable()) {
+ // On failure, zero is returned from GetPhysicalCPUCount()
+ // and we fallback to PR_GetNumberOfProcessors below.
+ processorCount = nsMacUtilsImpl::GetPhysicalCPUCount();
+ }
+#endif
+ if (processorCount == 0) {
+ processorCount = PR_GetNumberOfProcessors();
+ }
+ mozilla::glean::characteristics::processor_count.Set(processorCount);
+
+ AutoTArray<char16_t, 128> tzBuffer;
+ auto result = mozilla::intl::TimeZone::GetDefaultTimeZone(tzBuffer);
+ if (result.isOk()) {
+ NS_ConvertUTF16toUTF8 timeZone(
+ nsDependentString(tzBuffer.Elements(), tzBuffer.Length()));
+ mozilla::glean::characteristics::timezone.Set(timeZone);
+ } else {
+ mozilla::glean::characteristics::timezone.Set("<error>"_ns);
+ }
+
+ return NS_OK;
+}
+
+/* static */
+nsresult nsUserCharacteristics::SubmitPing() {
+ MOZ_LOG(gUserCharacteristicsLog, mozilla::LogLevel::Warning,
+ ("Submitting Ping"));
+ mozilla::glean_pings::UserCharacteristics.Submit();
+
+ return NS_OK;
+}
diff --git a/toolkit/components/resistfingerprinting/nsUserCharacteristics.h b/toolkit/components/resistfingerprinting/nsUserCharacteristics.h
new file mode 100644
index 0000000000..a52bc9aea7
--- /dev/null
+++ b/toolkit/components/resistfingerprinting/nsUserCharacteristics.h
@@ -0,0 +1,28 @@
+/* -*- 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/. */
+
+#ifndef __nsUserCharacteristics_h__
+#define __nsUserCharacteristics_h__
+
+#include "ErrorList.h"
+
+class nsUserCharacteristics {
+ public:
+ static void MaybeSubmitPing();
+
+ // Public For testing
+ static nsresult PopulateData(bool aTesting = false);
+ static nsresult SubmitPing();
+};
+
+namespace testing {
+extern "C" { // Needed to call these in the gtest
+
+int MaxTouchPoints();
+
+} // extern "C"
+}; // namespace testing
+
+#endif /* __nsUserCharacteristics_h__ */
diff --git a/toolkit/components/resistfingerprinting/pings.yaml b/toolkit/components/resistfingerprinting/pings.yaml
new file mode 100644
index 0000000000..46a4b2da19
--- /dev/null
+++ b/toolkit/components/resistfingerprinting/pings.yaml
@@ -0,0 +1,19 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+---
+$schema: moz://mozilla.org/schemas/glean/pings/2-0-0
+
+user-characteristics:
+ description: |
+ A ping representing user hardware and software settings. Note that this
+ ping does not include client_id. More details are available in Bug 1879151
+ include_client_id: false
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879151
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879154
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1879154#c8
+ notification_emails:
+ - tritter@mozilla.com
diff --git a/toolkit/components/resistfingerprinting/tests/gtest/moz.build b/toolkit/components/resistfingerprinting/tests/gtest/moz.build
index dcc6a7e12e..a677def009 100644
--- a/toolkit/components/resistfingerprinting/tests/gtest/moz.build
+++ b/toolkit/components/resistfingerprinting/tests/gtest/moz.build
@@ -1,5 +1,6 @@
UNIFIED_SOURCES += [
"test_reduceprecision.cpp",
+ "test_usercharping.cpp",
]
FINAL_LIBRARY = "xul-gtest"
diff --git a/toolkit/components/resistfingerprinting/tests/gtest/test_usercharping.cpp b/toolkit/components/resistfingerprinting/tests/gtest/test_usercharping.cpp
new file mode 100644
index 0000000000..dd1cbe7d46
--- /dev/null
+++ b/toolkit/components/resistfingerprinting/tests/gtest/test_usercharping.cpp
@@ -0,0 +1,143 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+#include "mozilla/gtest/nsUserCharacteristics.h"
+
+#include "mozilla/glean/GleanPings.h"
+#include "mozilla/glean/GleanMetrics.h"
+
+using namespace mozilla;
+
+const auto* const kUUIDPref =
+ "toolkit.telemetry.user_characteristics_ping.uuid";
+
+TEST(ResistFingerprinting, UserCharacteristics_Simple)
+{
+ mozilla::glean::characteristics::max_touch_points.Set(7);
+
+ bool submitted = false;
+ mozilla::glean_pings::UserCharacteristics.TestBeforeNextSubmit(
+ [&submitted](const nsACString& aReason) {
+ submitted = true;
+
+ ASSERT_EQ(
+ 7, mozilla::glean::characteristics::max_touch_points.TestGetValue()
+ .unwrap()
+ .ref());
+ });
+ mozilla::glean_pings::UserCharacteristics.Submit();
+ ASSERT_TRUE(submitted);
+}
+
+TEST(ResistFingerprinting, UserCharacteristics_Complex)
+{
+ nsUserCharacteristics::PopulateData(true);
+
+ bool submitted = false;
+ mozilla::glean_pings::UserCharacteristics.TestBeforeNextSubmit(
+ [&submitted](const nsACString& aReason) {
+ submitted = true;
+
+ ASSERT_STRNE("", mozilla::glean::characteristics::client_identifier
+ .TestGetValue()
+ .unwrap()
+ .value()
+ .get());
+
+ nsCString fullUuidStr;
+ Preferences::GetCString(kUUIDPref, fullUuidStr);
+
+ // Remove the '{' and '}'
+ nsAutoCString uuidString;
+ uuidString = Substring(fullUuidStr, 1, NSID_LENGTH - 3);
+
+ ASSERT_STREQ(
+ uuidString.get(),
+ mozilla::glean::characteristics::client_identifier.TestGetValue()
+ .unwrap()
+ .value()
+ .get());
+ ASSERT_EQ(
+ testing::MaxTouchPoints(),
+ mozilla::glean::characteristics::max_touch_points.TestGetValue()
+ .unwrap()
+ .ref());
+ });
+ nsUserCharacteristics::SubmitPing();
+ ASSERT_TRUE(submitted);
+}
+
+TEST(ResistFingerprinting, UserCharacteristics_ClearPref)
+{
+ nsCString originalUUID;
+
+ mozilla::glean_pings::UserCharacteristics.TestBeforeNextSubmit(
+ [&originalUUID](const nsACString& aReason) {
+ originalUUID =
+ mozilla::glean::characteristics::client_identifier.TestGetValue()
+ .unwrap()
+ .value()
+ .get();
+ ASSERT_STRNE("", mozilla::glean::characteristics::client_identifier
+ .TestGetValue()
+ .unwrap()
+ .value()
+ .get());
+
+ nsCString fullUuidStr;
+ Preferences::GetCString(kUUIDPref, fullUuidStr);
+
+ // Remove the '{' and '}'
+ nsAutoCString uuidString;
+ uuidString = Substring(fullUuidStr, 1, NSID_LENGTH - 3);
+
+ ASSERT_STREQ(
+ uuidString.get(),
+ mozilla::glean::characteristics::client_identifier.TestGetValue()
+ .unwrap()
+ .value()
+ .get());
+ });
+ nsUserCharacteristics::PopulateData(true);
+ nsUserCharacteristics::SubmitPing();
+
+ auto original_value =
+ Preferences::GetBool("datareporting.healthreport.uploadEnabled");
+ Preferences::SetBool("datareporting.healthreport.uploadEnabled", true);
+ Preferences::SetBool("datareporting.healthreport.uploadEnabled", false);
+
+ mozilla::glean_pings::UserCharacteristics.TestBeforeNextSubmit(
+ [](const nsACString& aReason) {
+ // Assert that the pref is blank
+ nsAutoCString uuidValue;
+ Preferences::GetCString(kUUIDPref, uuidValue);
+ ASSERT_STREQ("", uuidValue.get());
+ });
+ nsUserCharacteristics::SubmitPing();
+
+ Preferences::SetBool("datareporting.healthreport.uploadEnabled", true);
+ mozilla::glean_pings::UserCharacteristics.TestBeforeNextSubmit(
+ [&originalUUID](const nsACString& aReason) {
+ // Assert that the new UUID is different from the old one
+ ASSERT_STRNE(
+ originalUUID.get(),
+ mozilla::glean::characteristics::client_identifier.TestGetValue()
+ .unwrap()
+ .value()
+ .get());
+
+ // Assert that the pref is not blank
+ nsAutoCString uuidValue;
+ Preferences::GetCString(kUUIDPref, uuidValue);
+ ASSERT_STRNE("", uuidValue.get());
+ });
+ nsUserCharacteristics::PopulateData(true);
+ nsUserCharacteristics::SubmitPing();
+
+ Preferences::SetBool("datareporting.healthreport.uploadEnabled",
+ original_value);
+}
diff --git a/toolkit/components/satchel/FormHandlerChild.sys.mjs b/toolkit/components/satchel/FormHandlerChild.sys.mjs
new file mode 100644
index 0000000000..6b1af3dbc3
--- /dev/null
+++ b/toolkit/components/satchel/FormHandlerChild.sys.mjs
@@ -0,0 +1,72 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 FormHandlerChild is the place to implement logic that is shared
+ * by child actors like FormAutofillChild, LoginManagerChild and FormHistoryChild
+ * or in general components that deal with form data.
+ */
+
+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",
+};
+
+export class FormHandlerChild extends JSWindowActorChild {
+ handleEvent(event) {
+ if (!event.isTrusted) {
+ return;
+ }
+ switch (event.type) {
+ case "DOMFormBeforeSubmit":
+ this.processDOMFormBeforeSubmitEvent(event);
+ break;
+ default:
+ throw new Error("Unexpected event type");
+ }
+ }
+
+ /**
+ * Process the DOMFormBeforeSubmitEvent that is dispatched
+ * after a form submit event. Extract event data
+ * that is relevant to the form submission listeners
+ *
+ * @param {Event} event DOMFormBeforeSubmit
+ */
+ processDOMFormBeforeSubmitEvent(event) {
+ const form = event.target;
+ const formSubmissionReason = FORM_SUBMISSION_REASON.FORM_SUBMIT_EVENT;
+
+ this.#dispatchFormSubmissionEvent(form, formSubmissionReason);
+ }
+
+ // handle form-removal-after-fetch
+ processFormRemovalAfterFetch(params) {}
+
+ // handle iframe-pagehide
+ processIframePagehide(params) {}
+
+ // handle page-navigation
+ processPageNavigation(params) {}
+
+ /**
+ * Dispatch the CustomEvent form-submission-detected also transfer
+ * the 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);
+ }
+}
diff --git a/toolkit/components/satchel/FormHistoryChild.sys.mjs b/toolkit/components/satchel/FormHistoryChild.sys.mjs
index 242d8f2e29..e97b5238e8 100644
--- a/toolkit/components/satchel/FormHistoryChild.sys.mjs
+++ b/toolkit/components/satchel/FormHistoryChild.sys.mjs
@@ -35,15 +35,16 @@ function log(message) {
export class FormHistoryChild extends JSWindowActorChild {
handleEvent(event) {
switch (event.type) {
- case "DOMFormBeforeSubmit":
- this.#onDOMFormBeforeSubmit(event.target);
+ case "form-submission-detected":
+ this.#onFormSubmission(event);
break;
default:
throw new Error("Unexpected event");
}
}
- #onDOMFormBeforeSubmit(form) {
+ #onFormSubmission(event) {
+ const form = event.detail.form;
if (
!lazy.gEnabled ||
lazy.PrivateBrowsingUtils.isContentWindowPrivate(form.ownerGlobal)
diff --git a/toolkit/components/satchel/FormHistoryStartup.sys.mjs b/toolkit/components/satchel/FormHistoryStartup.sys.mjs
index 104756c583..aa76f16dc5 100644
--- a/toolkit/components/satchel/FormHistoryStartup.sys.mjs
+++ b/toolkit/components/satchel/FormHistoryStartup.sys.mjs
@@ -67,7 +67,7 @@ export class FormHistoryStartup {
target,
}) {
// This case is only used for the search field. There is a
- // similar algorithm in FormHistoryParent.jsm that uses
+ // similar algorithm in FormHistoryParent.sys.mjs that uses
// sendQuery for other form fields.
const instance = (this._queryInstance = {});
diff --git a/toolkit/components/satchel/moz.build b/toolkit/components/satchel/moz.build
index 90dbd9ad2d..4b6d08cdbf 100644
--- a/toolkit/components/satchel/moz.build
+++ b/toolkit/components/satchel/moz.build
@@ -50,6 +50,7 @@ TESTING_JS_MODULES += [
include("/ipc/chromium/chromium-config.mozbuild")
FINAL_TARGET_FILES.actors += [
+ "FormHandlerChild.sys.mjs",
"FormHistoryChild.sys.mjs",
"FormHistoryParent.sys.mjs",
]
diff --git a/toolkit/components/satchel/nsFormFillController.cpp b/toolkit/components/satchel/nsFormFillController.cpp
index 7872ab36c8..1bcbde08df 100644
--- a/toolkit/components/satchel/nsFormFillController.cpp
+++ b/toolkit/components/satchel/nsFormFillController.cpp
@@ -85,7 +85,7 @@ nsFormFillController::nsFormFillController()
mListNode(nullptr),
// 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/passwordmgr/LoginManagerChild.jsm.
+ // toolkit/components/passwordmgr/LoginManagerChild.sys.mjs.
mFocusAfterRightClickThreshold(400),
mTimeout(50),
mMinResultsForPopup(1),
diff --git a/toolkit/components/satchel/test/browser/browser_close_tab.js b/toolkit/components/satchel/test/browser/browser_close_tab.js
index 37962d37d8..f0ed806b3b 100644
--- a/toolkit/components/satchel/test/browser/browser_close_tab.js
+++ b/toolkit/components/satchel/test/browser/browser_close_tab.js
@@ -10,10 +10,7 @@ add_task(async function test() {
const url = `data:text/html,<input type="text" name="field1">`;
// Open a dummy tab.
- await BrowserTestUtils.withNewTab(
- { gBrowser, url },
- async function (browser) {}
- );
+ await BrowserTestUtils.withNewTab({ gBrowser, url }, async function () {});
await BrowserTestUtils.withNewTab(
{ gBrowser, url },
diff --git a/toolkit/components/satchel/test/browser/browser_privbrowsing_perwindowpb.js b/toolkit/components/satchel/test/browser/browser_privbrowsing_perwindowpb.js
index 3abc6ebe54..2cf755cd6b 100644
--- a/toolkit/components/satchel/test/browser/browser_privbrowsing_perwindowpb.js
+++ b/toolkit/components/satchel/test/browser/browser_privbrowsing_perwindowpb.js
@@ -30,7 +30,7 @@ add_task(async function test() {
}
}
- function testOnWindow(aOptions, aCallback) {
+ function testOnWindow(aOptions) {
return BrowserTestUtils.openNewBrowserWindow(aOptions).then(win => {
windowsToClose.push(win);
return win;
diff --git a/toolkit/components/satchel/test/test_form_submission.html b/toolkit/components/satchel/test/test_form_submission.html
index d1c0542609..b838f138bc 100644
--- a/toolkit/components/satchel/test/test_form_submission.html
+++ b/toolkit/components/satchel/test/test_form_submission.html
@@ -306,10 +306,10 @@ function setScriptInput(formNumber, inputName, value) {
getFormElementByName(formNumber, inputName).value = value;
}
-function checkSubmitDoesNotSave(formNumber, inputName, value) {
- return new Promise((resolve, reject) => {
+function checkSubmitDoesNotSave(formNumber) {
+ return new Promise(resolve => {
const form = document.getElementById("form" + formNumber);
- form.addEventListener("submit", async e => {
+ form.addEventListener("submit", async () => {
const historyEntriesCount = await countEntries(null, null);
ok(!historyEntriesCount, form.getAttribute("purpose"));
resolve();
@@ -319,11 +319,11 @@ function checkSubmitDoesNotSave(formNumber, inputName, value) {
});
}
-function checkInvalidFirstInputDoesNotSave(formNumber, value) {
- return new Promise((resolve, reject) => {
+function checkInvalidFirstInputDoesNotSave(formNumber) {
+ return new Promise((resolve) => {
const form = document.getElementById("form" + formNumber);
const input = form.querySelector("input");
- input.addEventListener("invalid", async e => {
+ input.addEventListener("invalid", async _e => {
const historyEntriesCount = await countEntries(null, null);
ok(!historyEntriesCount, form.getAttribute("purpose"));
resolve();
@@ -479,12 +479,12 @@ add_task(async function form19_does_not_save() {
add_task(async function form20_does_not_save() {
setUserInput(20, "test1", "dontSaveThis");
- await checkInvalidFirstInputDoesNotSave(20, "invalid");
+ await checkInvalidFirstInputDoesNotSave(20);
});
add_task(async function form21_does_not_save() {
setUserInput(21, "test1", "dontSaveThis");
- await checkInvalidFirstInputDoesNotSave(21, "invalid");
+ await checkInvalidFirstInputDoesNotSave(21);
});
add_task(async function form22_does_not_save() {
diff --git a/toolkit/components/satchel/test/test_popup_enter_event.html b/toolkit/components/satchel/test/test_popup_enter_event.html
index 6150a8a57a..8dfbe71e67 100644
--- a/toolkit/components/satchel/test/test_popup_enter_event.html
+++ b/toolkit/components/satchel/test/test_popup_enter_event.html
@@ -49,11 +49,11 @@ add_task(async function popupEnterEvent() {
}
const submitTested = new Promise(resolve => {
- SpecialPowers.addSystemEventListener(input, "keypress", handleEnter, true);
+ SpecialPowers.wrap(input).addEventListener("keypress", handleEnter, { capture: true, mozSystemGroup: true });
form.addEventListener("submit", e => {
e.preventDefault();
is(input.value, expectedValue, "Check input value in the submit handler");
- SpecialPowers.removeSystemEventListener(input, "keypress", handleEnter, true);
+ SpecialPowers.wrap(input).removeEventListener("keypress", handleEnter, { capture: true, mozSystemGroup: true });
resolve();
}, { once: true });
});
diff --git a/toolkit/components/satchel/test/unit/test_db_corrupt.js b/toolkit/components/satchel/test/unit/test_db_corrupt.js
index b53b5cd6d0..df0d1fae51 100644
--- a/toolkit/components/satchel/test/unit/test_db_corrupt.js
+++ b/toolkit/components/satchel/test/unit/test_db_corrupt.js
@@ -52,7 +52,7 @@ add_test(function test_corruptFormHistoryDB_emptyInit() {
count = await FormHistory.count({ fieldname: "name-A", value: "value-A" });
Assert.equal(count, 0);
run_next_test();
- })().catch(error => {
+ })().catch(_error => {
do_throw("DB initialized after reading a corrupt DB file is not empty.");
});
});
diff --git a/toolkit/components/search/AppProvidedSearchEngine.sys.mjs b/toolkit/components/search/AppProvidedSearchEngine.sys.mjs
index a8db801ac4..7401ba115c 100644
--- a/toolkit/components/search/AppProvidedSearchEngine.sys.mjs
+++ b/toolkit/components/search/AppProvidedSearchEngine.sys.mjs
@@ -12,10 +12,95 @@ import {
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
+ RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
SearchUtils: "resource://gre/modules/SearchUtils.sys.mjs",
});
/**
+ * Handles loading application provided search engine icons from remote settings.
+ */
+class IconHandler {
+ #iconList = null;
+ #iconCollection = null;
+
+ /**
+ * 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 => {
+ return r.engineIdentifiers.some(i => {
+ if (i.endsWith("*")) {
+ return engineIdentifier.startsWith(i.slice(0, -1));
+ }
+ return engineIdentifier == i;
+ });
+ });
+
+ 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 iconURL;
+ try {
+ iconURL = await this.#iconCollection.attachments.get(iconRecord);
+ } catch (ex) {
+ console.error(ex);
+ return null;
+ }
+ if (!iconURL) {
+ console.warn("Unable to find the icon for", engineIdentifier);
+ return null;
+ }
+ return URL.createObjectURL(
+ new Blob([iconURL.buffer]),
+ iconRecord.attachment.mimetype
+ );
+ }
+
+ /**
+ * Obtains the icon list from the remote settings collection.
+ */
+ async #getIconList() {
+ this.#iconCollection = lazy.RemoteSettings("search-config-icons");
+ 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");
+ }
+ }
+}
+
+/**
* AppProvidedSearchEngine represents a search engine defined by the
* search configuration.
*/
@@ -25,6 +110,20 @@ export class AppProvidedSearchEngine extends SearchEngine {
["suggestions", lazy.SearchUtils.URL_TYPE.SUGGEST_JSON],
["trending", lazy.SearchUtils.URL_TYPE.TRENDING_JSON],
]);
+ static iconHandler = new IconHandler();
+
+ /**
+ * @typedef {?Promise<string>}
+ * A promise for the blob URL of the icon. We save the promise to avoid
+ * reentrancy issues.
+ */
+ #blobURLPromise = null;
+
+ /**
+ * @typedef {?string}
+ * The identifier from the configuration.
+ */
+ #configurationId = null;
/**
* @param {object} options
@@ -51,26 +150,35 @@ export class AppProvidedSearchEngine extends SearchEngine {
this._extensionID = extensionId;
this._locale = config.webExtension.locale;
+ 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.locale
- * The locale that is being used for the engine.
* @param {object} options.configuration
* The search engine configuration for application provided engines.
*/
- update({ locale, configuration } = {}) {
+ update({ configuration } = {}) {
this._urls = [];
- this._iconMapObj = null;
this.#init(configuration);
lazy.SearchUtils.notifyAction(this, lazy.SearchUtils.MODIFIED_TYPE.CHANGED);
}
@@ -89,24 +197,10 @@ export class AppProvidedSearchEngine extends SearchEngine {
* Returns true if the engine was updated, false otherwise.
*/
async updateIfNoNameChange({ configuration, locale }) {
- let newName;
- if (locale != "default") {
- newName = configuration.webExtension.searchProvider[locale].name;
- } else if (
- locale == "default" &&
- configuration.webExtension.default_locale
- ) {
- newName =
- configuration.webExtension.searchProvider[
- configuration.webExtension.default_locale
- ].name;
- } else {
- newName = configuration.webExtension.name;
- }
-
- if (this.name != newName.trim()) {
+ if (this.name != configuration.name.trim()) {
return false;
}
+
this.update({ locale, configuration });
return true;
}
@@ -145,6 +239,25 @@ export class AppProvidedSearchEngine extends SearchEngine {
}
/**
+ * 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;
+ }
+
+ /**
* Creates a JavaScript object that represents this engine.
*
* @returns {object}
@@ -175,38 +288,6 @@ export class AppProvidedSearchEngine extends SearchEngine {
this._telemetryId += `-${engineConfig.telemetrySuffix}`;
}
- // 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);
- // }
- // }
-
- // this._initWithDetails(config);
-
- // this._sendAttributionRequest = config.sendAttributionRequest ?? false; // TODO check if we need to this?
- // if (details.iconURL) {
- // this._setIcon(details.iconURL, true);
- // }
-
this._name = engineConfig.name.trim();
this._definedAliases =
engineConfig.aliases?.map(alias => `@${alias}`) ?? [];
diff --git a/toolkit/components/search/SearchEngine.sys.mjs b/toolkit/components/search/SearchEngine.sys.mjs
index a043c7d4ca..832ffbe2d0 100644
--- a/toolkit/components/search/SearchEngine.sys.mjs
+++ b/toolkit/components/search/SearchEngine.sys.mjs
@@ -1685,9 +1685,9 @@ export class SearchEngine {
* @param {number} preferredWidth
* Width of the requested icon. If not specified, it is assumed that
* 16x16 is desired.
- * @returns {string|undefined}
+ * @returns {Promise<string|undefined>}
*/
- getIconURL(preferredWidth) {
+ async getIconURL(preferredWidth) {
// XPCOM interfaces pass optional number parameters as 0 and can't be
// handled in the same way.
if (!preferredWidth) {
diff --git a/toolkit/components/search/SearchService.sys.mjs b/toolkit/components/search/SearchService.sys.mjs
index 4e1a99671f..b9de8e0bb3 100644
--- a/toolkit/components/search/SearchService.sys.mjs
+++ b/toolkit/components/search/SearchService.sys.mjs
@@ -69,6 +69,18 @@ export const NON_SPLIT_ENGINE_IDS = [
"wolnelektury-pl",
"yahoo-jp",
"yahoo-jp-auctions",
+ // below are test engines
+ "engine-pref",
+ "engine-rel-searchform-purpose",
+ "engine-chromeicon",
+ "engine-resourceicon",
+ "engine-resourceicon-gd",
+ "engine-reordered",
+ "engine-same-name",
+ "engine-same-name-gd",
+ "engine-purpose",
+ "engine-fr",
+ "fixup_search",
];
const TOPIC_LOCALES_CHANGE = "intl:app-locales-changed";
@@ -627,7 +639,6 @@ export class SearchService {
* An Extension object containing data about the extension.
*/
async addEnginesFromExtension(extension) {
- lazy.logConsole.debug("addEnginesFromExtension: " + extension.id);
// 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...
@@ -640,7 +651,7 @@ export class SearchService {
// In either case, there will not be an existing engine.
let existing = await this.#upgradeExtensionEngine(extension);
if (existing?.length) {
- return existing;
+ return;
}
}
@@ -653,28 +664,32 @@ export class SearchService {
let { engines } = await this._fetchEngineSelectorEngines();
let inConfig = engines.filter(el => el.webExtension.id == extension.id);
if (inConfig.length) {
- return this.#installExtensionEngine(
+ await this.#installExtensionEngine(
extension,
inConfig.map(el => el.webExtension.locale)
);
+ return;
}
}
lazy.logConsole.debug(
- "addEnginesFromExtension: Ignoring builtIn engine."
+ "addEnginesFromExtension: Ignoring app engine during init or reload:",
+ extension.id
);
- return [];
+ return;
}
+ lazy.logConsole.debug("addEnginesFromExtension:", extension.id);
// If we havent started SearchService yet, store this extension
// to install in SearchService.init().
if (!this.isInitialized) {
this.#startupExtensions.add(extension);
- return [];
+ return;
}
- return this.#installExtensionEngine(extension, [
- lazy.SearchUtils.DEFAULT_TAG,
- ]);
+ await this.#createAndAddAddonEngine({
+ extension,
+ locale: lazy.SearchUtils.DEFAULT_TAG,
+ });
}
async addOpenSearchEngine(engineURL, iconURL) {
@@ -1082,7 +1097,7 @@ export class SearchService {
/**
* A Set of installed search extensions reported by AddonManager
* startup before SearchSevice has started. Will be installed
- * during init().
+ * during init(). Does not contain application provided engines.
*
* @type {Set<object>}
*/
@@ -1800,21 +1815,21 @@ export class SearchService {
}
lazy.logConsole.debug(
- "#loadEngines: loading",
+ "#loadStartupEngines: loading",
this.#startupExtensions.size,
"engines reported by AddonManager startup"
);
for (let extension of this.#startupExtensions) {
try {
- await this.#installExtensionEngine(
+ await this.#createAndAddAddonEngine({
extension,
- [lazy.SearchUtils.DEFAULT_TAG],
+ locale: lazy.SearchUtils.DEFAULT_TAG,
settings,
- true
- );
+ initEngine: true,
+ });
} catch (ex) {
lazy.logConsole.error(
- `#installExtensionEngine failed for ${extension.id}`,
+ `#createAndAddAddonEngine failed for ${extension.id}`,
ex
);
}
@@ -2196,60 +2211,7 @@ export class SearchService {
// 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.
-
- for (let engine of this._engines.values()) {
- if (!engine.pendingRemoval) {
- continue;
- }
-
- // If we have other engines that use the same extension ID, then
- // we do not want to remove the add-on - only remove the engine itself.
- let inUseEngines = [...this._engines.values()].filter(
- e => e._extensionID == engine._extensionID
- );
-
- if (inUseEngines.length <= 1) {
- if (inUseEngines.length == 1 && inUseEngines[0] == engine) {
- // No other engines are using this extension ID.
-
- // The internal remove is done first to avoid a call to removeEngine
- // which could adjust the sort order when we don't want it to.
- this.#internalRemoveEngine(engine);
-
- // Only uninstall application provided engines. We don't want to
- // remove third-party add-ons. Their search engine names might conflict,
- // but we still allow the add-on to be installed.
- if (engine.isAppProvided) {
- let addon = await lazy.AddonManager.getAddonByID(
- engine._extensionID
- );
- if (addon) {
- // AddonManager won't call removeEngine if an engine with the
- // WebExtension id doesn't exist in the search service.
- await addon.uninstall();
- }
- }
- }
- // For the case where `inUseEngines[0] != engine`:
- // This is a situation where there was an engine added earlier in this
- // function with the same name.
- // For example, eBay has the same name for both US and GB, but has
- // a different domain and uses a different locale of the same
- // WebExtension.
- // The result of this is the earlier addition has already replaced
- // the engine in `this._engines` (which is indexed by name), so all that
- // needs to be done here is to pretend the old engine was removed
- // which is notified below.
- } else {
- // More than one engine is using this extension ID, so we don't want to
- // remove the add-on.
- this.#internalRemoveEngine(engine);
- }
- lazy.SearchUtils.notifyAction(
- engine,
- lazy.SearchUtils.MODIFIED_TYPE.REMOVED
- );
- }
+ await this.#maybeRemoveEnginesAfterReload(this._engines);
// Save app default engine to the user's settings metaData incase it has
// been updated
@@ -2343,6 +2305,78 @@ export class SearchService {
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;
+ }
+
+ if (lazy.SearchUtils.newSearchConfigEnabled) {
+ // 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();
+ }
+ } else {
+ // If we have other engines that use the same extension ID, then
+ // we do not want to remove the add-on - only remove the engine itself.
+ let inUseEngines = [...this._engines.values()].filter(
+ e => e._extensionID == engine._extensionID
+ );
+
+ if (inUseEngines.length <= 1) {
+ if (inUseEngines.length == 1 && inUseEngines[0] == engine) {
+ // No other engines are using this extension ID.
+
+ // The internal remove is done first to avoid a call to removeEngine
+ // which could adjust the sort order when we don't want it to.
+ this.#internalRemoveEngine(engine);
+
+ // Only uninstall application provided engines. We don't want to
+ // remove third-party add-ons. Their search engine names might conflict,
+ // but we still allow the add-on to be installed.
+ if (engine.isAppProvided) {
+ let addon = await lazy.AddonManager.getAddonByID(
+ engine._extensionID
+ );
+ if (addon) {
+ // AddonManager won't call removeEngine if an engine with the
+ // WebExtension id doesn't exist in the search service.
+ await addon.uninstall();
+ }
+ }
+ }
+ // For the case where `inUseEngines[0] != engine`:
+ // This is a situation where there was an engine added earlier in this
+ // function with the same name.
+ // For example, eBay has the same name for both US and GB, but has
+ // a different domain and uses a different locale of the same
+ // WebExtension.
+ // The result of this is the earlier addition has already replaced
+ // the engine in `this._engines` (which is indexed by name), so all that
+ // needs to be done here is to pretend the old engine was removed
+ // which is notified below.
+ } else {
+ // More than one engine is using this extension ID, so we don't want to
+ // remove the add-on.
+ this.#internalRemoveEngine(engine);
+ }
+ }
+ lazy.SearchUtils.notifyAction(
+ engine,
+ lazy.SearchUtils.MODIFIED_TYPE.REMOVED
+ );
+ }
+ }
+
#addEngineToStore(engine, skipDuplicateCheck = false) {
if (this.#engineMatchesIgnoreLists(engine)) {
lazy.logConsole.debug("#addEngineToStore: Ignoring engine");
@@ -2885,7 +2919,7 @@ export class SearchService {
* @param {initEngine} [options.initEngine]
* Set to true if this engine is being loaded during initialization.
*/
- async _createAndAddEngine({
+ async #createAndAddAddonEngine({
extension,
locale = lazy.SearchUtils.DEFAULT_TAG,
settings,
@@ -2904,7 +2938,7 @@ export class SearchService {
"Engine already loaded via settings, skipping due to APP_STARTUP:",
extension.id
);
- return engine;
+ return;
}
}
@@ -2915,6 +2949,12 @@ export class SearchService {
await this.init();
}
+ lazy.logConsole.debug(
+ "#createAndAddAddonEngine: installing:",
+ extension.id,
+ locale
+ );
+
let shouldSetAsDefault = false;
let changeReason = Ci.nsISearchService.CHANGE_REASON_UNKNOWN;
@@ -2988,7 +3028,6 @@ export class SearchService {
if (shouldSetAsDefault) {
this.#setEngineDefault(false, newEngine, changeReason);
}
- return newEngine;
}
/**
@@ -3046,26 +3085,14 @@ export class SearchService {
) {
lazy.logConsole.debug("installExtensionEngine:", extension.id);
- let installLocale = async locale => {
- return this._createAndAddEngine({
+ for (let locale of locales) {
+ await this.#createAndAddAddonEngine({
extension,
locale,
settings,
initEngine,
});
- };
-
- let engines = [];
- for (let locale of locales) {
- lazy.logConsole.debug(
- "addEnginesFromExtension: installing:",
- extension.id,
- ":",
- locale
- );
- engines.push(await installLocale(locale));
}
- return engines;
}
#internalRemoveEngine(engine) {
@@ -3953,11 +3980,7 @@ XPCOMUtils.defineLazyServiceGetter(
* Handles getting and checking extensions against the allow list.
*/
class SearchDefaultOverrideAllowlistHandler {
- /**
- * @param {Function} listener
- * A listener for configuration update changes.
- */
- constructor(listener) {
+ constructor() {
this._remoteConfig = lazy.RemoteSettings(
lazy.SearchUtils.SETTINGS_ALLOWLIST_KEY
);
diff --git a/toolkit/components/search/SearchSuggestionController.sys.mjs b/toolkit/components/search/SearchSuggestionController.sys.mjs
index b528066d84..871931c280 100644
--- a/toolkit/components/search/SearchSuggestionController.sys.mjs
+++ b/toolkit/components/search/SearchSuggestionController.sys.mjs
@@ -525,14 +525,14 @@ export class SearchSuggestionController {
this.#onRemoteLoaded(context, deferredResponse);
});
- request.addEventListener("error", evt => {
+ 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", evt => {
+ request.addEventListener("abort", () => {
context.timer.cancel();
this.#reportTelemetryForEngine(context);
deferredResponse.reject("HTTP request aborted");
diff --git a/toolkit/components/search/SearchUtils.sys.mjs b/toolkit/components/search/SearchUtils.sys.mjs
index c1a956e56e..27c8f8ad03 100644
--- a/toolkit/components/search/SearchUtils.sys.mjs
+++ b/toolkit/components/search/SearchUtils.sys.mjs
@@ -106,8 +106,8 @@ class LoadListener {
}
// nsIProgressEventSink
- onProgress(request, progress, progressMax) {}
- onStatus(request, status, statusArg) {}
+ onProgress() {}
+ onStatus() {}
}
export var SearchUtils = {
diff --git a/toolkit/components/search/nsISearchService.idl b/toolkit/components/search/nsISearchService.idl
index 769ba4eca7..4421c25974 100644
--- a/toolkit/components/search/nsISearchService.idl
+++ b/toolkit/components/search/nsISearchService.idl
@@ -99,7 +99,7 @@ interface nsISearchEngine : nsISupports
* Width of the requested icon. If not specified, it is assumed that
* 16x16 is desired.
*/
- jsval getIconURL([optional] in unsigned short preferredWidth);
+ Promise getIconURL([optional] in unsigned short preferredWidth);
/**
* Opens a speculative connection to the engine's search URI
diff --git a/toolkit/components/search/schema/search-config-overrides-ui-schema.json b/toolkit/components/search/schema/search-config-overrides-ui-schema.json
new file mode 100644
index 0000000000..d5c3fdc240
--- /dev/null
+++ b/toolkit/components/search/schema/search-config-overrides-ui-schema.json
@@ -0,0 +1,3 @@
+{
+ "ui:order": ["telemetryId", "telemetrySuffix", "clickUrl", "params"]
+}
diff --git a/toolkit/components/search/schema/search-config-overrides-v2-schema.json b/toolkit/components/search/schema/search-config-overrides-v2-schema.json
index cac02d8f2a..1fcb8e9cc0 100644
--- a/toolkit/components/search/schema/search-config-overrides-v2-schema.json
+++ b/toolkit/components/search/schema/search-config-overrides-v2-schema.json
@@ -56,6 +56,7 @@
}
},
"type": "object",
+ "required": ["identifier", "partnerCode", "clickUrl", "urls"],
"properties": {
"identifier": {
"title": "Identifier",
diff --git a/toolkit/components/search/schema/search-config-overrides-v2-ui-schema.json b/toolkit/components/search/schema/search-config-overrides-v2-ui-schema.json
new file mode 100644
index 0000000000..60fca49b8d
--- /dev/null
+++ b/toolkit/components/search/schema/search-config-overrides-v2-ui-schema.json
@@ -0,0 +1,9 @@
+{
+ "ui:order": [
+ "identifier",
+ "clickUrl",
+ "telemetrySuffix",
+ "partnerCode",
+ "urls"
+ ]
+}
diff --git a/toolkit/components/search/schema/search-default-override-allowlist-ui-schema.json b/toolkit/components/search/schema/search-default-override-allowlist-ui-schema.json
index 1b85489c13..65fb4f07e2 100644
--- a/toolkit/components/search/schema/search-default-override-allowlist-ui-schema.json
+++ b/toolkit/components/search/schema/search-default-override-allowlist-ui-schema.json
@@ -1,3 +1,3 @@
{
- "ui:order": ["thirdPartyId", "overridesId", "urls"]
+ "ui:order": ["thirdPartyId", "overridesId", "engineName", "urls"]
}
diff --git a/toolkit/components/search/tests/SearchTestUtils.sys.mjs b/toolkit/components/search/tests/SearchTestUtils.sys.mjs
index 8922070154..c3577d2887 100644
--- a/toolkit/components/search/tests/SearchTestUtils.sys.mjs
+++ b/toolkit/components/search/tests/SearchTestUtils.sys.mjs
@@ -429,6 +429,8 @@ export var SearchTestUtils = {
*
* @param {object} [options]
* The options for the manifest.
+ * @param {object} [options.icons]
+ * The icons to use for the WebExtension.
* @param {string} [options.id]
* The id to use for the WebExtension.
* @param {string} [options.name]
@@ -478,6 +480,10 @@ export var SearchTestUtils = {
},
};
+ if (options.icons) {
+ manifest.icons = options.icons;
+ }
+
if (options.default_locale) {
manifest.default_locale = options.default_locale;
}
@@ -541,11 +547,11 @@ export var SearchTestUtils = {
QueryInterface: ChromeUtils.generateQI(["nsIUserIdleService"]),
idleTime: 19999,
- addIdleObserver(observer, time) {
+ addIdleObserver(observer) {
this._observers.add(observer);
},
- removeIdleObserver(observer, time) {
+ removeIdleObserver(observer) {
this._observers.delete(observer);
},
},
diff --git a/toolkit/components/search/tests/xpcshell/data/search-config-v2-no-order-hint.json b/toolkit/components/search/tests/xpcshell/data/search-config-v2-no-order-hint.json
new file mode 100644
index 0000000000..6371a328f3
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/data/search-config-v2-no-order-hint.json
@@ -0,0 +1,207 @@
+{
+ "data": [
+ {
+ "recordType": "engine",
+ "identifier": "engine",
+ "base": {
+ "name": "Test search engine",
+ "urls": {
+ "search": {
+ "base": "https://www.google.com/search",
+ "params": [
+ {
+ "name": "channel",
+ "searchAccessPoint": {
+ "addressbar": "fflb",
+ "contextmenu": "rcs"
+ }
+ }
+ ],
+ "searchTermParamName": "q"
+ },
+ "suggestions": {
+ "base": "https://suggestqueries.google.com/complete/search?output=firefox&client=firefox&hl={moz:locale}",
+ "searchTermParamName": "q"
+ }
+ }
+ },
+ "variants": [
+ {
+ "environment": { "excludedLocales": ["gd"] }
+ }
+ ]
+ },
+ {
+ "recordType": "engine",
+ "identifier": "engine-rel-searchform-purpose",
+ "base": {
+ "name": "engine-rel-searchform-purpose",
+ "urls": {
+ "search": {
+ "base": "https://www.google.com/search",
+ "params": [
+ {
+ "name": "channel",
+ "searchAccessPoint": {
+ "addressbar": "fflb",
+ "contextmenu": "rcs",
+ "searchbar": "sb"
+ }
+ }
+ ],
+ "searchTermParamName": "q"
+ }
+ }
+ },
+ "variants": [
+ {
+ "environment": { "excludedLocales": ["de", "fr"] }
+ }
+ ]
+ },
+ {
+ "recordType": "engine",
+ "identifier": "engine-chromeicon",
+ "base": {
+ "name": "engine-chromeicon",
+ "urls": {
+ "search": {
+ "base": "https://www.google.com/search",
+ "searchTermParamName": "q"
+ }
+ }
+ },
+ "variants": [
+ {
+ "environment": { "excludedLocales": ["de", "fr"] }
+ },
+ {
+ "environment": { "regions": ["ru"] }
+ }
+ ]
+ },
+ {
+ "recordType": "engine",
+ "identifier": "engine-resourceicon",
+ "base": {
+ "name": "engine-resourceicon",
+ "urls": {
+ "search": {
+ "base": "https://www.google.com/search",
+ "searchTermParamName": "q"
+ }
+ }
+ },
+ "variants": [
+ {
+ "environment": {
+ "excludedRegions": ["ru"],
+ "locales": ["en-US", "fr"]
+ }
+ }
+ ]
+ },
+ {
+ "recordType": "engine",
+ "identifier": "engine-resourceicon-gd",
+ "base": {
+ "name": "engine-resourceicon-gd",
+ "urls": {
+ "search": {
+ "base": "https://www.google.com/search",
+ "searchTermParamName": "q"
+ }
+ }
+ },
+ "variants": [
+ {
+ "environment": {
+ "locales": ["gd"]
+ }
+ }
+ ]
+ },
+ {
+ "recordType": "engine",
+ "identifier": "engine-reordered",
+ "base": {
+ "name": "Test search engine (Reordered)",
+ "urls": {
+ "search": {
+ "base": "https://www.google.com/search",
+ "params": [
+ {
+ "name": "channel",
+ "searchAccessPoint": {
+ "addressbar": "fflb",
+ "contextmenu": "rcs"
+ }
+ }
+ ],
+ "searchTermParamName": "q"
+ },
+ "suggestions": {
+ "base": "https://suggestqueries.google.com/complete/search?output=firefox&client=firefox&hl={moz:locale}",
+ "searchTermParamName": "q"
+ }
+ }
+ },
+ "variants": [
+ {
+ "environment": { "excludedLocales": ["de", "fr"] }
+ }
+ ]
+ },
+ {
+ "recordType": "engine",
+ "identifier": "engine-pref",
+ "base": {
+ "name": "engine-pref",
+ "urls": {
+ "search": {
+ "base": "https://www.google.com/search",
+ "params": [
+ {
+ "name": "code",
+ "experimentConfig": "code"
+ },
+ {
+ "name": "test",
+ "experimentConfig": "test"
+ }
+ ],
+ "searchTermParamName": "q"
+ }
+ }
+ },
+ "variants": [
+ {
+ "environment": { "excludedLocales": ["de"] }
+ }
+ ]
+ },
+ {
+ "recordType": "defaultEngines",
+ "globalDefault": "engine",
+ "specificDefaults": [
+ {
+ "defaultPrivate": "engine-pref",
+ "environment": { "excludedLocales": ["de"] }
+ },
+ {
+ "default": "engine-resourceicon-gd",
+ "environment": { "locales": ["gd"] }
+ }
+ ]
+ },
+ {
+ "recordType": "engineOrders",
+ "orders": [
+ {
+ "environment": { "allRegionsAndLocales": true },
+ "order": ["engine-chromeicon", "engine-rel-searchform-purpose"]
+ }
+ ]
+ }
+ ]
+}
diff --git a/toolkit/components/search/tests/xpcshell/data/search-config-v2.json b/toolkit/components/search/tests/xpcshell/data/search-config-v2.json
new file mode 100644
index 0000000000..569e16dfe4
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/data/search-config-v2.json
@@ -0,0 +1,223 @@
+{
+ "data": [
+ {
+ "recordType": "engine",
+ "identifier": "engine",
+ "base": {
+ "name": "Test search engine",
+ "urls": {
+ "search": {
+ "base": "https://www.google.com/search",
+ "params": [
+ {
+ "name": "channel",
+ "searchAccessPoint": {
+ "addressbar": "fflb",
+ "contextmenu": "rcs"
+ }
+ }
+ ],
+ "searchTermParamName": "q"
+ },
+ "suggestions": {
+ "base": "https://suggestqueries.google.com/complete/search?output=firefox&client=firefox&hl={moz:locale}",
+ "searchTermParamName": "q"
+ }
+ }
+ },
+ "variants": [
+ {
+ "environment": { "excludedLocales": ["gd"] }
+ }
+ ]
+ },
+ {
+ "recordType": "engine",
+ "identifier": "engine-pref",
+ "base": {
+ "name": "engine-pref",
+ "urls": {
+ "search": {
+ "base": "https://www.google.com/search",
+ "params": [
+ {
+ "name": "code",
+ "experimentConfig": "code"
+ },
+ {
+ "name": "test",
+ "experimentConfig": "test"
+ }
+ ],
+ "searchTermParamName": "q"
+ }
+ }
+ },
+ "variants": [
+ {
+ "environment": { "excludedLocales": ["de"] }
+ }
+ ]
+ },
+ {
+ "recordType": "engine",
+ "identifier": "engine-rel-searchform-purpose",
+ "base": {
+ "name": "engine-rel-searchform-purpose",
+ "urls": {
+ "search": {
+ "base": "https://www.google.com/search",
+ "params": [
+ {
+ "name": "channel",
+ "searchAccessPoint": {
+ "addressbar": "fflb",
+ "contextmenu": "rcs",
+ "searchbar": "sb"
+ }
+ }
+ ],
+ "searchTermParamName": "q"
+ }
+ }
+ },
+ "variants": [
+ {
+ "environment": { "excludedLocales": ["de", "fr"] }
+ }
+ ]
+ },
+ {
+ "recordType": "engine",
+ "identifier": "engine-chromeicon",
+ "base": {
+ "name": "engine-chromeicon",
+ "urls": {
+ "search": {
+ "base": "https://www.google.com/search",
+ "searchTermParamName": "q"
+ }
+ }
+ },
+ "variants": [
+ {
+ "environment": { "excludedLocales": ["de", "fr"] }
+ },
+ {
+ "environment": { "regions": ["ru"] }
+ }
+ ]
+ },
+ {
+ "recordType": "engine",
+ "identifier": "engine-resourceicon",
+ "base": {
+ "name": "engine-resourceicon",
+ "urls": {
+ "search": {
+ "base": "https://www.google.com/search",
+ "searchTermParamName": "q"
+ }
+ }
+ },
+ "variants": [
+ {
+ "environment": {
+ "excludedRegions": ["ru"],
+ "locales": ["en-US", "fr"]
+ }
+ }
+ ]
+ },
+ {
+ "recordType": "engine",
+ "identifier": "engine-resourceicon-gd",
+ "base": {
+ "name": "engine-resourceicon-gd",
+ "urls": {
+ "search": {
+ "base": "https://www.google.com/search",
+ "searchTermParamName": "q"
+ }
+ }
+ },
+ "variants": [
+ {
+ "environment": { "locales": ["gd"] }
+ }
+ ]
+ },
+ {
+ "recordType": "engine",
+ "identifier": "engine-reordered",
+ "base": {
+ "name": "Test search engine (Reordered)",
+ "urls": {
+ "search": {
+ "base": "https://www.google.com/search",
+ "params": [
+ {
+ "name": "channel",
+ "searchAccessPoint": {
+ "addressbar": "fflb",
+ "contextmenu": "rcs"
+ }
+ }
+ ],
+ "searchTermParamName": "q"
+ },
+ "suggestions": {
+ "base": "https://suggestqueries.google.com/complete/search?output=firefox&client=firefox&hl={moz:locale}",
+ "searchTermParamName": "q"
+ }
+ }
+ },
+ "variants": [
+ {
+ "environment": { "excludedLocales": ["de", "fr"] }
+ }
+ ]
+ },
+ {
+ "recordType": "defaultEngines",
+ "globalDefault": "engine",
+ "specificDefaults": [
+ {
+ "defaultPrivate": "engine-pref",
+ "environment": { "excludedLocales": ["de"] }
+ },
+ {
+ "default": "engine-resourceicon-gd",
+ "environment": { "locales": ["gd"] }
+ }
+ ]
+ },
+ {
+ "recordType": "engineOrders",
+ "orders": [
+ {
+ "environment": { "allRegionsAndLocales": true },
+ "order": [
+ "engine",
+ "engine-resourceicon",
+ "engine-chromeicon",
+ "engine-pref",
+ "engine-rel-searchform-purpose",
+ "engine-reordered"
+ ]
+ },
+ {
+ "environment": { "locales": ["gd"] },
+ "order": [
+ "engine",
+ "engine-rel-searchform-purpose",
+ "engine-resourceicon",
+ "engine-chromeicon",
+ "engine-pref",
+ "engine-reordered"
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/toolkit/components/search/tests/xpcshell/method-extensions/search-config-v2.json b/toolkit/components/search/tests/xpcshell/method-extensions/search-config-v2.json
new file mode 100644
index 0000000000..a8d3fcc515
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/method-extensions/search-config-v2.json
@@ -0,0 +1,83 @@
+{
+ "data": [
+ {
+ "recordType": "engine",
+ "identifier": "get",
+ "base": {
+ "name": "Get Engine",
+ "urls": {
+ "search": {
+ "base": "https://example.com",
+ "params": [
+ {
+ "name": "config",
+ "value": "1"
+ }
+ ],
+ "searchTermParamName": "search"
+ },
+ "suggestions": {
+ "base": "https://example.com",
+ "params": [
+ {
+ "name": "config",
+ "value": "1"
+ }
+ ],
+ "searchTermParamName": "suggest"
+ }
+ }
+ },
+ "variants": [
+ {
+ "environment": { "allRegionsAndLocales": true }
+ }
+ ]
+ },
+ {
+ "recordType": "engine",
+ "identifier": "post",
+ "base": {
+ "name": "Post Engine",
+ "urls": {
+ "search": {
+ "base": "https://example.com",
+ "method": "POST",
+ "params": [
+ {
+ "name": "config",
+ "value": "1"
+ }
+ ],
+ "searchTermParamName": "search"
+ },
+ "suggestions": {
+ "base": "https://example.com",
+ "method": "POST",
+ "params": [
+ {
+ "name": "config",
+ "value": "1"
+ }
+ ],
+ "searchTermParamName": "suggest"
+ }
+ }
+ },
+ "variants": [
+ {
+ "environment": { "allRegionsAndLocales": true }
+ }
+ ]
+ },
+ {
+ "recordType": "defaultEngines",
+ "globalDefault": "get",
+ "specificDefaults": []
+ },
+ {
+ "recordType": "engineOrders",
+ "orders": []
+ }
+ ]
+}
diff --git a/toolkit/components/search/tests/xpcshell/searchconfigs/test_searchconfig_ui_schemas_valid.js b/toolkit/components/search/tests/xpcshell/searchconfigs/test_searchconfig_ui_schemas_valid.js
new file mode 100644
index 0000000000..3315bf974f
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/searchconfigs/test_searchconfig_ui_schemas_valid.js
@@ -0,0 +1,36 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+let schemas = [
+ ["search-config-schema.json", "search-config-ui-schema.json"],
+ ["search-config-v2-schema.json", "search-config-v2-ui-schema.json"],
+ ["search-config-icons-schema.json", "search-config-icons-ui-schema.json"],
+ [
+ "search-config-overrides-schema.json",
+ "search-config-overrides-ui-schema.json",
+ ],
+ [
+ "search-config-overrides-v2-schema.json",
+ "search-config-overrides-v2-ui-schema.json",
+ ],
+ [
+ "search-default-override-allowlist-schema.json",
+ "search-default-override-allowlist-ui-schema.json",
+ ],
+];
+
+add_task(async function test_ui_schemas_valid() {
+ for (let [schema, uiSchema] of schemas) {
+ info(`Validating ${uiSchema} has every top-level from ${schema}`);
+ let schemaData = await IOUtils.readJSON(
+ PathUtils.join(do_get_cwd().path, schema)
+ );
+ let uiSchemaData = await IOUtils.readJSON(
+ PathUtils.join(do_get_cwd().path, uiSchema)
+ );
+
+ await checkUISchemaValid(schemaData, uiSchemaData);
+ }
+});
diff --git a/toolkit/components/search/tests/xpcshell/searchconfigs/test_searchconfig_validates.js b/toolkit/components/search/tests/xpcshell/searchconfigs/test_searchconfig_validates.js
index 51e71ff573..86686b62f7 100644
--- a/toolkit/components/search/tests/xpcshell/searchconfigs/test_searchconfig_validates.js
+++ b/toolkit/components/search/tests/xpcshell/searchconfigs/test_searchconfig_validates.js
@@ -65,103 +65,79 @@ function disallowAdditionalProperties(section) {
}
}
-let searchConfigSchemaV1;
-let searchConfigSchema;
-
-add_setup(async function () {
- searchConfigSchemaV1 = await IOUtils.readJSON(
- PathUtils.join(do_get_cwd().path, "search-config-schema.json")
- );
- searchConfigSchema = await IOUtils.readJSON(
- PathUtils.join(do_get_cwd().path, "search-config-v2-schema.json")
+/**
+ * Asserts the remote setting collection validates against the schema.
+ *
+ * @param {object} options
+ * The options for the assertion.
+ * @param {string} options.collectionName
+ * The name of the collection under validation.
+ * @param {object[]} options.collectionData
+ * The collection data to validate.
+ * @param {string[]} [options.ignoreFields=[]]
+ * A list of fields to ignore in the collection data, e.g. where remote
+ * settings itself adds extra fields. `schema`, `id`, and `last_modified` are
+ * always ignored.
+ * @param {Function} [options.extraAssertsFn]
+ * An optional function to run additional assertions on each entry in the
+ * collection.
+ * @param {Function} options.getEntryId
+ * A function to get the identifier for each entry in the collection.
+ */
+async function assertSearchConfigValidates({
+ collectionName,
+ collectionData,
+ ignoreFields = [],
+ extraAssertsFn,
+ getEntryId,
+}) {
+ let schema = await IOUtils.readJSON(
+ PathUtils.join(do_get_cwd().path, `${collectionName}-schema.json`)
);
-});
-async function checkSearchConfigValidates(schema, searchConfig) {
disallowAdditionalProperties(schema);
let validator = new JsonSchema.Validator(schema);
- for (let entry of searchConfig) {
+ for (let entry of collectionData) {
// Records in Remote Settings contain additional properties independent of
// the schema. Hence, we don't want to validate their presence.
- delete entry.schema;
- delete entry.id;
- delete entry.last_modified;
+ for (let field of [...ignoreFields, "schema", "id", "last_modified"]) {
+ delete entry[field];
+ }
let result = validator.validate(entry);
- // entry.webExtension.id supports search-config v1.
- let message = `Should validate ${
- entry.identifier ?? entry.recordType ?? entry.webExtension.id
- }`;
+ let message = `Should validate ${getEntryId(entry)}`;
if (!result.valid) {
message += `:\n${JSON.stringify(result.errors, null, 2)}`;
}
Assert.ok(result.valid, message);
- // All engine objects should have the base URL defined for each entry in
- // entry.base.urls.
- // Unfortunately this is difficult to enforce in the schema as it would
- // need a `required` field that works across multiple levels.
- if (entry.recordType == "engine") {
- for (let urlEntry of Object.values(entry.base.urls)) {
- Assert.ok(
- urlEntry.base,
- "Should have a base url for every URL defined on the top-level base object."
- );
- }
- }
+ extraAssertsFn?.(entry);
}
}
-async function checkSearchConfigOverrideValidates(
- schema,
- searchConfigOverride
-) {
- let validator = new JsonSchema.Validator(schema);
-
- for (let entry of searchConfigOverride) {
- // Records in Remote Settings contain additional properties independent of
- // the schema. Hence, we don't want to validate their presence.
- delete entry.schema;
- delete entry.id;
- delete entry.last_modified;
-
- let result = validator.validate(entry);
-
- let message = `Should validate ${entry.identifier ?? entry.telemetryId}`;
- if (!result.valid) {
- message += `:\n${JSON.stringify(result.errors, null, 2)}`;
- }
- Assert.ok(result.valid, message);
- }
-}
+add_setup(async function () {
+ updateAppInfo({ ID: "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}" });
+});
add_task(async function test_search_config_validates_to_schema_v1() {
let selector = new SearchEngineSelectorOld(() => {});
- let searchConfig = await selector.getEngineConfiguration();
- await checkSearchConfigValidates(searchConfigSchemaV1, searchConfig);
-});
-
-add_task(async function test_ui_schema_valid_v1() {
- let uiSchema = await IOUtils.readJSON(
- PathUtils.join(do_get_cwd().path, "search-config-ui-schema.json")
- );
-
- await checkUISchemaValid(searchConfigSchemaV1, uiSchema);
+ await assertSearchConfigValidates({
+ collectionName: "search-config",
+ collectionData: await selector.getEngineConfiguration(),
+ getEntryId: entry => entry.webExtension.id,
+ });
});
add_task(async function test_search_config_override_validates_to_schema_v1() {
let selector = new SearchEngineSelectorOld(() => {});
- let searchConfigOverrides = await selector.getEngineConfigurationOverrides();
- let overrideSchema = await IOUtils.readJSON(
- PathUtils.join(do_get_cwd().path, "search-config-overrides-schema.json")
- );
- await checkSearchConfigOverrideValidates(
- overrideSchema,
- searchConfigOverrides
- );
+ await assertSearchConfigValidates({
+ collectionName: "search-config-overrides",
+ collectionData: await selector.getEngineConfigurationOverrides(),
+ getEntryId: entry => entry.telemetryId,
+ });
});
add_task(
@@ -171,20 +147,26 @@ add_task(
SearchUtils.newSearchConfigEnabled = true;
let selector = new SearchEngineSelector(() => {});
- let searchConfig = await selector.getEngineConfiguration();
-
- await checkSearchConfigValidates(searchConfigSchema, searchConfig);
- }
-);
-
-add_task(
- { skip_if: () => !SearchUtils.newSearchConfigEnabled },
- async function test_ui_schema_valid() {
- let uiSchema = await IOUtils.readJSON(
- PathUtils.join(do_get_cwd().path, "search-config-v2-ui-schema.json")
- );
- await checkUISchemaValid(searchConfigSchema, uiSchema);
+ await assertSearchConfigValidates({
+ collectionName: "search-config-v2",
+ collectionData: await selector.getEngineConfiguration(),
+ getEntryId: entry => entry.identifier,
+ extraAssertsFn: entry => {
+ // All engine objects should have the base URL defined for each entry in
+ // entry.base.urls.
+ // Unfortunately this is difficult to enforce in the schema as it would
+ // need a `required` field that works across multiple levels.
+ if (entry.recordType == "engine") {
+ for (let urlEntry of Object.values(entry.base.urls)) {
+ Assert.ok(
+ urlEntry.base,
+ "Should have a base url for every URL defined on the top-level base object."
+ );
+ }
+ }
+ },
+ });
}
);
@@ -192,18 +174,33 @@ add_task(
{ skip_if: () => !SearchUtils.newSearchConfigEnabled },
async function test_search_config_override_validates_to_schema() {
let selector = new SearchEngineSelector(() => {});
- let searchConfigOverrides =
- await selector.getEngineConfigurationOverrides();
- let overrideSchema = await IOUtils.readJSON(
- PathUtils.join(
- do_get_cwd().path,
- "search-config-overrides-v2-schema.json"
- )
- );
-
- await checkSearchConfigOverrideValidates(
- overrideSchema,
- searchConfigOverrides
- );
+
+ await assertSearchConfigValidates({
+ collectionName: "search-config-overrides-v2",
+ collectionData: await selector.getEngineConfigurationOverrides(),
+ getEntryId: entry => entry.identifier,
+ });
}
);
+
+add_task(async function test_search_config_icons_validates_to_schema() {
+ let searchIcons = RemoteSettings("search-config-icons");
+
+ await assertSearchConfigValidates({
+ collectionName: "search-config-icons",
+ collectionData: await searchIcons.get(),
+ ignoreFields: ["attachment"],
+ getEntryId: entry => entry.engineIdentifiers[0],
+ });
+});
+
+add_task(async function test_search_default_override_allowlist_validates() {
+ let allowlist = RemoteSettings("search-default-override-allowlist");
+
+ await assertSearchConfigValidates({
+ collectionName: "search-default-override-allowlist",
+ collectionData: await allowlist.get(),
+ ignoreFields: ["attachment"],
+ getEntryId: entry => entry.engineName || entry.thirdPartyId,
+ });
+});
diff --git a/toolkit/components/search/tests/xpcshell/searchconfigs/test_searchicons_validates.js b/toolkit/components/search/tests/xpcshell/searchconfigs/test_searchicons_validates.js
deleted file mode 100644
index c830bb7ade..0000000000
--- a/toolkit/components/search/tests/xpcshell/searchconfigs/test_searchicons_validates.js
+++ /dev/null
@@ -1,20 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-let searchIconsSchema;
-
-add_setup(async function () {
- searchIconsSchema = await IOUtils.readJSON(
- PathUtils.join(do_get_cwd().path, "search-config-icons-schema.json")
- );
-});
-
-add_task(async function test_ui_schema_valid() {
- let uiSchema = await IOUtils.readJSON(
- PathUtils.join(do_get_cwd().path, "search-config-icons-ui-schema.json")
- );
-
- await checkUISchemaValid(searchIconsSchema, uiSchema);
-});
diff --git a/toolkit/components/search/tests/xpcshell/searchconfigs/xpcshell.toml b/toolkit/components/search/tests/xpcshell/searchconfigs/xpcshell.toml
index 8baff2a38d..07567005d6 100644
--- a/toolkit/components/search/tests/xpcshell/searchconfigs/xpcshell.toml
+++ b/toolkit/components/search/tests/xpcshell/searchconfigs/xpcshell.toml
@@ -40,20 +40,30 @@ requesttimeoutfactor = 2
["test_rakuten.js"]
-["test_searchconfig_validates.js"]
+["test_searchconfig_ui_schemas_valid.js"]
support-files = [
+ "../../../schema/search-config-icons-schema.json",
+ "../../../schema/search-config-icons-ui-schema.json",
"../../../schema/search-config-overrides-schema.json",
+ "../../../schema/search-config-overrides-ui-schema.json",
"../../../schema/search-config-overrides-v2-schema.json",
+ "../../../schema/search-config-overrides-v2-ui-schema.json",
"../../../schema/search-config-schema.json",
"../../../schema/search-config-ui-schema.json",
"../../../schema/search-config-v2-schema.json",
"../../../schema/search-config-v2-ui-schema.json",
+ "../../../schema/search-default-override-allowlist-schema.json",
+ "../../../schema/search-default-override-allowlist-ui-schema.json",
]
-["test_searchicons_validates.js"]
+["test_searchconfig_validates.js"]
support-files = [
"../../../schema/search-config-icons-schema.json",
- "../../../schema/search-config-icons-ui-schema.json",
+ "../../../schema/search-config-overrides-schema.json",
+ "../../../schema/search-config-overrides-v2-schema.json",
+ "../../../schema/search-config-schema.json",
+ "../../../schema/search-config-v2-schema.json",
+ "../../../schema/search-default-override-allowlist-schema.json",
]
["test_selector_db_out_of_date.js"]
diff --git a/toolkit/components/search/tests/xpcshell/test-extensions/search-config-v2.json b/toolkit/components/search/tests/xpcshell/test-extensions/search-config-v2.json
new file mode 100644
index 0000000000..4f2e88a05b
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/test-extensions/search-config-v2.json
@@ -0,0 +1,139 @@
+{
+ "data": [
+ {
+ "recordType": "engine",
+ "identifier": "plainengine",
+ "base": {
+ "name": "Plain",
+ "urls": {
+ "search": {
+ "base": "https://duckduckgo.com/",
+ "params": [
+ {
+ "name": "t",
+ "searchAccessPoint": {
+ "newtab": "ffnt",
+ "homepage": "ffhp",
+ "searchbar": "ffsb",
+ "addressbar": "ffab",
+ "contextmenu": "ffcm"
+ }
+ }
+ ],
+ "searchTermParamName": "q"
+ },
+ "suggestions": {
+ "base": "https://ac.duckduckgo.com/ac/q={searchTerms}&type=list"
+ }
+ }
+ },
+ "variants": [
+ {
+ "environment": { "allRegionsAndLocales": true }
+ }
+ ]
+ },
+ {
+ "recordType": "engine",
+ "identifier": "special-engine",
+ "base": {
+ "name": "Special",
+ "urls": {
+ "search": {
+ "base": "https://www.google.com/search",
+ "params": [
+ {
+ "name": "client",
+ "searchAccessPoint": {
+ "searchbar": "firefox-b-1",
+ "addressbar": "firefox-b-1-ab"
+ }
+ }
+ ],
+ "searchTermParamName": "q"
+ },
+ "suggestions": {
+ "base": "https://www.google.com/complete/search?client=firefox&q={searchTerms}"
+ }
+ }
+ },
+ "variants": [
+ {
+ "environment": { "allRegionsAndLocales": true }
+ }
+ ]
+ },
+ {
+ "recordType": "engine",
+ "identifier": "multilocale-an",
+ "base": {
+ "name": "Multilocale AN",
+ "urls": {
+ "search": {
+ "base": "https://an.wikipedia.org/wiki/Especial:Mirar",
+ "searchTermParamName": "q"
+ },
+ "suggestions": {
+ "base": "https://an.wikipedia.org/w/api.php",
+ "searchTermParamName": "q"
+ }
+ }
+ },
+ "variants": [
+ {
+ "environment": { "regions": ["an"] }
+ }
+ ]
+ },
+ {
+ "recordType": "engine",
+ "identifier": "multilocale-af",
+ "base": {
+ "name": "Multilocale AF",
+ "urls": {
+ "search": {
+ "base": "https://af.wikipedia.org/wiki/Spesiaal:Soek",
+ "searchTermParamName": "q"
+ },
+ "suggestions": {
+ "base": "https://af.wikipedia.org/w/api.php",
+ "searchTermParamName": "q"
+ }
+ }
+ },
+ "variants": [
+ {
+ "environment": { "regions": ["af"] }
+ }
+ ]
+ },
+ {
+ "recordType": "defaultEngines",
+ "globalDefault": "plainengine",
+ "specificDefaults": [
+ {
+ "default": "special-engine",
+ "environment": { "regions": ["tr"] }
+ },
+ {
+ "default": "multilocale-an",
+ "environment": { "regions": ["an"] }
+ }
+ ]
+ },
+ {
+ "recordType": "engineOrders",
+ "orders": [
+ {
+ "order": [
+ "plainengine",
+ "special-engine",
+ "multilocale-af",
+ "multilocale-an"
+ ],
+ "environment": { "allRegionsAndLocales": true }
+ }
+ ]
+ }
+ ]
+}
diff --git a/toolkit/components/search/tests/xpcshell/test_appProvided_icons.js b/toolkit/components/search/tests/xpcshell/test_appProvided_icons.js
new file mode 100644
index 0000000000..e4d8033993
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/test_appProvided_icons.js
@@ -0,0 +1,211 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests to ensure that icons for application provided engines are correctly
+ * loaded from remote settings.
+ */
+
+"use strict";
+
+// A skeleton configuration that gets filled in from TESTS during `add_setup`.
+let CONFIG = [
+ {
+ recordType: "defaultEngines",
+ globalDefault: "engine_no_icon",
+ specificDefaults: [],
+ },
+ {
+ recordType: "engineOrders",
+ orders: [],
+ },
+];
+
+let TESTS = [
+ {
+ engineId: "engine_no_icon",
+ expectedIcon: null,
+ },
+ {
+ engineId: "engine_exact_match",
+ icons: [
+ {
+ filename: "remoteIcon.ico",
+ engineIdentifiers: ["engine_exact_match"],
+ imageSize: 16,
+ },
+ ],
+ expectedIcon: "remoteIcon.ico",
+ },
+ {
+ engineId: "engine_begins_with",
+ icons: [
+ {
+ filename: "remoteIcon.ico",
+ engineIdentifiers: ["engine_begins*"],
+ imageSize: 16,
+ },
+ ],
+ expectedIcon: "remoteIcon.ico",
+ },
+ {
+ engineId: "engine_non_default_sized_icon",
+ icons: [
+ {
+ filename: "remoteIcon.ico",
+ engineIdentifiers: ["engine_non_default_sized_icon"],
+ imageSize: 32,
+ },
+ ],
+ expectedIcon: "remoteIcon.ico",
+ },
+ {
+ engineId: "engine_multiple_icons",
+ icons: [
+ {
+ filename: "bigIcon.ico",
+ engineIdentifiers: ["engine_multiple_icons"],
+ imageSize: 16,
+ },
+ {
+ filename: "remoteIcon.ico",
+ engineIdentifiers: ["engine_multiple_icons"],
+ imageSize: 32,
+ },
+ ],
+ expectedIcon: "bigIcon.ico",
+ },
+];
+
+async function getFileDataBuffer(filename) {
+ let data = await IOUtils.read(
+ PathUtils.join(do_get_cwd().path, "data", filename)
+ );
+ return new TextEncoder().encode(data).buffer;
+}
+
+async function mockRecordWithAttachment({
+ filename,
+ engineIdentifiers,
+ imageSize,
+}) {
+ let buffer = await getFileDataBuffer(filename);
+
+ let stream = Cc["@mozilla.org/io/arraybuffer-input-stream;1"].createInstance(
+ Ci.nsIArrayBufferInputStream
+ );
+ stream.setData(buffer, 0, buffer.byteLength);
+
+ // Generate a hash.
+ let hasher = Cc["@mozilla.org/security/hash;1"].createInstance(
+ Ci.nsICryptoHash
+ );
+ hasher.init(Ci.nsICryptoHash.SHA256);
+ hasher.updateFromStream(stream, -1);
+ let hash = hasher.finish(false);
+ hash = Array.from(hash, (_, i) =>
+ ("0" + hash.charCodeAt(i).toString(16)).slice(-2)
+ ).join("");
+
+ let record = {
+ id: Services.uuid.generateUUID().toString(),
+ engineIdentifiers,
+ imageSize,
+ attachment: {
+ hash,
+ location: `main-workspace/search-config-icons/${filename}`,
+ filename,
+ size: buffer.byteLength,
+ mimetype: "application/json",
+ },
+ };
+
+ let attachment = {
+ record,
+ blob: new Blob([buffer]),
+ };
+
+ return { record, attachment };
+}
+
+async function insertRecordIntoCollection(client, db, item) {
+ let { record, attachment } = await mockRecordWithAttachment(item);
+ await db.create(record);
+ await client.attachments.cacheImpl.set(record.id, attachment);
+ await db.importChanges({}, Date.now());
+}
+
+add_setup(async function () {
+ let client = RemoteSettings("search-config-icons");
+ let db = client.db;
+
+ await db.clear();
+
+ for (let test of TESTS) {
+ CONFIG.push({
+ identifier: test.engineId,
+ recordType: "engine",
+ base: {
+ name: test.engineId + " name",
+ urls: {
+ search: {
+ base: "https://example.com/" + test.engineId,
+ searchTermParamName: "q",
+ },
+ },
+ },
+ variants: [{ environment: { allRegionsAndLocales: true } }],
+ });
+
+ if ("icons" in test) {
+ for (let icon of test.icons) {
+ await insertRecordIntoCollection(client, db, {
+ ...icon,
+ id: test.engineId,
+ });
+ }
+ }
+ }
+
+ await SearchTestUtils.useTestEngines("simple-engines", null, CONFIG);
+ await Services.search.init();
+});
+
+for (let test of TESTS) {
+ add_task(async function () {
+ info("Testing engine: " + test.engineId);
+
+ let engine = Services.search.getEngineByName(test.engineId + " name");
+ if (test.expectedIcon) {
+ let engineIconURL = await engine.getIconURL(16);
+ Assert.notEqual(
+ engineIconURL,
+ null,
+ "Should have an icon URL for the engine."
+ );
+
+ let response = await fetch(engineIconURL);
+ let buffer = new Uint8Array(await response.arrayBuffer());
+
+ let expectedBuffer = new Uint8Array(
+ await getFileDataBuffer(test.expectedIcon)
+ );
+
+ Assert.equal(
+ buffer.length,
+ expectedBuffer.length,
+ "Should have received matching buffer lengths for the expected icon"
+ );
+ Assert.ok(
+ buffer.every((value, index) => value === expectedBuffer[index]),
+ "Should have received matching data for the expected icon"
+ );
+ } else {
+ Assert.equal(
+ await engine.getIconURL(),
+ null,
+ "Should not have an icon URL for the engine."
+ );
+ }
+ });
+}
diff --git a/toolkit/components/search/tests/xpcshell/test_defaultPrivateEngine.js b/toolkit/components/search/tests/xpcshell/test_defaultPrivateEngine.js
index 053d91fe48..392700d84a 100644
--- a/toolkit/components/search/tests/xpcshell/test_defaultPrivateEngine.js
+++ b/toolkit/components/search/tests/xpcshell/test_defaultPrivateEngine.js
@@ -108,7 +108,9 @@ add_task(async function test_defaultPrivateEngine() {
loadPath: SearchUtils.newSearchConfigEnabled
? "[app]engine-rel-searchform-purpose@search.mozilla.org"
: "[addon]engine-rel-searchform-purpose@search.mozilla.org",
- submissionUrl: "https://www.google.com/search?q=&channel=sb",
+ submissionUrl: SearchUtils.newSearchConfigEnabled
+ ? "https://www.google.com/search?channel=sb&q="
+ : "https://www.google.com/search?q=&channel=sb",
verified: "default",
},
});
diff --git a/toolkit/components/search/tests/xpcshell/test_engine_ids.js b/toolkit/components/search/tests/xpcshell/test_engine_ids.js
index cef6a17c92..57b9ad26cf 100644
--- a/toolkit/components/search/tests/xpcshell/test_engine_ids.js
+++ b/toolkit/components/search/tests/xpcshell/test_engine_ids.js
@@ -47,9 +47,56 @@ const CONFIG = [
},
];
+const CONFIG_V2 = [
+ {
+ recordType: "engine",
+ identifier: "engine",
+ base: {
+ name: "Test search engine",
+ urls: {
+ search: {
+ base: "https://www.google.com/search",
+ params: [
+ {
+ name: "channel",
+ searchAccessPoint: {
+ addressbar: "fflb",
+ contextmenu: "rcs",
+ },
+ },
+ ],
+ searchTermParamName: "q",
+ },
+ suggestions: {
+ base: "https://suggestqueries.google.com/complete/search?output=firefox&client=firefox&hl={moz:locale}",
+ searchTermParamName: "q",
+ },
+ },
+ },
+ variants: [
+ {
+ environment: { allRegionsAndLocales: true },
+ },
+ ],
+ },
+ {
+ recordType: "defaultEngines",
+ globalDefault: "engine",
+ specificDefaults: [],
+ },
+ {
+ recordType: "engineOrders",
+ orders: [],
+ },
+];
+
add_setup(async function () {
useHttpServer("opensearch");
- await SearchTestUtils.useTestEngines("data", null, CONFIG);
+ await SearchTestUtils.useTestEngines(
+ "data",
+ null,
+ SearchUtils.newSearchConfigEnabled ? CONFIG_V2 : CONFIG
+ );
await AddonTestUtils.promiseStartupManager();
await Services.search.init();
});
diff --git a/toolkit/components/search/tests/xpcshell/test_engine_set_alias.js b/toolkit/components/search/tests/xpcshell/test_engine_set_alias.js
index ec79fe6783..f15e257996 100644
--- a/toolkit/components/search/tests/xpcshell/test_engine_set_alias.js
+++ b/toolkit/components/search/tests/xpcshell/test_engine_set_alias.js
@@ -119,7 +119,7 @@ add_task(async function test_engine_change_alias() {
);
let observed = false;
- Services.obs.addObserver(function observer(aSubject, aTopic, aData) {
+ Services.obs.addObserver(function observer() {
observed = true;
}, SearchUtils.TOPIC_ENGINE_MODIFIED);
diff --git a/toolkit/components/search/tests/xpcshell/test_getSubmission_params_pref.js b/toolkit/components/search/tests/xpcshell/test_getSubmission_params_pref.js
index 5283207394..54fd028474 100644
--- a/toolkit/components/search/tests/xpcshell/test_getSubmission_params_pref.js
+++ b/toolkit/components/search/tests/xpcshell/test_getSubmission_params_pref.js
@@ -14,6 +14,7 @@ const defaultBranch = Services.prefs.getDefaultBranch(
SearchUtils.BROWSER_SEARCH_PREF
);
const baseURL = "https://www.google.com/search?q=foo";
+const baseURLSearchConfigV2 = "https://www.google.com/search?";
add_setup(async function () {
// The test engines used in this test need to be recognized as 'default'
@@ -40,7 +41,9 @@ add_task(async function test_pref_initial_value() {
const engine = Services.search.getEngineByName("engine-pref");
Assert.equal(
engine.getSubmission("foo").uri.spec,
- baseURL + "&code=good%26id%3Dunique",
+ SearchUtils.newSearchConfigEnabled
+ ? baseURLSearchConfigV2 + "code=good%26id%3Dunique&q=foo"
+ : baseURL + "&code=good%26id%3Dunique",
"Should have got the submission URL with the correct code"
);
@@ -59,7 +62,9 @@ add_task(async function test_pref_updated() {
const engine = Services.search.getEngineByName("engine-pref");
Assert.equal(
engine.getSubmission("foo").uri.spec,
- baseURL + "&code=supergood%26id%3Dunique123456",
+ SearchUtils.newSearchConfigEnabled
+ ? baseURLSearchConfigV2 + "code=supergood%26id%3Dunique123456&q=foo"
+ : baseURL + "&code=supergood%26id%3Dunique123456",
"Should have got the submission URL with the updated code"
);
});
diff --git a/toolkit/components/search/tests/xpcshell/test_getSubmission_params_prefNimbus.js b/toolkit/components/search/tests/xpcshell/test_getSubmission_params_prefNimbus.js
index e9b55ff3dc..3963a07368 100644
--- a/toolkit/components/search/tests/xpcshell/test_getSubmission_params_prefNimbus.js
+++ b/toolkit/components/search/tests/xpcshell/test_getSubmission_params_prefNimbus.js
@@ -11,6 +11,7 @@ const { NimbusFeatures } = ChromeUtils.importESModule(
);
const baseURL = "https://www.google.com/search?q=foo";
+const baseURLSearchConfigV2 = "https://www.google.com/search?";
let getVariableStub;
let updateStub;
@@ -48,7 +49,9 @@ add_task(async function test_pref_initial_value() {
const engine = Services.search.getEngineByName("engine-pref");
Assert.equal(
engine.getSubmission("foo").uri.spec,
- baseURL + "&code=good%26id%3Dunique",
+ SearchUtils.newSearchConfigEnabled
+ ? baseURLSearchConfigV2 + "code=good%26id%3Dunique&q=foo"
+ : baseURL + "&code=good%26id%3Dunique",
"Should have got the submission URL with the correct code"
);
});
@@ -68,7 +71,9 @@ add_task(async function test_pref_updated() {
const engine = Services.search.getEngineByName("engine-pref");
Assert.equal(
engine.getSubmission("foo").uri.spec,
- baseURL + "&code=supergood%26id%3Dunique123456",
+ SearchUtils.newSearchConfigEnabled
+ ? baseURLSearchConfigV2 + "code=supergood%26id%3Dunique123456&q=foo"
+ : baseURL + "&code=supergood%26id%3Dunique123456",
"Should have got the submission URL with the updated code"
);
});
@@ -90,7 +95,9 @@ add_task(async function test_multiple_params() {
let engine = Services.search.getEngineByName("engine-pref");
Assert.equal(
engine.getSubmission("foo").uri.spec,
- baseURL + "&code=sng&test=sup",
+ SearchUtils.newSearchConfigEnabled
+ ? baseURLSearchConfigV2 + "code=sng&test=sup&q=foo"
+ : baseURL + "&code=sng&test=sup",
"Should have got the submission URL with both parameters"
);
@@ -107,7 +114,9 @@ add_task(async function test_multiple_params() {
engine = Services.search.getEngineByName("engine-pref");
Assert.equal(
engine.getSubmission("foo").uri.spec,
- baseURL + "&code=sng",
+ SearchUtils.newSearchConfigEnabled
+ ? baseURLSearchConfigV2 + "code=sng&q=foo"
+ : baseURL + "&code=sng",
"Should have got the submission URL with one parameter"
);
});
diff --git a/toolkit/components/search/tests/xpcshell/test_getSubmission_params_prefNimbus_invalid.js b/toolkit/components/search/tests/xpcshell/test_getSubmission_params_prefNimbus_invalid.js
index e519a74c64..090b108acf 100644
--- a/toolkit/components/search/tests/xpcshell/test_getSubmission_params_prefNimbus_invalid.js
+++ b/toolkit/components/search/tests/xpcshell/test_getSubmission_params_prefNimbus_invalid.js
@@ -15,6 +15,7 @@ const { NimbusFeatures } = ChromeUtils.importESModule(
);
const baseURL = "https://www.google.com/search?q=foo";
+const baseURLSearchConfigV2 = "https://www.google.com/search?";
let getVariableStub;
let updateStub;
@@ -66,7 +67,9 @@ add_task(async function test_switch_to_good_nimbus_setting() {
const engine = Services.search.getEngineByName("engine-pref");
Assert.equal(
engine.getSubmission("foo").uri.spec,
- baseURL + "&code=supergood%26id%3Dunique123456",
+ SearchUtils.newSearchConfigEnabled
+ ? baseURLSearchConfigV2 + "code=supergood%26id%3Dunique123456&q=foo"
+ : baseURL + "&code=supergood%26id%3Dunique123456",
"Should have got the submission URL with the updated code"
);
});
diff --git a/toolkit/components/search/tests/xpcshell/test_initialization.js b/toolkit/components/search/tests/xpcshell/test_initialization.js
index c89f3cfcb3..57894e1e55 100644
--- a/toolkit/components/search/tests/xpcshell/test_initialization.js
+++ b/toolkit/components/search/tests/xpcshell/test_initialization.js
@@ -43,13 +43,60 @@ const CONFIG = [
},
];
+const CONFIG_V2 = [
+ {
+ recordType: "engine",
+ identifier: "engine",
+ base: {
+ name: "Test search engine",
+ urls: {
+ search: {
+ base: "https://www.google.com/search",
+ params: [
+ {
+ name: "channel",
+ searchAccessPoint: {
+ addressbar: "fflb",
+ contextmenu: "rcs",
+ },
+ },
+ ],
+ searchTermParamName: "q",
+ },
+ suggestions: {
+ base: "https://suggestqueries.google.com/complete/search?output=firefox&client=firefox&hl={moz:locale}",
+ searchTermParamName: "q",
+ },
+ },
+ },
+ variants: [
+ {
+ environment: { allRegionsAndLocales: true },
+ },
+ ],
+ },
+ {
+ recordType: "defaultEngines",
+ globalDefault: "engine",
+ specificDefaults: [],
+ },
+ {
+ recordType: "engineOrders",
+ orders: [],
+ },
+];
+
add_setup(() => {
do_get_profile();
Services.fog.initializeFOG();
});
add_task(async function test_initialization_delayed_addon_manager() {
- let stub = await SearchTestUtils.useTestEngines("data", null, CONFIG);
+ let stub = await SearchTestUtils.useTestEngines(
+ "data",
+ null,
+ SearchUtils.newSearchConfigEnabled ? CONFIG_V2 : CONFIG
+ );
// Wait until the search service gets its configuration before starting
// to initialise the add-on manager. This simulates the add-on manager
// starting late which used to cause the search service to fail to load any
@@ -58,7 +105,7 @@ add_task(async function test_initialization_delayed_addon_manager() {
Services.tm.dispatchToMainThread(() => {
AddonTestUtils.promiseStartupManager();
});
- return CONFIG;
+ return SearchUtils.newSearchConfigEnabled ? CONFIG_V2 : CONFIG;
});
await Services.search.init();
diff --git a/toolkit/components/search/tests/xpcshell/test_initialization_with_region.js b/toolkit/components/search/tests/xpcshell/test_initialization_with_region.js
index fb9cee5124..377772c6e1 100644
--- a/toolkit/components/search/tests/xpcshell/test_initialization_with_region.js
+++ b/toolkit/components/search/tests/xpcshell/test_initialization_with_region.js
@@ -76,6 +76,87 @@ const CONFIG = [
},
];
+const CONFIG_V2 = [
+ {
+ recordType: "engine",
+ identifier: "engine",
+ base: {
+ name: "Test search engine",
+ urls: {
+ search: {
+ base: "https://www.google.com/search",
+ params: [
+ {
+ name: "channel",
+ searchAccessPoint: {
+ addressbar: "fflb",
+ contextmenu: "rcs",
+ },
+ },
+ ],
+ searchTermParamName: "q",
+ },
+ suggestions: {
+ base: "https://suggestqueries.google.com/complete/search?output=firefox&client=firefox&hl={moz:locale}",
+ searchTermParamName: "q",
+ },
+ },
+ },
+ variants: [
+ {
+ environment: { excludedRegions: ["FR"] },
+ },
+ ],
+ },
+ {
+ recordType: "engine",
+ identifier: "engine-pref",
+ base: {
+ name: "engine-pref",
+ urls: {
+ search: {
+ base: "https://www.google.com/search",
+ params: [
+ {
+ name: "code",
+ experimentConfig: "code",
+ },
+ {
+ name: "test",
+ experimentConfig: "test",
+ },
+ ],
+ searchTermParamName: "q",
+ },
+ },
+ },
+ variants: [
+ {
+ environment: { allRegionsAndLocales: true },
+ },
+ ],
+ },
+ {
+ recordType: "defaultEngines",
+ specificDefaults: [
+ {
+ default: "engine",
+ defaultPrivate: "engine",
+ environment: { excludedRegions: ["FR"] },
+ },
+ {
+ default: "engine-pref",
+ defaultPrivate: "engine-pref",
+ environment: { regions: ["FR"] },
+ },
+ ],
+ },
+ {
+ recordType: "engineOrders",
+ orders: [],
+ },
+];
+
// Default engine with no region defined.
const DEFAULT = "Test search engine";
// Default engine with region set to FR.
@@ -104,7 +185,11 @@ add_setup(async function () {
);
SearchTestUtils.useMockIdleService();
- await SearchTestUtils.useTestEngines("data", null, CONFIG);
+ await SearchTestUtils.useTestEngines(
+ "data",
+ null,
+ SearchUtils.newSearchConfigEnabled ? CONFIG_V2 : CONFIG
+ );
await AddonTestUtils.promiseStartupManager();
});
diff --git a/toolkit/components/search/tests/xpcshell/test_maybereloadengine_order.js b/toolkit/components/search/tests/xpcshell/test_maybereloadengine_order.js
index 663b7205a7..bdb8510812 100644
--- a/toolkit/components/search/tests/xpcshell/test_maybereloadengine_order.js
+++ b/toolkit/components/search/tests/xpcshell/test_maybereloadengine_order.js
@@ -1,56 +1,14 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
-"use strict";
+/*
+ * Test engine order is not set after engine reload.
+ */
-const TEST_CONFIG = [
- {
- webExtension: {
- id: "plainengine@search.mozilla.org",
- name: "Plain",
- search_url: "https://duckduckgo.com/",
- params: [
- {
- name: "q",
- value: "{searchTerms}",
- },
- ],
- suggest_url: "https://ac.duckduckgo.com/ac/q={searchTerms}&type=list",
- },
- appliesTo: [{ included: { everywhere: true } }],
- },
- {
- webExtension: {
- id: "special-engine@search.mozilla.org",
- name: "Special",
- search_url: "https://www.google.com/search",
- params: [
- {
- name: "q",
- value: "{searchTerms}",
- },
- {
- name: "client",
- condition: "purpose",
- purpose: "keyword",
- value: "firefox-b-1-ab",
- },
- {
- name: "client",
- condition: "purpose",
- purpose: "searchbar",
- value: "firefox-b-1",
- },
- ],
- suggest_url:
- "https://www.google.com/complete/search?client=firefox&q={searchTerms}",
- },
- appliesTo: [{ default: "yes", included: { regions: ["FR"] } }],
- },
-];
+"use strict";
add_setup(async function () {
- await SearchTestUtils.useTestEngines("test-extensions", null, TEST_CONFIG);
+ await SearchTestUtils.useTestEngines("test-extensions");
await AddonTestUtils.promiseStartupManager();
registerCleanupFunction(AddonTestUtils.promiseShutdownManager);
@@ -59,7 +17,7 @@ add_setup(async function () {
add_task(async function basic_multilocale_test() {
let resolver;
let initPromise = new Promise(resolve => (resolver = resolve));
- useCustomGeoServer("FR", initPromise);
+ useCustomGeoServer("TR", initPromise);
await Services.search.init();
await Services.search.getAppProvidedEngines();
diff --git a/toolkit/components/search/tests/xpcshell/test_missing_engine.js b/toolkit/components/search/tests/xpcshell/test_missing_engine.js
index 3da9fe14a6..259baf9c1a 100644
--- a/toolkit/components/search/tests/xpcshell/test_missing_engine.js
+++ b/toolkit/components/search/tests/xpcshell/test_missing_engine.js
@@ -55,6 +55,48 @@ const BAD_CONFIG = [
},
];
+const CONFIG_V2 = [
+ {
+ recordType: "engine",
+ identifier: "engine",
+ base: {
+ name: "Test search engine",
+ urls: {
+ search: {
+ base: "https://www.google.com/search",
+ params: [
+ {
+ name: "channel",
+ searchAccessPoint: {
+ addressbar: "fflb",
+ contextmenu: "rcs",
+ },
+ },
+ ],
+ searchTermParamName: "q",
+ },
+ suggestions: {
+ base: "https://suggestqueries.google.com/complete/search?output=firefox&client=firefox&hl={moz:locale}",
+ searchTermParamName: "q",
+ },
+ },
+ },
+ variants: [
+ {
+ environment: { allRegionsAndLocales: true },
+ },
+ ],
+ },
+ {
+ recordType: "defaultEngines",
+ specificDefaults: [],
+ },
+ {
+ recordType: "engineOrders",
+ orders: [],
+ },
+];
+
add_setup(async function () {
SearchTestUtils.useMockIdleService();
await AddonTestUtils.promiseStartupManager();
@@ -66,7 +108,11 @@ add_setup(async function () {
});
add_task(async function test_startup_with_missing() {
- await SearchTestUtils.useTestEngines("data", null, BAD_CONFIG);
+ await SearchTestUtils.useTestEngines(
+ "data",
+ null,
+ SearchUtils.newSearchConfigEnabled ? CONFIG_V2 : BAD_CONFIG
+ );
const result = await Services.search.init();
Assert.ok(
@@ -89,7 +135,7 @@ add_task(async function test_update_with_missing() {
await RemoteSettings(SearchUtils.SETTINGS_KEY).emit("sync", {
data: {
- current: GOOD_CONFIG,
+ current: SearchUtils.newSearchConfigEnabled ? CONFIG_V2 : GOOD_CONFIG,
},
});
@@ -110,7 +156,7 @@ add_task(async function test_update_with_missing() {
await RemoteSettings(SearchUtils.SETTINGS_KEY).emit("sync", {
data: {
- current: BAD_CONFIG,
+ current: SearchUtils.newSearchConfigEnabled ? CONFIG_V2 : BAD_CONFIG,
},
});
diff --git a/toolkit/components/search/tests/xpcshell/test_opensearch_icon.js b/toolkit/components/search/tests/xpcshell/test_opensearch_icon.js
index 68cd9577c1..23f2d9d575 100644
--- a/toolkit/components/search/tests/xpcshell/test_opensearch_icon.js
+++ b/toolkit/components/search/tests/xpcshell/test_opensearch_icon.js
@@ -56,9 +56,9 @@ add_task(async function test_icon_types() {
engine.QueryInterface(Ci.nsISearchEngine);
await promiseEngineChanged;
- Assert.ok(engine.getIconURL(), `${test.name} engine has an icon`);
+ Assert.ok(await engine.getIconURL(), `${test.name} engine has an icon`);
Assert.ok(
- engine.getIconURL().startsWith(test.expected),
+ (await engine.getIconURL()).startsWith(test.expected),
`${test.name} iconURI starts with the expected information`
);
}
@@ -69,13 +69,13 @@ add_task(async function test_multiple_icons_in_file() {
url: `${gDataUrl}engineImages.xml`,
});
- Assert.ok(engine.getIconURL().includes("ico16"));
- Assert.ok(engine.getIconURL(16).includes("ico16"));
- Assert.ok(engine.getIconURL(32).includes("ico32"));
- Assert.ok(engine.getIconURL(74).includes("ico74"));
+ Assert.ok((await engine.getIconURL()).includes("ico16"));
+ Assert.ok((await engine.getIconURL(16)).includes("ico16"));
+ Assert.ok((await engine.getIconURL(32)).includes("ico32"));
+ Assert.ok((await engine.getIconURL(74)).includes("ico74"));
info("Invalid dimensions should return null until bug 1655070 is fixed.");
- Assert.equal(null, engine.getIconURL(50));
+ Assert.equal(null, await engine.getIconURL(50));
});
add_task(async function test_icon_not_in_opensearch_file() {
@@ -86,6 +86,6 @@ add_task(async function test_icon_not_in_opensearch_file() {
);
// Even though the icon wasn't specified inside the XML file, it should be
- // available both in the iconURI attribute and with getIconURLBySize.
- Assert.ok(engine.getIconURL(16).includes("ico16"));
+ // available.
+ Assert.ok((await engine.getIconURL(16)).includes("ico16"));
});
diff --git a/toolkit/components/search/tests/xpcshell/test_opensearch_icons_invalid.js b/toolkit/components/search/tests/xpcshell/test_opensearch_icons_invalid.js
index 6db13a0da8..744d46052e 100644
--- a/toolkit/components/search/tests/xpcshell/test_opensearch_icons_invalid.js
+++ b/toolkit/components/search/tests/xpcshell/test_opensearch_icons_invalid.js
@@ -21,8 +21,8 @@ add_task(async function test_installedresourceicon() {
url: `${gDataUrl}opensearch/chromeicon.xml`,
});
- Assert.equal(undefined, engine1.getIconURL());
- Assert.equal(undefined, engine2.getIconURL());
+ Assert.equal(undefined, await engine1.getIconURL());
+ Assert.equal(undefined, await engine2.getIconURL());
});
add_task(async function test_installedhttpplace() {
@@ -50,7 +50,7 @@ add_task(async function test_installedhttpplace() {
Assert.equal(
undefined,
- engine.getIconURL(),
+ await engine.getIconURL(),
"Should not have set an iconURI"
);
});
diff --git a/toolkit/components/search/tests/xpcshell/test_override_allowlist_switch.js b/toolkit/components/search/tests/xpcshell/test_override_allowlist_switch.js
index cd51e7bdee..5ec485c4ec 100644
--- a/toolkit/components/search/tests/xpcshell/test_override_allowlist_switch.js
+++ b/toolkit/components/search/tests/xpcshell/test_override_allowlist_switch.js
@@ -306,8 +306,16 @@ add_task(async function test_app_provided_engine_deployment_extended() {
await assertCorrectlySwitchedWhenRemoved(async () => {
info("Change configuration to remove engine from user's environment");
- await SearchTestUtils.updateRemoteSettingsConfig(CONFIG_SIMPLE_LOCALE_DE);
- configStub.returns(CONFIG_SIMPLE_LOCALE_DE);
+ await SearchTestUtils.updateRemoteSettingsConfig(
+ SearchUtils.newSearchConfigEnabled
+ ? CONFIG_SIMPLE_LOCALE_DE_V2
+ : CONFIG_SIMPLE_LOCALE_DE
+ );
+ configStub.returns(
+ SearchUtils.newSearchConfigEnabled
+ ? CONFIG_SIMPLE_LOCALE_DE_V2
+ : CONFIG_SIMPLE_LOCALE_DE
+ );
});
});
diff --git a/toolkit/components/search/tests/xpcshell/test_reload_engines.js b/toolkit/components/search/tests/xpcshell/test_reload_engines.js
index ac63b62e59..60cd3c2a13 100644
--- a/toolkit/components/search/tests/xpcshell/test_reload_engines.js
+++ b/toolkit/components/search/tests/xpcshell/test_reload_engines.js
@@ -238,6 +238,260 @@ const CONFIG = [
},
];
+const CONFIG_V2 = [
+ {
+ recordType: "engine",
+ identifier: "engine",
+ base: {
+ name: "Test search engine",
+ urls: {
+ search: {
+ base: "https://www.google.com/search",
+ params: [
+ {
+ name: "channel",
+ searchAccessPoint: {
+ addressbar: "fflb",
+ contextmenu: "rcs",
+ },
+ },
+ ],
+ searchTermParamName: "q",
+ },
+ suggestions: {
+ base: "https://suggestqueries.google.com/complete/search?output=firefox&client=firefox&hl={moz:locale}",
+ searchTermParamName: "q",
+ },
+ },
+ },
+ variants: [
+ {
+ environment: { allRegionsAndLocales: true },
+ },
+ ],
+ },
+ {
+ recordType: "engine",
+ identifier: "engine-pref",
+ base: {
+ name: "engine-pref",
+ urls: {
+ search: {
+ base: "https://www.google.com/search",
+ params: [
+ {
+ name: "code",
+ experimentConfig: "code",
+ },
+ {
+ name: "test",
+ experimentConfig: "test",
+ },
+ ],
+ searchTermParamName: "q",
+ },
+ },
+ },
+ variants: [
+ {
+ environment: { allRegionsAndLocales: true },
+ },
+ ],
+ },
+ {
+ recordType: "engine",
+ identifier: "engine-chromeicon",
+ base: {
+ name: "engine-chromeicon",
+ urls: {
+ search: {
+ base: "https://www.google.com/search",
+ searchTermParamName: "q",
+ },
+ },
+ },
+ variants: [
+ {
+ environment: { allRegionsAndLocales: true },
+ },
+ {
+ environment: { regions: ["FR"] },
+ urls: {
+ search: {
+ base: "https://www.google.com/search",
+ params: [
+ {
+ name: "c",
+ value: "my-test",
+ },
+ ],
+ searchTermParamName: "q1",
+ },
+ },
+ },
+ ],
+ },
+ {
+ recordType: "engine",
+ identifier: "engine-rel-searchform-purpose",
+ base: {
+ name: "engine-rel-searchform-purpose",
+ urls: {
+ search: {
+ base: "https://www.google.com/search",
+ searchTermParamName: "q",
+ },
+ },
+ },
+ variants: [
+ {
+ environment: { excludedRegions: ["FR"] },
+ urls: {
+ search: {
+ params: [
+ {
+ name: "channel",
+ searchAccessPoint: {
+ addressbar: "fflb",
+ contextmenu: "rcs",
+ searchbar: "sb",
+ },
+ },
+ ],
+ },
+ },
+ },
+ ],
+ },
+ {
+ recordType: "engine",
+ identifier: "engine-reordered",
+ base: {
+ name: "Test search engine (Reordered)",
+ urls: {
+ search: {
+ base: "https://www.google.com/search",
+ params: [
+ {
+ name: "channel",
+ searchAccessPoint: {
+ addressbar: "fflb",
+ contextmenu: "rcs",
+ },
+ },
+ ],
+ searchTermParamName: "q",
+ },
+ suggestions: {
+ base: "https://suggestqueries.google.com/complete/search?output=firefox&client=firefox&hl={moz:locale}",
+ searchTermParamName: "q",
+ },
+ },
+ },
+ variants: [
+ {
+ environment: { regions: ["FR"] },
+ },
+ ],
+ },
+ {
+ recordType: "engine",
+ identifier: "engine-resourceicon",
+ base: {
+ name: "engine-resourceicon",
+ urls: {
+ search: {
+ base: "https://www.google.com/search",
+ searchTermParamName: "q",
+ },
+ },
+ },
+ variants: [
+ {
+ environment: { excludedRegions: ["FR"] },
+ },
+ ],
+ },
+ {
+ recordType: "engine",
+ identifier: "engine-resourceicon-gd",
+ base: {
+ name: "engine-resourceicon-gd",
+ urls: {
+ search: {
+ base: "https://www.google.com/search",
+ searchTermParamName: "q",
+ },
+ },
+ },
+ variants: [
+ {
+ environment: { regions: ["FR"] },
+ },
+ ],
+ },
+ {
+ recordType: "engine",
+ identifier: "engine-same-name",
+ base: {
+ name: "engine-same-name",
+ urls: {
+ search: {
+ base: "https://www.google.com/search",
+ searchTermParamName: "q",
+ },
+ },
+ },
+ variants: [
+ {
+ environment: { excludedRegions: ["FR"] },
+ },
+ ],
+ },
+ {
+ recordType: "engine",
+ identifier: "engine-same-name-gd",
+ base: {
+ name: "engine-same-name-gd",
+ urls: {
+ search: {
+ base: "https://www.example.com/search",
+ searchTermParamName: "q",
+ },
+ },
+ },
+ variants: [
+ {
+ environment: { regions: ["FR"] },
+ },
+ ],
+ },
+ {
+ recordType: "defaultEngines",
+ specificDefaults: [
+ {
+ default: "engine",
+ defaultPrivate: "engine",
+ environment: { excludedRegions: ["FR"] },
+ },
+ {
+ default: "engine-pref",
+ defaultPrivate: "engine-pref",
+ environment: { regions: ["FR"] },
+ },
+ ],
+ },
+ {
+ recordType: "engineOrders",
+ orders: [
+ {
+ order: ["engine-resourceicon-gd"],
+ environment: { regions: ["FR"] },
+ },
+ ],
+ },
+];
+
async function visibleEngines() {
return (await Services.search.getVisibleEngines()).map(e => e.identifier);
}
@@ -250,7 +504,11 @@ add_setup(async function () {
);
SearchTestUtils.useMockIdleService();
- await SearchTestUtils.useTestEngines("data", null, CONFIG);
+ await SearchTestUtils.useTestEngines(
+ "data",
+ null,
+ SearchUtils.newSearchConfigEnabled ? CONFIG_V2 : CONFIG
+ );
await AddonTestUtils.promiseStartupManager();
});
@@ -325,23 +583,47 @@ add_task(async function test_config_updated_engine_changes() {
await reloadObserved;
Services.obs.removeObserver(enginesObs, SearchUtils.TOPIC_ENGINE_MODIFIED);
- Assert.deepEqual(
- enginesAdded,
- ["engine-resourceicon-gd", "engine-reordered"],
- "Should have added the correct engines"
- );
-
- Assert.deepEqual(
- enginesModified.sort(),
- ["engine", "engine-chromeicon", "engine-pref", "engine-same-name-gd"],
- "Should have modified the expected engines"
- );
-
- Assert.deepEqual(
- enginesRemoved,
- ["engine-rel-searchform-purpose", "engine-resourceicon"],
- "Should have removed the expected engine"
- );
+ if (SearchUtils.newSearchConfigEnabled) {
+ Assert.deepEqual(
+ enginesAdded,
+ ["engine-resourceicon-gd", "engine-reordered", "engine-same-name-gd"],
+ "Should have added the correct engines"
+ );
+
+ Assert.deepEqual(
+ enginesModified.sort(),
+ ["engine", "engine-chromeicon", "engine-pref"],
+ "Should have modified the expected engines"
+ );
+
+ Assert.deepEqual(
+ enginesRemoved,
+ [
+ "engine-rel-searchform-purpose",
+ "engine-resourceicon",
+ "engine-same-name",
+ ],
+ "Should have removed the expected engine"
+ );
+ } else {
+ Assert.deepEqual(
+ enginesAdded,
+ ["engine-resourceicon-gd", "engine-reordered"],
+ "Should have added the correct engines"
+ );
+
+ Assert.deepEqual(
+ enginesModified.sort(),
+ ["engine", "engine-chromeicon", "engine-pref", "engine-same-name-gd"],
+ "Should have modified the expected engines"
+ );
+
+ Assert.deepEqual(
+ enginesRemoved,
+ ["engine-rel-searchform-purpose", "engine-resourceicon"],
+ "Should have removed the expected engine"
+ );
+ }
const installedEngines = await Services.search.getAppProvidedEngines();
@@ -382,7 +664,9 @@ add_task(async function test_config_updated_engine_changes() {
);
const engineWithSameName = await Services.search.getEngineByName(
- "engine-same-name"
+ SearchUtils.newSearchConfigEnabled
+ ? "engine-same-name-gd"
+ : "engine-same-name"
);
Assert.equal(
engineWithSameName.getSubmission("test").uri.spec,
diff --git a/toolkit/components/search/tests/xpcshell/test_reload_engines_experiment.js b/toolkit/components/search/tests/xpcshell/test_reload_engines_experiment.js
index ea5cdee3e5..5edcb6081b 100644
--- a/toolkit/components/search/tests/xpcshell/test_reload_engines_experiment.js
+++ b/toolkit/components/search/tests/xpcshell/test_reload_engines_experiment.js
@@ -72,8 +72,87 @@ const CONFIG = [
},
];
+const CONFIG_V2 = [
+ {
+ recordType: "engine",
+ identifier: "engine",
+ base: {
+ name: "Test search engine",
+ urls: {
+ search: {
+ base: "https://www.google.com/search",
+ params: [
+ {
+ name: "channel",
+ searchAccessPoint: {
+ addressbar: "fflb",
+ contextmenu: "rcs",
+ },
+ },
+ ],
+ searchTermParamName: "q",
+ },
+ },
+ },
+ variants: [
+ {
+ environment: { allRegionsAndLocales: true },
+ },
+ ],
+ },
+ {
+ recordType: "engine",
+ identifier: "engine-same-name-en",
+ base: {
+ name: "engine-same-name",
+ urls: {
+ search: {
+ base: "https://www.google.com/search",
+ searchTermParamName: "q",
+ },
+ },
+ },
+ variants: [
+ {
+ environment: { allRegionsAndLocales: true },
+ },
+ ],
+ },
+ {
+ recordType: "engine",
+ identifier: "engine-same-name-gd",
+ base: {
+ name: "engine-same-name",
+ urls: {
+ search: {
+ base: "https://www.example.com/search",
+ searchTermParamName: "q",
+ },
+ },
+ },
+ variants: [
+ {
+ environment: { allRegionsAndLocales: true, experiment: "xpcshell" },
+ },
+ ],
+ },
+ {
+ recordType: "defaultEngines",
+ globalDefault: "engine",
+ specificDefaults: [],
+ },
+ {
+ recordType: "engineOrders",
+ orders: [],
+ },
+];
+
add_setup(async function () {
- await SearchTestUtils.useTestEngines("data", null, CONFIG);
+ await SearchTestUtils.useTestEngines(
+ "data",
+ null,
+ SearchUtils.newSearchConfigEnabled ? CONFIG_V2 : CONFIG
+ );
await AddonTestUtils.promiseStartupManager();
});
@@ -125,19 +204,42 @@ add_task(async function test_config_updated_engine_changes() {
await reloadObserved;
Services.obs.removeObserver(enginesObs, SearchUtils.TOPIC_ENGINE_MODIFIED);
- Assert.deepEqual(enginesAdded, [], "Should have added the correct engines");
+ if (SearchUtils.newSearchConfigEnabled) {
+ // In the new config, engine-same-name-en and engine-same-name-gd are two
+ // different engine configs and they will be treated as different engines
+ // and not the same. That's the reason why the assertions are different below.
+ Assert.deepEqual(
+ enginesAdded,
+ ["engine-same-name-gd"],
+ "Should have added the correct engines"
+ );
- Assert.deepEqual(
- enginesModified.sort(),
- ["engine", "engine-same-name-gd"],
- "Should have modified the expected engines"
- );
+ Assert.deepEqual(
+ enginesModified.sort(),
+ ["engine", "engine-same-name-en"],
+ "Should have modified the expected engines"
+ );
- Assert.deepEqual(
- enginesRemoved,
- [],
- "Should have removed the expected engine"
- );
+ Assert.deepEqual(
+ enginesRemoved,
+ ["engine-same-name"],
+ "Should have removed the expected engine"
+ );
+ } else {
+ Assert.deepEqual(enginesAdded, [], "Should have added the correct engines");
+
+ Assert.deepEqual(
+ enginesModified.sort(),
+ ["engine", "engine-same-name-gd"],
+ "Should have modified the expected engines"
+ );
+
+ Assert.deepEqual(
+ enginesRemoved,
+ [],
+ "Should have removed the expected engine"
+ );
+ }
const installedEngines = await Services.search.getAppProvidedEngines();
diff --git a/toolkit/components/search/tests/xpcshell/test_reload_engines_locales.js b/toolkit/components/search/tests/xpcshell/test_reload_engines_locales.js
index 6fa277655d..ed516c5a15 100644
--- a/toolkit/components/search/tests/xpcshell/test_reload_engines_locales.js
+++ b/toolkit/components/search/tests/xpcshell/test_reload_engines_locales.js
@@ -72,6 +72,85 @@ const CONFIG = [
},
];
+const CONFIG_V2 = [
+ {
+ recordType: "engine",
+ identifier: "engine",
+ base: {
+ name: "Test search engine",
+ urls: {
+ search: {
+ base: "https://www.google.com/search",
+ params: [
+ {
+ name: "channel",
+ searchAccessPoint: {
+ addressbar: "fflb",
+ contextmenu: "rcs",
+ },
+ },
+ ],
+ searchTermParamName: "q",
+ },
+ suggestions: {
+ base: "https://suggestqueries.google.com/complete/search?output=firefox&client=firefox&hl={moz:locale}",
+ searchTermParamName: "q",
+ },
+ },
+ },
+ variants: [
+ {
+ environment: { allRegionsAndLocales: true },
+ },
+ ],
+ },
+ {
+ recordType: "engine",
+ identifier: "engine-diff-name-en",
+ base: {
+ name: "engine-diff-name-en",
+ urls: {
+ search: {
+ base: "https://en.wikipedia.com/search",
+ searchTermParamName: "q",
+ },
+ },
+ },
+ variants: [
+ {
+ environment: { excludedLocales: ["gd"] },
+ },
+ ],
+ },
+ {
+ recordType: "engine",
+ identifier: "engine-diff-name-gd",
+ base: {
+ name: "engine-diff-name-gd",
+ urls: {
+ search: {
+ base: "https://gd.wikipedia.com/search",
+ searchTermParamName: "q",
+ },
+ },
+ },
+ variants: [
+ {
+ environment: { locales: ["gd"] },
+ },
+ ],
+ },
+ {
+ recordType: "defaultEngines",
+ globalDefault: "engine",
+ specificDefaults: [],
+ },
+ {
+ recordType: "engineOrders",
+ orders: [],
+ },
+];
+
add_setup(async () => {
Services.locale.availableLocales = [
...Services.locale.availableLocales,
@@ -80,7 +159,11 @@ add_setup(async () => {
];
Services.locale.requestedLocales = ["gd"];
- await SearchTestUtils.useTestEngines("data", null, CONFIG);
+ await SearchTestUtils.useTestEngines(
+ "data",
+ null,
+ SearchUtils.newSearchConfigEnabled ? CONFIG_V2 : CONFIG
+ );
await AddonTestUtils.promiseStartupManager();
await Services.search.init();
});
@@ -101,7 +184,9 @@ add_task(async function test_config_updated_engine_changes() {
);
Assert.equal(
engine.getSubmission("test").uri.spec,
- "https://gd.wikipedia.com/search",
+ SearchUtils.newSearchConfigEnabled
+ ? "https://gd.wikipedia.com/search?q=test"
+ : "https://gd.wikipedia.com/search",
"Should have the gd search url"
);
@@ -122,7 +207,9 @@ add_task(async function test_config_updated_engine_changes() {
);
Assert.equal(
engine.getSubmission("test").uri.spec,
- "https://en.wikipedia.com/search",
+ SearchUtils.newSearchConfigEnabled
+ ? "https://en.wikipedia.com/search?q=test"
+ : "https://en.wikipedia.com/search",
"Should have the en search url"
);
});
diff --git a/toolkit/components/search/tests/xpcshell/test_remove_engine_notification_box.js b/toolkit/components/search/tests/xpcshell/test_remove_engine_notification_box.js
index 0222334255..d5b1366b93 100644
--- a/toolkit/components/search/tests/xpcshell/test_remove_engine_notification_box.js
+++ b/toolkit/components/search/tests/xpcshell/test_remove_engine_notification_box.js
@@ -57,6 +57,100 @@ const CONFIG_UPDATED = CONFIG.filter(r =>
r.webExtension.id.startsWith("engine-pref")
);
+const CONFIG_V2 = [
+ {
+ recordType: "engine",
+ identifier: "engine",
+ base: {
+ name: "Test search engine",
+ urls: {
+ search: {
+ base: "https://www.google.com/search",
+ searchTermParamName: "q",
+ },
+ },
+ },
+ variants: [
+ {
+ environment: { allRegionsAndLocales: true },
+ },
+ ],
+ },
+ {
+ recordType: "engine",
+ identifier: "engine-pref",
+ base: {
+ name: "engine-pref",
+ urls: {
+ search: {
+ base: "https://www.google.com/search",
+ searchTermParamName: "q",
+ },
+ },
+ },
+ variants: [
+ {
+ environment: { allRegionsAndLocales: true },
+ },
+ ],
+ },
+ {
+ recordType: "defaultEngines",
+ specificDefaults: [
+ {
+ default: "engine",
+ environment: { excludedRegions: ["FR"] },
+ },
+ {
+ default: "engine-pref",
+ environment: { regions: ["FR"] },
+ },
+ ],
+ },
+ {
+ recordType: "engineOrders",
+ orders: [],
+ },
+];
+
+const CONFIG_V2_UPDATED = [
+ {
+ recordType: "engine",
+ identifier: "engine-pref",
+ base: {
+ name: "engine-pref",
+ urls: {
+ search: {
+ base: "https://www.google.com/search",
+ searchTermParamName: "q",
+ },
+ },
+ },
+ variants: [
+ {
+ environment: { allRegionsAndLocales: true },
+ },
+ ],
+ },
+ {
+ recordType: "defaultEngines",
+ specificDefaults: [
+ {
+ default: "engine",
+ environment: { excludedRegions: ["FR"] },
+ },
+ {
+ default: "engine-pref",
+ environment: { regions: ["FR"] },
+ },
+ ],
+ },
+ {
+ recordType: "engineOrders",
+ orders: [],
+ },
+];
+
let stub;
let settingsFilePath;
let userSettings;
@@ -64,7 +158,11 @@ let userSettings;
add_setup(async function () {
SearchSettings.SETTINGS_INVALIDATION_DELAY = 100;
SearchTestUtils.useMockIdleService();
- await SearchTestUtils.useTestEngines("data", null, CONFIG);
+ await SearchTestUtils.useTestEngines(
+ "data",
+ null,
+ SearchUtils.newSearchConfigEnabled ? CONFIG_V2 : CONFIG
+ );
await AddonTestUtils.promiseStartupManager();
stub = sinon.stub(
@@ -246,7 +344,9 @@ add_task(async function test_default_engine_changed_and_metadata_unchanged() {
};
// Update config by removing the app default engine
- await setConfigToLoad(CONFIG_UPDATED);
+ await setConfigToLoad(
+ SearchUtils.newSearchConfigEnabled ? CONFIG_V2_UPDATED : CONFIG_UPDATED
+ );
await reloadEngines(structuredClone(userSettings));
Assert.ok(
@@ -293,7 +393,9 @@ add_task(async function test_app_default_engine_changed_on_start_up() {
settings.metaData.current = "";
// Update config by removing the app default engine
- await setConfigToLoad(CONFIG_UPDATED);
+ await setConfigToLoad(
+ SearchUtils.newSearchConfigEnabled ? CONFIG_V2_UPDATED : CONFIG_UPDATED
+ );
await loadEngines(settings);
Assert.ok(
@@ -311,7 +413,9 @@ add_task(async function test_app_default_engine_change_start_up_still_exists() {
settings.metaData.current = "";
settings.metaData.appDefaultEngine = "Test search engine";
- await setConfigToLoad(CONFIG);
+ await setConfigToLoad(
+ SearchUtils.newSearchConfigEnabled ? CONFIG_V2 : CONFIG
+ );
await loadEngines(settings);
Assert.ok(
diff --git a/toolkit/components/search/tests/xpcshell/test_searchSuggest.js b/toolkit/components/search/tests/xpcshell/test_searchSuggest.js
index 48769be41e..4a30eb741a 100644
--- a/toolkit/components/search/tests/xpcshell/test_searchSuggest.js
+++ b/toolkit/components/search/tests/xpcshell/test_searchSuggest.js
@@ -581,7 +581,7 @@ add_task(async function stop_search() {
let histogram = TelemetryTestUtils.getAndClearKeyedHistogram(
SEARCH_TELEMETRY_LATENCY
);
- let controller = new SearchSuggestionController(result => {
+ let controller = new SearchSuggestionController(() => {
do_throw("The callback shouldn't be called after stop()");
});
let resultPromise = controller.fetch("mo", false, getEngine);
diff --git a/toolkit/components/search/tests/xpcshell/test_searchSuggest_cookies.js b/toolkit/components/search/tests/xpcshell/test_searchSuggest_cookies.js
index cfa9e56144..042c74d86a 100644
--- a/toolkit/components/search/tests/xpcshell/test_searchSuggest_cookies.js
+++ b/toolkit/components/search/tests/xpcshell/test_searchSuggest_cookies.js
@@ -28,7 +28,7 @@ function countCacheEntries() {
);
storage.asyncVisitStorage(
{
- onCacheStorageInfo(num, consumption) {
+ onCacheStorageInfo(num) {
this._num = num;
},
onCacheEntryInfo(uri) {
diff --git a/toolkit/components/search/tests/xpcshell/test_searchSuggest_extraParams.js b/toolkit/components/search/tests/xpcshell/test_searchSuggest_extraParams.js
index 8ecf4b02f3..3261174ebf 100644
--- a/toolkit/components/search/tests/xpcshell/test_searchSuggest_extraParams.js
+++ b/toolkit/components/search/tests/xpcshell/test_searchSuggest_extraParams.js
@@ -22,8 +22,62 @@ const TEST_CONFIG = [
},
];
+const TEST_CONFIG_V2 = [
+ {
+ recordType: "engine",
+ identifier: "get",
+ base: {
+ name: "Get Engine",
+ urls: {
+ search: {
+ base: "https://example.com",
+ params: [
+ {
+ name: "webExtension",
+ value: "1",
+ },
+ ],
+ searchTermParamName: "search",
+ },
+ suggestions: {
+ base: "https://example.com",
+ params: [
+ {
+ name: "custom_param",
+ experimentConfig: "test_pref_param",
+ },
+ {
+ name: "webExtension",
+ value: "1",
+ },
+ ],
+ searchTermParamName: "suggest",
+ },
+ },
+ },
+ variants: [
+ {
+ environment: { allRegionsAndLocales: true },
+ },
+ ],
+ },
+ {
+ recordType: "defaultEngines",
+ globalDefault: "get",
+ specificDefaults: [],
+ },
+ {
+ recordType: "engineOrders",
+ orders: [],
+ },
+];
+
add_setup(async function () {
- await SearchTestUtils.useTestEngines("method-extensions", null, TEST_CONFIG);
+ await SearchTestUtils.useTestEngines(
+ "method-extensions",
+ null,
+ SearchUtils.newSearchConfigEnabled ? TEST_CONFIG_V2 : TEST_CONFIG
+ );
await AddonTestUtils.promiseStartupManager();
await Services.search.init();
});
diff --git a/toolkit/components/search/tests/xpcshell/test_searchTermFromResult.js b/toolkit/components/search/tests/xpcshell/test_searchTermFromResult.js
index 700c6a3450..ea040aacd4 100644
--- a/toolkit/components/search/tests/xpcshell/test_searchTermFromResult.js
+++ b/toolkit/components/search/tests/xpcshell/test_searchTermFromResult.js
@@ -6,6 +6,53 @@
* Tests searchTermFromResult API.
*/
+let CONFIG_V2 = [
+ {
+ recordType: "engine",
+ identifier: "engine-purposes",
+ base: {
+ name: "Test Engine With Purposes",
+ urls: {
+ search: {
+ base: "https://www.example.com/search",
+ params: [
+ { name: "pc", value: "FIREFOX" },
+ {
+ name: "form",
+ searchAccessPoint: {
+ newtab: "MOZNEWTAB",
+ homepage: "MOZHOMEPAGE",
+ searchbar: "MOZSEARCHBAR",
+ addressbar: "MOZKEYWORD",
+ contextmenu: "MOZCONTEXT",
+ },
+ },
+ {
+ name: "channel",
+ experimentConfig: "testChannelEnabled",
+ },
+ ],
+ searchTermParamName: "q",
+ },
+ },
+ },
+ variants: [
+ {
+ environment: { allRegionsAndLocales: true },
+ },
+ ],
+ },
+ {
+ recordType: "defaultEngines",
+ globalDefault: "engine-purpose",
+ specificDefaults: [],
+ },
+ {
+ recordType: "engineOrders",
+ orders: [],
+ },
+];
+
let defaultEngine;
// The test string contains special characters to ensure
@@ -14,66 +61,72 @@ const TERM = "c;,?:@&=+$-_.!~*'()# d\u00E8f";
const TERM_ENCODED = "c%3B%2C%3F%3A%40%26%3D%2B%24-_.!~*'()%23+d%C3%A8f";
add_setup(async function () {
- await SearchTestUtils.useTestEngines("data", null, [
- {
- webExtension: {
- id: "engine-purposes@search.mozilla.org",
- name: "Test Engine With Purposes",
- search_url: "https://www.example.com/search",
- params: [
- {
- name: "form",
- condition: "purpose",
- purpose: "keyword",
- value: "MOZKEYWORD",
- },
- {
- name: "form",
- condition: "purpose",
- purpose: "contextmenu",
- value: "MOZCONTEXT",
- },
+ await SearchTestUtils.useTestEngines(
+ "data",
+ null,
+ SearchUtils.newSearchConfigEnabled
+ ? CONFIG_V2
+ : [
{
- name: "form",
- condition: "purpose",
- purpose: "newtab",
- value: "MOZNEWTAB",
+ webExtension: {
+ id: "engine-purposes@search.mozilla.org",
+ name: "Test Engine With Purposes",
+ search_url: "https://www.example.com/search",
+ params: [
+ {
+ name: "form",
+ condition: "purpose",
+ purpose: "keyword",
+ value: "MOZKEYWORD",
+ },
+ {
+ name: "form",
+ condition: "purpose",
+ purpose: "contextmenu",
+ value: "MOZCONTEXT",
+ },
+ {
+ name: "form",
+ condition: "purpose",
+ purpose: "newtab",
+ value: "MOZNEWTAB",
+ },
+ {
+ name: "form",
+ condition: "purpose",
+ purpose: "searchbar",
+ value: "MOZSEARCHBAR",
+ },
+ {
+ name: "form",
+ condition: "purpose",
+ purpose: "homepage",
+ value: "MOZHOMEPAGE",
+ },
+ {
+ name: "pc",
+ value: "FIREFOX",
+ },
+ {
+ name: "channel",
+ condition: "pref",
+ pref: "testChannelEnabled",
+ },
+ {
+ name: "q",
+ value: "{searchTerms}",
+ },
+ ],
+ },
+ appliesTo: [
+ {
+ included: { everywhere: true },
+ default: "yes",
+ },
+ ],
},
- {
- name: "form",
- condition: "purpose",
- purpose: "searchbar",
- value: "MOZSEARCHBAR",
- },
- {
- name: "form",
- condition: "purpose",
- purpose: "homepage",
- value: "MOZHOMEPAGE",
- },
- {
- name: "pc",
- value: "FIREFOX",
- },
- {
- name: "channel",
- condition: "pref",
- pref: "testChannelEnabled",
- },
- {
- name: "q",
- value: "{searchTerms}",
- },
- ],
- },
- appliesTo: [
- {
- included: { everywhere: true },
- default: "yes",
- },
- ],
- },
- ]);
+ ]
+ );
await AddonTestUtils.promiseStartupManager();
await Services.search.init();
diff --git a/toolkit/components/search/tests/xpcshell/test_settings_persist.js b/toolkit/components/search/tests/xpcshell/test_settings_persist.js
index bed6771554..5c2cbd85c4 100644
--- a/toolkit/components/search/tests/xpcshell/test_settings_persist.js
+++ b/toolkit/components/search/tests/xpcshell/test_settings_persist.js
@@ -1,9 +1,13 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
+/**
+ * Tests the removal of an engine is persisted in search settings.
+ */
+
"use strict";
-const CONFIG_DEFAULT = [
+const CONFIG_UPDATED = [
{
webExtension: {
id: "plainengine@search.mozilla.org",
@@ -18,26 +22,38 @@ const CONFIG_DEFAULT = [
},
appliesTo: [{ included: { everywhere: true } }],
},
+];
+
+const SEARCH_CONFIG_V2_UPDATED = [
{
- webExtension: {
- id: "special-engine@search.mozilla.org",
- name: "Special",
- search_url: "https://www.google.com/search",
- params: [
- {
- name: "q",
- value: "{searchTerms}",
+ recordType: "engine",
+ identifier: "plainengine",
+ base: {
+ name: "Plain",
+ urls: {
+ search: {
+ base: "https://duckduckgo.com/",
+ searchTermParamName: "q",
},
- ],
+ },
},
- appliesTo: [{ included: { everywhere: true } }],
+ variants: [
+ {
+ environment: { allRegionsAndLocales: true },
+ },
+ ],
+ },
+ {
+ recordType: "defaultEngines",
+ globalDefault: "plainengine",
+ specificDefaults: [],
+ },
+ {
+ recordType: "engineOrders",
+ orders: [],
},
];
-const CONFIG_UPDATED = CONFIG_DEFAULT.filter(r =>
- r.webExtension.id.startsWith("plainengine")
-);
-
async function startup() {
let settingsFileWritten = promiseAfterSettings();
let ss = new SearchService();
@@ -50,7 +66,10 @@ async function startup() {
async function updateConfig(config) {
const settings = await RemoteSettings(SearchUtils.SETTINGS_KEY);
settings.get.restore();
- sinon.stub(settings, "get").returns(config);
+
+ config == "test-extensions"
+ ? await SearchTestUtils.useTestEngines("test-extensions")
+ : sinon.stub(settings, "get").returns(config);
}
async function visibleEngines(ss) {
@@ -58,7 +77,7 @@ async function visibleEngines(ss) {
}
add_setup(async function () {
- await SearchTestUtils.useTestEngines("test-extensions", null, CONFIG_DEFAULT);
+ await SearchTestUtils.useTestEngines("test-extensions");
registerCleanupFunction(AddonTestUtils.promiseShutdownManager);
await AddonTestUtils.promiseStartupManager();
// This is only needed as otherwise events will not be properly notified
@@ -87,7 +106,11 @@ add_task(async function () {
);
ss._removeObservers();
- updateConfig(CONFIG_UPDATED);
+ await updateConfig(
+ SearchUtils.newSearchConfigEnabled
+ ? SEARCH_CONFIG_V2_UPDATED
+ : CONFIG_UPDATED
+ );
ss = await startup();
Assert.ok(
@@ -96,7 +119,8 @@ add_task(async function () {
);
ss._removeObservers();
- updateConfig(CONFIG_DEFAULT);
+ await updateConfig("test-extensions");
+
ss = await startup();
Assert.ok(
diff --git a/toolkit/components/search/tests/xpcshell/test_sort_orders-no-hints.js b/toolkit/components/search/tests/xpcshell/test_sort_orders-no-hints.js
index d2a2d7dd93..cb7e314fd4 100644
--- a/toolkit/components/search/tests/xpcshell/test_sort_orders-no-hints.js
+++ b/toolkit/components/search/tests/xpcshell/test_sort_orders-no-hints.js
@@ -15,7 +15,13 @@ add_setup(async function () {
"data",
null,
(
- await readJSONFile(do_get_file("data/engines-no-order-hint.json"))
+ await readJSONFile(
+ do_get_file(
+ SearchUtils.newSearchConfigEnabled
+ ? "data/search-config-v2-no-order-hint.json"
+ : "data/engines-no-order-hint.json"
+ )
+ )
).data
);
diff --git a/toolkit/components/search/tests/xpcshell/test_telemetry_event_default.js b/toolkit/components/search/tests/xpcshell/test_telemetry_event_default.js
index 84bd1be9cc..f1b6208326 100644
--- a/toolkit/components/search/tests/xpcshell/test_telemetry_event_default.js
+++ b/toolkit/components/search/tests/xpcshell/test_telemetry_event_default.js
@@ -45,6 +45,49 @@ const BASE_CONFIG = [
default: "yes",
},
];
+
+const BASE_CONFIG_V2 = [
+ {
+ recordType: "engine",
+ identifier: "engine",
+ base: {
+ name: "Test search engine",
+ urls: {
+ search: {
+ base: "https://www.google.com/search",
+ params: [
+ {
+ name: "channel",
+ searchAccessPoint: {
+ addressbar: "fflb",
+ contextmenu: "rcs",
+ },
+ },
+ ],
+ searchTermParamName: "q",
+ },
+ suggestions: {
+ base: "https://suggestqueries.google.com/complete/search?output=firefox&client=firefox&hl={moz:locale}",
+ searchTermParamName: "q",
+ },
+ },
+ },
+ variants: [
+ {
+ environment: { allRegionsAndLocales: true },
+ },
+ ],
+ },
+ {
+ recordType: "defaultEngines",
+ globalDefault: "engine",
+ specificDefaults: [],
+ },
+ {
+ recordType: "engineOrders",
+ orders: [],
+ },
+];
const MAIN_CONFIG = [
{
webExtension: {
@@ -78,7 +121,6 @@ const MAIN_CONFIG = [
{
webExtension: {
id: "engine-chromeicon@search.mozilla.org",
-
name: "engine-chromeicon",
search_url: "https://www.google.com/search",
params: [
@@ -163,6 +205,154 @@ const MAIN_CONFIG = [
},
];
+const MAIN_CONFIG_V2 = [
+ {
+ recordType: "engine",
+ identifier: "engine",
+ base: {
+ name: "Test search engine",
+ urls: {
+ search: {
+ base: "https://www.google.com/search",
+ params: [
+ {
+ name: "channel",
+ searchAccessPoint: {
+ addressbar: "fflb",
+ contextmenu: "rcs",
+ },
+ },
+ ],
+ searchTermParamName: "q",
+ },
+ suggestions: {
+ base: "https://suggestqueries.google.com/complete/search?output=firefox&client=firefox&hl={moz:locale}",
+ searchTermParamName: "q",
+ },
+ },
+ },
+ variants: [
+ {
+ environment: { allRegionsAndLocales: true },
+ },
+ ],
+ },
+ {
+ recordType: "engine",
+ identifier: "engine-chromeicon",
+ base: {
+ name: "engine-chromeicon",
+ urls: {
+ search: {
+ base: "https://www.google.com/search",
+ searchTermParamName: "q",
+ },
+ },
+ },
+ variants: [
+ {
+ environment: { allRegionsAndLocales: true },
+ },
+ ],
+ },
+ {
+ recordType: "engine",
+ identifier: "engine-fr",
+ base: {
+ name: "Test search engine (fr)",
+ urls: {
+ search: {
+ base: "https://www.google.fr/search",
+ params: [
+ {
+ name: "ie",
+ value: "iso-8859-1",
+ },
+ {
+ name: "oe",
+ value: "iso-8859-1",
+ },
+ ],
+ searchTermParamName: "q",
+ },
+ },
+ },
+ variants: [
+ {
+ environment: { allRegionsAndLocales: true },
+ },
+ ],
+ },
+ {
+ recordType: "engine",
+ identifier: "engine-pref",
+ base: {
+ name: "engine-pref",
+ urls: {
+ search: {
+ base: "https://www.google.com/search",
+ params: [
+ {
+ name: "code",
+ experimentConfig: "code",
+ },
+ {
+ name: "test",
+ experimentConfig: "test",
+ },
+ ],
+ searchTermParamName: "q",
+ },
+ },
+ },
+ variants: [
+ {
+ environment: { allRegionsAndLocales: true },
+ },
+ ],
+ },
+ {
+ recordType: "engine",
+ identifier: "engine2",
+ base: {
+ name: "A second test engine",
+ urls: {
+ search: {
+ base: "https://duckduckgo.com/",
+ searchTermParamName: "q",
+ },
+ },
+ },
+ variants: [
+ {
+ environment: { allRegionsAndLocales: true },
+ },
+ ],
+ },
+ {
+ recordType: "defaultEngines",
+ globalDefault: "engine-chromeicon",
+ specificDefaults: [
+ {
+ default: "engine-fr",
+ environment: { excludedRegions: ["DE"], locales: ["fr"] },
+ },
+ {
+ default: "engine-pref",
+ environment: { regions: ["DE"] },
+ },
+ {
+ default: "engine2",
+ environment: { experiment: "test1" },
+ },
+ ],
+ },
+ {
+ recordType: "engineOrders",
+ orders: [],
+ },
+];
+
const testSearchEngine = {
id: "engine",
name: "Test search engine",
@@ -186,7 +376,9 @@ const testFrEngine = {
loadPath: SearchUtils.newSearchConfigEnabled
? "[app]engine-fr@search.mozilla.org"
: "[addon]engine-fr@search.mozilla.org",
- submissionURL: "https://www.google.fr/search?q=&ie=iso-8859-1&oe=iso-8859-1",
+ submissionURL: SearchUtils.newSearchConfigEnabled
+ ? "https://www.google.fr/search?ie=iso-8859-1&oe=iso-8859-1&q="
+ : "https://www.google.fr/search?q=&ie=iso-8859-1&oe=iso-8859-1",
};
const testPrefEngine = {
id: "engine-pref",
@@ -335,7 +527,11 @@ add_setup(async () => {
"_showRemovalOfSearchEngineNotificationBox"
);
- await SearchTestUtils.useTestEngines("data", null, BASE_CONFIG);
+ await SearchTestUtils.useTestEngines(
+ "data",
+ null,
+ SearchUtils.newSearchConfigEnabled ? BASE_CONFIG_V2 : BASE_CONFIG
+ );
await AddonTestUtils.promiseStartupManager();
await Services.search.init();
@@ -344,7 +540,9 @@ add_setup(async () => {
add_task(async function test_configuration_changes_default() {
clearTelemetry();
- await SearchTestUtils.updateRemoteSettingsConfig(MAIN_CONFIG);
+ await SearchTestUtils.updateRemoteSettingsConfig(
+ SearchUtils.newSearchConfigEnabled ? MAIN_CONFIG_V2 : MAIN_CONFIG
+ );
await checkTelemetry(
"config",
diff --git a/toolkit/components/search/tests/xpcshell/test_validate_engines.js b/toolkit/components/search/tests/xpcshell/test_validate_engines.js
index 8a25216057..d311253183 100644
--- a/toolkit/components/search/tests/xpcshell/test_validate_engines.js
+++ b/toolkit/components/search/tests/xpcshell/test_validate_engines.js
@@ -15,18 +15,52 @@ const ss = new SearchService();
add_task(async function test_validate_engines() {
let settings = RemoteSettings(SearchUtils.SETTINGS_KEY);
let config = await settings.get();
- config = config.map(e => {
- return {
- appliesTo: [
- {
- included: {
- everywhere: true,
+
+ if (SearchUtils.newSearchConfigEnabled) {
+ // We do not load engines with the same name. However, in search-config-v2
+ // we have multiple engines named eBay, so we error out.
+ // We never deploy more than one eBay in each environment so this issue
+ // won't be a problem.
+ // Ignore the error and test the configs can be created to engine objects.
+ consoleAllowList.push("Could not load engine");
+ config = config.map(obj => {
+ if (obj.recordType == "engine") {
+ return {
+ recordType: "engine",
+ identifier: obj.identifier,
+ base: {
+ name: obj.base.name,
+ urls: {
+ search: {
+ base: obj.base.urls.search.base || "",
+ searchTermParamName: "q",
+ },
+ },
+ },
+ variants: [
+ {
+ environment: { allRegionsAndLocales: true },
+ },
+ ],
+ };
+ }
+
+ return obj;
+ });
+ } else {
+ config = config.map(e => {
+ return {
+ appliesTo: [
+ {
+ included: {
+ everywhere: true,
+ },
},
- },
- ],
- webExtension: e.webExtension,
- };
- });
+ ],
+ webExtension: e.webExtension,
+ };
+ });
+ }
sinon.stub(settings, "get").returns(config);
await AddonTestUtils.promiseStartupManager();
diff --git a/toolkit/components/search/tests/xpcshell/test_webextensions_install.js b/toolkit/components/search/tests/xpcshell/test_webextensions_install.js
index 6183d6f95a..a05218824a 100644
--- a/toolkit/components/search/tests/xpcshell/test_webextensions_install.js
+++ b/toolkit/components/search/tests/xpcshell/test_webextensions_install.js
@@ -80,7 +80,9 @@ add_task(async function test_install_duplicate_engine() {
let submission = engine.getSubmission("foo");
Assert.equal(
submission.uri.spec,
- "https://duckduckgo.com/?q=foo&t=ffsb",
+ SearchUtils.newSearchConfigEnabled
+ ? "https://duckduckgo.com/?t=ffsb&q=foo"
+ : "https://duckduckgo.com/?q=foo&t=ffsb",
"Should have not changed the app provided engine."
);
@@ -127,7 +129,7 @@ add_task(
let engine = await Services.search.getEngineByName("Multilocale AN");
Assert.ok(
- engine.getIconURL().endsWith("favicon-an.ico"),
+ (await engine.getIconURL()).endsWith("favicon-an.ico"),
"Should have the correct favicon for an extension of one locale using a different locale."
);
Assert.equal(
@@ -156,7 +158,11 @@ add_task(async function test_load_favicon_invalid() {
await observed;
let engine = await Services.search.getEngineByName("Example");
- Assert.equal(null, engine.getIconURL(), "Should not have set an iconURI");
+ Assert.equal(
+ null,
+ await engine.getIconURL(),
+ "Should not have set an iconURI"
+ );
// User uninstalls their engine
await extension.awaitStartup();
@@ -182,7 +188,11 @@ add_task(async function test_load_favicon_invalid_redirect() {
await observed;
let engine = await Services.search.getEngineByName("Example");
- Assert.equal(null, engine.getIconURL(), "Should not have set an iconURI");
+ Assert.equal(
+ null,
+ await engine.getIconURL(),
+ "Should not have set an iconURI"
+ );
// User uninstalls their engine
await extension.awaitStartup();
@@ -208,9 +218,9 @@ add_task(async function test_load_favicon_redirect() {
await promiseEngineChanged;
- Assert.ok(engine.getIconURL(), "Should have set an iconURI");
+ Assert.ok(await engine.getIconURL(), "Should have set an iconURI");
Assert.ok(
- engine.getIconURL().startsWith("data:image/x-icon;base64,"),
+ (await engine.getIconURL()).startsWith("data:image/x-icon;base64,"),
"Should have saved the expected content type for the icon"
);
diff --git a/toolkit/components/search/tests/xpcshell/test_webextensions_startup_duplicate.js b/toolkit/components/search/tests/xpcshell/test_webextensions_startup_duplicate.js
index 3e42fdee13..eb7e67e66e 100644
--- a/toolkit/components/search/tests/xpcshell/test_webextensions_startup_duplicate.js
+++ b/toolkit/components/search/tests/xpcshell/test_webextensions_startup_duplicate.js
@@ -27,7 +27,7 @@ add_task(async function test_install_duplicate_engine_startup() {
let name = "Plain";
let id = "plain@tests.mozilla.org";
consoleAllowList.push(
- `#installExtensionEngine failed for ${id}`,
+ `#createAndAddAddonEngine failed for ${id}`,
`An engine called ${name} already exists`
);
// Do not use SearchTestUtils.installSearchExtension, as we need to manually
@@ -51,7 +51,9 @@ add_task(async function test_install_duplicate_engine_startup() {
let submission = engine.getSubmission("foo");
Assert.equal(
submission.uri.spec,
- "https://duckduckgo.com/?q=foo&t=ffsb",
+ SearchUtils.newSearchConfigEnabled
+ ? "https://duckduckgo.com/?t=ffsb&q=foo"
+ : "https://duckduckgo.com/?q=foo&t=ffsb",
"Should have not changed the app provided engine."
);
diff --git a/toolkit/components/search/tests/xpcshell/xpcshell.toml b/toolkit/components/search/tests/xpcshell/xpcshell.toml
index 47847c93de..7dd023cbec 100644
--- a/toolkit/components/search/tests/xpcshell/xpcshell.toml
+++ b/toolkit/components/search/tests/xpcshell/xpcshell.toml
@@ -33,6 +33,8 @@ support-files = [
"data/engine-same-name/_locales/gd/messages.json",
"data/engines-no-order-hint.json",
"data/engines.json",
+ "data/search-config-v2.json",
+ "data/search-config-v2-no-order-hint.json",
"data/iconsRedirect.sjs",
"data/search.json",
"data/search-legacy.json",
@@ -60,6 +62,7 @@ support-files = [
"simple-engines/basic/manifest.json",
"simple-engines/simple/manifest.json",
"test-extensions/engines.json",
+ "test-extensions/search-config-v2.json",
"test-extensions/plainengine/favicon.ico",
"test-extensions/plainengine/manifest.json",
"test-extensions/special-engine/favicon.ico",
@@ -75,6 +78,9 @@ support-files = [
["test_appDefaultEngine.js"]
+["test_appProvided_icons.js"]
+prefs = ["browser.search.newSearchConfig.enabled=true"]
+
["test_async.js"]
["test_config_engine_params.js"]
@@ -82,6 +88,7 @@ support-files = [
"method-extensions/get/manifest.json",
"method-extensions/post/manifest.json",
"method-extensions/engines.json",
+ "method-extensions/search-config-v2.json",
]
["test_defaultEngine.js"]
@@ -110,15 +117,15 @@ support-files = [
["test_engine_old_selector_override.js"]
+["test_engine_old_selector_remote_override.js"]
+
["test_engine_old_selector_remote_settings.js"]
tags = "remotesettings searchmain"
-["test_engine_old_selector_remote_override.js"]
+["test_engine_selector_defaults.js"]
["test_engine_selector_engine_orders.js"]
-["test_engine_selector_defaults.js"]
-
["test_engine_selector_environment.js"]
["test_engine_selector_variants.js"]
diff --git a/toolkit/components/sessionstore/SessionStoreChangeListener.cpp b/toolkit/components/sessionstore/SessionStoreChangeListener.cpp
index f9de5ec9a0..753c7324fd 100644
--- a/toolkit/components/sessionstore/SessionStoreChangeListener.cpp
+++ b/toolkit/components/sessionstore/SessionStoreChangeListener.cpp
@@ -134,8 +134,7 @@ SessionStoreChangeListener::HandleEvent(dom::Event* aEvent) {
/* static */ already_AddRefed<SessionStoreChangeListener>
SessionStoreChangeListener::Create(BrowsingContext* aBrowsingContext) {
- MOZ_RELEASE_ASSERT(
- StaticPrefs::browser_sessionstore_platform_collection_AtStartup());
+ MOZ_RELEASE_ASSERT(SessionStorePlatformCollection());
if (!aBrowsingContext) {
return nullptr;
}
@@ -356,9 +355,7 @@ void SessionStoreChangeListener::AddEventListeners() {
if (EventTarget* target = GetEventTarget()) {
target->AddSystemEventListener(kInput, this, false);
target->AddSystemEventListener(kScroll, this, false);
- if (StaticPrefs::browser_sessionstore_collect_zoom_AtStartup()) {
- target->AddSystemEventListener(kResize, this, false);
- }
+ target->AddSystemEventListener(kResize, this, false);
mCurrentEventTarget = target;
}
}
@@ -367,9 +364,7 @@ void SessionStoreChangeListener::RemoveEventListeners() {
if (mCurrentEventTarget) {
mCurrentEventTarget->RemoveSystemEventListener(kInput, this, false);
mCurrentEventTarget->RemoveSystemEventListener(kScroll, this, false);
- if (StaticPrefs::browser_sessionstore_collect_zoom_AtStartup()) {
- mCurrentEventTarget->RemoveSystemEventListener(kResize, this, false);
- }
+ mCurrentEventTarget->RemoveSystemEventListener(kResize, this, false);
}
mCurrentEventTarget = nullptr;
diff --git a/toolkit/components/taskscheduler/TaskScheduler.sys.mjs b/toolkit/components/taskscheduler/TaskScheduler.sys.mjs
index 2225328c41..4de5d0a76c 100644
--- a/toolkit/components/taskscheduler/TaskScheduler.sys.mjs
+++ b/toolkit/components/taskscheduler/TaskScheduler.sys.mjs
@@ -93,7 +93,7 @@ export var TaskScheduler = {
*
* options.disabled
* If true the task will be created disabled, so that it will not be run.
- * Ignored on macOS: see comments in TaskSchedulerMacOSImpl.jsm.
+ * Ignored on macOS: see comments in TaskSchedulerMacOSImpl.sys.mjs.
* Default false, intended for tests.
*
* options.executionTimeoutSec
diff --git a/toolkit/components/taskscheduler/TaskSchedulerMacOSImpl.sys.mjs b/toolkit/components/taskscheduler/TaskSchedulerMacOSImpl.sys.mjs
index d47d3c5c14..3cd5b5c51d 100644
--- a/toolkit/components/taskscheduler/TaskSchedulerMacOSImpl.sys.mjs
+++ b/toolkit/components/taskscheduler/TaskSchedulerMacOSImpl.sys.mjs
@@ -36,7 +36,7 @@ ChromeUtils.defineLazyGetter(lazy, "log", () => {
/**
* Task generation and management for macOS, using `launchd` via `launchctl`.
*
- * Implements the API exposed in TaskScheduler.jsm
+ * Implements the API exposed in TaskScheduler.sys.mjs
* Not intended for external use, this is in a separate module to ship the code only
* on macOS, and to expose for testing.
*/
diff --git a/toolkit/components/taskscheduler/TaskSchedulerWinImpl.sys.mjs b/toolkit/components/taskscheduler/TaskSchedulerWinImpl.sys.mjs
index 8d9c15c314..009fc18c38 100644
--- a/toolkit/components/taskscheduler/TaskSchedulerWinImpl.sys.mjs
+++ b/toolkit/components/taskscheduler/TaskSchedulerWinImpl.sys.mjs
@@ -22,7 +22,7 @@ XPCOMUtils.defineLazyServiceGetters(lazy, {
/**
* Task generation and management for Windows, using Task Scheduler 2.0 (taskschd).
*
- * Implements the API exposed in TaskScheduler.jsm
+ * Implements the API exposed in TaskScheduler.sys.mjs
* Not intended for external use, this is in a separate module to ship the code only
* on Windows, and to expose for testing.
*/
diff --git a/toolkit/components/telemetry/Events.yaml b/toolkit/components/telemetry/Events.yaml
index 4fc7482a77..ddba1c690d 100644
--- a/toolkit/components/telemetry/Events.yaml
+++ b/toolkit/components/telemetry/Events.yaml
@@ -1550,7 +1550,18 @@ messaging_experiments:
"moments_page",
"infobar",
"spotlight",
- "featureCallout"
+ "featureCallout",
+ "fxms_message_1",
+ "fxms_message_2",
+ "fxms_message_3",
+ "fxms_message_4",
+ "fxms_message_5",
+ "fxms_message_6",
+ "fxms_message_7",
+ "fxms_message_8",
+ "fxms_message_9",
+ "fxms_message_10",
+ "fxms_message_11"
]
methods: ["reach"]
release_channel_collection: opt-out
@@ -2513,9 +2524,6 @@ security.ui.protectionspopup:
objects: [
"etp_toggle_on",
"etp_toggle_off",
- "sitenotworking_link",
- "send_report_link",
- "send_report_submit",
"social",
"cookies",
"trackers",
@@ -3318,6 +3326,24 @@ security.ui.certerror:
has_sts: If the error page is for a site with HSTS headers or with a pinned key.
panel_open: If the advanced panel was open at the time of the interaction.
+security.ui.tlserror:
+ load:
+ objects: ["abouttlserror"]
+ bug_numbers:
+ - 1881335
+ description: >
+ The about:neterror page is loaded with a TLS error or non-overridable certificate error, keyed by error code.
+ expiry_version: never
+ notification_emails:
+ - dkeeler@mozilla.com
+ - seceng-telemetry@mozilla.com
+ release_channel_collection: opt-out
+ products:
+ - "firefox"
+ record_in_processes: ["content"]
+ extra_keys:
+ is_frame: If the error page is loaded in an iframe.
+
slow_script_warning:
shown:
bug_numbers:
@@ -3893,6 +3919,20 @@ firefoxview_next:
- 1842616
expiry_version: "never"
release_channel_collection: opt-out
+ close_open_tab:
+ objects: ["tabs"]
+ description: >
+ Recorded when a tab is closed via the close button on an open tab row.
+ notification_emails:
+ - firefoxview@mozilla.com
+ products:
+ - "firefox"
+ record_in_processes:
+ - main
+ bug_numbers:
+ - 1857298
+ expiry_version: "never"
+ release_channel_collection: opt-out
browser_context_menu:
objects: ["tabs"]
description: >
diff --git a/toolkit/components/telemetry/Histograms.json b/toolkit/components/telemetry/Histograms.json
index 5611ca930d..223b96f082 100644
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -2491,7 +2491,7 @@
"record_in_processes": ["main"],
"products": ["firefox", "fennec"],
"alert_emails": ["seceng-telemetry@mozilla.com"],
- "expires_in_version": "126",
+ "expires_in_version": "132",
"keyed": true,
"keys": ["known_text", "unknown"],
"kind": "enumerated",
@@ -2504,7 +2504,7 @@
"record_in_processes": ["main"],
"products": ["firefox", "fennec"],
"alert_emails": ["seceng-telemetry@mozilla.com"],
- "expires_in_version": "126",
+ "expires_in_version": "132",
"kind": "boolean",
"releaseChannelCollection": "opt-out",
"description": "Whether a probable font fingerprinting attempt was detected",
@@ -2936,19 +2936,6 @@
"keyed": true,
"description": "The time between navigationStart and the first contentful paint of a foreground http or https root content document, in milliseconds. The contentful paint timestamp is taken during display list building and does not include rasterization or compositing of that paint. This is collected only on page load where the main document uses or suppports HTTP3"
},
- "HTTPS_RR_OPEN_TO_FIRST_SENT": {
- "alert_emails": ["necko@mozilla.com", "kershaw@mozilla.com"],
- "record_in_processes": ["main", "content"],
- "products": ["firefox"],
- "bug_numbers": [1697480],
- "expires_in_version": "126",
- "releaseChannelCollection": "opt-out",
- "kind": "exponential",
- "high": 30000,
- "n_buckets": 50,
- "keyed": true,
- "description": "HTTP channel(keys: uses_https_rr_page, uses_https_rr_sub, no_https_rr_page, no_https_rr_sub): Open -> first byte of request sent (ms)"
- },
"H3P_PERF_PAGE_LOAD_TIME_MS": {
"record_in_processes": ["content"],
"products": ["firefox"],
@@ -3984,25 +3971,12 @@
"n_values": 16,
"description": "SSL Handshake Key Exchange Algorithm for resumed handshake (null=0, rsa=1, dh=2, fortezza=3, ecdh=4)"
},
- "OCSP_AGE_AT_CRLITE_OVERRIDE": {
- "record_in_processes": ["main", "socket"],
- "products": ["firefox"],
- "alert_emails": ["seceng-telemetry@mozilla.com", "jschanck@mozilla.com"],
- "bug_numbers": [1794479, 1817101, 1846897],
- "expires_in_version": "125",
- "kind": "linear",
- "releaseChannelCollection": "opt-out",
- "low": 1,
- "high": 240,
- "n_buckets": 20,
- "description": "When OCSP and CRLite differ, how old is the OCSP response (in hours)?"
- },
"CRLITE_VS_OCSP_RESULT": {
"record_in_processes": ["main", "socket"],
"products": ["firefox"],
"alert_emails": ["seceng-telemetry@mozilla.com", "dkeeler@mozilla.com"],
- "bug_numbers": [1675655, 1758827, 1817102, 1846898],
- "expires_in_version": "125",
+ "bug_numbers": [1675655, 1758827, 1817102, 1846898, 1876443],
+ "expires_in_version": "131",
"kind": "categorical",
"releaseChannelCollection": "opt-out",
"description": "Does CRLite and OCSP fetching agree when a certificate is revoked?",
@@ -4023,8 +3997,8 @@
"record_in_processes": ["main", "socket"],
"products": ["firefox"],
"alert_emails": ["seceng-telemetry@mozilla.com", "jschanck@mozilla.com"],
- "bug_numbers": [1794450, 1817101, 1846897],
- "expires_in_version": "125",
+ "bug_numbers": [1794450, 1817101, 1846897, 1876442],
+ "expires_in_version": "never",
"kind": "categorical",
"releaseChannelCollection": "opt-out",
"description": "Which revocation checking mechanisms were used?",
@@ -7037,7 +7011,7 @@
"n_values": 50,
"releaseChannelCollection": "opt-out",
"bug_numbers": [353804],
- "description": "Update: background update check result code except for no updates found (after we already have an update ready). Possible codes are enumerated by constants starting with CHK_ in toolkit/mozapps/update/UpdateTelemetry.jsm"
+ "description": "Update: background update check result code except for no updates found (after we already have an update ready). Possible codes are enumerated by constants starting with CHK_ in toolkit/mozapps/update/UpdateTelemetry.sys.mjs"
},
"UPDATE_CHECK_EXTENDED_ERROR_EXTERNAL": {
"record_in_processes": ["main"],
@@ -7783,7 +7757,7 @@
"n_values": 30,
"releaseChannelCollection": "opt-out",
"bug_numbers": [1137447],
- "description": "Update: the update wizard page displayed when the UI was closed (mapped in toolkit/mozapps/update/UpdateTelemetry.jsm)"
+ "description": "Update: the update wizard page displayed when the UI was closed (mapped in toolkit/mozapps/update/UpdateTelemetry.sys.mjs)"
},
"UPDATE_NOTIFICATION_SHOWN": {
"record_in_processes": ["main"],
@@ -8348,7 +8322,7 @@
"kind": "enumerated",
"n_values": 15,
"releaseChannelCollection": "opt-out",
- "description": "The browser that data is pulled from. The values correspond to the internal browser ID (see MigrationUtils.jsm)"
+ "description": "The browser that data is pulled from. The values correspond to the internal browser ID (see MigrationUtils.sys.mjs)"
},
"FX_MIGRATION_ERRORS": {
"record_in_processes": ["main"],
@@ -8428,7 +8402,7 @@
"high": 60000,
"releaseChannelCollection": "opt-out",
"keyed": true,
- "description": "Accumulated timer delay (variance between when the timer was expected to fire and when it actually fired) in milliseconds as an indicator for decreased main-thread responsiveness while importing bookmarks from another browser, keyed by the name of the browser (see gAvailableMigratorKeys in MigrationUtils.jsm). The import is happening on a background thread and should ideally not affect the UI noticeably."
+ "description": "Accumulated timer delay (variance between when the timer was expected to fire and when it actually fired) in milliseconds as an indicator for decreased main-thread responsiveness while importing bookmarks from another browser, keyed by the name of the browser (see gAvailableMigratorKeys in MigrationUtils.sys.mjs). The import is happening on a background thread and should ideally not affect the UI noticeably."
},
"FX_MIGRATION_HISTORY_JANK_MS": {
"record_in_processes": ["main"],
@@ -8441,7 +8415,7 @@
"high": 60000,
"releaseChannelCollection": "opt-out",
"keyed": true,
- "description": "Accumulated timer delay (variance between when the timer was expected to fire and when it actually fired) in milliseconds as an indicator for decreased main-thread responsiveness while importing history from another browser, keyed by the name of the browser (see gAvailableMigratorKeys in MigrationUtils.jsm). The import is happening on a background thread and should ideally not affect the UI noticeably."
+ "description": "Accumulated timer delay (variance between when the timer was expected to fire and when it actually fired) in milliseconds as an indicator for decreased main-thread responsiveness while importing history from another browser, keyed by the name of the browser (see gAvailableMigratorKeys in MigrationUtils.sys.mjs). The import is happening on a background thread and should ideally not affect the UI noticeably."
},
"FX_MIGRATION_LOGINS_JANK_MS": {
"record_in_processes": ["main"],
@@ -8454,7 +8428,7 @@
"high": 60000,
"releaseChannelCollection": "opt-out",
"keyed": true,
- "description": "Accumulated timer delay (variance between when the timer was expected to fire and when it actually fired) in milliseconds as an indicator for decreased main-thread responsiveness while importing logins / passwords from another browser, keyed by the name of the browser (see gAvailableMigratorKeys in MigrationUtils.jsm). The import is happening on a background thread and should ideally not affect the UI noticeably. The time with the blocking Keychain dialog on macOS can skew this data."
+ "description": "Accumulated timer delay (variance between when the timer was expected to fire and when it actually fired) in milliseconds as an indicator for decreased main-thread responsiveness while importing logins / passwords from another browser, keyed by the name of the browser (see gAvailableMigratorKeys in MigrationUtils.sys.mjs). The import is happening on a background thread and should ideally not affect the UI noticeably. The time with the blocking Keychain dialog on macOS can skew this data."
},
"FX_MIGRATION_BOOKMARKS_QUANTITY": {
"record_in_processes": ["main"],
@@ -12278,7 +12252,7 @@
"record_in_processes": ["main"],
"products": ["firefox"],
"operating_systems": ["windows"],
- "expires_in_version": "126",
+ "expires_in_version": "never",
"kind": "categorical",
"labels": [
"Success",
@@ -12294,7 +12268,7 @@
"ErrBuild"
],
"releaseChannelCollection": "opt-out",
- "bug_numbers": [1805509],
+ "bug_numbers": [1805509, 1883466],
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"description": "Result of each attempt to set the default browser with SetDefaultExtensionHandlersUserChoice() for pdf extension"
},
@@ -12302,7 +12276,7 @@
"record_in_processes": ["main"],
"products": ["firefox"],
"operating_systems": ["windows"],
- "expires_in_version": "126",
+ "expires_in_version": "never",
"kind": "categorical",
"labels": [
"Success",
@@ -12318,7 +12292,7 @@
"ErrBuild"
],
"releaseChannelCollection": "opt-out",
- "bug_numbers": [1703578, 1736631, 1791928],
+ "bug_numbers": [1703578, 1736631, 1791928, 1881397],
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"description": "Result of each attempt to set the default browser with SetDefaultBrowserUserChoice()"
},
@@ -12373,14 +12347,14 @@
"n_values": 10,
"description": "How often would blocked mixed content be allowed if HSTS upgrades were allowed? 0=display/no-HSTS, 1=display/HSTS, 2=active/no-HSTS, 3=active/HSTS"
},
- "MIXED_CONTENT_DOWNLOADS": {
+ "INSECURE_DOWNLOADS": {
"record_in_processes": ["main", "content"],
"products": ["firefox"],
- "alert_emails": ["seceng-telemetry@mozilla.com", "sstreich@mozilla.com"],
- "bug_numbers": [1646768],
- "expires_in_version": "90",
+ "alert_emails": ["seceng-telemetry@mozilla.com", "ckerschb@mozilla.com"],
+ "bug_numbers": [1877195],
+ "expires_in_version": "130",
"kind": "boolean",
- "description": "Accumulates how many downloads are mixed-content (True = The download is MixedContent, False= is not MixedContent)"
+ "description": "Accumulates how many downloads are insecure (True = The download is insecure, False= The download is secure)"
},
"MIXED_CONTENT_IMAGES": {
"record_in_processes": ["main", "content"],
@@ -12505,7 +12479,7 @@
"expires_in_version": "default",
"kind": "enumerated",
"n_values": 10,
- "description": "BACKGROUND THUMBNAILS: Reason the capture completed (see TEL_CAPTURE_DONE_* constants in BackgroundPageThumbs.jsm)"
+ "description": "BACKGROUND THUMBNAILS: Reason the capture completed (see TEL_CAPTURE_DONE_* constants in BackgroundPageThumbs.sys.mjs)"
},
"FX_THUMBNAILS_BG_CAPTURE_PAGE_LOAD_TIME_MS": {
"record_in_processes": ["main", "content"],
@@ -15990,8 +15964,8 @@
"record_in_processes": ["main"],
"products": ["firefox"],
"alert_emails": ["addons-dev-internal@mozilla.com"],
- "bug_numbers": [1803363, 1850890],
- "expires_in_version": "126",
+ "bug_numbers": [1803363, 1850890, 1881406],
+ "expires_in_version": "138",
"kind": "exponential",
"releaseChannelCollection": "opt-out",
"high": 100000,
@@ -16002,8 +15976,8 @@
"record_in_processes": ["main"],
"products": ["firefox"],
"alert_emails": ["addons-dev-internal@mozilla.com"],
- "bug_numbers": [1803363, 1850890],
- "expires_in_version": "126",
+ "bug_numbers": [1803363, 1850890, 1881406],
+ "expires_in_version": "138",
"kind": "exponential",
"high": 50000000,
"n_buckets": 50,
@@ -16013,8 +15987,8 @@
"record_in_processes": ["main"],
"products": ["firefox"],
"alert_emails": ["addons-dev-internal@mozilla.com"],
- "bug_numbers": [1803363, 1850890],
- "expires_in_version": "126",
+ "bug_numbers": [1803363, 1850890, 1881406],
+ "expires_in_version": "138",
"kind": "exponential",
"releaseChannelCollection": "opt-out",
"high": 100000,
@@ -16025,8 +15999,8 @@
"record_in_processes": ["main"],
"products": ["firefox"],
"alert_emails": ["addons-dev-internal@mozilla.com"],
- "bug_numbers": [1803363, 1850890],
- "expires_in_version": "126",
+ "bug_numbers": [1803363, 1850890, 1881406],
+ "expires_in_version": "138",
"kind": "exponential",
"high": 50000000,
"n_buckets": 50,
@@ -16036,8 +16010,8 @@
"record_in_processes": ["main"],
"products": ["firefox"],
"alert_emails": ["addons-dev-internal@mozilla.com"],
- "bug_numbers": [1803363, 1850890],
- "expires_in_version": "126",
+ "bug_numbers": [1803363, 1850890, 1881406],
+ "expires_in_version": "138",
"kind": "exponential",
"releaseChannelCollection": "opt-out",
"high": 100000,
@@ -16048,8 +16022,8 @@
"record_in_processes": ["main"],
"products": ["firefox"],
"alert_emails": ["addons-dev-internal@mozilla.com"],
- "bug_numbers": [1803363, 1850890],
- "expires_in_version": "126",
+ "bug_numbers": [1803363, 1850890, 1881406],
+ "expires_in_version": "138",
"kind": "exponential",
"releaseChannelCollection": "opt-out",
"high": 100000,
diff --git a/toolkit/components/telemetry/Scalars.yaml b/toolkit/components/telemetry/Scalars.yaml
index 66a8b787e7..fee6594b81 100644
--- a/toolkit/components/telemetry/Scalars.yaml
+++ b/toolkit/components/telemetry/Scalars.yaml
@@ -155,6 +155,24 @@ a11y:
record_in_processes:
- 'main'
+browser.backup:
+ prof_d_disk_space:
+ bug_numbers:
+ - 1884407
+ description: >
+ The total disk space available on the storage device that the profile
+ directory is stored on. To reduce fingerprintability, we round to the
+ nearest 10 megabytes and return the result in kilobytes.
+ expires: never
+ kind: uint
+ notification_emails:
+ - mconley@mozilla.com
+ release_channel_collection: opt-out
+ products:
+ - 'firefox'
+ record_in_processes:
+ - 'main'
+
# The following section contains the browser engagement scalars.
browser.engagement:
max_concurrent_tab_count:
@@ -1444,13 +1462,14 @@ extensions.apis.dnr:
bug_numbers:
- 1803363
- 1850890
+ - 1881406
description: >
Counters for startup cache data hits or misses on initializating
DNR rules for extensions loaded on application startup.
The expected keys are 'hit' and 'miss'.
This probe is mirrored from a Glean metric with the same name.
keyed: true
- expires: '126'
+ expires: '138'
kind: uint
notification_emails:
- addons-dev-internal@mozilla.com
@@ -1462,9 +1481,10 @@ extensions.apis.dnr:
bug_numbers:
- 1803363
- 1850890
+ - 1881406
description: >
Max amount of DNR rules being evaluated.
- expires: '126'
+ expires: '138'
kind: uint
notification_emails:
- addons-dev-internal@mozilla.com
@@ -4028,7 +4048,7 @@ apz:
- 1836870
description: >
Count of overshoot events, where the user reverses scrollwheel direction soon after the last scrollwheel input.
- expires: "126"
+ expires: "134"
kind: uint
notification_emails:
- botond@mozilla.com
@@ -5014,7 +5034,7 @@ update:
to be moved from the downloading update directory to the ready update
directory. This probe counts the results that we get when attempting to
perform this file move. Valid values for the keys for this probe are
- stored in the MOVE_RESULT_* values in UpdateTelemetry.jsm.
+ stored in the MOVE_RESULT_* values in UpdateTelemetry.sys.mjs.
expires: never
kind: uint
keyed: true
@@ -5894,10 +5914,11 @@ browser.searchinit:
- 1789438
- 1811158
- 1846899
+ - 1881408
description: >
Records the number of secure (i.e., using https) OpenSearch search
engines a given user has installed
- expires: "126"
+ expires: "133"
keyed: false
kind: uint
notification_emails:
@@ -5913,10 +5934,11 @@ browser.searchinit:
- 1789438
- 1811158
- 1846899
+ - 1881408
description: >
Records the number of insecure (i.e., using http) OpenSearch search
engines a given user has installed
- expires: "126"
+ expires: "133"
keyed: false
kind: uint
notification_emails:
@@ -5932,10 +5954,11 @@ browser.searchinit:
- 1789438
- 1811158
- 1846899
+ - 1881408
description: >
Records the number of OpenSearch search engines with secure updates
enabled (i.e., using https) a given user has installed
- expires: "126"
+ expires: "133"
keyed: false
kind: uint
notification_emails:
@@ -5951,10 +5974,11 @@ browser.searchinit:
- 1789438
- 1811158
- 1846899
+ - 1881408
description: >
Records the number of OpenSearch search engines with insecure updates
enabled (i.e., using http) a given user has installed
- expires: "126"
+ expires: "133"
keyed: false
kind: uint
notification_emails:
diff --git a/toolkit/components/telemetry/TelemetryStartup.sys.mjs b/toolkit/components/telemetry/TelemetryStartup.sys.mjs
index 445cd906c2..dae0c268db 100644
--- a/toolkit/components/telemetry/TelemetryStartup.sys.mjs
+++ b/toolkit/components/telemetry/TelemetryStartup.sys.mjs
@@ -12,14 +12,14 @@ ChromeUtils.defineESModuleGetters(lazy, {
/**
* TelemetryStartup is needed to forward the "profile-after-change" notification
- * to TelemetryController.jsm.
+ * to TelemetryController.sys.mjs.
*/
export function TelemetryStartup() {}
TelemetryStartup.prototype.QueryInterface = ChromeUtils.generateQI([
"nsIObserver",
]);
-TelemetryStartup.prototype.observe = function (aSubject, aTopic, aData) {
+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);
diff --git a/toolkit/components/telemetry/app/TelemetryControllerContent.sys.mjs b/toolkit/components/telemetry/app/TelemetryControllerContent.sys.mjs
index e5c11d5d28..e4a06a8eea 100644
--- a/toolkit/components/telemetry/app/TelemetryControllerContent.sys.mjs
+++ b/toolkit/components/telemetry/app/TelemetryControllerContent.sys.mjs
@@ -67,7 +67,7 @@ var Impl = {
/**
* This observer drives telemetry.
*/
- observe(aSubject, aTopic, aData) {
+ observe(aSubject, aTopic) {
if (aTopic == "content-process-ready-for-script") {
TelemetryControllerBase.configureLogging();
diff --git a/toolkit/components/telemetry/app/TelemetryEnvironment.sys.mjs b/toolkit/components/telemetry/app/TelemetryEnvironment.sys.mjs
index 302a4040b3..76bc2579eb 100644
--- a/toolkit/components/telemetry/app/TelemetryEnvironment.sys.mjs
+++ b/toolkit/components/telemetry/app/TelemetryEnvironment.sys.mjs
@@ -674,7 +674,7 @@ EnvironmentAddonBuilder.prototype = {
},
// nsIObserver
- observe(aSubject, aTopic, aData) {
+ observe(aSubject, aTopic) {
this._environment._log.trace("observe - Topic " + aTopic);
if (aTopic == GMP_PROVIDER_REGISTERED_TOPIC) {
Services.obs.removeObserver(this, GMP_PROVIDER_REGISTERED_TOPIC);
diff --git a/toolkit/components/telemetry/app/TelemetryReportingPolicy.sys.mjs b/toolkit/components/telemetry/app/TelemetryReportingPolicy.sys.mjs
index 6fe155a2fa..d49dae95f9 100644
--- a/toolkit/components/telemetry/app/TelemetryReportingPolicy.sys.mjs
+++ b/toolkit/components/telemetry/app/TelemetryReportingPolicy.sys.mjs
@@ -514,8 +514,7 @@ var TelemetryReportingPolicyImpl = {
aBrowser,
aWebProgress,
aRequest,
- aStateFlags,
- aStatus
+ aStateFlags
) => {
if (
aWebProgress.isTopLevel &&
@@ -555,7 +554,7 @@ var TelemetryReportingPolicyImpl = {
return true;
},
- observe(aSubject, aTopic, aData) {
+ observe(aSubject, aTopic) {
if (aTopic != "sessionstore-windows-restored") {
return;
}
diff --git a/toolkit/components/telemetry/app/TelemetryScheduler.sys.mjs b/toolkit/components/telemetry/app/TelemetryScheduler.sys.mjs
index 2fc94dc8fc..539447a8ca 100644
--- a/toolkit/components/telemetry/app/TelemetryScheduler.sys.mjs
+++ b/toolkit/components/telemetry/app/TelemetryScheduler.sys.mjs
@@ -248,7 +248,7 @@ export var TelemetryScheduler = {
/**
* The notifications handler.
*/
- observe(aSubject, aTopic, aData) {
+ observe(aSubject, aTopic) {
this._log.trace("observe - aTopic: " + aTopic);
switch (aTopic) {
case "idle":
diff --git a/toolkit/components/telemetry/app/TelemetryStorage.sys.mjs b/toolkit/components/telemetry/app/TelemetryStorage.sys.mjs
index 062a050a9f..ef7ff88bb2 100644
--- a/toolkit/components/telemetry/app/TelemetryStorage.sys.mjs
+++ b/toolkit/components/telemetry/app/TelemetryStorage.sys.mjs
@@ -664,10 +664,10 @@ var TelemetryStorageImpl = {
let promise = this._saveArchivedPingTask(ping);
this._activelyArchiving.add(promise);
promise.then(
- r => {
+ () => {
this._activelyArchiving.delete(promise);
},
- e => {
+ () => {
this._activelyArchiving.delete(promise);
}
);
@@ -1047,7 +1047,7 @@ var TelemetryStorageImpl = {
ping.id,
ping.timestampCreated,
ping.type
- ).catch(e =>
+ ).catch(() =>
this._log.error(
"_enforceArchiveQuota - failed to remove archived ping" + ping.id
)
diff --git a/toolkit/components/telemetry/dap/DAPTelemetrySender.sys.mjs b/toolkit/components/telemetry/dap/DAPTelemetrySender.sys.mjs
index 0a2516ad1f..9dcc949788 100644
--- a/toolkit/components/telemetry/dap/DAPTelemetrySender.sys.mjs
+++ b/toolkit/components/telemetry/dap/DAPTelemetrySender.sys.mjs
@@ -73,7 +73,7 @@ export const DAPTelemetrySender = new (class {
this.timeout_value()
);
- lazy.NimbusFeatures.dapTelemetry.onUpdate(async (event, reason) => {
+ lazy.NimbusFeatures.dapTelemetry.onUpdate(async () => {
if (typeof this.counters !== "undefined") {
await this.sendTestReports(tasks, 30 * 1000, "nimbus-update");
}
diff --git a/toolkit/components/telemetry/dap/DAPVisitCounter.sys.mjs b/toolkit/components/telemetry/dap/DAPVisitCounter.sys.mjs
index dfee5d0ff6..fef2c48c4d 100644
--- a/toolkit/components/telemetry/dap/DAPVisitCounter.sys.mjs
+++ b/toolkit/components/telemetry/dap/DAPVisitCounter.sys.mjs
@@ -50,7 +50,7 @@ export const DAPVisitCounter = new (class {
}
};
- lazy.NimbusFeatures.dapTelemetry.onUpdate(async (event, reason) => {
+ lazy.NimbusFeatures.dapTelemetry.onUpdate(async () => {
if (typeof this.counters !== "undefined") {
await this.send(30 * 1000, "nimbus-update");
}
diff --git a/toolkit/components/telemetry/docs/data/health-ping.rst b/toolkit/components/telemetry/docs/data/health-ping.rst
index e5655924e1..8b1be78105 100644
--- a/toolkit/components/telemetry/docs/data/health-ping.rst
+++ b/toolkit/components/telemetry/docs/data/health-ping.rst
@@ -38,7 +38,7 @@ The client id is submitted with this ping.
Send behavior
-------------
-``HealthPing.jsm`` tracks several problems:
+``HealthPing.sys.mjs`` tracks several problems:
* The size of other assembled pings exceeds the ping limit.
* Failures while sending other pings.
diff --git a/toolkit/components/telemetry/docs/obsolete/fhr/architecture.rst b/toolkit/components/telemetry/docs/obsolete/fhr/architecture.rst
index 2e9c37f3d3..bfdce6daab 100644
--- a/toolkit/components/telemetry/docs/obsolete/fhr/architecture.rst
+++ b/toolkit/components/telemetry/docs/obsolete/fhr/architecture.rst
@@ -4,11 +4,11 @@
Architecture
============
-``healthreporter.jsm`` contains the main interface for FHR, the
+``healthreporter.sys.mjs`` contains the main interface for FHR, the
``HealthReporter`` type. An instance of this is created by the
``data_reporting_service``.
-``providers.jsm`` contains numerous ``Metrics.Provider`` and
+``providers.sys.mjs`` contains numerous ``Metrics.Provider`` and
``Metrics.Measurement`` used for collecting application metrics. If you
are looking for the FHR probes, this is where they are.
@@ -157,7 +157,7 @@ See ``HealthReportComponents.manifest`` for providers defined in this
directory.
Essentially, the category manager receives the name of a JS type and the
-URI of a JSM to import that exports this symbol. At run-time, the
+URI of a sys.mjs to import that exports this symbol. At run-time, the
providers registered in the category manager are instantiated.
Providers are registered via the category manager to make registration
diff --git a/toolkit/components/telemetry/docs/obsolete/fhr/dataformat.rst b/toolkit/components/telemetry/docs/obsolete/fhr/dataformat.rst
index 730d7514b8..8a9d9a591b 100644
--- a/toolkit/components/telemetry/docs/obsolete/fhr/dataformat.rst
+++ b/toolkit/components/telemetry/docs/obsolete/fhr/dataformat.rst
@@ -1700,7 +1700,7 @@ Version 1 was introduced with Firefox 37 and includes the following properties:
state
Corresponds to either a STATE_USER_* string or a STATE_INTERNAL_* string in
- FxaMigration.jsm. This reflects a state where we are waiting for the user,
+ FxaMigration.sys.mjs. This reflects a state where we are waiting for the user,
or waiting for some internal process to complete on the way to completing
the migration.
diff --git a/toolkit/components/telemetry/pings/EventPing.sys.mjs b/toolkit/components/telemetry/pings/EventPing.sys.mjs
index 36e8489e37..c3ece746f3 100644
--- a/toolkit/components/telemetry/pings/EventPing.sys.mjs
+++ b/toolkit/components/telemetry/pings/EventPing.sys.mjs
@@ -103,7 +103,7 @@ export var TelemetryEventPing = {
this._clearTimer();
},
- observe(aSubject, aTopic, aData) {
+ observe(aSubject, aTopic) {
switch (aTopic) {
case EVENT_LIMIT_REACHED_TOPIC:
this._log.trace("event limit reached");
diff --git a/toolkit/components/telemetry/pings/TelemetrySession.sys.mjs b/toolkit/components/telemetry/pings/TelemetrySession.sys.mjs
index f564cd41dd..c7332c2a36 100644
--- a/toolkit/components/telemetry/pings/TelemetrySession.sys.mjs
+++ b/toolkit/components/telemetry/pings/TelemetrySession.sys.mjs
@@ -1122,7 +1122,7 @@ var Impl = {
/**
* This observer drives telemetry.
*/
- observe(aSubject, aTopic, aData) {
+ observe(aSubject, aTopic) {
this._log.trace("observe - " + aTopic + " notified.");
switch (aTopic) {
diff --git a/toolkit/components/telemetry/tests/browser/browser_DynamicScalars.js b/toolkit/components/telemetry/tests/browser/browser_DynamicScalars.js
index 966c7c1be3..1b0814e416 100644
--- a/toolkit/components/telemetry/tests/browser/browser_DynamicScalars.js
+++ b/toolkit/components/telemetry/tests/browser/browser_DynamicScalars.js
@@ -12,7 +12,7 @@ const CONTENT_CREATED = "ipc:content-created";
async function waitForProcessesScalars(
aProcesses,
aKeyed,
- aAdditionalCondition = data => true
+ aAdditionalCondition = () => true
) {
await TestUtils.waitForCondition(() => {
const scalars = aKeyed
diff --git a/toolkit/components/telemetry/tests/integration/tests/conftest.py b/toolkit/components/telemetry/tests/integration/tests/conftest.py
index e9cbdeff08..147ce7bc0a 100644
--- a/toolkit/components/telemetry/tests/integration/tests/conftest.py
+++ b/toolkit/components/telemetry/tests/integration/tests/conftest.py
@@ -127,8 +127,8 @@ class Browser(object):
with self.marionette.using_context(self.marionette.CONTEXT_CHROME):
return self.marionette.execute_script(
"""\
- const { ClientID } = ChromeUtils.import(
- "resource://gre/modules/ClientID.jsm"
+ const { ClientID } = ChromeUtils.importESModule(
+ "resource://gre/modules/ClientID.sys.mjs"
);
return ClientID.getCachedClientID();
"""
@@ -165,8 +165,8 @@ class Browser(object):
# triggers an "environment-change" ping.
script = """\
let [resolve] = arguments;
- const { TelemetryEnvironment } = ChromeUtils.import(
- "resource://gre/modules/TelemetryEnvironment.jsm"
+ const { TelemetryEnvironment } = ChromeUtils.importESModule(
+ "resource://gre/modules/TelemetryEnvironment.sys.mjs"
);
TelemetryEnvironment.onInitialized().then(resolve);
"""
diff --git a/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/runner.py b/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/runner.py
index 37a91023ce..29b03e4f55 100644
--- a/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/runner.py
+++ b/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/runner.py
@@ -52,6 +52,9 @@ class TelemetryTestRunner(BaseMarionetteTestRunner):
# Disable Normandy a little harder (bug 1608807).
# This should also disable Nimbus.
"app.shield.optoutstudies.enabled": False,
+ # Bug 1789727: Keep the screenshots extension disabled to avoid
+ # disabling the addon resulting in extra subsessions
+ "screenshots.browser.component.enabled": False,
}
)
diff --git a/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/testcase.py b/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/testcase.py
index d30fd67986..91d0336975 100644
--- a/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/testcase.py
+++ b/toolkit/components/telemetry/tests/marionette/harness/telemetry_harness/testcase.py
@@ -175,8 +175,8 @@ class TelemetryTestCase(WindowManagerMixin, MarionetteTestCase):
# triggers an "environment-change" ping.
script = """\
let [resolve] = arguments;
- const { TelemetryEnvironment } = ChromeUtils.import(
- "resource://gre/modules/TelemetryEnvironment.jsm"
+ const { TelemetryEnvironment } = ChromeUtils.importESModule(
+ "resource://gre/modules/TelemetryEnvironment.sys.mjs"
);
TelemetryEnvironment.onInitialized().then(resolve);
"""
@@ -205,8 +205,8 @@ class TelemetryTestCase(WindowManagerMixin, MarionetteTestCase):
with self.marionette.using_context(self.marionette.CONTEXT_CHROME):
return self.marionette.execute_script(
"""\
- const { ClientID } = ChromeUtils.import(
- "resource://gre/modules/ClientID.jsm"
+ const { ClientID } = ChromeUtils.importESModule(
+ "resource://gre/modules/ClientID.sys.mjs"
);
return ClientID.getCachedClientID();
"""
@@ -218,8 +218,8 @@ class TelemetryTestCase(WindowManagerMixin, MarionetteTestCase):
with self.marionette.using_context(self.marionette.CONTEXT_CHROME):
ping_data = self.marionette.execute_script(
"""\
- const { TelemetryController } = ChromeUtils.import(
- "resource://gre/modules/TelemetryController.jsm"
+ const { TelemetryController } = ChromeUtils.importESModule(
+ "resource://gre/modules/TelemetryController.sys.mjs"
);
return TelemetryController.getCurrentPingData(true);
"""
diff --git a/toolkit/components/telemetry/tests/marionette/tests/client/test_deletion_request_ping.py b/toolkit/components/telemetry/tests/marionette/tests/client/test_deletion_request_ping.py
index 92219c7dc7..f376c4416e 100644
--- a/toolkit/components/telemetry/tests/marionette/tests/client/test_deletion_request_ping.py
+++ b/toolkit/components/telemetry/tests/marionette/tests/client/test_deletion_request_ping.py
@@ -55,8 +55,8 @@ class TestDeletionRequestPing(TelemetryTestCase):
return self.marionette.execute_async_script(
"""
let [resolve] = arguments;
- const { ClientID } = ChromeUtils.import(
- "resource://gre/modules/ClientID.jsm"
+ const { ClientID } = ChromeUtils.importESModule(
+ "resource://gre/modules/ClientID.sys.mjs"
);
ClientID.getClientID().then(resolve);
""",
diff --git a/toolkit/components/telemetry/tests/marionette/tests/client/test_main_tab_scalars.py b/toolkit/components/telemetry/tests/marionette/tests/client/test_main_tab_scalars.py
index d548d7ccc9..b62fbc9f2b 100644
--- a/toolkit/components/telemetry/tests/marionette/tests/client/test_main_tab_scalars.py
+++ b/toolkit/components/telemetry/tests/marionette/tests/client/test_main_tab_scalars.py
@@ -20,8 +20,8 @@ class TestMainTabScalars(TelemetryTestCase):
# test.
self.marionette.execute_script(
"""
- const { BrowserUsageTelemetry } = ChromeUtils.import(
- "resource:///modules/BrowserUsageTelemetry.jsm"
+ const { BrowserUsageTelemetry } = ChromeUtils.importESModule(
+ "resource:///modules/BrowserUsageTelemetry.sys.mjs"
);
BrowserUsageTelemetry._onTabsOpenedTask._timeoutMs = 0;
diff --git a/toolkit/components/telemetry/tests/unit/data/search-extensions/search-config-v2.json b/toolkit/components/telemetry/tests/unit/data/search-extensions/search-config-v2.json
new file mode 100644
index 0000000000..4117f72662
--- /dev/null
+++ b/toolkit/components/telemetry/tests/unit/data/search-extensions/search-config-v2.json
@@ -0,0 +1,40 @@
+{
+ "data": [
+ {
+ "recordType": "engine",
+ "identifier": "telemetrySearchIdentifier",
+ "base": {
+ "name": "telemetrySearchIdentifier",
+ "urls": {
+ "search": {
+ "base": "https://ar.wikipedia.org/wiki/%D8%AE%D8%A7%D8%B5:%D8%A8%D8%AD%D8%AB",
+ "params": [
+ {
+ "name": "sourceId",
+ "value": "Mozilla-search"
+ }
+ ],
+ "searchTermParamName": "search"
+ },
+ "suggestions": {
+ "base": "https://ar.wikipedia.org/w/api.php?action=opensearch",
+ "searchTermParamName": "search"
+ }
+ }
+ },
+ "variants": [
+ {
+ "environment": { "allRegionsAndLocales": true }
+ }
+ ]
+ },
+ {
+ "recordType": "defaultEngines",
+ "specificDefaults": []
+ },
+ {
+ "recordType": "engineOrders",
+ "orders": []
+ }
+ ]
+}
diff --git a/toolkit/components/telemetry/tests/unit/head.js b/toolkit/components/telemetry/tests/unit/head.js
index 7a9d8e41a2..e6cc7f4aa8 100644
--- a/toolkit/components/telemetry/tests/unit/head.js
+++ b/toolkit/components/telemetry/tests/unit/head.js
@@ -76,7 +76,7 @@ const PingServer = {
},
resetPingHandler() {
- this.registerPingHandler((request, response) => {
+ this.registerPingHandler(request => {
let r = request;
this._log.trace(
`defaultPingHandler() - ${r.method} ${r.scheme}://${r.host}:${r.port}${r.path}`
@@ -410,7 +410,7 @@ function fakeGzipCompressStringForNextPing(length) {
"resource://gre/modules/TelemetrySend.sys.mjs"
);
let largePayload = generateString(length);
- Policy.gzipCompressString = data => {
+ Policy.gzipCompressString = () => {
Policy.gzipCompressString = gzipCompressString;
return largePayload;
};
@@ -543,7 +543,7 @@ if (runningInParent) {
}
fakePingSendTimer(
- (callback, timeout) => {
+ callback => {
Services.tm.dispatchToMainThread(() => callback());
},
() => {}
diff --git a/toolkit/components/telemetry/tests/unit/test_EventPing.js b/toolkit/components/telemetry/tests/unit/test_EventPing.js
index 450e88a846..8ff05ee2e2 100644
--- a/toolkit/components/telemetry/tests/unit/test_EventPing.js
+++ b/toolkit/components/telemetry/tests/unit/test_EventPing.js
@@ -8,7 +8,7 @@ ChromeUtils.defineESModuleGetters(this, {
TelemetryEventPing: "resource://gre/modules/EventPing.sys.mjs",
});
-function checkPingStructure(type, payload, options) {
+function checkPingStructure(type, payload) {
Assert.equal(
type,
TelemetryEventPing.EVENT_PING_TYPE,
@@ -84,7 +84,7 @@ add_task(async function test_eventLimitReached() {
fakePolicy(pass, pass, fail);
recordEvents(999);
fakePolicy(
- (callback, delay) => {
+ () => {
Telemetry.recordEvent("telemetry.test", "test2", "object1");
fakePolicy(pass, pass, (type, payload, options) => {
checkPingStructure(type, payload, options);
@@ -120,7 +120,7 @@ add_task(async function test_eventLimitReached() {
fakePolicy(fail, fail, fail);
recordEvents(998);
fakePolicy(
- (callback, delay) => {
+ callback => {
Telemetry.recordEvent("telemetry.test", "test2", "object2");
Telemetry.recordEvent("telemetry.test", "test2", "object2");
fakePolicy(pass, pass, (type, payload, options) => {
@@ -162,7 +162,7 @@ add_task(async function test_eventLimitReached() {
// the two events we lost.
fakePolicy(fail, fail, fail);
recordEvents(999);
- fakePolicy((callback, delay) => {
+ fakePolicy(callback => {
fakePolicy(pass, pass, (type, payload, options) => {
checkPingStructure(type, payload, options);
Assert.ok(options.addClientId, "Adds the client id.");
diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetryController.js b/toolkit/components/telemetry/tests/unit/test_TelemetryController.js
index 5af7adf6b0..d18dfd55e8 100644
--- a/toolkit/components/telemetry/tests/unit/test_TelemetryController.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetryController.js
@@ -536,7 +536,7 @@ add_task(async function test_midnightPingSendFuzzing() {
// A ping after midnight within the fuzzing delay should not get sent.
now = new Date(2030, 5, 2, 0, 40, 0);
fakeNow(now);
- PingServer.registerPingHandler((req, res) => {
+ PingServer.registerPingHandler(() => {
Assert.ok(false, "No ping should be received yet.");
});
let timerPromise = waitForTimer();
diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetryEnvironment.js b/toolkit/components/telemetry/tests/unit/test_TelemetryEnvironment.js
index aa9fbf11f0..a67788623d 100644
--- a/toolkit/components/telemetry/tests/unit/test_TelemetryEnvironment.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetryEnvironment.js
@@ -426,7 +426,7 @@ add_task(async function test_addonsWatch_InterestingChange() {
return new Promise(resolve =>
TelemetryEnvironment.registerChangeListener(
"testWatchAddons_Changes" + aExpected,
- (reason, data) => {
+ reason => {
Assert.equal(reason, "addons-changed");
receivedNotifications++;
resolve();
@@ -630,13 +630,10 @@ add_task(async function test_addons() {
};
let deferred = Promise.withResolvers();
- TelemetryEnvironment.registerChangeListener(
- "test_WebExtension",
- (reason, data) => {
- Assert.equal(reason, "addons-changed");
- deferred.resolve();
- }
- );
+ TelemetryEnvironment.registerChangeListener("test_WebExtension", reason => {
+ Assert.equal(reason, "addons-changed");
+ deferred.resolve();
+ });
// Install an add-on so we have some data.
let addon = await installXPIFromURL(ADDON_INSTALL_URL);
@@ -881,7 +878,7 @@ add_task(async function test_collectionWithbrokenAddonData() {
return new Promise(resolve =>
TelemetryEnvironment.registerChangeListener(
"testBrokenAddon_collection" + aExpected,
- (reason, data) => {
+ reason => {
Assert.equal(reason, "addons-changed");
receivedNotifications++;
resolve();
@@ -1052,7 +1049,7 @@ add_task(async function test_experimentsAPI() {
const EXPERIMENT2 = "experiment-2";
const EXPERIMENT2_BRANCH = "other-branch";
- let checkExperiment = (environmentData, id, branch, type = null) => {
+ let checkExperiment = (environmentData, id, branch) => {
Assert.ok(
"experiments" in environmentData,
"The current environment must report the experiment annotations."
diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetryEnvironment_search.js b/toolkit/components/telemetry/tests/unit/test_TelemetryEnvironment_search.js
index 0dc3849508..5d042cfb33 100644
--- a/toolkit/components/telemetry/tests/unit/test_TelemetryEnvironment_search.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetryEnvironment_search.js
@@ -7,6 +7,10 @@ const { TelemetryEnvironment } = ChromeUtils.importESModule(
const { SearchTestUtils } = ChromeUtils.importESModule(
"resource://testing-common/SearchTestUtils.sys.mjs"
);
+
+const { SearchUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/SearchUtils.sys.mjs"
+);
const { TelemetryEnvironmentTesting } = ChromeUtils.importESModule(
"resource://testing-common/TelemetryEnvironmentTesting.sys.mjs"
);
@@ -140,10 +144,13 @@ async function checkDefaultSearch(privateOn, reInitSearchService) {
Assert.equal(data.settings.defaultSearchEngine, "telemetrySearchIdentifier");
let expectedSearchEngineData = {
name: "telemetrySearchIdentifier",
- loadPath: "[addon]telemetrySearchIdentifier@search.mozilla.org",
+ loadPath: SearchUtils.newSearchConfigEnabled
+ ? "[app]telemetrySearchIdentifier@search.mozilla.org"
+ : "[addon]telemetrySearchIdentifier@search.mozilla.org",
origin: "default",
- submissionURL:
- "https://ar.wikipedia.org/wiki/%D8%AE%D8%A7%D8%B5:%D8%A8%D8%AD%D8%AB?search=&sourceId=Mozilla-search",
+ submissionURL: SearchUtils.newSearchConfigEnabled
+ ? "https://ar.wikipedia.org/wiki/%D8%AE%D8%A7%D8%B5:%D8%A8%D8%AD%D8%AB?sourceId=Mozilla-search&search="
+ : "https://ar.wikipedia.org/wiki/%D8%AE%D8%A7%D8%B5:%D8%A8%D8%AD%D8%AB?search=&sourceId=Mozilla-search",
};
Assert.deepEqual(
data.settings.defaultSearchEngineData,
diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetryEvents.js b/toolkit/components/telemetry/tests/unit/test_TelemetryEvents.js
index 4369c5a608..3d5e826914 100644
--- a/toolkit/components/telemetry/tests/unit/test_TelemetryEvents.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetryEvents.js
@@ -279,7 +279,7 @@ add_task(async function test_recording() {
// Check that the events were summarized properly.
let summaries = {};
- expected.forEach(({ optout, event }) => {
+ expected.forEach(({ event }) => {
let [category, eObject, method] = event;
let uniqueEventName = `${category}#${eObject}#${method}`;
if (!(uniqueEventName in summaries)) {
diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetryHistograms.js b/toolkit/components/telemetry/tests/unit/test_TelemetryHistograms.js
index 1ac0c76351..bf66ef0d99 100644
--- a/toolkit/components/telemetry/tests/unit/test_TelemetryHistograms.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetryHistograms.js
@@ -12,7 +12,7 @@ function numberRange(lower, upper) {
return a;
}
-function check_histogram(histogram_type, name, min, max, bucket_count) {
+function check_histogram(histogram_type, name, min, max) {
var h = Telemetry.getHistogramById(name);
h.add(0);
var s = h.snapshot();
@@ -507,7 +507,7 @@ add_task(async function test_keyed_boolean_histogram() {
sum: 1,
values: { 0: 0, 1: 1, 2: 0 },
};
- let testHistograms = numberRange(0, 3).map(i =>
+ let testHistograms = numberRange(0, 3).map(() =>
JSON.parse(JSON.stringify(histogramBase))
);
let testKeys = [];
@@ -558,7 +558,7 @@ add_task(async function test_keyed_count_histogram() {
sum: 0,
values: { 0: 1, 1: 0 },
};
- let testHistograms = numberRange(0, 5).map(i =>
+ let testHistograms = numberRange(0, 5).map(() =>
JSON.parse(JSON.stringify(histogramBase))
);
let testKeys = [];
diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetryLateWrites.js b/toolkit/components/telemetry/tests/unit/test_TelemetryLateWrites.js
index 00891c36e8..fbc2e57271 100644
--- a/toolkit/components/telemetry/tests/unit/test_TelemetryLateWrites.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetryLateWrites.js
@@ -112,11 +112,7 @@ function actual_test() {
Assert.ok("memoryMap" in lateWrites);
Assert.equal(lateWrites.memoryMap.length, N_MODULES);
for (let id in LOADED_MODULES) {
- let matchingLibrary = lateWrites.memoryMap.filter(function (
- library,
- idx,
- array
- ) {
+ let matchingLibrary = lateWrites.memoryMap.filter(function (library) {
return library[1] == id;
});
Assert.equal(matchingLibrary.length, 1);
@@ -132,7 +128,7 @@ function actual_test() {
let second_stack = lateWrites.stacks[1];
function stackChecker(canonicalStack) {
let unevalCanonicalStack = uneval(canonicalStack);
- return function (obj, idx, array) {
+ return function (obj) {
return unevalCanonicalStack == obj;
};
}
diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetrySendOldPings.js b/toolkit/components/telemetry/tests/unit/test_TelemetrySendOldPings.js
index 7b7b0f674d..716b459680 100644
--- a/toolkit/components/telemetry/tests/unit/test_TelemetrySendOldPings.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetrySendOldPings.js
@@ -91,7 +91,7 @@ function assertReceivedPings(aExpectedNum) {
*
* @param aRequest the HTTP request sent from HttpServer.
*/
-function pingHandler(aRequest) {
+function pingHandler() {
gSeenPings++;
}
diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetrySession.js b/toolkit/components/telemetry/tests/unit/test_TelemetrySession.js
index 9267c96ce1..fbd2e5bee2 100644
--- a/toolkit/components/telemetry/tests/unit/test_TelemetrySession.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetrySession.js
@@ -923,7 +923,7 @@ add_task(async function test_dailyDuplication() {
Assert.equal(ping.payload.info.reason, REASON_DAILY);
// We don't expect to receive any other daily ping in this test, so assert if we do.
- PingServer.registerPingHandler((req, res) => {
+ PingServer.registerPingHandler(() => {
Assert.ok(
false,
"No more daily pings should be sent/received in this test."
@@ -967,7 +967,7 @@ add_task(async function test_dailyOverdue() {
fakeNow(now);
// Assert if we receive something!
- PingServer.registerPingHandler((req, res) => {
+ PingServer.registerPingHandler(() => {
Assert.ok(false, "No daily ping should be received if not overdue!.");
});
@@ -1396,7 +1396,7 @@ add_task(async function test_sendFirstShutdownPing() {
// the appropriate behavior from the preference flags.
// Assert failure if we recive a ping.
- PingServer.registerPingHandler((req, res) => {
+ PingServer.registerPingHandler(req => {
const receivedPing = decodeRequestPayload(req);
Assert.ok(
false,
@@ -2038,7 +2038,7 @@ add_task(async function test_schedulerEnvironmentReschedules() {
);
// We don't expect to receive any daily ping in this test, so assert if we do.
- PingServer.registerPingHandler((req, res) => {
+ PingServer.registerPingHandler(req => {
const receivedPing = decodeRequestPayload(req);
Assert.ok(
false,
@@ -2072,7 +2072,7 @@ add_task(async function test_schedulerNothingDue() {
await TelemetryController.testReset();
// We don't expect to receive any ping in this test, so assert if we do.
- PingServer.registerPingHandler((req, res) => {
+ PingServer.registerPingHandler(req => {
const receivedPing = decodeRequestPayload(req);
Assert.ok(
false,
diff --git a/toolkit/components/telemetry/tests/unit/xpcshell.toml b/toolkit/components/telemetry/tests/unit/xpcshell.toml
index 2dbaaff1a3..dbff9c627c 100644
--- a/toolkit/components/telemetry/tests/unit/xpcshell.toml
+++ b/toolkit/components/telemetry/tests/unit/xpcshell.toml
@@ -5,6 +5,7 @@ firefox-appdir = "browser"
# xpcshell fails to install tests if we move them under the test entry.
support-files = [
"data/search-extensions/engines.json",
+ "data/search-extensions/search-config-v2.json",
"data/search-extensions/telemetrySearchIdentifier/manifest.json",
"engine.xml",
"system.xpi",
diff --git a/toolkit/components/terminator/nsTerminator.cpp b/toolkit/components/terminator/nsTerminator.cpp
index 3a8f9f4ba6..5a1f9693b8 100644
--- a/toolkit/components/terminator/nsTerminator.cpp
+++ b/toolkit/components/terminator/nsTerminator.cpp
@@ -606,9 +606,7 @@ void nsTerminator::UpdateTelemetry() {
void nsTerminator::UpdateCrashReport(const char* aTopic) {
// In case of crash, we wish to know where in shutdown we are
- nsAutoCString report(aTopic);
-
- Unused << CrashReporter::AnnotateCrashReport(
- CrashReporter::Annotation::ShutdownProgress, report);
+ CrashReporter::RecordAnnotationCString(
+ CrashReporter::Annotation::ShutdownProgress, aTopic);
}
} // namespace mozilla
diff --git a/toolkit/components/tooltiptext/TooltipTextProvider.sys.mjs b/toolkit/components/tooltiptext/TooltipTextProvider.sys.mjs
index 0ddd594cb6..96a3e8dd5f 100644
--- a/toolkit/components/tooltiptext/TooltipTextProvider.sys.mjs
+++ b/toolkit/components/tooltiptext/TooltipTextProvider.sys.mjs
@@ -4,6 +4,37 @@
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.
@@ -29,142 +60,98 @@ TooltipTextProvider.prototype = {
var titleText = null;
var XLinkTitleText = null;
- var SVGTitleText = null;
- var XULtooltiptextText = null;
var lookingForSVGTitle = true;
var direction = tipElement.ownerDocument.dir;
- // If the element is invalid per HTML5 Forms specifications and has no title,
- // show the constraint validation error message.
- if (
- (defView.HTMLInputElement.isInstance(tipElement) ||
- defView.HTMLTextAreaElement.isInstance(tipElement) ||
- defView.HTMLSelectElement.isInstance(tipElement) ||
- defView.HTMLButtonElement.isInstance(tipElement)) &&
- !tipElement.hasAttribute("title") &&
- (!tipElement.form || !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;
- }
+ for (; tipElement; tipElement = tipElement.flattenedTreeParentNode) {
+ if (tipElement.nodeType != defView.Node.ELEMENT_NODE) {
+ continue;
+ }
+ if (tipElement.namespaceURI == XUL_NS) {
+ lookingForSVGTitle = false;
+ // NOTE: getAttribute behaves differently for XUL so we can't rely on
+ // it returning null, see bug 232598.
+ titleText = tipElement.hasAttribute("tooltiptext")
+ ? tipElement.getAttribute("tooltiptext")
+ : null;
+ } else if (!defView.SVGElement.isInstance(tipElement)) {
+ lookingForSVGTitle = false;
+ titleText = tipElement.getAttribute("title");
+ }
- // If the element is an <input type='file'> without a title, we should show
- // the current file selection.
- if (
- !titleText &&
- defView.HTMLInputElement.isInstance(tipElement) &&
- tipElement.type == "file" &&
- !tipElement.hasAttribute("title")
- ) {
- let files = tipElement.files;
-
- try {
- var bundle = Services.strings.createBundle(
- "chrome://global/locale/layout/HtmlForm.properties"
- );
- if (!files.length) {
- if (tipElement.multiple) {
- titleText = bundle.GetStringFromName("NoFilesSelected");
- } else {
- titleText = bundle.GetStringFromName("NoFileSelected");
- }
- } else {
- 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;
- }
- }
- } catch (e) {}
- }
+ if (
+ (defView.HTMLAnchorElement.isInstance(tipElement) ||
+ defView.HTMLAreaElement.isInstance(tipElement) ||
+ defView.HTMLLinkElement.isInstance(tipElement) ||
+ defView.SVGAElement.isInstance(tipElement)) &&
+ tipElement.href
+ ) {
+ XLinkTitleText = tipElement.getAttributeNS(XLinkNS, "title");
+ }
- // Check texts against null so that title="" can be used to undefine a
- // title on a child element.
- let usedTipElement = null;
- while (
- tipElement &&
- titleText == null &&
- XLinkTitleText == null &&
- SVGTitleText == null &&
- XULtooltiptextText == null
- ) {
- if (tipElement.nodeType == defView.Node.ELEMENT_NODE) {
- if (tipElement.namespaceURI == XUL_NS) {
- XULtooltiptextText = tipElement.hasAttribute("tooltiptext")
- ? tipElement.getAttribute("tooltiptext")
- : null;
- } else if (!defView.SVGElement.isInstance(tipElement)) {
- titleText = tipElement.getAttribute("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 (
- (defView.HTMLAnchorElement.isInstance(tipElement) ||
- defView.HTMLAreaElement.isInstance(tipElement) ||
- defView.HTMLLinkElement.isInstance(tipElement) ||
- defView.SVGAElement.isInstance(tipElement)) &&
- tipElement.href
- ) {
- XLinkTitleText = tipElement.getAttributeNS(XLinkNS, "title");
- }
- if (
- lookingForSVGTitle &&
- (!defView.SVGElement.isInstance(tipElement) ||
- tipElement.parentNode.nodeType == defView.Node.DOCUMENT_NODE)
- ) {
- lookingForSVGTitle = false;
- }
- if (lookingForSVGTitle) {
- for (let childNode of tipElement.childNodes) {
- if (defView.SVGTitleElement.isInstance(childNode)) {
- SVGTitleText = childNode.textContent;
- break;
- }
+ // 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;
}
}
-
- usedTipElement = tipElement;
}
- tipElement = tipElement.flattenedTreeParentNode;
+ // 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, SVGTitleText, XULtooltiptextText].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 (usedTipElement) {
- direction = defView
- .getComputedStyle(usedTipElement)
- .getPropertyValue("direction");
- }
+ 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");
- directionOut.value = direction;
- return true;
+ if (tipElement) {
+ direction = defView
+ .getComputedStyle(tipElement)
+ .getPropertyValue("direction");
}
- return false;
+ directionOut.value = direction;
+ return true;
}
- );
+
+ return false;
+ });
},
classID: Components.ID("{f376627f-0bbc-47b8-887e-fc92574cc91f}"),
diff --git a/toolkit/components/tooltiptext/tests/browser.toml b/toolkit/components/tooltiptext/tests/browser.toml
index 189f880be2..d03716e683 100644
--- a/toolkit/components/tooltiptext/tests/browser.toml
+++ b/toolkit/components/tooltiptext/tests/browser.toml
@@ -13,4 +13,6 @@ support-files = ["xul_tooltiptext.xhtml"]
["browser_input_file_tooltips.js"]
skip-if = ["os == 'win' && os_version == '10.0'"] # Permafail on Win 10 (bug 1400368)
+["browser_nac_tooltip.js"]
+
["browser_shadow_dom_tooltip.js"]
diff --git a/toolkit/components/tooltiptext/tests/browser_input_file_tooltips.js b/toolkit/components/tooltiptext/tests/browser_input_file_tooltips.js
index 7d6c5043c4..2f1385f37f 100644
--- a/toolkit/components/tooltiptext/tests/browser_input_file_tooltips.js
+++ b/toolkit/components/tooltiptext/tests/browser_input_file_tooltips.js
@@ -2,7 +2,7 @@
let tempFile;
add_setup(async function () {
- await SpecialPowers.pushPrefEnv({ set: [["ui.tooltipDelay", 0]] });
+ await SpecialPowers.pushPrefEnv({ set: [["ui.tooltip.delay_ms", 0]] });
tempFile = createTempFile();
registerCleanupFunction(function () {
tempFile.remove(true);
@@ -63,7 +63,7 @@ async function do_test(test) {
if (test.value) {
info("Creating mock filepicker to select files");
let MockFilePicker = SpecialPowers.MockFilePicker;
- MockFilePicker.init(window);
+ MockFilePicker.init(window.browsingContext);
MockFilePicker.returnValue = MockFilePicker.returnOK;
MockFilePicker.displayDirectory = FileUtils.getDir("TmpD", []);
MockFilePicker.setFiles([tempFile]);
diff --git a/toolkit/components/tooltiptext/tests/browser_nac_tooltip.js b/toolkit/components/tooltiptext/tests/browser_nac_tooltip.js
new file mode 100644
index 0000000000..449c8b9da7
--- /dev/null
+++ b/toolkit/components/tooltiptext/tests/browser_nac_tooltip.js
@@ -0,0 +1,66 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+/* eslint-disable mozilla/no-arbitrary-setTimeout */
+
+"use strict";
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({ set: [["ui.tooltip.delay_ms", 0]] });
+});
+
+add_task(async function () {
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: "data:text/html,<!DOCTYPE html>",
+ },
+ async function (browser) {
+ info("Moving mouse out of the way.");
+ await EventUtils.synthesizeAndWaitNativeMouseMove(browser, 300, 300);
+
+ await SpecialPowers.spawn(browser, [], function () {
+ let widget = content.document.insertAnonymousContent();
+ widget.root.innerHTML = `<button style="pointer-events: auto; position: absolute; width: 200px; height: 200px;" title="foo">bar</button>`;
+ let tttp = Cc[
+ "@mozilla.org/embedcomp/default-tooltiptextprovider;1"
+ ].getService(Ci.nsITooltipTextProvider);
+
+ let text = {};
+ let dir = {};
+ ok(
+ tttp.getNodeText(widget.root.querySelector("button"), text, dir),
+ "A tooltip should be shown for NAC"
+ );
+ is(text.value, "foo", "Tooltip text should be correct");
+ });
+
+ let awaitTooltipOpen = new Promise(resolve => {
+ let tooltipId = Services.appinfo.browserTabsRemoteAutostart
+ ? "remoteBrowserTooltip"
+ : "aHTMLTooltip";
+ let tooltip = document.getElementById(tooltipId);
+ tooltip.addEventListener(
+ "popupshown",
+ function (event) {
+ resolve(event.target);
+ },
+ { once: true }
+ );
+ });
+
+ info("Initial mouse move");
+ await EventUtils.synthesizeAndWaitNativeMouseMove(browser, 50, 5);
+ info("Waiting");
+ await new Promise(resolve => setTimeout(resolve, 400));
+ info("Second mouse move");
+ await EventUtils.synthesizeAndWaitNativeMouseMove(browser, 70, 5);
+ info("Waiting for tooltip to open");
+ let tooltip = await awaitTooltipOpen;
+ is(
+ tooltip.getAttribute("label"),
+ "foo",
+ "tooltip label should match expectation"
+ );
+ }
+ );
+});
diff --git a/toolkit/components/tooltiptext/tests/browser_shadow_dom_tooltip.js b/toolkit/components/tooltiptext/tests/browser_shadow_dom_tooltip.js
index 50386e07e2..4ceb918da1 100644
--- a/toolkit/components/tooltiptext/tests/browser_shadow_dom_tooltip.js
+++ b/toolkit/components/tooltiptext/tests/browser_shadow_dom_tooltip.js
@@ -1,7 +1,7 @@
/* eslint-disable mozilla/no-arbitrary-setTimeout */
add_setup(async function () {
- await SpecialPowers.pushPrefEnv({ set: [["ui.tooltipDelay", 0]] });
+ await SpecialPowers.pushPrefEnv({ set: [["ui.tooltip.delay_ms", 0]] });
});
add_task(async function test_title_in_shadow_dom() {
diff --git a/toolkit/components/translations/TranslationsTelemetry.sys.mjs b/toolkit/components/translations/TranslationsTelemetry.sys.mjs
index f01ee1d144..8550a1c1af 100644
--- a/toolkit/components/translations/TranslationsTelemetry.sys.mjs
+++ b/toolkit/components/translations/TranslationsTelemetry.sys.mjs
@@ -41,6 +41,7 @@ export class TranslationsTelemetry {
/**
* Telemetry functions for the Translations panel.
+ *
* @returns {Panel}
*/
static panel() {
@@ -49,6 +50,7 @@ export class TranslationsTelemetry {
/**
* Forces the creation of a new Translations telemetry flowId and returns it.
+ *
* @returns {string}
*/
static createFlowId() {
@@ -60,6 +62,7 @@ export class TranslationsTelemetry {
/**
* Returns a Translations telemetry flowId by retrieving the cached value
* if available, or creating a new one otherwise.
+ *
* @returns {string}
*/
static getOrCreateFlowId() {
diff --git a/toolkit/components/translations/actors/AboutTranslationsChild.sys.mjs b/toolkit/components/translations/actors/AboutTranslationsChild.sys.mjs
index c501c1b0cd..9d0b27a6a1 100644
--- a/toolkit/components/translations/actors/AboutTranslationsChild.sys.mjs
+++ b/toolkit/components/translations/actors/AboutTranslationsChild.sys.mjs
@@ -84,17 +84,6 @@ export class AboutTranslationsChild extends JSWindowActorChild {
}
/**
- * @returns {TranslationsChild}
- */
- #getTranslationsChild() {
- const child = this.contentWindow.windowGlobalChild.getActor("Translations");
- if (!child) {
- throw new Error("Unable to find the TranslationsChild");
- }
- return child;
- }
-
- /**
* A privileged promise can't be used in the content page, so convert a privileged
* promise into a content one.
*
@@ -194,6 +183,7 @@ export class AboutTranslationsChild extends JSWindowActorChild {
/**
* Does this device support the translation engine?
+ *
* @returns {Promise<boolean>}
*/
AT_isTranslationEngineSupported() {
diff --git a/toolkit/components/translations/actors/AboutTranslationsParent.sys.mjs b/toolkit/components/translations/actors/AboutTranslationsParent.sys.mjs
index 4680dbeef5..236e17bbc3 100644
--- a/toolkit/components/translations/actors/AboutTranslationsParent.sys.mjs
+++ b/toolkit/components/translations/actors/AboutTranslationsParent.sys.mjs
@@ -5,6 +5,7 @@
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
TranslationsParent: "resource://gre/actors/TranslationsParent.sys.mjs",
+ EngineProcess: "chrome://global/content/ml/EngineProcess.sys.mjs",
});
/**
@@ -22,12 +23,13 @@ export class AboutTranslationsParent extends JSWindowActorParent {
switch (name) {
case "AboutTranslations:GetTranslationsPort": {
const { fromLanguage, toLanguage } = data;
- const engineProcess = await lazy.TranslationsParent.getEngineProcess();
+ const translationsEngineParent =
+ await lazy.EngineProcess.getTranslationsEngineParent();
if (this.#isDestroyed) {
return undefined;
}
const { port1, port2 } = new MessageChannel();
- engineProcess.actor.startTranslation(
+ translationsEngineParent.startTranslation(
fromLanguage,
toLanguage,
port1,
diff --git a/toolkit/components/translations/actors/TranslationsChild.sys.mjs b/toolkit/components/translations/actors/TranslationsChild.sys.mjs
index a3f8d15c85..d318b7284f 100644
--- a/toolkit/components/translations/actors/TranslationsChild.sys.mjs
+++ b/toolkit/components/translations/actors/TranslationsChild.sys.mjs
@@ -24,6 +24,7 @@ export class TranslationsChild extends JSWindowActorChild {
/**
* 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;
diff --git a/toolkit/components/translations/actors/TranslationsEngineChild.sys.mjs b/toolkit/components/translations/actors/TranslationsEngineChild.sys.mjs
index a4ab8e2640..d638858e52 100644
--- a/toolkit/components/translations/actors/TranslationsEngineChild.sys.mjs
+++ b/toolkit/components/translations/actors/TranslationsEngineChild.sys.mjs
@@ -19,6 +19,7 @@ export class TranslationsEngineChild extends JSWindowActorChild {
/**
* The resolve function for the Promise returned by the
* "TranslationsEngine:ForceShutdown" message.
+ *
* @type {null | () => {}}
*/
#resolveForceShutdown = null;
@@ -130,9 +131,10 @@ export class TranslationsEngineChild extends JSWindowActorChild {
}
/**
- * @param {Object} options
+ * @param {object} options
* @param {number?} options.startTime
* @param {string} options.message
+ * @param {number} options.innerWindowId
*/
TE_addProfilerMarker({ startTime, message, innerWindowId }) {
ChromeUtils.addProfilerMarker(
@@ -199,7 +201,7 @@ export class TranslationsEngineChild extends JSWindowActorChild {
}
/**
- * No engines are still alive, destroy the process.
+ * No engines are still alive, signal that the process can be destroyed.
*/
TE_destroyEngineProcess() {
this.sendAsyncMessage("TranslationsEngine:DestroyEngineProcess");
diff --git a/toolkit/components/translations/actors/TranslationsEngineParent.sys.mjs b/toolkit/components/translations/actors/TranslationsEngineParent.sys.mjs
index 77b16d7ae9..0b35117d7a 100644
--- a/toolkit/components/translations/actors/TranslationsEngineParent.sys.mjs
+++ b/toolkit/components/translations/actors/TranslationsEngineParent.sys.mjs
@@ -5,6 +5,7 @@
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
TranslationsParent: "resource://gre/actors/TranslationsParent.sys.mjs",
+ EngineProcess: "chrome://global/content/ml/EngineProcess.sys.mjs",
});
/**
@@ -22,12 +23,12 @@ export class TranslationsEngineParent extends JSWindowActorParent {
async receiveMessage({ name, data }) {
switch (name) {
case "TranslationsEngine:Ready":
- if (!lazy.TranslationsParent.resolveEngine) {
+ if (!lazy.EngineProcess.resolveTranslationsEngineParent) {
throw new Error(
"Unable to find the resolve function for when the translations engine is ready."
);
}
- lazy.TranslationsParent.resolveEngine(this);
+ lazy.EngineProcess.resolveTranslationsEngineParent(this);
return undefined;
case "TranslationsEngine:RequestEnginePayload": {
const { fromLanguage, toLanguage } = data;
@@ -62,12 +63,7 @@ export class TranslationsEngineParent extends JSWindowActorParent {
return undefined;
}
case "TranslationsEngine:DestroyEngineProcess":
- ChromeUtils.addProfilerMarker(
- "TranslationsEngine",
- {},
- "Loading bergamot wasm array buffer"
- );
- lazy.TranslationsParent.destroyEngineProcess().catch(error =>
+ lazy.EngineProcess.destroyTranslationsEngine().catch(error =>
console.error(error)
);
return undefined;
@@ -79,7 +75,6 @@ export class TranslationsEngineParent extends JSWindowActorParent {
/**
* @param {string} fromLanguage
* @param {string} toLanguage
- * @param {number} innerWindowId
* @param {MessagePort} port
* @param {number} innerWindowId
* @param {TranslationsParent} [translationsParent]
diff --git a/toolkit/components/translations/actors/TranslationsParent.sys.mjs b/toolkit/components/translations/actors/TranslationsParent.sys.mjs
index 44b761e6b0..70754d95c4 100644
--- a/toolkit/components/translations/actors/TranslationsParent.sys.mjs
+++ b/toolkit/components/translations/actors/TranslationsParent.sys.mjs
@@ -58,7 +58,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
setTimeout: "resource://gre/modules/Timer.sys.mjs",
TranslationsTelemetry:
"chrome://global/content/translations/TranslationsTelemetry.sys.mjs",
- HiddenFrame: "resource://gre/modules/HiddenFrame.sys.mjs",
+ EngineProcess: "chrome://global/content/ml/EngineProcess.sys.mjs",
});
ChromeUtils.defineLazyGetter(lazy, "console", () => {
@@ -142,11 +142,11 @@ const VERIFY_SIGNATURES_FROM_FS = false;
*/
/**
- * @typedef {Object} TranslationPair
- * @prop {string} fromLanguage
- * @prop {string} toLanguage
- * @prop {string} [fromDisplayLanguage]
- * @prop {string} [toDisplayLanguage]
+ * @typedef {object} TranslationPair
+ * @property {string} fromLanguage
+ * @property {string} toLanguage
+ * @property {string} [fromDisplayLanguage]
+ * @property {string} [toDisplayLanguage]
*/
/**
@@ -332,6 +332,7 @@ export class TranslationsParent extends JSWindowActorParent {
/**
* Telemetry functions for Translations
+ *
* @returns {TranslationsTelemetry}
*/
static telemetry() {
@@ -349,108 +350,9 @@ export class TranslationsParent extends JSWindowActorParent {
}
/**
- * @type {Promise<{ hiddenFrame: HiddenFrame, actor: TranslationsEngineParent }> | null}
- */
- static #engine = null;
-
- static async getEngineProcess() {
- if (!TranslationsParent.#engine) {
- TranslationsParent.#engine = TranslationsParent.#getEngineProcessImpl();
- }
- const enginePromise = TranslationsParent.#engine;
-
- // Determine if the actor was destroyed, or if there was an error. In this case
- // attempt to rebuild the process.
- let needsRebuilding = true;
- try {
- const { actor } = await enginePromise;
- needsRebuilding = actor.isDestroyed;
- } catch {}
-
- if (
- TranslationsParent.#engine &&
- enginePromise !== TranslationsParent.#engine
- ) {
- // This call lost the race, something else updated the engine promise, return that.
- return TranslationsParent.#engine;
- }
-
- if (needsRebuilding) {
- // The engine was destroyed, attempt to re-create the engine process.
- const rebuild = TranslationsParent.destroyEngineProcess().then(() =>
- TranslationsParent.#getEngineProcessImpl()
- );
- TranslationsParent.#engine = rebuild;
- return rebuild;
- }
-
- return enginePromise;
- }
-
- static destroyEngineProcess() {
- const enginePromise = this.#engine;
- this.#engine = null;
- if (enginePromise) {
- ChromeUtils.addProfilerMarker(
- "TranslationsParent",
- {},
- "Destroying the translations engine process"
- );
- return enginePromise.then(({ actor, hiddenFrame }) =>
- actor
- .forceShutdown()
- .catch(error => {
- lazy.console.error(
- "There was an error shutting down the engine.",
- error
- );
- })
- .then(() => {
- hiddenFrame.destroy();
- })
- );
- }
- return Promise.resolve();
- }
-
- /**
- * @type {Promise<{ hiddenFrame: HiddenFrame, actor: TranslationsEngineParent }> | null}
- */
- static async #getEngineProcessImpl() {
- ChromeUtils.addProfilerMarker(
- "TranslationsParent",
- {},
- "Creating the translations engine process"
- );
-
- // Manages the hidden ChromeWindow.
- const hiddenFrame = new lazy.HiddenFrame();
- const chromeWindow = await hiddenFrame.get();
- const doc = chromeWindow.document;
-
- const actorPromise = new Promise(resolve => {
- this.resolveEngine = resolve;
- });
-
- const browser = doc.createXULElement("browser");
- browser.setAttribute("remote", "true");
- browser.setAttribute("remoteType", "web");
- browser.setAttribute("disableglobalhistory", "true");
- browser.setAttribute("type", "content");
- browser.setAttribute(
- "src",
- "chrome://global/content/translations/translations-engine.html"
- );
- doc.documentElement.appendChild(browser);
-
- const actor = await actorPromise;
- this.resolveEngine = null;
- return { hiddenFrame, browser, actor };
- }
-
- /**
* 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) {
@@ -559,10 +461,6 @@ export class TranslationsParent extends JSWindowActorParent {
detectedLanguages
);
- TranslationsParent.getEngineProcess().catch(error =>
- console.error(error)
- );
-
browser.dispatchEvent(
new CustomEvent("TranslationsParent:OfferTranslation", {
bubbles: true,
@@ -616,7 +514,7 @@ export class TranslationsParent extends JSWindowActorParent {
* 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 {string} scheme - The URI spec
+ * @param {object} gBrowser
* @returns {boolean}
*/
static isRestrictedPage(gBrowser) {
@@ -657,6 +555,7 @@ export class TranslationsParent extends JSWindowActorParent {
/**
* Provide a way for tests to override the system locales.
+ *
* @type {null | string[]}
*/
static mockedSystemLocales = null;
@@ -814,9 +713,9 @@ export class TranslationsParent extends JSWindowActorParent {
return undefined;
}
- let engineProcess;
+ let actor;
try {
- engineProcess = await TranslationsParent.getEngineProcess();
+ actor = await lazy.EngineProcess.getTranslationsEngineParent();
} catch (error) {
console.error("Failed to get the translation engine process", error);
return undefined;
@@ -836,7 +735,7 @@ export class TranslationsParent extends JSWindowActorParent {
// The MessageChannel will be used for communicating directly between the content
// process and the engine's process.
const { port1, port2 } = new MessageChannel();
- engineProcess.actor.startTranslation(
+ actor.startTranslation(
requestedTranslationPair.fromLanguage,
requestedTranslationPair.toLanguage,
port1,
@@ -952,6 +851,7 @@ export class TranslationsParent extends JSWindowActorParent {
/**
* The cached language pairs.
+ *
* @type {Promise<Array<LanguagePair>> | null}
*/
static #languagePairs = null;
@@ -1041,8 +941,8 @@ export class TranslationsParent extends JSWindowActorParent {
/**
* Create a unique list of languages, sorted by the display name.
*
- * @param {Object} supportedLanguages
- * @returns {Array<{ langTag: string, displayName: string}}
+ * @param {object} supportedLanguages
+ * @returns {Array<{ langTag: string, displayName: string}>}
*/
static getLanguageList(supportedLanguages) {
const displayNames = new Map();
@@ -1070,8 +970,8 @@ export class TranslationsParent extends JSWindowActorParent {
}
/**
- * @param {Object} event
- * @param {Object} event.data
+ * @param {object} event
+ * @param {object} event.data
* @param {TranslationModelRecord[]} event.data.created
* @param {TranslationModelRecord[]} event.data.updated
* @param {TranslationModelRecord[]} event.data.deleted
@@ -1147,12 +1047,13 @@ export class TranslationsParent extends JSWindowActorParent {
* then only the 1.1-version record will be returned in the resulting collection.
*
* @param {RemoteSettingsClient} remoteSettingsClient
- * @param {Object} [options]
- * @param {Object} [options.filters={}]
+ * @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.
@@ -1537,7 +1438,7 @@ export class TranslationsParent extends JSWindowActorParent {
/**
* Deletes language files that match a language.
*
- * @param {string} requestedLanguage The BCP 47 language tag.
+ * @param {string} language The BCP 47 language tag.
*/
static async deleteLanguageFiles(language) {
const client = TranslationsParent.#getTranslationModelsRemoteClient();
@@ -1558,7 +1459,7 @@ export class TranslationsParent extends JSWindowActorParent {
/**
* Download language files that match a language.
*
- * @param {string} requestedLanguage The BCP 47 language tag.
+ * @param {string} language The BCP 47 language tag.
*/
static async downloadLanguageFiles(language) {
const client = TranslationsParent.#getTranslationModelsRemoteClient();
@@ -1607,6 +1508,7 @@ export class TranslationsParent extends JSWindowActorParent {
/**
* Delete all language model files.
+ *
* @returns {Promise<string[]>} A list of record IDs.
*/
static async deleteAllLanguageFiles() {
@@ -1867,8 +1769,10 @@ export class TranslationsParent extends JSWindowActorParent {
* @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.
+ * @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,
@@ -1964,6 +1868,7 @@ export class TranslationsParent extends JSWindowActorParent {
/**
* 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.
@@ -2001,9 +1906,9 @@ export class TranslationsParent extends JSWindowActorParent {
} else {
const { docLangTag } = this.languageState.detectedLanguages;
- let engineProcess;
+ let actor;
try {
- engineProcess = await TranslationsParent.getEngineProcess();
+ actor = await lazy.EngineProcess.getTranslationsEngineParent();
} catch (error) {
console.error("Failed to get the translation engine process", error);
return;
@@ -2018,7 +1923,7 @@ export class TranslationsParent extends JSWindowActorParent {
// The MessageChannel will be used for communicating directly between the content
// process and the engine's process.
const { port1, port2 } = new MessageChannel();
- engineProcess.actor.startTranslation(
+ actor.startTranslation(
fromLanguage,
toLanguage,
port1,
@@ -2152,6 +2057,46 @@ export class TranslationsParent extends JSWindowActorParent {
}
/**
+ * 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(({ fromLang }) => fromLang === langTag));
+ }
+
+ /**
+ * Retrieves the top preferred user language for which translation
+ * is supported when translating to that language.
+ */
+ static async getTopPreferredSupportedToLang() {
+ return TranslationsParent.getPreferredLanguages().find(
+ async langTag => await TranslationsParent.isSupportedAsToLang(langTag)
+ );
+ }
+
+ /**
* 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.
*
@@ -2584,15 +2529,15 @@ export class TranslationsParent extends JSWindowActorParent {
* are misbehaving.
*/
#ensureTranslationsDiscarded() {
- if (!TranslationsParent.#engine) {
+ if (!lazy.EngineProcess.translationsEngineParent) {
return;
}
- TranslationsParent.#engine
+ lazy.EngineProcess.translationsEngineParent
// If the engine fails to load, ignore it since we are ending translations.
.catch(() => null)
- .then(engineProcess => {
- if (engineProcess && this.languageState.requestedTranslationPair) {
- engineProcess.actor.discardTranslations(this.innerWindowId);
+ .then(actor => {
+ if (actor && this.languageState.requestedTranslationPair) {
+ actor.discardTranslations(this.innerWindowId);
}
})
// This error will be one from the endTranslation code, which we need to
@@ -2804,11 +2749,11 @@ class TranslationsLanguageState {
}
/**
- * @typedef {Object} QueueItem
- * @prop {Function} download
- * @prop {Function} [onSuccess]
- * @prop {Function} [onFailure]
- * @prop {number} [retriesLeft]
+ * @typedef {object} QueueItem
+ * @property {Function} download
+ * @property {Function} [onSuccess]
+ * @property {Function} [onFailure]
+ * @property {number} [retriesLeft]
*/
/**
diff --git a/toolkit/components/translations/content/translations-document.sys.mjs b/toolkit/components/translations/content/translations-document.sys.mjs
index 7f436575d8..ed75fe9ec6 100644
--- a/toolkit/components/translations/content/translations-document.sys.mjs
+++ b/toolkit/components/translations/content/translations-document.sys.mjs
@@ -254,7 +254,7 @@ export class TranslationsDocument {
/**
* The BCP 47 language tag that is used on the page.
*
- * @type {string} */
+ @type {string} */
documentLanguage;
/**
@@ -292,7 +292,7 @@ export class TranslationsDocument {
* The list of nodes that need updating with the translated HTML. These are batched
* into an update.
*
- * @type {Set<{ node: Node, translatedHTML: string }}
+ * @type {Set<{ node: Node, translatedHTML: string }>}
*/
#nodesWithTranslatedHTML = new Set();
@@ -300,7 +300,7 @@ export class TranslationsDocument {
* 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 }}
+ * @type {Set<{ node: Node, translation: string, attribute: string }>}
*/
#nodesWithTranslatedAttributes = new Set();
@@ -475,8 +475,9 @@ export class TranslationsDocument {
/**
* Queue a node for translation of attributes.
+ *
* @param {Node} node
- * @param {Array<String>}
+ * @param {Array<string>} attributeList
*/
queueAttributeNodeForTranslation(node, attributeList) {
/** @type {NodeVisibility} */
@@ -522,6 +523,7 @@ export class TranslationsDocument {
/**
* Helper function for adding a new root to the mutation
* observer.
+ *
* @param {Node} root
*/
observeNewRoot(root) {
@@ -689,6 +691,7 @@ export class TranslationsDocument {
* 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}
*/
@@ -773,7 +776,7 @@ export class TranslationsDocument {
* Runs `determineTranslationStatus`, but only on unprocessed nodes.
*
* @param {Node} node
- * @return {number} - One of the NodeStatus values.
+ * @returns {number} - One of the NodeStatus values.
*/
determineTranslationStatusForUnprocessedNodes = node => {
if (this.#processedNodes.has(node)) {
@@ -840,6 +843,7 @@ export class TranslationsDocument {
/**
* Queue a node for translation.
+ *
* @param {Node} node
*/
queueNodeForTranslation(node) {
@@ -856,6 +860,7 @@ export class TranslationsDocument {
/**
* Submit the translations giving priority to nodes in the viewport.
+ *
* @returns {Array<Promise<void>> | null}
*/
dispatchQueuedTranslations() {
@@ -905,6 +910,7 @@ export class TranslationsDocument {
/**
* Submit the Attribute translations giving priority to nodes in the viewport.
+ *
* @returns {Array<Promise<void>> | null}
*/
dispatchQueuedAttributeTranslations() {
@@ -1124,9 +1130,10 @@ export class TranslationsDocument {
/**
* A single function to update pendingTranslationsCount while
* calling the translate function
+ *
* @param {Node} node
* @param {string} text
- * @prop {boolean} isHTML
+ * @property {boolean} isHTML
* @returns {Promise<string | null>}
*/
async maybeTranslate(node, text, isHTML) {
@@ -1223,6 +1230,7 @@ export class TranslationsDocument {
/**
* 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) {
@@ -1286,7 +1294,8 @@ export class TranslationsDocument {
/**
* Get the list of attributes that need to be translated
* in a given node.
- * @returns Array<string>
+ *
+ * @returns {Array<string>}
*/
function getTranslatableAttributes(node) {
if (node.nodeType !== Node.ELEMENT_NODE) {
@@ -1342,7 +1351,7 @@ function langTagsMatch(knownLanguage, otherLanguage) {
* style of node.
*
* @param {Node} node
- * @returns {HTMLElement} */
+ @returns {HTMLElement} */
function getElementForStyle(node) {
if (node.nodeType != Node.TEXT_NODE) {
return node;
@@ -1424,6 +1433,7 @@ function updateElement(translationsDocument, element) {
/**
* The Set of translation IDs for nodes that have been cloned.
+ *
* @type {Set<number>}
*/
const clonedNodes = new Set();
@@ -1649,6 +1659,7 @@ function removeTextNodes(node) {
* - `<p>test</p>`: yes
* - `<p> </p>`: no
* - `<p><b>test</b></p>`: no
+ *
* @param {Node} node
* @returns {boolean}
*/
@@ -1723,18 +1734,21 @@ function isNodeQueued(node, queuedNodes) {
}
/**
- * Reads the elements computed style and determines if the element is inline or not.
+ * 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 getIsInline(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 false;
+ return true;
}
- return win.getComputedStyle(element).display === "inline";
+ const { display } = win.getComputedStyle(element);
+ return display !== "inline" && display !== "none";
}
/**
@@ -1751,7 +1765,8 @@ function nodeNeedsSubdividing(node) {
return false;
}
- if (getIsInline(node)) {
+ if (!getIsBlockLike(node)) {
+ // This element is inline, or not displayed.
return false;
}
@@ -1761,12 +1776,12 @@ function nodeNeedsSubdividing(node) {
// Keep checking for more inline or text nodes.
continue;
case Node.ELEMENT_NODE: {
- if (getIsInline(child)) {
- // Keep checking for more inline or text nodes.
- continue;
+ if (getIsBlockLike(child)) {
+ // This node is a block node, so it needs further subdividing.
+ return true;
}
- // A child element is not inline, so subdivide this node further.
- return true;
+ // Keep checking for more inline or text nodes.
+ continue;
}
default:
return true;
@@ -1795,12 +1810,12 @@ function* getAncestorsIterator(node) {
/**
* This contains all of the information needed to perform a translation request.
*
- * @typedef {Object} TranslationRequest
- * @prop {Node} node
- * @prop {string} sourceText
- * @prop {boolean} isHTML
- * @prop {Function} resolve
- * @prop {Function} reject
+ * @typedef {object} TranslationRequest
+ * @property {Node} node
+ * @property {string} sourceText
+ * @property {boolean} isHTML
+ * @property {Function} resolve
+ * @property {Function} reject
*/
/**
@@ -1827,13 +1842,15 @@ class QueuedTranslator {
/**
* Tie together a message id to a resolved response.
- * @type {Map<number, TranslationRequest}
+ *
+ * @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();
@@ -1845,7 +1862,6 @@ class QueuedTranslator {
/**
* @param {MessagePort} port
- * @param {Document} document
* @param {() => void} actorRequestNewPort
*/
constructor(port, actorRequestNewPort) {
@@ -1902,6 +1918,7 @@ class QueuedTranslator {
/**
* Request a new port. The port will come in via `acquirePort`, and then resolved
* through the `this.#portRequest.resolve`.
+ *
* @returns {Promise<void>}
*/
#requestNewPort() {
@@ -1956,7 +1973,7 @@ class QueuedTranslator {
* then the request is stale. A rejection means there was an error in the translation.
* This request may be queued.
*
- * @param {node} Node
+ * @param {Node} node
* @param {string} sourceText
* @param {boolean} isHTML
*/
@@ -1997,7 +2014,7 @@ class QueuedTranslator {
* @param {Node} node
* @param {string} sourceText
* @param {boolean} isHTML
- * @return {{ translateText: TranslationFunction, translateHTML: TranslationFunction}}
+ * @returns {{ translateText: TranslationFunction, translateHTML: TranslationFunction}}
*/
#postTranslationRequest(node, sourceText, isHTML) {
return new Promise((resolve, reject) => {
@@ -2049,6 +2066,7 @@ class QueuedTranslator {
/**
* Acquires a port, checks on the engine status, and then starts or resumes
* translations.
+ *
* @param {MessagePort} port
*/
acquirePort(port) {
diff --git a/toolkit/components/translations/content/translations-engine.sys.mjs b/toolkit/components/translations/content/translations-engine.sys.mjs
index e9aeb8076b..72b5757e21 100644
--- a/toolkit/components/translations/content/translations-engine.sys.mjs
+++ b/toolkit/components/translations/content/translations-engine.sys.mjs
@@ -150,6 +150,7 @@ export class TranslationsEngine {
/**
* 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.
@@ -207,6 +208,7 @@ export class TranslationsEngine {
/**
* Terminates the engine and its worker after a timeout.
+ *
* @param {boolean} force
*/
terminate = (force = false) => {
@@ -419,7 +421,8 @@ function getLanguagePairKey(fromLanguage, toLanguage) {
/**
* Maps the innerWindowId to the port.
- * @type {Map<number, { fromLanguage: string, toLanguage: string, port: MessagePort }}
+ *
+ * @type {Map<number, { fromLanguage: string, toLanguage: string, port: MessagePort }>}
*/
const ports = new Map();
@@ -427,6 +430,7 @@ 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
@@ -511,7 +515,7 @@ function listenForPortMessages(fromLanguage, toLanguage, innerWindowId, port) {
/**
* Discards the queue and removes the port.
*
- * @param {innerWindowId} number
+ * @param {number} innerWindowId
*/
function discardTranslations(innerWindowId) {
TE_log("Discarding translations, innerWindowId:", innerWindowId);
diff --git a/toolkit/components/translations/content/translations.mjs b/toolkit/components/translations/content/translations.mjs
index 0ec8b2d475..478f854bb5 100644
--- a/toolkit/components/translations/content/translations.mjs
+++ b/toolkit/components/translations/content/translations.mjs
@@ -57,6 +57,7 @@ class TranslationsState {
/**
* Only send one translation in at a time to the worker.
+ *
* @type {Promise<string[]>}
*/
translationRequest = Promise.resolve([]);
@@ -75,6 +76,7 @@ class TranslationsState {
constructor(isSupported) {
/**
* Is the engine supported by the device?
+ *
* @type {boolean}
*/
this.isTranslationEngineSupported = isSupported;
@@ -607,7 +609,7 @@ window.addEventListener("AboutTranslationsChromeToContent", ({ detail }) => {
* 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 {object} settings
* @param {Function} settings.onDebounce
* @param {Function} settings.doEveryTime
* @returns {Function}
@@ -671,7 +673,8 @@ class Translator {
/**
* Tie together a message id to a resolved response.
- * @type {Map<number, TranslationRequest}
+ *
+ * @type {Map<number, TranslationRequest>}
*/
#requests = new Map();
diff --git a/toolkit/components/translations/tests/browser/browser_translations_translation_document.js b/toolkit/components/translations/tests/browser/browser_translations_translation_document.js
index 9a00da9ccf..d3d56fd387 100644
--- a/toolkit/components/translations/tests/browser/browser_translations_translation_document.js
+++ b/toolkit/components/translations/tests/browser/browser_translations_translation_document.js
@@ -51,6 +51,7 @@ async function createDoc(html, options) {
/**
* Test utility to check that the document matches the expected markup
*
+ * @param {string} message
* @param {string} html
*/
async function htmlMatches(message, html) {
@@ -720,6 +721,105 @@ add_task(async function test_presumed_inlines3() {
cleanup();
});
+/**
+ * Test the display "none" properties properly subdivide in block elements.
+ */
+add_task(async function test_display_none() {
+ const { translate, htmlMatches, cleanup } = await createDoc(
+ /* html */ `
+ <p>
+ This is some text.
+ <span>It has inline elements</span>
+ <style></style>
+ </p>
+ `,
+ { mockedTranslatorPort: createBatchedMockedTranslatorPort() }
+ );
+
+ translate();
+
+ // Note: The bergamot translator does not translate style elements, while our fake
+ // translator does translate the inside of style elements. That is why in the assertion
+ // here the style element is blank rather than containing style.
+ await htmlMatches(
+ "Display none",
+ /* html */ `
+ <p>
+ aaaa aa aaaa aaaa.
+ <span data-moz-translations-id="0">
+ aa aaa aaaaaa aaaaaaaa
+ </span>
+ <style data-moz-translations-id="1">
+ </style>
+ </p>
+ `
+ );
+
+ cleanup();
+});
+
+/**
+ * Test the display "none" properties properly subdivide in block elements.
+ *
+ * TODO - See Bug 1885235
+ *
+ * This assertion is wrong, as our test suite doesn't properly compute the style for
+ * elements. The div with "display; none;" is still block, not "none".
+ */
+add_task(async function test_display_none_div() {
+ const { translate, htmlMatches, cleanup } = await createDoc(
+ /* html */ `
+ <div>
+ <span>
+ Start of inline text
+ </span>
+ <div style="display: none;">
+ hidden portion of
+ </div>
+ <span>
+ rest of inline text.
+ </span>
+ </div>
+ `,
+ { mockedTranslatorPort: createBatchedMockedTranslatorPort() }
+ );
+
+ translate();
+
+ // eslint-disable-next-line no-unused-vars
+ const _realExpectedResults = /* html */ `
+ <div>
+ <span>
+ aaaaa aa aaaaaa aaaa
+ </span>
+ <div style="display: none;">
+ aaaaaa aaaaaaa aa
+ </div>
+ <span>
+ aaaa aa aaaaaa aaaa.
+ </span>
+ </div>
+ `;
+
+ const currentResults = /* html */ `
+ <div>
+ <span>
+ aaaaa aa aaaaaa aaaa
+ </span>
+ <div style="display: none;">
+ bbbbbb bbbbbbb bb
+ </div>
+ <span>
+ cccc cc cccccc cccc.
+ </span>
+ </div>
+ `;
+
+ await htmlMatches("Display none", currentResults);
+
+ cleanup();
+});
+
add_task(async function test_chunking_large_text() {
const { translate, htmlMatches, cleanup } = await createDoc(
/* html */ `
diff --git a/toolkit/components/translations/tests/browser/shared-head.js b/toolkit/components/translations/tests/browser/shared-head.js
index bad8e48a1b..82b3e783a7 100644
--- a/toolkit/components/translations/tests/browser/shared-head.js
+++ b/toolkit/components/translations/tests/browser/shared-head.js
@@ -3,6 +3,13 @@
"use strict";
+/**
+ * @type {import("../../../ml/content/EngineProcess.sys.mjs")}
+ */
+const { EngineProcess } = ChromeUtils.importESModule(
+ "chrome://global/content/ml/EngineProcess.sys.mjs"
+);
+
// Avoid about:blank's non-standard behavior.
const BLANK_PAGE =
"data:text/html;charset=utf-8,<!DOCTYPE html><title>Blank</title>Blank page";
@@ -134,7 +141,7 @@ async function openAboutTranslations({
BrowserTestUtils.removeTab(tab);
await removeMocks();
- await TranslationsParent.destroyEngineProcess();
+ await EngineProcess.destroyTranslationsEngine();
await SpecialPowers.popPrefEnv();
}
@@ -142,6 +149,7 @@ async function openAboutTranslations({
/**
* Naively prettify's html based on the opening and closing tags. This is not robust
* for general usage, but should be adequate for these tests.
+ *
* @param {string} html
* @returns {string}
*/
@@ -340,11 +348,23 @@ function getTranslationsParent() {
}
/**
- * Closes the context menu if it is open.
+ * Closes all open panels and menu popups related to Translations.
*/
-function closeContextMenuIfOpen() {
- return waitForCondition(async () => {
- const contextMenu = document.getElementById("contentAreaContextMenu");
+async function closeAllOpenPanelsAndMenus() {
+ await closeSettingsMenuIfOpen();
+ await closeFullPageTranslationsPanelIfOpen();
+ await closeSelectTranslationsPanelIfOpen();
+ await closeContextMenuIfOpen();
+}
+
+/**
+ * Closes the popup element with the given Id if it is open.
+ *
+ * @param {string} popupElementId
+ */
+async function closePopupIfOpen(popupElementId) {
+ await waitForCondition(async () => {
+ const contextMenu = document.getElementById(popupElementId);
if (!contextMenu) {
return true;
}
@@ -362,50 +382,31 @@ function closeContextMenuIfOpen() {
}
/**
+ * Closes the context menu if it is open.
+ */
+async function closeContextMenuIfOpen() {
+ await closePopupIfOpen("contentAreaContextMenu");
+}
+
+/**
* Closes the translations panel settings menu if it is open.
*/
-function closeSettingsMenuIfOpen() {
- return waitForCondition(async () => {
- const settings = document.getElementById(
- "translations-panel-settings-menupopup"
- );
- if (!settings) {
- return true;
- }
- if (settings.state === "closed") {
- return true;
- }
- let popuphiddenPromise = BrowserTestUtils.waitForEvent(
- settings,
- "popuphidden"
- );
- PanelMultiView.hidePopup(settings);
- await popuphiddenPromise;
- return false;
- });
+async function closeSettingsMenuIfOpen() {
+ await closePopupIfOpen("full-page-translations-panel-settings-menupopup");
}
/**
* Closes the translations panel if it is open.
*/
-async function closeTranslationsPanelIfOpen() {
- await closeSettingsMenuIfOpen();
- return waitForCondition(async () => {
- const panel = document.getElementById("translations-panel");
- if (!panel) {
- return true;
- }
- if (panel.state === "closed") {
- return true;
- }
- let popuphiddenPromise = BrowserTestUtils.waitForEvent(
- panel,
- "popuphidden"
- );
- PanelMultiView.hidePopup(panel);
- await popuphiddenPromise;
- return false;
- });
+async function closeFullPageTranslationsPanelIfOpen() {
+ await closePopupIfOpen("full-page-translations-panel");
+}
+
+/**
+ * Closes the translations panel if it is open.
+ */
+async function closeSelectTranslationsPanelIfOpen() {
+ await closePopupIfOpen("select-translations-panel");
}
/**
@@ -442,10 +443,9 @@ async function setupActorTest({
actor,
remoteClients,
async cleanup() {
+ await closeAllOpenPanelsAndMenus();
await loadBlankPage();
- await TranslationsParent.destroyEngineProcess();
- await closeTranslationsPanelIfOpen();
- await closeContextMenuIfOpen();
+ await EngineProcess.destroyTranslationsEngine();
BrowserTestUtils.removeTab(tab);
await removeMocks();
TestTranslationsTelemetry.reset();
@@ -500,7 +500,7 @@ async function loadTestPage({
}) {
info(`Loading test page starting at url: ${page}`);
// Ensure no engine is being carried over from a previous test.
- await TranslationsParent.destroyEngineProcess();
+ await EngineProcess.destroyTranslationsEngine();
Services.fog.testResetFOG();
await SpecialPowers.pushPrefEnv({
set: [
@@ -552,7 +552,7 @@ async function loadTestPage({
if (autoOffer && TranslationsParent.shouldAlwaysOfferTranslations()) {
info("Waiting for the popup to be automatically shown.");
await waitForCondition(() => {
- const panel = document.getElementById("translations-panel");
+ const panel = document.getElementById("full-page-translations-panel");
return panel && panel.state === "open";
});
}
@@ -585,10 +585,9 @@ async function loadTestPage({
* @returns {Promise<void>}
*/
async cleanup() {
+ await closeAllOpenPanelsAndMenus();
await loadBlankPage();
- await TranslationsParent.destroyEngineProcess();
- await closeTranslationsPanelIfOpen();
- await closeContextMenuIfOpen();
+ await EngineProcess.destroyTranslationsEngine();
await removeMocks();
Services.fog.testResetFOG();
TranslationsParent.testAutomaticPopup = false;
@@ -658,7 +657,8 @@ async function captureTranslationsError(callback) {
/**
* Load a test page and run
- * @param {Object} options - The options for `loadTestPage` plus a `runInPage` function.
+ *
+ * @param {object} options - The options for `loadTestPage` plus a `runInPage` function.
*/
async function autoTranslatePage(options) {
const { prefs, languagePairs, ...otherOptions } = options;
@@ -676,6 +676,10 @@ async function autoTranslatePage(options) {
}
/**
+ * @typedef {ReturnType<createAttachmentMock>} AttachmentMock
+ */
+
+/**
* @param {RemoteSettingsClient} client
* @param {string} mockedCollectionName - The name of the mocked collection without
* the incrementing "id" part. This is provided so that attachments can be asserted
@@ -826,7 +830,7 @@ let _remoteSettingsMockId = 0;
* Creates a local RemoteSettingsClient for use within tests.
*
* @param {boolean} autoDownloadFromRemoteSettings
- * @param {Object[]} langPairs
+ * @param {object[]} langPairs
* @returns {RemoteSettingsClient}
*/
async function createTranslationModelsRemoteClient(
@@ -980,7 +984,7 @@ function hitEnterKey(button, message) {
*
* @see assertVisibility
*
- * @param {Object} options
+ * @param {object} options
* @param {string} options.message
* @param {Record<string, Element[]>} options.visible
* @param {Record<string, Element[]>} options.hidden
@@ -1011,7 +1015,7 @@ async function ensureVisibility({ message = null, visible = {}, hidden = {} }) {
/**
* Asserts that the provided elements are either visible or hidden.
*
- * @param {Object} options
+ * @param {object} options
* @param {string} options.message
* @param {Record<string, Element[]>} options.visible
* @param {Record<string, Element[]>} options.hidden
@@ -1066,10 +1070,9 @@ async function setupAboutPreferences(
const elements = await selectAboutPreferencesElements();
async function cleanup() {
+ await closeAllOpenPanelsAndMenus();
await loadBlankPage();
- await TranslationsParent.destroyEngineProcess();
- await closeTranslationsPanelIfOpen();
- await closeContextMenuIfOpen();
+ await EngineProcess.destroyTranslationsEngine();
BrowserTestUtils.removeTab(tab);
await removeMocks();
await SpecialPowers.popPrefEnv();
@@ -1137,8 +1140,8 @@ class TestTranslationsTelemetry {
* Asserts qualities about a counter telemetry metric.
*
* @param {string} name - The name of the metric.
- * @param {Object} counter - The Glean counter object.
- * @param {Object} expectedCount - The expected value of the counter.
+ * @param {object} counter - The Glean counter object.
+ * @param {object} expectedCount - The expected value of the counter.
*/
static async assertCounter(name, counter, expectedCount) {
// Ensures that glean metrics are collected from all child processes
@@ -1155,16 +1158,16 @@ class TestTranslationsTelemetry {
/**
* Asserts qualities about an event telemetry metric.
*
- * @param {string} name - The name of the metric.
- * @param {Object} event - The Glean event object.
- * @param {Object} expectations - The test expectations.
+ * @param {object} event - The Glean event object.
+ * @param {object} expectations - The test expectations.
* @param {number} expectations.expectedEventCount - The expected count of events.
* @param {boolean} expectations.expectNewFlowId
+ * @param {boolean} [expectations.expectFirstInteraction]
* - Expects the flowId to be different than the previous flowId if true,
* and expects it to be the same if false.
- * @param {Array<function>} [expectations.allValuePredicates=[]]
+ * @param {Array<Function>} [expectations.allValuePredicates=[]]
* - An array of function predicates to assert for all event values.
- * @param {Array<function>} [expectations.finalValuePredicates=[]]
+ * @param {Array<Function>} [expectations.finalValuePredicates=[]]
* - An array of function predicates to assert for only the final event value.
*/
static async assertEvent(
@@ -1264,8 +1267,8 @@ class TestTranslationsTelemetry {
* Asserts qualities about a rate telemetry metric.
*
* @param {string} name - The name of the metric.
- * @param {Object} rate - The Glean rate object.
- * @param {Object} expectations - The test expectations.
+ * @param {object} rate - The Glean rate object.
+ * @param {object} expectations - The test expectations.
* @param {number} expectations.expectedNumerator - The expected value of the numerator.
* @param {number} expectations.expectedDenominator - The expected value of the denominator.
*/
@@ -1295,7 +1298,7 @@ class TestTranslationsTelemetry {
* Provide longer defaults for the waitForCondition.
*
* @param {Function} callback
- * @param {string} messages
+ * @param {string} message
*/
function waitForCondition(callback, message) {
const interval = 100;
@@ -1346,9 +1349,10 @@ function getNeverTranslateSitesFromPerms() {
/**
* Opens a dialog window for about:preferences
+ *
* @param {string} dialogUrl - The URL of the dialog window
* @param {Function} callback - The function to open the dialog via UI
- * @returns {Object} The dialog window object
+ * @returns {object} The dialog window object
*/
async function waitForOpenDialogWindow(dialogUrl, callback) {
const dialogLoaded = promiseLoadSubDialog(dialogUrl);
@@ -1360,7 +1364,7 @@ async function waitForOpenDialogWindow(dialogUrl, callback) {
/**
* Closes an open dialog window and waits for it to close.
*
- * @param {Object} dialogWindow
+ * @param {object} dialogWindow
*/
async function waitForCloseDialogWindow(dialogWindow) {
const closePromise = BrowserTestUtils.waitForEvent(
diff --git a/toolkit/components/translations/tests/browser/translations-test.mjs b/toolkit/components/translations/tests/browser/translations-test.mjs
index 3e16be57e9..a740a2d1cc 100644
--- a/toolkit/components/translations/tests/browser/translations-test.mjs
+++ b/toolkit/components/translations/tests/browser/translations-test.mjs
@@ -60,7 +60,7 @@ export function getSelectors() {
* Provide longer defaults for the waitForCondition.
*
* @param {Function} callback
- * @param {string} messages
+ * @param {string} message
*/
function waitForCondition(callback, message) {
const interval = 100;
diff --git a/toolkit/components/translations/translations.d.ts b/toolkit/components/translations/translations.d.ts
index ec1e899af4..cc8d462a9c 100644
--- a/toolkit/components/translations/translations.d.ts
+++ b/toolkit/components/translations/translations.d.ts
@@ -187,7 +187,7 @@ export namespace Bergamot {
/**
* The client to interact with RemoteSettings.
- * See services/settings/RemoteSettingsClient.jsm
+ * See services/settings/RemoteSettingsClient.sys.mjs
*/
interface RemoteSettingsClient {
on: Function,
diff --git a/toolkit/components/uniffi-js/UniFFIPointer.cpp b/toolkit/components/uniffi-js/UniFFIPointer.cpp
index 87a1d5fe69..c3a1eba93d 100644
--- a/toolkit/components/uniffi-js/UniFFIPointer.cpp
+++ b/toolkit/components/uniffi-js/UniFFIPointer.cpp
@@ -40,12 +40,12 @@ already_AddRefed<UniFFIPointer> UniFFIPointer::Read(
MOZ_LOG(sUniFFIPointerLogger, LogLevel::Info,
("[UniFFI] Reading Pointer from buffer"));
+ CheckedUint32 end = CheckedUint32(aPosition) + 8;
uint8_t data_ptr[8];
- if (!aArrayBuff.CopyDataTo(
- data_ptr,
- [aPosition](size_t aLength) -> Maybe<std::pair<size_t, size_t>> {
- CheckedUint32 end = aPosition + 8;
- if (!end.isValid() || end.value() > aLength) {
+ if (!end.isValid() ||
+ !aArrayBuff.CopyDataTo(
+ data_ptr, [&](size_t aLength) -> Maybe<std::pair<size_t, size_t>> {
+ if (end.value() > aLength) {
return Nothing();
}
return Some(std::make_pair(aPosition, 8));
@@ -72,18 +72,22 @@ void UniFFIPointer::Write(const ArrayBuffer& aArrayBuff, uint32_t aPosition,
MOZ_LOG(sUniFFIPointerLogger, LogLevel::Info,
("[UniFFI] Writing Pointer to buffer"));
- aArrayBuff.ProcessData([&](const Span<uint8_t>& aData,
- JS::AutoCheckCannotGC&&) {
- CheckedUint32 end = aPosition + 8;
- if (!end.isValid() || end.value() > aData.Length()) {
- aError.ThrowRangeError("position is out of range");
- return;
- }
- // in Rust and Read(), a u64 is read as BigEndian and then converted to
- // a pointer we do the reverse here
- const auto& data_ptr = aData.Subspan(aPosition, 8);
- mozilla::BigEndian::writeUint64(data_ptr.Elements(), (uint64_t)GetPtr());
- });
+ CheckedUint32 end = CheckedUint32(aPosition) + 8;
+ if (!end.isValid() || !aArrayBuff.ProcessData([&](const Span<uint8_t>& aData,
+ JS::AutoCheckCannotGC&&) {
+ if (end.value() > aData.Length()) {
+ return false;
+ }
+ // in Rust and Read(), a u64 is read as BigEndian and then converted to
+ // a pointer we do the reverse here
+ const auto& data_ptr = aData.Subspan(aPosition, 8);
+ mozilla::BigEndian::writeUint64(data_ptr.Elements(),
+ (uint64_t)GetPtr());
+ return true;
+ })) {
+ aError.ThrowRangeError("position is out of range");
+ return;
+ }
}
UniFFIPointer::UniFFIPointer(void* aPtr, const UniFFIPointerType* aType) {
diff --git a/toolkit/components/url-classifier/UrlClassifierHashCompleter.sys.mjs b/toolkit/components/url-classifier/UrlClassifierHashCompleter.sys.mjs
index 48a4c432fb..d5e22babdf 100644
--- a/toolkit/components/url-classifier/UrlClassifierHashCompleter.sys.mjs
+++ b/toolkit/components/url-classifier/UrlClassifierHashCompleter.sys.mjs
@@ -858,7 +858,7 @@ HashCompleterRequest.prototype = {
this._response += sis.readBytes(aCount);
},
- onStartRequest: function HCR_onStartRequest(aRequest) {
+ 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;
@@ -945,7 +945,7 @@ HashCompleterRequest.prototype = {
}
},
- observe: function HCR_observe(aSubject, aTopic, aData) {
+ observe: function HCR_observe(aSubject, aTopic) {
if (aTopic == "quit-application") {
this._shuttingDown = true;
if (this._channel) {
diff --git a/toolkit/components/url-classifier/UrlClassifierLib.sys.mjs b/toolkit/components/url-classifier/UrlClassifierLib.sys.mjs
index aad054ce15..d03cc35c11 100644
--- a/toolkit/components/url-classifier/UrlClassifierLib.sys.mjs
+++ b/toolkit/components/url-classifier/UrlClassifierLib.sys.mjs
@@ -29,7 +29,7 @@ const PREF_DISABLE_TEST_BACKOFF =
*
* @returns {function} A partially-applied form of the speficied function.
*/
-export function BindToObject(fn, self, opt_args) {
+export function BindToObject(fn, self) {
var boundargs = fn.boundArgs_ || [];
boundargs = boundargs.concat(
Array.prototype.slice.call(arguments, 2, arguments.length)
diff --git a/toolkit/components/url-classifier/UrlClassifierListManager.sys.mjs b/toolkit/components/url-classifier/UrlClassifierListManager.sys.mjs
index 410e203672..a96c6bad15 100644
--- a/toolkit/components/url-classifier/UrlClassifierListManager.sys.mjs
+++ b/toolkit/components/url-classifier/UrlClassifierListManager.sys.mjs
@@ -70,7 +70,7 @@ function PROT_ListManager() {
this.updateCheckers_ = {};
this.requestBackoffs_ = {};
- // This is only used by testcases to ensure SafeBrowsing.jsm is inited
+ // 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(
diff --git a/toolkit/components/url-classifier/UrlClassifierRemoteSettingsService.sys.mjs b/toolkit/components/url-classifier/UrlClassifierRemoteSettingsService.sys.mjs
index 42f8cf272a..55f6efd5f0 100644
--- a/toolkit/components/url-classifier/UrlClassifierRemoteSettingsService.sys.mjs
+++ b/toolkit/components/url-classifier/UrlClassifierRemoteSettingsService.sys.mjs
@@ -55,7 +55,7 @@ UrlClassifierRemoteSettingsService.prototype = {
});
},
- // Parse the update request. See UrlClassifierListManager.jsm makeUpdateRequest
+ // 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
diff --git a/toolkit/components/url-classifier/nsIUrlListManager.idl b/toolkit/components/url-classifier/nsIUrlListManager.idl
index 6bf01020ee..fb2408ca3d 100644
--- a/toolkit/components/url-classifier/nsIUrlListManager.idl
+++ b/toolkit/components/url-classifier/nsIUrlListManager.idl
@@ -88,7 +88,7 @@ interface nsIUrlListManager : nsISupports
/**
* Return true if someone registers a table, this is used by testcase
- * to figure out it SafeBrowsing.jsm is initialized.
+ * to figure out it SafeBrowsing.sys.mjs is initialized.
*/
boolean isRegistered();
};
diff --git a/toolkit/components/url-classifier/tests/UrlClassifierTestUtils.sys.mjs b/toolkit/components/url-classifier/tests/UrlClassifierTestUtils.sys.mjs
index c69d0c24b4..a05260b6b3 100644
--- a/toolkit/components/url-classifier/tests/UrlClassifierTestUtils.sys.mjs
+++ b/toolkit/components/url-classifier/tests/UrlClassifierTestUtils.sys.mjs
@@ -225,12 +225,12 @@ export var UrlClassifierTestUtils = {
throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
},
- updateUrlRequested: url => {},
- streamFinished: status => {},
- updateError: errorCode => {
+ updateUrlRequested: () => {},
+ streamFinished: () => {},
+ updateError: () => {
reject("Got updateError when updating " + table.name);
},
- updateSuccess: requestedTimeout => {
+ updateSuccess: () => {
resolve();
},
};
diff --git a/toolkit/components/url-classifier/tests/mochitest/allowlistAnnotatedFrame.html b/toolkit/components/url-classifier/tests/mochitest/allowlistAnnotatedFrame.html
index 1096274260..48ca1662c8 100644
--- a/toolkit/components/url-classifier/tests/mochitest/allowlistAnnotatedFrame.html
+++ b/toolkit/components/url-classifier/tests/mochitest/allowlistAnnotatedFrame.html
@@ -134,7 +134,7 @@ fetch("http://tracking.example.com/tests/toolkit/components/url-classifier/tests
fetchItem = "badresponse";
loaded("fetch");
}
- }).catch(function(error) {
+ }).catch(function() {
fetchItem = "error";
loaded("fetch");
});
diff --git a/toolkit/components/url-classifier/tests/mochitest/classifiedAnnotatedFrame.html b/toolkit/components/url-classifier/tests/mochitest/classifiedAnnotatedFrame.html
index 9b12f529cb..a1ca1ad303 100644
--- a/toolkit/components/url-classifier/tests/mochitest/classifiedAnnotatedFrame.html
+++ b/toolkit/components/url-classifier/tests/mochitest/classifiedAnnotatedFrame.html
@@ -141,7 +141,7 @@ fetch("http://tracking.example.com/tests/toolkit/components/url-classifier/tests
fetchItem = "badresponse";
loaded("fetch");
}
- }).catch(function(error) {
+ }).catch(function() {
fetchItem = "error";
loaded("fetch");
});
diff --git a/toolkit/components/url-classifier/tests/mochitest/classifierCommon.js b/toolkit/components/url-classifier/tests/mochitest/classifierCommon.js
index ca288986e3..00a4e8d08b 100644
--- a/toolkit/components/url-classifier/tests/mochitest/classifierCommon.js
+++ b/toolkit/components/url-classifier/tests/mochitest/classifierCommon.js
@@ -23,12 +23,12 @@ function setTimeout(callback, delay) {
function doUpdate(update) {
let listener = {
QueryInterface: ChromeUtils.generateQI(["nsIUrlClassifierUpdateObserver"]),
- updateUrlRequested(url) {},
- streamFinished(status) {},
+ updateUrlRequested() {},
+ streamFinished() {},
updateError(errorCode) {
sendAsyncMessage("updateError", errorCode);
},
- updateSuccess(requestedTimeout) {
+ updateSuccess() {
sendAsyncMessage("updateSuccess");
},
};
diff --git a/toolkit/components/url-classifier/tests/mochitest/classifierHelper.js b/toolkit/components/url-classifier/tests/mochitest/classifierHelper.js
index 157e5d54f7..b07bfc7fc5 100644
--- a/toolkit/components/url-classifier/tests/mochitest/classifierHelper.js
+++ b/toolkit/components/url-classifier/tests/mochitest/classifierHelper.js
@@ -27,7 +27,7 @@ classifierHelper._initsCB = [];
// This function return a Promise, promise is resolved when SafeBrowsing.jsm
// is initialized.
classifierHelper.waitForInit = function () {
- return new Promise(function (resolve, reject) {
+ return new Promise(function (resolve) {
classifierHelper._initsCB.push(resolve);
gScript.sendAsyncMessage("waitForInit");
});
@@ -115,7 +115,7 @@ classifierHelper.resetDatabase = function () {
};
classifierHelper.reloadDatabase = function () {
- return new Promise(function (resolve, reject) {
+ return new Promise(function (resolve) {
gScript.addMessageListener("reloadSuccess", function handler() {
gScript.removeMessageListener("reloadSuccess", handler);
resolve();
diff --git a/toolkit/components/url-classifier/tests/mochitest/dnt.html b/toolkit/components/url-classifier/tests/mochitest/dnt.html
index 2246263688..2ce66d4961 100644
--- a/toolkit/components/url-classifier/tests/mochitest/dnt.html
+++ b/toolkit/components/url-classifier/tests/mochitest/dnt.html
@@ -13,7 +13,7 @@ function makeXHR(url, callback) {
xhr.send();
}
-function loaded(type) {
+function loaded() {
window.parent.postMessage("navigator.doNotTrack=" + navigator.doNotTrack, "*");
makeXHR("dnt.sjs", (res) => {
diff --git a/toolkit/components/url-classifier/tests/mochitest/features.js b/toolkit/components/url-classifier/tests/mochitest/features.js
index 0004693d81..fc2d8d1c81 100644
--- a/toolkit/components/url-classifier/tests/mochitest/features.js
+++ b/toolkit/components/url-classifier/tests/mochitest/features.js
@@ -247,7 +247,7 @@ var chromeScript;
function runTests(flag, prefs, trackingResource) {
chromeScript = SpecialPowers.loadChromeScript(_ => {
/* eslint-env mozilla/chrome-script */
- function onExamResp(subject, topic, data) {
+ function onExamResp(subject) {
let channel = subject.QueryInterface(Ci.nsIHttpChannel);
let classifiedChannel = subject.QueryInterface(Ci.nsIClassifiedChannel);
if (
diff --git a/toolkit/components/url-classifier/tests/mochitest/head.js b/toolkit/components/url-classifier/tests/mochitest/head.js
index dca6abe279..f105b1718d 100644
--- a/toolkit/components/url-classifier/tests/mochitest/head.js
+++ b/toolkit/components/url-classifier/tests/mochitest/head.js
@@ -1,6 +1,6 @@
// calculate the fullhash and send it to gethash server
function addCompletionToServer(list, url, mochitestUrl) {
- return new Promise(function (resolve, reject) {
+ return new Promise(function (resolve) {
var listParam = "list=" + list;
var fullhashParam = "fullhash=" + hash(url);
diff --git a/toolkit/components/url-classifier/tests/mochitest/test_bug1254766.html b/toolkit/components/url-classifier/tests/mochitest/test_bug1254766.html
index 2e528a7242..67dff80255 100644
--- a/toolkit/components/url-classifier/tests/mochitest/test_bug1254766.html
+++ b/toolkit/components/url-classifier/tests/mochitest/test_bug1254766.html
@@ -36,7 +36,7 @@ var gCurGethashCounter = 0;
var expectLoad = false;
function loadTestFrame() {
- return new Promise(function(resolve, reject) {
+ return new Promise(function(resolve) {
var iframe = document.createElement("iframe");
iframe.setAttribute("src", "gethashFrame.html");
document.body.appendChild(iframe);
@@ -49,7 +49,7 @@ function loadTestFrame() {
}
function getGethashCounter() {
- return new Promise(function(resolve, reject) {
+ return new Promise(function(resolve) {
var xhr = new XMLHttpRequest;
xhr.open("PUT", GETHASH_URL + "?gethashcount");
xhr.setRequestHeader("Content-Type", "text/plain");
diff --git a/toolkit/components/url-classifier/tests/mochitest/test_cachemiss.html b/toolkit/components/url-classifier/tests/mochitest/test_cachemiss.html
index 2af0285884..71d14b6d47 100644
--- a/toolkit/components/url-classifier/tests/mochitest/test_cachemiss.html
+++ b/toolkit/components/url-classifier/tests/mochitest/test_cachemiss.html
@@ -29,7 +29,7 @@ var gPreGethashCounter = 0;
var gCurGethashCounter = 0;
function loadTestFrame() {
- return new Promise(function(resolve, reject) {
+ return new Promise(function(resolve) {
var iframe = document.createElement("iframe");
iframe.setAttribute("src", "gethashFrame.html");
document.body.appendChild(iframe);
@@ -42,7 +42,7 @@ function loadTestFrame() {
}
function getGethashCounter() {
- return new Promise(function(resolve, reject) {
+ return new Promise(function(resolve) {
var xhr = new XMLHttpRequest;
xhr.open("PUT", GETHASH_URL + "?gethashcount");
xhr.setRequestHeader("Content-Type", "text/plain");
diff --git a/toolkit/components/url-classifier/tests/mochitest/test_classified_annotations.html b/toolkit/components/url-classifier/tests/mochitest/test_classified_annotations.html
index 44a0c92549..6d255dd094 100644
--- a/toolkit/components/url-classifier/tests/mochitest/test_classified_annotations.html
+++ b/toolkit/components/url-classifier/tests/mochitest/test_classified_annotations.html
@@ -28,7 +28,7 @@ const {TestUtils} = ChromeUtils.importESModule(
);
function testOnWindow() {
- return new Promise((resolve, reject) => {
+ return new Promise((resolve) => {
let win = mainWindow.OpenBrowserWindow();
win.addEventListener("load", function() {
TestUtils.topicObserved("browser-delayed-startup-finished",
diff --git a/toolkit/components/url-classifier/tests/mochitest/test_classifier_changetablepref.html b/toolkit/components/url-classifier/tests/mochitest/test_classifier_changetablepref.html
index ea16c14e74..7b3674c04a 100644
--- a/toolkit/components/url-classifier/tests/mochitest/test_classifier_changetablepref.html
+++ b/toolkit/components/url-classifier/tests/mochitest/test_classifier_changetablepref.html
@@ -49,7 +49,7 @@ function checkLoads(aWindow, aBlocked) {
}
function testOnWindow() {
- return new Promise((resolve, reject) => {
+ return new Promise((resolve) => {
let win = mainWindow.OpenBrowserWindow();
win.addEventListener("load", function() {
TestUtils.topicObserved("browser-delayed-startup-finished",
@@ -63,7 +63,7 @@ function testOnWindow() {
}
win.removeEventListener("DOMContentLoaded", onInnerLoad, true);
- win.content.addEventListener("load", function innerLoad2(e) {
+ win.content.addEventListener("load", function innerLoad2() {
win.content.removeEventListener("load", innerLoad2);
SimpleTest.executeSoon(function() {
resolve(win);
@@ -81,7 +81,7 @@ function testOnWindow() {
}
function setup() {
- return new Promise(function(resolve, reject) {
+ return new Promise(function(resolve) {
// gethash url of test table "moz-track-digest256" should be updated
// after setting preference.
var url = listmanager.getGethashUrl(testTable);
diff --git a/toolkit/components/url-classifier/tests/mochitest/test_classify_by_default.html b/toolkit/components/url-classifier/tests/mochitest/test_classify_by_default.html
index c10a0c62f9..a3ede628e0 100644
--- a/toolkit/components/url-classifier/tests/mochitest/test_classify_by_default.html
+++ b/toolkit/components/url-classifier/tests/mochitest/test_classify_by_default.html
@@ -77,7 +77,7 @@ async function setupAndRun(hasCookie, topLevelSite = TEST_TOP_SITE) {
});
}
-async function cleanup(topLevelSite = TEST_TOP_SITE) {
+async function cleanup() {
function clearServiceWorker() {
return new Promise(resolve => {
let w;
diff --git a/toolkit/components/url-classifier/tests/mochitest/test_classify_ping.html b/toolkit/components/url-classifier/tests/mochitest/test_classify_ping.html
index 6a8a189ed2..72bfd35ece 100644
--- a/toolkit/components/url-classifier/tests/mochitest/test_classify_ping.html
+++ b/toolkit/components/url-classifier/tests/mochitest/test_classify_ping.html
@@ -31,7 +31,7 @@
var id = "1111";
ping(id, host_nottrack);
- return new Promise(function(resolve, reject) {
+ return new Promise(function(resolve) {
// Retry at most 30 seconds.
isPingedWithRetry(id, expectPing, msg, resolve, 30 * 1000 / RETRY_TIMEOUT_MS);
});
@@ -45,7 +45,7 @@
var id = "2222";
ping(id, host_track);
- return new Promise(function(resolve, reject) {
+ return new Promise(function(resolve) {
// Retry at most 30 seconds.
isPingedWithRetry(id, expectPing, msg, resolve, 30 * 1000 / RETRY_TIMEOUT_MS);
});
@@ -59,7 +59,7 @@
var id = "3333";
ping(id, host_track);
- return new Promise(function(resolve, reject) {
+ return new Promise(function(resolve) {
setTimeout(function() {
isPinged(id, expectPing, msg, resolve);
}, timeout);
diff --git a/toolkit/components/url-classifier/tests/mochitest/test_classify_top_sandboxed.html b/toolkit/components/url-classifier/tests/mochitest/test_classify_top_sandboxed.html
index 7e3ae97751..51e1d0ae41 100644
--- a/toolkit/components/url-classifier/tests/mochitest/test_classify_top_sandboxed.html
+++ b/toolkit/components/url-classifier/tests/mochitest/test_classify_top_sandboxed.html
@@ -15,7 +15,7 @@ async function runTests() {
var chromeScript;
chromeScript = SpecialPowers.loadChromeScript(_ => {
/* eslint-env mozilla/chrome-script */
- function onExamResp(subject, topic, data) {
+ function onExamResp(subject) {
let channel = subject.QueryInterface(Ci.nsIHttpChannel);
let classifiedChannel = subject.QueryInterface(Ci.nsIClassifiedChannel);
if (
diff --git a/toolkit/components/url-classifier/tests/mochitest/test_classify_track.html b/toolkit/components/url-classifier/tests/mochitest/test_classify_track.html
index cbb6e67c78..3bc1e9e8f9 100644
--- a/toolkit/components/url-classifier/tests/mochitest/test_classify_track.html
+++ b/toolkit/components/url-classifier/tests/mochitest/test_classify_track.html
@@ -28,7 +28,7 @@
function testValidTrack() {
SpecialPowers.setBoolPref(PREF, true);
- return new Promise(function(resolve, reject) {
+ return new Promise(function(resolve) {
var track = document.createElement("track");
track.track.mode = "hidden";
track.src = validtrack_url;
@@ -58,7 +58,7 @@
function testBlocklistTrackSafebrowsingOff() {
SpecialPowers.setBoolPref(PREF, false);
- return new Promise(function(resolve, reject) {
+ return new Promise(function(resolve) {
var track = document.createElement("track");
track.track.mode = "hidden";
track.src = malware_url;
@@ -88,7 +88,7 @@
function testBlocklistTrackSafebrowsingOn() {
SpecialPowers.setBoolPref(PREF, true);
- return new Promise(function(resolve, reject) {
+ return new Promise(function(resolve) {
var track = document.createElement("track");
track.track.mode = "hidden";
// Add a query string parameter here to avoid url classifier bypass classify
diff --git a/toolkit/components/url-classifier/tests/mochitest/test_donottrack.html b/toolkit/components/url-classifier/tests/mochitest/test_donottrack.html
index 96278ae49d..e542b97b39 100644
--- a/toolkit/components/url-classifier/tests/mochitest/test_donottrack.html
+++ b/toolkit/components/url-classifier/tests/mochitest/test_donottrack.html
@@ -78,7 +78,7 @@ function executeTest(test) {
var win = mainWindow.OpenBrowserWindow({private: test.setting.pbMode});
- return new Promise(function(resolve, reject) {
+ return new Promise(function(resolve) {
win.addEventListener("load", function() {
TestUtils.topicObserved("browser-delayed-startup-finished",
subject => subject == win).then(() => {
diff --git a/toolkit/components/url-classifier/tests/mochitest/test_gethash.html b/toolkit/components/url-classifier/tests/mochitest/test_gethash.html
index b9d3a2fd44..a917e59342 100644
--- a/toolkit/components/url-classifier/tests/mochitest/test_gethash.html
+++ b/toolkit/components/url-classifier/tests/mochitest/test_gethash.html
@@ -32,7 +32,7 @@ var shouldLoad = false;
// When access the test page gecko should trigger gethash request to server and
// get the completion response.
function loadTestFrame(id) {
- return new Promise(function(resolve, reject) {
+ return new Promise(function(resolve) {
var iframe = document.getElementById(id);
iframe.setAttribute("src", "gethashFrame.html");
diff --git a/toolkit/components/url-classifier/tests/mochitest/test_privatebrowsing_trackingprotection.html b/toolkit/components/url-classifier/tests/mochitest/test_privatebrowsing_trackingprotection.html
index 0959ecf42e..e7c7fc9909 100644
--- a/toolkit/components/url-classifier/tests/mochitest/test_privatebrowsing_trackingprotection.html
+++ b/toolkit/components/url-classifier/tests/mochitest/test_privatebrowsing_trackingprotection.html
@@ -27,7 +27,7 @@ const {TestUtils} = ChromeUtils.importESModule(
);
function testOnWindow(aPrivate) {
- return new Promise((resolve, reject) => {
+ return new Promise((resolve) => {
let win = mainWindow.OpenBrowserWindow({private: aPrivate});
win.addEventListener("load", function() {
TestUtils.topicObserved("browser-delayed-startup-finished",
diff --git a/toolkit/components/url-classifier/tests/mochitest/test_reporturl.html b/toolkit/components/url-classifier/tests/mochitest/test_reporturl.html
index 36d128cdf7..201a86f94a 100644
--- a/toolkit/components/url-classifier/tests/mochitest/test_reporturl.html
+++ b/toolkit/components/url-classifier/tests/mochitest/test_reporturl.html
@@ -103,7 +103,7 @@ var createBlockedIframe = function(aWindow, aBrowser, aTopUrl, aUrl) {
await SpecialPowers.spawn(aBrowser, [aUrl], async function(url) {
return new Promise(resolve => {
- let listener = e => {
+ let listener = () => {
docShell.chromeEventHandler.removeEventListener("AboutBlockedLoaded", listener, false, true);
resolve();
};
@@ -120,7 +120,7 @@ var createBlockedIframe = function(aWindow, aBrowser, aTopUrl, aUrl) {
})();
};
-var createBlockedPage = function(aWindow, aBrowser, aTopUrl, aUrl) {
+var createBlockedPage = function(aWindow, aBrowser, aTopUrl) {
(async function() {
BrowserTestUtils.startLoadingURIString(aBrowser, aTopUrl);
await BrowserTestUtils.waitForContentEvent(aBrowser, "DOMContentLoaded");
diff --git a/toolkit/components/url-classifier/tests/mochitest/test_threathit_report.html b/toolkit/components/url-classifier/tests/mochitest/test_threathit_report.html
index 341d26fa3f..307c708785 100644
--- a/toolkit/components/url-classifier/tests/mochitest/test_threathit_report.html
+++ b/toolkit/components/url-classifier/tests/mochitest/test_threathit_report.html
@@ -76,7 +76,7 @@ var testDatas = [
];
function addDataV4ToServer(list, type, data) {
- return new Promise(function(resolve, reject) {
+ return new Promise(function(resolve) {
var xhr = new XMLHttpRequest;
let params = new URLSearchParams();
params.append("action", "store");
@@ -185,7 +185,7 @@ function testOnWindow(aTestData) {
let expected;
let browser = win.gBrowser.selectedBrowser;
let progressListener = {
- onContentBlockingEvent(aWebProgress, aRequest, aEvent) {
+ onContentBlockingEvent() {
expected = aTestData.reportUrl;
},
QueryInterface: ChromeUtils.generateQI(["nsISupportsWeakReference"]),
diff --git a/toolkit/components/url-classifier/tests/mochitest/test_trackingprotection_bug1312515.html b/toolkit/components/url-classifier/tests/mochitest/test_trackingprotection_bug1312515.html
index 31c69ac293..d29c4e8b99 100644
--- a/toolkit/components/url-classifier/tests/mochitest/test_trackingprotection_bug1312515.html
+++ b/toolkit/components/url-classifier/tests/mochitest/test_trackingprotection_bug1312515.html
@@ -86,7 +86,7 @@ function checkPriority(aSubject, aCallback, aPriority, aMessage) {
}
function testXHR1() {
- return new Promise(function(aResolve, aReject) {
+ return new Promise(function(aResolve) {
testUrl = "http://tracking.example.com/";
info("Not blocklisted: " + testUrl);
resolve = aResolve;
@@ -96,7 +96,7 @@ function testXHR1() {
}
function testXHR2() {
- return new Promise(function(aResolve, aReject) {
+ return new Promise(function(aResolve) {
testUrl = "http://trackertest.org/";
info("Blocklisted and not entitylisted: " + testUrl);
resolve = aResolve;
@@ -106,7 +106,7 @@ function testXHR2() {
}
function testFetch1() {
- return new Promise(function(aResolve, aReject) {
+ return new Promise(function(aResolve) {
testUrl = "http://itisatracker.org/"; // only entitylisted in TP, not for annotations
info("Blocklisted and not entitylisted: " + testUrl);
resolve = aResolve;
@@ -116,7 +116,7 @@ function testFetch1() {
}
function testFetch2() {
- return new Promise(function(aResolve, aReject) {
+ return new Promise(function(aResolve) {
testUrl = "http://tracking.example.org/"; // only entitylisted for annotations, not in TP
info("Blocklisted but also entitylisted: " + testUrl);
resolve = aResolve;
diff --git a/toolkit/components/url-classifier/tests/mochitest/test_trackingprotection_bug1580416.html b/toolkit/components/url-classifier/tests/mochitest/test_trackingprotection_bug1580416.html
index 75d69b2596..9ca8eebb88 100644
--- a/toolkit/components/url-classifier/tests/mochitest/test_trackingprotection_bug1580416.html
+++ b/toolkit/components/url-classifier/tests/mochitest/test_trackingprotection_bug1580416.html
@@ -28,7 +28,7 @@ const {TestUtils} = ChromeUtils.importESModule(
);
function testOnWindow(contentPage) {
- return new Promise((resolve, reject) => {
+ return new Promise((resolve) => {
var win = mainWindow.OpenBrowserWindow();
win.addEventListener("load", function() {
TestUtils.topicObserved("browser-delayed-startup-finished",
diff --git a/toolkit/components/url-classifier/tests/mochitest/test_trackingprotection_whitelist.html b/toolkit/components/url-classifier/tests/mochitest/test_trackingprotection_whitelist.html
index 69ca337c33..55070dd3ae 100644
--- a/toolkit/components/url-classifier/tests/mochitest/test_trackingprotection_whitelist.html
+++ b/toolkit/components/url-classifier/tests/mochitest/test_trackingprotection_whitelist.html
@@ -31,7 +31,7 @@ const {TestUtils} = ChromeUtils.importESModule(
);
function testOnWindow(contentPage) {
- return new Promise((resolve, reject) => {
+ return new Promise((resolve) => {
var win = mainWindow.OpenBrowserWindow();
win.addEventListener("load", function() {
TestUtils.topicObserved("browser-delayed-startup-finished",
diff --git a/toolkit/components/url-classifier/tests/mochitest/workerFrame.html b/toolkit/components/url-classifier/tests/mochitest/workerFrame.html
index 69e8dd0074..20a682c529 100644
--- a/toolkit/components/url-classifier/tests/mochitest/workerFrame.html
+++ b/toolkit/components/url-classifier/tests/mochitest/workerFrame.html
@@ -16,7 +16,7 @@ function startCleanWorker() {
window.parent.postMessage("finish", "*");
};
- worker.onerror = function(event) {
+ worker.onerror = function() {
window.parent.postmessage("failure:failed to load cleanWorker.js", "*");
window.parent.postMessage("finish", "*");
};
@@ -27,12 +27,12 @@ function startCleanWorker() {
function startEvilWorker() {
var worker = new Worker("evilWorker.js");
- worker.onmessage = function(event) {
+ worker.onmessage = function() {
window.parent.postMessage("failure:failed to block evilWorker.js", "*");
startUnwantedWorker();
};
- worker.onerror = function(event) {
+ worker.onerror = function() {
window.parent.postMessage("success:blocked evilWorker.js", "*");
startUnwantedWorker();
};
@@ -43,12 +43,12 @@ function startEvilWorker() {
function startUnwantedWorker() {
var worker = new Worker("unwantedWorker.js");
- worker.onmessage = function(event) {
+ worker.onmessage = function() {
window.parent.postMessage("failure:failed to block unwantedWorker.js", "*");
startCleanWorker();
};
- worker.onerror = function(event) {
+ worker.onerror = function() {
window.parent.postMessage("success:blocked unwantedWorker.js", "*");
startCleanWorker();
};
diff --git a/toolkit/components/url-classifier/tests/unit/head_urlclassifier.js b/toolkit/components/url-classifier/tests/unit/head_urlclassifier.js
index 0ed731b564..b38fa447bf 100644
--- a/toolkit/components/url-classifier/tests/unit/head_urlclassifier.js
+++ b/toolkit/components/url-classifier/tests/unit/head_urlclassifier.js
@@ -186,8 +186,8 @@ function doSimpleUpdate(updateText, success, failure) {
var listener = {
QueryInterface: ChromeUtils.generateQI(["nsIUrlClassifierUpdateObserver"]),
- updateUrlRequested(url) {},
- streamFinished(status) {},
+ updateUrlRequested() {},
+ streamFinished() {},
updateError(errorCode) {
failure(errorCode);
},
@@ -210,8 +210,8 @@ function doErrorUpdate(tables, success, failure) {
var listener = {
QueryInterface: ChromeUtils.generateQI(["nsIUrlClassifierUpdateObserver"]),
- updateUrlRequested(url) {},
- streamFinished(status) {},
+ updateUrlRequested() {},
+ streamFinished() {},
updateError(errorCode) {
success(errorCode);
},
@@ -442,7 +442,7 @@ function Timer(delay, cb) {
Timer.prototype = {
QueryInterface: ChromeUtils.generateQI(["nsITimerCallback"]),
- notify(timer) {
+ notify() {
this.cb();
},
};
diff --git a/toolkit/components/url-classifier/tests/unit/test_channelClassifierService.js b/toolkit/components/url-classifier/tests/unit/test_channelClassifierService.js
index 9d5f5edb65..b7c176c371 100644
--- a/toolkit/components/url-classifier/tests/unit/test_channelClassifierService.js
+++ b/toolkit/components/url-classifier/tests/unit/test_channelClassifierService.js
@@ -42,7 +42,7 @@ function setupChannel(uri, topUri = TOP_LEVEL_DOMAIN) {
function waitForBeforeBlockEvent(expected, callback) {
return new Promise(function (resolve) {
- let observer = function observe(aSubject, aTopic, aData) {
+ let observer = function observe(aSubject, aTopic) {
switch (aTopic) {
case "urlclassifier-before-block-channel":
let channel = aSubject.QueryInterface(
@@ -90,10 +90,10 @@ add_task(async function test_block_channel() {
null
);
- let openPromise = new Promise((resolve, reject) => {
+ let openPromise = new Promise(resolve => {
channel.asyncOpen({
- onStartRequest: (request, context) => {},
- onDataAvailable: (request, context, stream, offset, count) => {},
+ onStartRequest: () => {},
+ onDataAvailable: () => {},
onStopRequest: (request, status) => {
dump("status = " + status + "\n");
if (status == 200) {
@@ -140,10 +140,10 @@ add_task(async function test_unblock_channel() {
}
);
- let openPromise = new Promise((resolve, reject) => {
+ let openPromise = new Promise(resolve => {
channel.asyncOpen({
- onStartRequest: (request, context) => {},
- onDataAvailable: (request, context, stream, offset, count) => {},
+ onStartRequest: () => {},
+ onDataAvailable: () => {},
onStopRequest: (request, status) => {
if (status == Cr.NS_ERROR_SOCIALTRACKING_URI) {
Assert.ok(false, "Classifier should not cancel this channel");
@@ -191,10 +191,10 @@ add_task(async function test_allow_channel() {
}
);
- let openPromise = new Promise((resolve, reject) => {
+ let openPromise = new Promise(resolve => {
channel.asyncOpen({
- onStartRequest: (request, context) => {},
- onDataAvailable: (request, context, stream, offset, count) => {},
+ onStartRequest: () => {},
+ onDataAvailable: () => {},
onStopRequest: (request, status) => {
if (status == Cr.NS_ERROR_SOCIALTRACKING_URI) {
Assert.ok(false, "Classifier should not cancel this channel");
diff --git a/toolkit/components/url-classifier/tests/unit/test_dbservice.js b/toolkit/components/url-classifier/tests/unit/test_dbservice.js
index 70ac02021a..1e3b986625 100644
--- a/toolkit/components/url-classifier/tests/unit/test_dbservice.js
+++ b/toolkit/components/url-classifier/tests/unit/test_dbservice.js
@@ -112,7 +112,7 @@ function tablesCallbackWithoutSub(tables) {
checkNoHost();
}
-function expireSubSuccess(result) {
+function expireSubSuccess() {
dbservice.getTables(tablesCallbackWithoutSub);
}
diff --git a/toolkit/components/url-classifier/tests/unit/test_hashcompleter.js b/toolkit/components/url-classifier/tests/unit/test_hashcompleter.js
index b8d6c7b128..2c6bbe36e8 100644
--- a/toolkit/components/url-classifier/tests/unit/test_hashcompleter.js
+++ b/toolkit/components/url-classifier/tests/unit/test_hashcompleter.js
@@ -383,7 +383,7 @@ function callback(completion) {
}
callback.prototype = {
- completionV2: function completionV2(hash, table, chunkId, trusted) {
+ completionV2: function completionV2(hash, table, chunkId) {
Assert.ok(this._completion.expectCompletion);
if (this._completion.multipleCompletions) {
for (let completion of this._completion.completions) {
@@ -411,7 +411,7 @@ callback.prototype = {
}
},
- completionFinished: function completionFinished(status) {
+ completionFinished: function completionFinished() {
finishedCompletions++;
Assert.equal(!!this._completion.expectCompletion, !!this._completed);
this._completion._finished = true;
diff --git a/toolkit/components/url-classifier/tests/unit/test_partial.js b/toolkit/components/url-classifier/tests/unit/test_partial.js
index 1220665063..640862729c 100644
--- a/toolkit/components/url-classifier/tests/unit/test_partial.js
+++ b/toolkit/components/url-classifier/tests/unit/test_partial.js
@@ -417,7 +417,7 @@ function setupCachedResults(addUrls, part2) {
}
function testCachedResults() {
- setupCachedResults(["foo.com/a"], function (add) {
+ setupCachedResults(["foo.com/a"], function () {
// This is called after setupCachedResults(). Verify that
// checking the url again does not cause a completer request.
diff --git a/toolkit/components/url-classifier/tests/unit/test_rsListService.js b/toolkit/components/url-classifier/tests/unit/test_rsListService.js
index cd70f92885..107de3568d 100644
--- a/toolkit/components/url-classifier/tests/unit/test_rsListService.js
+++ b/toolkit/components/url-classifier/tests/unit/test_rsListService.js
@@ -164,7 +164,7 @@ add_task(async function test_empty_update() {
gListService.fetchList(buildPayload(TEST_TABLES), {
// nsIStreamListener observer
- onStartRequest(request) {},
+ onStartRequest() {},
onDataAvailable(aRequest, aStream, aOffset, aCount) {
let stream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
Ci.nsIScriptableInputStream
@@ -175,7 +175,7 @@ add_task(async function test_empty_update() {
});
updateEvent.dispatchEvent(event);
},
- onStopRequest(request, status) {},
+ onStopRequest() {},
});
let expected = "n:" + SBRS_UPDATE_MINIMUM_DELAY + "\n";
@@ -205,7 +205,7 @@ add_task(async function test_update() {
gListService.fetchList(buildPayload(TEST_TABLES), {
// observer
// nsIStreamListener observer
- onStartRequest(request) {},
+ onStartRequest() {},
onDataAvailable(aRequest, aStream, aOffset, aCount) {
let stream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
Ci.nsIScriptableInputStream
@@ -216,7 +216,7 @@ add_task(async function test_update() {
});
updateEvent.dispatchEvent(event);
},
- onStopRequest(request, status) {},
+ onStopRequest() {},
});
// Build request with no version
@@ -247,7 +247,7 @@ add_task(async function test_no_update() {
gListService.fetchList(buildPayload(TEST_TABLES), {
// nsIStreamListener observer
- onStartRequest(request) {},
+ onStartRequest() {},
onDataAvailable(aRequest, aStream, aOffset, aCount) {
let stream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
Ci.nsIScriptableInputStream
@@ -258,7 +258,7 @@ add_task(async function test_no_update() {
});
updateEvent.dispatchEvent(event);
},
- onStopRequest(request, status) {},
+ onStopRequest() {},
});
// No data is expected
@@ -338,11 +338,11 @@ add_test(function test_update_download_error() {
].getService(Ci.nsIUrlClassifierStreamUpdater);
// Download some updates, and don't continue until the downloads are done.
- function updateSuccessOrError(aEvent) {
+ function updateSuccessOrError() {
do_throw("Should be downbload error");
}
// Just throw if we ever get an update or download error.
- function downloadError(aEvent) {
+ function downloadError() {
run_next_test();
}
@@ -363,11 +363,11 @@ add_test(function test_update_update_error() {
].getService(Ci.nsIUrlClassifierStreamUpdater);
// Download some updates, and don't continue until the downloads are done.
- function updateSuccessOrDownloadError(aEvent) {
+ function updateSuccessOrDownloadError() {
do_throw("Should be update error");
}
// Just throw if we ever get an update or download error.
- function updateError(aEvent) {
+ function updateError() {
run_next_test();
}
@@ -393,7 +393,7 @@ add_task(async function test_update_large_file() {
gListService.fetchList(buildPayload(TEST_TABLES), {
// observer
// nsIStreamListener observer
- onStartRequest(request) {},
+ onStartRequest() {},
onDataAvailable(aRequest, aStream, aOffset, aCount) {
let stream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
Ci.nsIScriptableInputStream
@@ -404,7 +404,7 @@ add_task(async function test_update_large_file() {
});
updateEvent.dispatchEvent(event);
},
- onStopRequest(request, status) {},
+ onStopRequest() {},
});
// Build request with no version
diff --git a/toolkit/components/url-classifier/tests/unit/test_shouldclassify.js b/toolkit/components/url-classifier/tests/unit/test_shouldclassify.js
index 6abdbb5f85..deb5ae9bb6 100644
--- a/toolkit/components/url-classifier/tests/unit/test_shouldclassify.js
+++ b/toolkit/components/url-classifier/tests/unit/test_shouldclassify.js
@@ -141,7 +141,7 @@ add_task(async function testShouldClassify() {
await new Promise(resolve => {
channel.asyncOpen({
- onStartRequest: (request, context) => {
+ onStartRequest: request => {
Assert.equal(
!!(
request.QueryInterface(Ci.nsIClassifiedChannel)
@@ -154,8 +154,8 @@ add_task(async function testShouldClassify() {
resolve();
},
- onDataAvailable: (request, context, stream, offset, count) => {},
- onStopRequest: (request, context, status) => {},
+ onDataAvailable: () => {},
+ onStopRequest: () => {},
});
});
}
diff --git a/toolkit/components/utils/ClientEnvironment.sys.mjs b/toolkit/components/utils/ClientEnvironment.sys.mjs
index 7f1b4cb970..1c6bbfbaba 100644
--- a/toolkit/components/utils/ClientEnvironment.sys.mjs
+++ b/toolkit/components/utils/ClientEnvironment.sys.mjs
@@ -216,7 +216,7 @@ export class ClientEnvironmentBase {
/**
* Gets the windows build number by querying the OS directly. The initial
- * version was copied from toolkit/components/telemetry/app/TelemetryEnvironment.jsm
+ * 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() {
diff --git a/toolkit/components/utils/FilterExpressions.sys.mjs b/toolkit/components/utils/FilterExpressions.sys.mjs
index 0dba44ec70..b34813d8ee 100644
--- a/toolkit/components/utils/FilterExpressions.sys.mjs
+++ b/toolkit/components/utils/FilterExpressions.sys.mjs
@@ -5,13 +5,9 @@
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
+ mozjexl: "resource://gre/modules/components-utils/mozjexl.sys.mjs",
Sampling: "resource://gre/modules/components-utils/Sampling.sys.mjs",
});
-ChromeUtils.defineModuleGetter(
- lazy,
- "mozjexl",
- "resource://gre/modules/components-utils/mozjexl.js"
-);
function getPrefValue(prefKey, defaultValue) {
switch (Services.prefs.getPrefType(prefKey)) {
diff --git a/toolkit/components/utils/JsonSchemaValidator.sys.mjs b/toolkit/components/utils/JsonSchemaValidator.sys.mjs
index 529e60e310..cd07b5ff81 100644
--- a/toolkit/components/utils/JsonSchemaValidator.sys.mjs
+++ b/toolkit/components/utils/JsonSchemaValidator.sys.mjs
@@ -20,7 +20,7 @@ ChromeUtils.defineLazyGetter(lazy, "log", () => {
"resource://gre/modules/Console.sys.mjs"
);
return new ConsoleAPI({
- prefix: "JsonSchemaValidator.jsm",
+ 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",
diff --git a/toolkit/components/utils/moz.build b/toolkit/components/utils/moz.build
index 54b3a32bd3..4f9c5fcb1b 100644
--- a/toolkit/components/utils/moz.build
+++ b/toolkit/components/utils/moz.build
@@ -15,7 +15,7 @@ EXTRA_JS_MODULES["components-utils"] = [
"ClientEnvironment.sys.mjs",
"FilterExpressions.sys.mjs",
"JsonSchemaValidator.sys.mjs",
- "mozjexl.js",
+ "mozjexl.sys.mjs",
"Sampling.sys.mjs",
"WindowsInstallsInfo.sys.mjs",
"WindowsVersionInfo.sys.mjs",
diff --git a/toolkit/components/utils/mozjexl.js b/toolkit/components/utils/mozjexl.js
deleted file mode 100644
index 66d88bcacd..0000000000
--- a/toolkit/components/utils/mozjexl.js
+++ /dev/null
@@ -1 +0,0 @@
-/* eslint-disable */this.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}}}}});this.EXPORTED_SYMBOLS = ["mozjexl"]; \ No newline at end of file
diff --git a/toolkit/components/utils/mozjexl.sys.mjs b/toolkit/components/utils/mozjexl.sys.mjs
new file mode 100644
index 0000000000..d59ff0f395
--- /dev/null
+++ b/toolkit/components/utils/mozjexl.sys.mjs
@@ -0,0 +1 @@
+/* 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}}}}}); \ No newline at end of file
diff --git a/toolkit/components/windowcreator/nsIWindowCreator.idl b/toolkit/components/windowcreator/nsIWindowCreator.idl
index b03ec11448..b7ec688e68 100644
--- a/toolkit/components/windowcreator/nsIWindowCreator.idl
+++ b/toolkit/components/windowcreator/nsIWindowCreator.idl
@@ -44,9 +44,3 @@ interface nsIWindowCreator : nsISupports {
in nsIOpenWindowInfo aOpenWindowInfo,
out boolean cancel);
};
-
-%{C++
-// {30465632-A777-44cc-90F9-8145475EF999}
-#define NS_WINDOWCREATOR_IID \
- {0x30465632, 0xa777, 0x44cc, {0x90, 0xf9, 0x81, 0x45, 0x47, 0x5e, 0xf9, 0x99}}
-%}
diff --git a/toolkit/components/windowwatcher/moz.build b/toolkit/components/windowwatcher/moz.build
index 73479b38c9..5879fb8f16 100644
--- a/toolkit/components/windowwatcher/moz.build
+++ b/toolkit/components/windowwatcher/moz.build
@@ -16,7 +16,6 @@ XPIDL_SOURCES += [
"nsIPromptFactory.idl",
"nsIPromptService.idl",
"nsIWindowWatcher.idl",
- "nsPIPromptService.idl",
"nsPIWindowWatcher.idl",
]
diff --git a/toolkit/components/windowwatcher/nsIPromptService.idl b/toolkit/components/windowwatcher/nsIPromptService.idl
index 66f23ffb5b..890bd062ba 100644
--- a/toolkit/components/windowwatcher/nsIPromptService.idl
+++ b/toolkit/components/windowwatcher/nsIPromptService.idl
@@ -263,7 +263,7 @@ interface nsIPromptService : nsISupports
const unsigned long BUTTON_DELAY_ENABLE = 1 << 26;
/**
- * Causes a spinner to be displayed in the dialog box.
+ * Causes a spinner to be displayed next to the title in the dialog box.
*/
const unsigned long SHOW_SPINNER = 1 << 27;
diff --git a/toolkit/components/windowwatcher/nsPIPromptService.idl b/toolkit/components/windowwatcher/nsPIPromptService.idl
deleted file mode 100644
index 3bff27a993..0000000000
--- a/toolkit/components/windowwatcher/nsPIPromptService.idl
+++ /dev/null
@@ -1,30 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
-
-/* The general dialog posing function within nsPromptService, for
- private consumption, only. */
-
-#include "nsISupports.idl"
-
-interface nsIDOMWindow;
-interface nsIDialogParamBlock;
-
-[uuid(C60A1955-6CB3-4827-8EF8-4F5C668AF0B3)]
-interface nsPIPromptService : nsISupports
-{
-%{C++
- // eOpeningSound is obsolete but we need to support it for the compatibility.
- // The implementers should use eSoundEventId instead.
- enum {eMsg=0, eCheckboxMsg=1, eIconClass=2, eTitleMessage=3, eEditfield1Msg=4,
- eEditfield2Msg=5, eEditfield1Value=6, eEditfield2Value=7,
- eButton0Text=8, eButton1Text=9, eButton2Text=10, eButton3Text=11,
- eDialogTitle=12, eOpeningSound=13};
- enum {eButtonPressed=0, eCheckboxState=1, eNumberButtons=2,
- eNumberEditfields=3, eEditField1Password=4, eDefaultButton=5,
- eDelayButtonEnable=6, eSoundEventId=7};
-%}
-
- void doDialog(in nsIDOMWindow aParent, in nsIDialogParamBlock aParamBlock, in string aChromeURL);
-};
diff --git a/toolkit/content/aboutLogging.js b/toolkit/content/aboutLogging.js
index 5aaf0f9ecc..daff10fbec 100644
--- a/toolkit/content/aboutLogging.js
+++ b/toolkit/content/aboutLogging.js
@@ -50,7 +50,7 @@ function moduleEnvVarPresent() {
* as markers.
*
* [1]: The keys of the `presets` object defined in
- * https://searchfox.org/mozilla-central/source/devtools/client/performance-new/shared/background.jsm.js
+ * https://searchfox.org/mozilla-central/source/devtools/client/performance-new/shared/background.sys.mjs
*/
const gOsSpecificLoggingPresets = (() => {
@@ -74,7 +74,7 @@ const gOsSpecificLoggingPresets = (() => {
const gLoggingPresets = {
networking: {
modules:
- "timestamp,sync,nsHttp:5,cache2:5,nsSocketTransport:5,nsHostResolver:5",
+ "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",
@@ -228,7 +228,6 @@ function populatePresets() {
$("#log-modules").value = gLoggingPresets[dropdown.value].modules;
}
setPresetAndDescription(dropdown.value);
- setLogModules();
Services.prefs.setCharPref("logging.config.preset", dropdown.value);
};
@@ -376,7 +375,6 @@ function parseURL() {
$("#set-log-modules-button").disabled = true;
$("#logging-preset-dropdown").disabled = true;
someElementsDisabled = true;
- setLogModules();
updateLogModules();
}
if (outputTypeOverriden) {
diff --git a/toolkit/content/aboutNetError.mjs b/toolkit/content/aboutNetError.mjs
index 83f40fc479..554553fd62 100644
--- a/toolkit/content/aboutNetError.mjs
+++ b/toolkit/content/aboutNetError.mjs
@@ -430,11 +430,21 @@ function initPage() {
tryAgain.hidden = true;
break;
- // Pinning errors are of type nssFailure2
+ // TLS errors and non-overridable certificate errors (e.g. pinning
+ // failures) are of type nssFailure2.
case "nssFailure2": {
learnMore.hidden = false;
const errorCode = document.getNetErrorInfo().errorCodeString;
+ RPMRecordTelemetryEvent(
+ "security.ui.tlserror",
+ "load",
+ "abouttlserror",
+ errorCode,
+ {
+ is_frame: (window.parent != window).toString(),
+ }
+ );
switch (errorCode) {
case "SSL_ERROR_UNSUPPORTED_VERSION":
case "SSL_ERROR_PROTOCOL_VERSION_ALERT": {
diff --git a/toolkit/content/aboutSupport.js b/toolkit/content/aboutSupport.js
index f668fd671f..f9f35e7e76 100644
--- a/toolkit/content/aboutSupport.js
+++ b/toolkit/content/aboutSupport.js
@@ -1179,7 +1179,7 @@ var snapshotFormatters = {
$.new("td", cdmInfo.keySystemName),
$.new("td", getVideoRobustness(rvArray)),
$.new("td", getAudioRobustness(rvArray)),
- $.new("td", getCapabilities(rvArray)),
+ $.new("td", getCapabilities(rvArray), null, { colspan: "4" }),
$.new("td", cdmInfo.clearlead ? "Yes" : "No"),
$.new("td", cdmInfo.isHDCP22Compatible ? "Yes" : "No"),
]);
diff --git a/toolkit/content/aboutSupport.xhtml b/toolkit/content/aboutSupport.xhtml
index d3de7d0019..d19fb64d56 100644
--- a/toolkit/content/aboutSupport.xhtml
+++ b/toolkit/content/aboutSupport.xhtml
@@ -567,13 +567,13 @@
<tbody id="media-content-decryption-modules-tbody">
<tr>
- <th colspan="6" class="title-column" data-l10n-id="media-content-decryption-modules-title"/>
+ <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 data-l10n-id="media-cdm-capabilities"/>
+ <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>
diff --git a/toolkit/content/aboutwebrtc/aboutWebrtc.mjs b/toolkit/content/aboutwebrtc/aboutWebrtc.mjs
index 3c41a4aa66..7e2c92a4bd 100644
--- a/toolkit/content/aboutwebrtc/aboutWebrtc.mjs
+++ b/toolkit/content/aboutwebrtc/aboutWebrtc.mjs
@@ -230,7 +230,7 @@ class SavePage extends Control {
]);
let FilePicker = makeFilePickerService();
const lazyFileUtils = lazy.FileUtils;
- FilePicker.init(window, dialogTitle, FilePicker.modeSave);
+ 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) {
diff --git a/toolkit/content/contentAreaUtils.js b/toolkit/content/contentAreaUtils.js
index d9ee83026e..983fd9890d 100644
--- a/toolkit/content/contentAreaUtils.js
+++ b/toolkit/content/contentAreaUtils.js
@@ -11,7 +11,6 @@ var { XPCOMUtils } = ChromeUtils.importESModule(
ChromeUtils.defineESModuleGetters(this, {
BrowserUtils: "resource://gre/modules/BrowserUtils.sys.mjs",
- Deprecated: "resource://gre/modules/Deprecated.sys.mjs",
DownloadLastDir: "resource://gre/modules/DownloadLastDir.sys.mjs",
DownloadPaths: "resource://gre/modules/DownloadPaths.sys.mjs",
Downloads: "resource://gre/modules/Downloads.sys.mjs",
@@ -689,7 +688,7 @@ function promiseTargetFile(
let fp = makeFilePicker();
let titleKey = aFpP.fpTitleKey || "SaveLinkTitle";
fp.init(
- window,
+ window.browsingContext,
ContentAreaUtils.stringBundle.GetStringFromName(titleKey),
Ci.nsIFilePicker.modeSave
);
diff --git a/toolkit/content/customElements.js b/toolkit/content/customElements.js
index 30958a3a31..b0a8f33fe6 100644
--- a/toolkit/content/customElements.js
+++ b/toolkit/content/customElements.js
@@ -818,6 +818,8 @@
// like the previous Services.scriptloader.loadSubscript() function
function importCustomElementFromESModule(name) {
switch (name) {
+ case "moz-button":
+ return import("chrome://global/content/elements/moz-button.mjs");
case "moz-button-group":
return import(
"chrome://global/content/elements/moz-button-group.mjs"
diff --git a/toolkit/content/jar.mn b/toolkit/content/jar.mn
index 8b18c94525..d08037a84a 100644
--- a/toolkit/content/jar.mn
+++ b/toolkit/content/jar.mn
@@ -91,10 +91,15 @@ toolkit.jar:
content/global/elements/message-bar.js (widgets/message-bar.js)
content/global/elements/menu.js (widgets/menu.js)
content/global/elements/menupopup.js (widgets/menupopup.js)
+ content/global/elements/moz-button.css (widgets/moz-button/moz-button.css)
+ content/global/elements/moz-button.mjs (widgets/moz-button/moz-button.mjs)
content/global/elements/moz-button-group.css (widgets/moz-button-group/moz-button-group.css)
content/global/elements/moz-button-group.mjs (widgets/moz-button-group/moz-button-group.mjs)
content/global/elements/moz-card.css (widgets/moz-card/moz-card.css)
content/global/elements/moz-card.mjs (widgets/moz-card/moz-card.mjs)
+ content/global/elements/moz-page-nav.css (widgets/moz-page-nav/moz-page-nav.css)
+ content/global/elements/moz-page-nav-button.css (widgets/moz-page-nav/moz-page-nav-button.css)
+ content/global/elements/moz-page-nav.mjs (widgets/moz-page-nav/moz-page-nav.mjs)
content/global/elements/moz-five-star.css (widgets/moz-five-star/moz-five-star.css)
content/global/elements/moz-five-star.mjs (widgets/moz-five-star/moz-five-star.mjs)
content/global/elements/moz-input-box.js (widgets/moz-input-box.js)
diff --git a/toolkit/content/license.html b/toolkit/content/license.html
index 9e0721906b..e9d2642354 100644
--- a/toolkit/content/license.html
+++ b/toolkit/content/license.html
@@ -104,7 +104,6 @@
<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#khronos">Khronos group License</a></li>
- <li><a href="about:license#kiss_fft">Kiss FFT License</a></li>
#ifdef MOZ_USE_LIBCXX
<li><a href="about:license#libc++">libc++ License</a></li>
#endif
@@ -154,9 +153,6 @@
<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>
-#ifdef MOZ_DEFAULT_BROWSER_AGENT
- <li><a href="about:license#wintoast">WinToast License</a></li>
-#endif
<li><a href="about:license#x264">x264 License</a></li>
<li><a href="about:license#xiph">Xiph.org Foundation License</a></li>
</ul>
@@ -2041,7 +2037,6 @@ WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
<li><code>gfx/ots/</code></li>
<li><code>gfx/ycbcr/</code></li>
<li><code>ipc/chromium/</code></li>
- <li><code>media/openmax_dl/</code></li>
<li><code>toolkit/components/reputationservice/</code></li>
<li><code>toolkit/components/url-classifier/chromium/</code></li>
<li><code>tools/profiler/</code></li>
@@ -3116,80 +3111,6 @@ OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
</pre>
-
- <hr>
-
- <h1><a id="khronos"></a>Khronos group License</h1>
-
- <p>This license applies to the following files:</p>
-
- <ul>
- <li><code>media/openmax_dl/dl/api/omxtypes.h</code></li>
- <li><code>media/openmax_dl/dl/sp/api/omxSP.h</code></li>
- </ul>
-
-<pre>
-Copyright 2005-2008 The Khronos Group Inc. All Rights Reserved.
-
-These materials are protected by copyright laws and contain material
-proprietary to the Khronos Group, Inc. You may use these materials
-for implementing Khronos specifications, without altering or removing
-any trademark, copyright or other notice from the specification.
-
-Khronos Group makes no, and expressly disclaims any, representations
-or warranties, express or implied, regarding these materials, including,
-without limitation, any implied warranties of merchantability or fitness
-for a particular purpose or non-infringement of any intellectual property.
-Khronos Group makes no, and expressly disclaims any, warranties, express
-or implied, regarding the correctness, accuracy, completeness, timeliness,
-and reliability of these materials.
-
-Under no circumstances will the Khronos Group, or any of its Promoters,
-Contributors or Members or their respective partners, officers, directors,
-employees, agents or representatives be liable for any damages, whether
-direct, indirect, special or consequential damages for lost revenues,
-lost profits, or otherwise, arising from or in connection with these
-materials.
-
-Khronos and OpenMAX are trademarks of the Khronos Group Inc.
-</pre>
-
- <hr>
-
- <h1><a id="kiss_fft"></a>Kiss FFT License</h1>
-
- <p>This license applies to files in the directory
- <code>media/kiss_fft/</code>.</p>
-
-<pre>
-Copyright (c) 2003-2010 Mark Borgerding
-
-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 author nor the names of any 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>
-
<hr>
#ifdef MOZ_USE_LIBCXX
@@ -3695,9 +3616,6 @@ SOFTWARE.
<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>
-#ifdef MOZ_DEFAULT_BROWSER_AGENT
- <li><code>third_party/WinToast</code> unless otherwise specified</li>
-#endif
</ul>
See the individual LICENSE files or headers for copyright owners.</p>
@@ -5645,6 +5563,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
they are referred to below as "Distributable Code":
<ul>
<li><var>msvc*.dll</var> (C and C++ runtime libraries)</li>
+ <li><var>vcruntime*.dll</var> (Visual C++ Runtime)</li>
</ul>
</p>
diff --git a/toolkit/content/tests/browser/browser_about_logging.js b/toolkit/content/tests/browser/browser_about_logging.js
index f458b36e0d..fdf8eab57b 100644
--- a/toolkit/content/tests/browser/browser_about_logging.js
+++ b/toolkit/content/tests/browser/browser_about_logging.js
@@ -110,15 +110,12 @@ add_task(async function testURLParameters() {
!$("#some-elements-unavailable").hidden,
"If modules are selected via URL, a warning should be displayed."
);
- var inPageSorted = $("#current-log-modules")
- .innerText.split(",")
- .sort()
- .join(",");
- var inURLSorted = modulesInURL.split(",").sort().join(",");
+ var inInputSorted = $("#log-modules").value.split(",").sort().join(",");
+ var modulesSorted = modulesInURL.split(",").sort().join(",");
Assert.equal(
- inPageSorted,
- inURLSorted,
- "When selecting modules via URL params, the same modules are reflected in the page."
+ modulesSorted,
+ inInputSorted,
+ "When selecting modules via URL params, the log modules aren't immediately set"
);
});
}
@@ -135,19 +132,16 @@ add_task(async function testURLParameters() {
!$("#some-elements-unavailable").hidden,
"If a preset is selected via URL, a warning should be displayed."
);
- var inPageSorted = $("#current-log-modules")
- .innerText.split(",")
- .sort()
- .join(",");
+ var inInputSorted = $("#log-modules").value.split(",").sort().join(",");
var presetSorted = content
.presets()
[presetInURL].modules.split(",")
.sort()
.join(",");
Assert.equal(
- inPageSorted,
+ inInputSorted,
presetSorted,
- "When selecting a preset via URL params, the correct log modules are reflected in the page."
+ "When selecting a preset via URL params, the correct log modules are reflected in the input."
);
});
}
diff --git a/toolkit/content/tests/browser/browser_default_audio_filename.js b/toolkit/content/tests/browser/browser_default_audio_filename.js
index c32dda6878..2732c0e434 100644
--- a/toolkit/content/tests/browser/browser_default_audio_filename.js
+++ b/toolkit/content/tests/browser/browser_default_audio_filename.js
@@ -2,7 +2,7 @@
http://creativecommons.org/publicdomain/zero/1.0/ */
var MockFilePicker = SpecialPowers.MockFilePicker;
-MockFilePicker.init(window);
+MockFilePicker.init(window.browsingContext);
registerCleanupFunction(function () {
MockFilePicker.cleanup();
});
diff --git a/toolkit/content/tests/browser/browser_default_image_filename.js b/toolkit/content/tests/browser/browser_default_image_filename.js
index 9add704664..0f7847020c 100644
--- a/toolkit/content/tests/browser/browser_default_image_filename.js
+++ b/toolkit/content/tests/browser/browser_default_image_filename.js
@@ -2,7 +2,7 @@
http://creativecommons.org/publicdomain/zero/1.0/ */
var MockFilePicker = SpecialPowers.MockFilePicker;
-MockFilePicker.init(window);
+MockFilePicker.init(window.browsingContext);
const DATA_IMAGE_GIF_URL =
"data:image/gif;base64,R0lGODlhEAAOALMAAOazToeHh0tLS/7LZv/0jvb29t/f3//Ub//ge8WSLf/rhf/3kdbW1mxsbP//mf///yH5BAAAAAAALAAAAAAQAA4AAARe8L1Ekyky67QZ1hLnjM5UUde0ECwLJoExKcppV0aCcGCmTIHEIUEqjgaORCMxIC6e0CcguWw6aFjsVMkkIr7g77ZKPJjPZqIyd7sJAgVGoEGv2xsBxqNgYPj/gAwXEQA7";
registerCleanupFunction(function () {
diff --git a/toolkit/content/tests/browser/browser_default_image_filename_redirect.js b/toolkit/content/tests/browser/browser_default_image_filename_redirect.js
index a3fdd2d19e..82926b3d44 100644
--- a/toolkit/content/tests/browser/browser_default_image_filename_redirect.js
+++ b/toolkit/content/tests/browser/browser_default_image_filename_redirect.js
@@ -7,7 +7,7 @@
*/
let MockFilePicker = SpecialPowers.MockFilePicker;
-MockFilePicker.init(window);
+MockFilePicker.init(window.browsingContext);
add_task(async function () {
// This URL will redirect to doggy.png.
const URL_FIREBIRD =
diff --git a/toolkit/content/tests/browser/browser_saveImageURL.js b/toolkit/content/tests/browser/browser_saveImageURL.js
index c936b8ef84..0f7bf4b117 100644
--- a/toolkit/content/tests/browser/browser_saveImageURL.js
+++ b/toolkit/content/tests/browser/browser_saveImageURL.js
@@ -5,7 +5,7 @@ const IMAGE_PAGE =
var MockFilePicker = SpecialPowers.MockFilePicker;
-MockFilePicker.init(window);
+MockFilePicker.init(window.browsingContext);
MockFilePicker.returnValue = MockFilePicker.returnCancel;
registerCleanupFunction(function () {
diff --git a/toolkit/content/tests/browser/browser_save_folder_standalone_image.js b/toolkit/content/tests/browser/browser_save_folder_standalone_image.js
index ce45d04fdc..073e71a88b 100644
--- a/toolkit/content/tests/browser/browser_save_folder_standalone_image.js
+++ b/toolkit/content/tests/browser/browser_save_folder_standalone_image.js
@@ -43,7 +43,7 @@ async function clearHistoryAndWait() {
*/
let MockFilePicker = SpecialPowers.MockFilePicker;
-MockFilePicker.init(window);
+MockFilePicker.init(window.browsingContext);
add_task(async function () {
const IMAGE_URL =
diff --git a/toolkit/content/tests/browser/browser_save_resend_postdata.js b/toolkit/content/tests/browser/browser_save_resend_postdata.js
index 5eb1b1c904..3f3e729dab 100644
--- a/toolkit/content/tests/browser/browser_save_resend_postdata.js
+++ b/toolkit/content/tests/browser/browser_save_resend_postdata.js
@@ -3,7 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
var MockFilePicker = SpecialPowers.MockFilePicker;
-MockFilePicker.init(window);
+MockFilePicker.init(window.browsingContext);
/**
* Test for bug 471962 <https://bugzilla.mozilla.org/show_bug.cgi?id=471962>:
diff --git a/toolkit/content/tests/browser/datetime/browser.toml b/toolkit/content/tests/browser/datetime/browser.toml
index 6e8580ddc4..747014f386 100644
--- a/toolkit/content/tests/browser/datetime/browser.toml
+++ b/toolkit/content/tests/browser/datetime/browser.toml
@@ -8,8 +8,9 @@ skip-if = [
"os == 'linux' && fission && socketprocess_networking && !debug", # high frequency intermittent, Bug 1673140
]
+["browser_datetime_change_event.js"]
+
["browser_datetime_datepicker.js"]
-fail-if = ["a11y_checks"] # Bug 1854538 clicked td.outside may not be accessible
# This file was skipped before new tests were written based on it in Bug 1676068
skip-if = [
"tsan", # Frequently times out on TSan
@@ -46,7 +47,6 @@ skip-if = [
]
["browser_datetime_datepicker_min_max.js"]
-fail-if = ["a11y_checks"] # Bug 1854538 clicked TD may not be accessible
skip-if = [
"tsan", # Frequently times out on TSan
"os == 'win' && asan", # fails on asan
@@ -61,7 +61,6 @@ skip-if = [
]
["browser_datetime_datepicker_mousenav.js"]
-fail-if = ["a11y_checks"] # Bug 1854538 clicked td.weekend.outside may not be accessible
skip-if = [
"tsan", # Frequently times out on TSan
"os == 'win' && asan", # fails on asan
diff --git a/toolkit/content/tests/browser/datetime/browser_datetime_change_event.js b/toolkit/content/tests/browser/datetime/browser_datetime_change_event.js
new file mode 100644
index 0000000000..920653778a
--- /dev/null
+++ b/toolkit/content/tests/browser/datetime/browser_datetime_change_event.js
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+async function open_change_and_expect_one_change_event(page) {
+ await helper.openPicker(page);
+
+ let changeEventPromise = helper.promiseChange();
+
+ // Click the first item (top-left corner) of the calendar
+ helper.click(helper.getElement(DAYS_VIEW).children[0]);
+ await changeEventPromise;
+
+ await helper.closePicker();
+
+ let changeEvents = await SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [],
+ function () {
+ return content.wrappedJSObject.changeEventCount;
+ }
+ );
+ is(changeEvents, 1, "Should've got one change event");
+ await helper.tearDown();
+}
+
+add_task(async function test_change_event_simple() {
+ await open_change_and_expect_one_change_event(`data:text/html,
+ <!doctype html>
+ <script>
+ var changeEventCount = 0;
+ </script>
+ <input type="date" id="date" onchange="changeEventCount++">
+ `);
+});
+
+add_task(async function test_change_event_with_mutation() {
+ await open_change_and_expect_one_change_event(`data:text/html,
+ <!doctype html>
+ <script>
+ var changeEventCount = 0;
+ </script>
+ <input type="date" id="date" onchange="this.value = ''; changeEventCount++">
+ `);
+});
diff --git a/toolkit/content/tests/browser/datetime/head.js b/toolkit/content/tests/browser/datetime/head.js
index bbef72873c..46e2c78af5 100644
--- a/toolkit/content/tests/browser/datetime/head.js
+++ b/toolkit/content/tests/browser/datetime/head.js
@@ -113,15 +113,19 @@ class DateTimeTestHelper {
EventUtils.synthesizeMouseAtCenter(element, {}, this.frame.contentWindow);
}
- /**
- * Close the panel and the tab
- */
- async tearDown() {
+ async closePicker() {
if (this.panel.state != "closed") {
let pickerClosePromise = this.promisePickerClosed();
this.panel.hidePopup();
await pickerClosePromise;
}
+ }
+
+ /**
+ * Close the panel and the tab
+ */
+ async tearDown() {
+ await this.closePicker();
BrowserTestUtils.removeTab(this.tab);
this.tab = null;
}
diff --git a/toolkit/content/tests/chrome/chrome.toml b/toolkit/content/tests/chrome/chrome.toml
index 70fa12c4b6..3391a2923d 100644
--- a/toolkit/content/tests/chrome/chrome.toml
+++ b/toolkit/content/tests/chrome/chrome.toml
@@ -224,6 +224,8 @@ support-files = [
["test_menulist_in_popup.xhtml"]
+["test_menulist_initial_selection.xhtml"]
+
["test_menulist_keynav.xhtml"]
["test_menulist_null_value.xhtml"]
diff --git a/toolkit/content/tests/chrome/test_autocomplete_mac_caret.xhtml b/toolkit/content/tests/chrome/test_autocomplete_mac_caret.xhtml
index b49f8a1d5e..005c6ebffe 100644
--- a/toolkit/content/tests/chrome/test_autocomplete_mac_caret.xhtml
+++ b/toolkit/content/tests/chrome/test_autocomplete_mac_caret.xhtml
@@ -57,9 +57,9 @@ function checkKeyCaretTest(key, expectedStart, expectedEnd, result, testid)
keypressFired = true;
}
}
- SpecialPowers.addSystemEventListener(window, "keypress", listener, false);
+ SpecialPowers.wrap(window).addEventListener("keypress", listener, { mozSystemGroup: true });
synthesizeKey(key, {});
- SpecialPowers.removeSystemEventListener(window, "keypress", listener, false);
+ SpecialPowers.wrap(window).removeEventListener("keypress", listener, { mozSystemGroup: true });
is(keypressFired, result, `${testid} keypress event should${result ? "" : " not"} be fired`);
is(autocomplete.selectionStart, expectedStart, testid + " selectionStart");
is(autocomplete.selectionEnd, expectedEnd, testid + " selectionEnd");
diff --git a/toolkit/content/tests/chrome/test_menulist_initial_selection.xhtml b/toolkit/content/tests/chrome/test_menulist_initial_selection.xhtml
new file mode 100644
index 0000000000..19e9beae67
--- /dev/null
+++ b/toolkit/content/tests/chrome/test_menulist_initial_selection.xhtml
@@ -0,0 +1,55 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Menulist Initial Selection Test"
+ onload="setTimeout(runTest, 0)"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+async function runTest() {
+ const panel = document.querySelector("panel");
+ const menulist1 = document.getElementById("menulist1");
+ const menulist2 = document.getElementById("menulist2");
+
+ const panelShown = new Promise(r => panel.addEventListener("popupshown", r, { once: true }));
+ info("opening panel");
+ panel.openPopup(null, { x: 0, y: 0 });
+ await panelShown;
+ info("panel opened");
+
+ is(menulist1.value, "1", "menulist1 should have the first menuitem's value");
+ is(menulist1.label, "One", "menulist1 should have the first menuitem's label");
+
+ is(menulist2.value, "", "menulist2 should not be selected to the first item's value");
+ is(menulist2.label, "None", "menulist2 should not be selected to the first item's value");
+
+ SimpleTest.finish();
+}
+
+]]>
+</script>
+
+<panel>
+ <menulist id="menulist1" value="" label="None">
+ <menupopup id="menulistpopup">
+ <menuitem value="1" label="One"/>
+ <menuitem value="2" label="Two"/>
+ <menuitem value="3" label="Three"/>
+ </menupopup>
+ </menulist>
+ <menulist id="menulist2" value="" label="None" noinitialselection="true">
+ <menupopup id="menulistpopup">
+ <menuitem value="1" label="One"/>
+ <menuitem value="2" label="Two"/>
+ <menuitem value="3" label="Three"/>
+ </menupopup>
+ </menulist>
+</panel>
+
+</window>
diff --git a/toolkit/content/tests/chrome/window_tooltip.xhtml b/toolkit/content/tests/chrome/window_tooltip.xhtml
index 6a573f0bd9..b78075de45 100644
--- a/toolkit/content/tests/chrome/window_tooltip.xhtml
+++ b/toolkit/content/tests/chrome/window_tooltip.xhtml
@@ -15,12 +15,12 @@
<box id="parent" tooltiptext="Box Tooltip" style="margin: 10px">
<button id="withtext" label="Tooltip Text" tooltiptext="Button Tooltip"
- style="-moz-appearance: none; padding: 0;"/>
- <button id="without" label="No Tooltip" style="-moz-appearance: none; padding: 0;"/>
+ style="appearance: none; padding: 0;"/>
+ <button id="without" label="No Tooltip" style="appearance: none; padding: 0;"/>
<!-- remove the native theme and borders to avoid some platform
specific sizing differences -->
<button id="withtooltip" label="Tooltip Element" tooltip="thetooltip"
- class="plain" style="-moz-appearance: none; padding: 0;"/>
+ class="plain" style="appearance: none; padding: 0;"/>
</box>
<script class="testbody" type="application/javascript">
diff --git a/toolkit/content/tests/mochitest/test_autocomplete_change_after_focus.html b/toolkit/content/tests/mochitest/test_autocomplete_change_after_focus.html
index fe4a6ee67c..271cf50a79 100644
--- a/toolkit/content/tests/mochitest/test_autocomplete_change_after_focus.html
+++ b/toolkit/content/tests/mochitest/test_autocomplete_change_after_focus.html
@@ -77,7 +77,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=998893
resolve();
}
- SpecialPowers.addSystemEventListener(field, "keypress", handleEnter, true);
+ SpecialPowers.wrap(field).addEventListener("keypress", handleEnter, { capture: true, mozSystemGroup: true });
});
field.focus();
diff --git a/toolkit/content/tests/widgets/chrome.toml b/toolkit/content/tests/widgets/chrome.toml
index af2c778947..18fe0d153a 100644
--- a/toolkit/content/tests/widgets/chrome.toml
+++ b/toolkit/content/tests/widgets/chrome.toml
@@ -22,6 +22,8 @@ skip-if = ["os == 'linux'"] # Bug 1116215
["test_menubar.xhtml"]
skip-if = ["os == 'mac'"]
+["test_moz_button.html"]
+
["test_moz_button_group.html"]
["test_moz_card.html"]
@@ -32,6 +34,8 @@ skip-if = ["os == 'mac'"]
["test_moz_message_bar.html"]
+["test_moz_page_nav.html"]
+
["test_moz_support_link.html"]
["test_moz_toggle.html"]
@@ -65,4 +69,5 @@ skip-if = [
"os == 'android'",
"os == 'linux' && debug", # Bug 1765783
]
+
["test_videocontrols_onclickplay.html"]
diff --git a/toolkit/content/tests/widgets/test_moz_button.html b/toolkit/content/tests/widgets/test_moz_button.html
new file mode 100644
index 0000000000..473b2d1a1c
--- /dev/null
+++ b/toolkit/content/tests/widgets/test_moz_button.html
@@ -0,0 +1,158 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>MozButton Tests</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+ <script type="module" src="chrome://global/content/elements/moz-button.mjs"></script>
+ <link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+ <link rel="stylesheet" href="chrome://global/skin/design-system/tokens-brand.css">
+ <link rel="stylesheet" href="chrome://global/skin/design-system/text-and-typography.css">
+<style>
+.four::part(button),
+.five::part(button),
+.six::part(button) {
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' width='16' height='16' fill='context-fill' fill-opacity='context-fill-opacity'%3E%3Cpath d='M3 7 1.5 7l-.5.5L1 9l.5.5 1.5 0 .5-.5 0-1.5z'/%3E%3Cpath d='m8.75 7-1.5 0-.5.5 0 1.5.5.5 1.5 0 .5-.5 0-1.5z'/%3E%3Cpath d='M14.5 7 13 7l-.5.5 0 1.5.5.5 1.5 0L15 9l0-1.5z'/%3E%3C/svg%3E");
+}
+</style>
+ <script>
+ function normalizeColor(val, computedStyles) {
+ if (val.includes("currentColor")) {
+ val = val.replaceAll("currentColor", computedStyles.color);
+ }
+ if (val.startsWith("light-dark")) {
+ let [, light, dark] = val.match(/light-dark\(([^,]+),\s*([^)]+)\)/);
+ if (light && dark) {
+ val = window.matchMedia("(prefers-color-scheme: dark)").matches ? dark : light;
+ }
+ }
+ try {
+ let { r, g, b, a } = InspectorUtils.colorToRGBA(val);
+ return `rgba(${r}, ${g}, ${b}, ${a})`;
+ } catch (e) {
+ info(val);
+ throw e;
+ }
+ }
+
+ function assertButtonPropertiesMatch(el, propertyToCssVar) {
+ let elStyles = getComputedStyle(el.buttonEl);
+ for (let [property, cssVar] of Object.entries(propertyToCssVar)) {
+ let propertyVal = elStyles[property];
+ let cssVarVal = cssVar.startsWith("--") ? elStyles.getPropertyValue(cssVar) : cssVar;
+ if (propertyVal.startsWith("rgb") || propertyVal.startsWith("#") || propertyVal.startsWith("color")) {
+ propertyVal = normalizeColor(propertyVal, elStyles);
+ cssVarVal = normalizeColor(cssVarVal, elStyles);
+ }
+ info(`${propertyVal} == ${cssVarVal}`);
+ is(propertyVal, cssVarVal, `${property} should be ${cssVar}`);
+ }
+ }
+
+ add_task(async function testButtonTypes() {
+ let [...buttons] = document.querySelectorAll("moz-button");
+ let [one, two, three, four, five, six] = buttons;
+
+ await Promise.all(buttons.map(btn => btn.updateComplete));
+
+ is(one.textContent, "Test button", "Text is set");
+ is(two.buttonEl.textContent.trim(), "Test button", "Text is set");
+ is(three.textContent, "Test button", "Text is set");
+
+ assertButtonPropertiesMatch(one, {
+ backgroundColor: "--button-background-color",
+ color: "--button-text-color",
+ height: "--button-min-height",
+ });
+ assertButtonPropertiesMatch(two, {
+ backgroundColor: "--button-background-color",
+ color: "--button-text-color",
+ height: "--button-min-height",
+ });
+ assertButtonPropertiesMatch(three, {
+ backgroundColor: "--button-background-color-primary",
+ color: "--button-text-color-primary",
+ height: "--button-min-height",
+ });
+
+ assertButtonPropertiesMatch(four, {
+ width: "--button-size-icon",
+ height: "--button-size-icon",
+ backgroundColor: "--button-background-color",
+ fill: "--button-text-color",
+ });
+ assertButtonPropertiesMatch(five, {
+ width: "--button-size-icon",
+ height: "--button-size-icon",
+ backgroundColor: "transparent",
+ fill: "--button-text-color",
+ });
+ assertButtonPropertiesMatch(six, {
+ width: "--button-size-icon",
+ height: "--button-size-icon",
+ backgroundColor: "transparent",
+ fill: "--button-text-color",
+ });
+
+ buttons.forEach(btn => (btn.size = "small"));
+
+ await Promise.all(buttons.map(btn => btn.updateComplete));
+
+ assertButtonPropertiesMatch(one, {
+ height: "--button-min-height-small",
+ });
+ assertButtonPropertiesMatch(two, {
+ height: "--button-min-height-small",
+ });
+ assertButtonPropertiesMatch(three, {
+ height: "--button-min-height-small",
+ });
+ assertButtonPropertiesMatch(four, {
+ width: "--button-size-icon-small",
+ height: "--button-size-icon-small",
+ });
+ assertButtonPropertiesMatch(five, {
+ width: "--button-size-icon-small",
+ height: "--button-size-icon-small",
+ });
+ assertButtonPropertiesMatch(six, {
+ width: "--button-size-icon-small",
+ height: "--button-size-icon-small",
+ });
+ });
+
+ add_task(async function testA11yAttributes() {
+ let button = document.querySelector("moz-button");
+
+ async function testProperty(propName, jsPropName = propName) {
+ let propValue = `${propName} value`;
+ ok(!button.buttonEl.hasAttribute(propName), `No ${propName} on inner button`);
+ button.setAttribute(propName, propValue);
+
+ await button.updateComplete;
+
+ ok(!button.hasAttribute(propName), `moz-button ${propName} cleared`);
+ is(button.buttonEl.getAttribute(propName), propValue, `${propName} added to inner button`);
+
+ button[jsPropName] = null;
+ await button.updateComplete;
+
+ ok(!button.buttonEl.hasAttribute(propName), `${propName} cleared by setting property`);
+ }
+
+ await testProperty("title");
+ await testProperty("aria-label", "ariaLabel");
+ });
+
+ </script>
+</head>
+<body>
+ <moz-button class="one">Test button</moz-button>
+ <moz-button class="two" label="Test button"></moz-button>
+ <moz-button class="three" type="primary">Test button</moz-button>
+ <moz-button class="four" type="icon"></moz-button>
+ <moz-button class="five" type="icon ghost"></moz-button>
+ <moz-button class="six" type="ghost icon"></moz-button>
+</body>
+</html>
diff --git a/toolkit/content/tests/widgets/test_moz_page_nav.html b/toolkit/content/tests/widgets/test_moz_page_nav.html
new file mode 100644
index 0000000000..604df7c024
--- /dev/null
+++ b/toolkit/content/tests/widgets/test_moz_page_nav.html
@@ -0,0 +1,306 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>MozPageNav Tests</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+ <link rel="stylesheet" href="chrome://global/skin/in-content/common.css">
+ <script type="module" src="chrome://global/content/elements/moz-page-nav.mjs"></script>
+</head>
+<style>
+body {
+ display: flex;
+}
+#navigation {
+ width: var(--page-nav-width);
+}
+</style>
+<body>
+ <p id="display"></p>
+ <div id="content">
+ <div id="navigation">
+ <moz-page-nav heading="Heading">
+ <moz-page-nav-button view="view-one" iconSrc="chrome://mozapps/skin/extensions/category-discover.svg">
+ <span class="view-name">View 1</span>
+ </moz-page-nav-button>
+ <moz-page-nav-button view="view-two" iconSrc="chrome://mozapps/skin/extensions/category-discover.svg">
+ <span class="view-name">View 2</span>
+ </moz-page-nav-button>
+ <moz-page-nav-button view="view-three" iconSrc="chrome://mozapps/skin/extensions/category-discover.svg">
+ <span class="view-name">View 3</span>
+ </moz-page-nav-button>
+ <moz-page-nav-button view="view-four" iconSrc="chrome://mozapps/skin/extensions/category-discover.svg">
+ <span class="view-name">View 4</span>
+ </moz-page-nav-button>
+ <moz-page-nav-button view="view-five" iconSrc="chrome://mozapps/skin/extensions/category-discover.svg">
+ <span class="view-name">View 5</span>
+ </moz-page-nav-button>
+ </moz-page-nav>
+ </div>
+ </div>
+<pre id="test"></pre>
+<script>
+ Services.scriptloader.loadSubScript(
+ "chrome://browser/content/utilityOverlay.js",
+ this
+ );
+ const { BrowserTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/BrowserTestUtils.sys.mjs"
+ );
+
+const mozPageNav = document.querySelector("moz-page-nav");
+
+function isActiveElement(expectedActiveEl) {
+ return expectedActiveEl.getRootNode().activeElement == expectedActiveEl;
+ }
+
+ /**
+ * Tests that the first page nav button is selected by default
+ */
+ add_task(async function test_first_item_selected_by_default() {
+ is(
+ mozPageNav.pageNavButtons.length,
+ 5,
+ "Five page nav buttons are in the navigation"
+ );
+
+ ok(
+ mozPageNav.pageNavButtons[0].view === mozPageNav.currentView,
+ "The first page nav button is selected by default"
+ )
+ });
+
+ /**
+ * Tests that views are selected when clicked
+ */
+ add_task(async function test_select_view() {
+ let gBrowser = BrowserWindowTracker.getTopWindow().top.gBrowser;
+ let secondViewButton = mozPageNav.pageNavButtons[1];
+ let viewChanged = BrowserTestUtils.waitForEvent(
+ gBrowser,
+ "change-view"
+ );
+
+ secondViewButton.buttonEl.click();
+ await viewChanged;
+
+ ok(
+ secondViewButton.view === mozPageNav.currentView,
+ "The second page nav button is selected"
+ )
+
+ let thirdPageNavButton = mozPageNav.pageNavButtons[2];
+ viewChanged = BrowserTestUtils.waitForEvent(
+ gBrowser,
+ "change-view"
+ );
+
+ thirdPageNavButton.buttonEl.click();
+ await viewChanged;
+
+ ok(
+ thirdPageNavButton.view === mozPageNav.currentView,
+ "The third page nav button is selected"
+ )
+
+ let firstPageNavButton = mozPageNav.pageNavButtons[0];
+ viewChanged = BrowserTestUtils.waitForEvent(
+ gBrowser,
+ "change-view"
+ );
+
+ firstPageNavButton.buttonEl.click();
+ await viewChanged;
+
+ ok(
+ firstPageNavButton.view === mozPageNav.currentView,
+ "The first page nav button is selected"
+ )
+ });
+
+ /**
+ * Tests that categories are keyboard-navigable
+ */
+ add_task(async function test_keyboard_navigation() {
+ const arrowDown = async () => {
+ info("Arrow down");
+ synthesizeKey("KEY_ArrowDown", {});
+ await mozPageNav.updateComplete;
+ };
+ const arrowUp = async () => {
+ info("Arrow up");
+ synthesizeKey("KEY_ArrowUp", {});
+ await mozPageNav.updateComplete;
+ };
+ const arrowLeft = async () => {
+ info("Arrow left");
+ synthesizeKey("KEY_ArrowLeft", {});
+ await mozPageNav.updateComplete;
+ };
+ const arrowRight = async () => {
+ info("Arrow right");
+ synthesizeKey("KEY_ArrowRight", {});
+ await mozPageNav.updateComplete;
+ };
+
+ // Setting this pref allows the test to run as expected with a keyboard on MacOS
+ await SpecialPowers.pushPrefEnv({
+ set: [["accessibility.tabfocus", 7]],
+ });
+
+ let firstPageNavButton = mozPageNav.pageNavButtons[0];
+ let secondPageNavButton = mozPageNav.pageNavButtons[1];
+ let thirdPageNavButton = mozPageNav.pageNavButtons[2];
+ let fourthPageNavButton = mozPageNav.pageNavButtons[3];
+ let fifthPageNavButton = mozPageNav.pageNavButtons[4];
+
+ is(
+ firstPageNavButton.view,
+ mozPageNav.currentView,
+ "The first page nav button is selected"
+ )
+ firstPageNavButton.buttonEl.focus();
+ await arrowDown();
+ ok(
+ isActiveElement(secondPageNavButton),
+ "The second page nav button is the active element after first arrow down"
+ );
+ is(
+ secondPageNavButton.view,
+ mozPageNav.currentView,
+ "The second page nav button is selected"
+ )
+ await arrowDown();
+ is(
+ thirdPageNavButton.view,
+ mozPageNav.currentView,
+ "The third page nav button is selected"
+ )
+ await arrowDown();
+ is(
+ fourthPageNavButton.view,
+ mozPageNav.currentView,
+ "The fourth page nav button is selected"
+ )
+ await arrowDown();
+ is(
+ fifthPageNavButton.view,
+ mozPageNav.currentView,
+ "The fifth page nav button is selected"
+ )
+ await arrowDown();
+ is(
+ fifthPageNavButton.view,
+ mozPageNav.currentView,
+ "The fifth page nav button is still selected"
+ )
+ await arrowUp();
+ is(
+ fourthPageNavButton.view,
+ mozPageNav.currentView,
+ "The fourth page nav button is selected"
+ )
+ await arrowUp();
+ is(
+ thirdPageNavButton.view,
+ mozPageNav.currentView,
+ "The third page nav button is selected"
+ )
+ await arrowUp();
+ is(
+ secondPageNavButton.view,
+ mozPageNav.currentView,
+ "The second page nav button is selected"
+ )
+ await arrowUp();
+ is(
+ firstPageNavButton.view,
+ mozPageNav.currentView,
+ "The first page nav button is selected"
+ )
+ await arrowUp();
+ is(
+ firstPageNavButton.view,
+ mozPageNav.currentView,
+ "The first page nav button is still selected"
+ )
+
+ // Test navigation with arrow left/right keys
+ is(
+ firstPageNavButton.view,
+ mozPageNav.currentView,
+ "The first page nav button is selected"
+ )
+ firstPageNavButton.buttonEl.focus();
+ await arrowRight();
+ ok(
+ isActiveElement(secondPageNavButton),
+ "The second page nav button is the active element after first arrow right"
+ );
+ is(
+ secondPageNavButton.view,
+ mozPageNav.currentView,
+ "The second page nav button is selected"
+ )
+ await arrowRight();
+ is(
+ thirdPageNavButton.view,
+ mozPageNav.currentView,
+ "The third page nav button is selected"
+ )
+ await arrowRight();
+ is(
+ fourthPageNavButton.view,
+ mozPageNav.currentView,
+ "The fourth page nav button is selected"
+ )
+ await arrowRight();
+ is(
+ fifthPageNavButton.view,
+ mozPageNav.currentView,
+ "The fifth page nav button is selected"
+ )
+ await arrowRight();
+ is(
+ fifthPageNavButton.view,
+ mozPageNav.currentView,
+ "The fifth page nav button is still selected"
+ )
+ await arrowLeft();
+ is(
+ fourthPageNavButton.view,
+ mozPageNav.currentView,
+ "The fourth page nav button is selected"
+ )
+ await arrowLeft();
+ is(
+ thirdPageNavButton.view,
+ mozPageNav.currentView,
+ "The third page nav button is selected"
+ )
+ await arrowLeft();
+ is(
+ secondPageNavButton.view,
+ mozPageNav.currentView,
+ "The second page nav button is selected"
+ )
+ await arrowLeft();
+ is(
+ firstPageNavButton.view,
+ mozPageNav.currentView,
+ "The first page nav button is selected"
+ )
+ await arrowLeft();
+ is(
+ firstPageNavButton.view,
+ mozPageNav.currentView,
+ "The first page nav button is still selected"
+ )
+
+ await SpecialPowers.popPrefEnv();
+ });
+</script>
+</body>
+</html>
diff --git a/toolkit/content/tests/widgets/test_videocontrols.html b/toolkit/content/tests/widgets/test_videocontrols.html
index 32cd23df6a..076b4350fd 100644
--- a/toolkit/content/tests/widgets/test_videocontrols.html
+++ b/toolkit/content/tests/widgets/test_videocontrols.html
@@ -67,7 +67,7 @@ let expectingEventPromise;
async function isMuteButtonMuted() {
const muteButton = getElementWithinVideo(video, "muteButton");
await new Promise(SimpleTest.executeSoon);
- return muteButton.getAttribute("muted") === "true";
+ return muteButton.hasAttribute("muted");
}
async function isVolumeSliderShowingCorrectVolume(expectedVolume) {
diff --git a/toolkit/content/widgets/arrowscrollbox.js b/toolkit/content/widgets/arrowscrollbox.js
index 28de96c8d7..7109891faf 100644
--- a/toolkit/content/widgets/arrowscrollbox.js
+++ b/toolkit/content/widgets/arrowscrollbox.js
@@ -21,7 +21,7 @@
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"/>
+ <toolbarbutton id="scrollbutton-up" part="scrollbutton-up" keyNav="false" data-l10n-id="overflow-scroll-button-up"/>
<spacer part="overflow-start-indicator"/>
<box class="scrollbox-clip" part="scrollbox-clip" flex="1">
<scrollbox part="scrollbox" flex="1">
@@ -29,7 +29,7 @@
</scrollbox>
</box>
<spacer part="overflow-end-indicator"/>
- <toolbarbutton id="scrollbutton-down" part="scrollbutton-down" keyNav="false"/>
+ <toolbarbutton id="scrollbutton-down" part="scrollbutton-down" keyNav="false" data-l10n-id="overflow-scroll-button-down"/>
`;
}
@@ -43,6 +43,8 @@
this._scrollButtonDown =
this.shadowRoot.getElementById("scrollbutton-down");
+ MozXULElement.insertFTLIfNeeded("toolkit/global/arrowscrollbox.ftl");
+
this._arrowScrollAnim = {
scrollbox: this,
requestHandle: 0,
@@ -134,6 +136,8 @@
}
this.hasConnected = true;
+ document.l10n.connectRoot(this.shadowRoot);
+
if (!this.hasAttribute("smoothscroll")) {
this.smoothScroll = Services.prefs.getBoolPref(
"toolkit.scrollbox.smoothScroll",
@@ -639,6 +643,7 @@
this._scrollTimer.cancel();
this._scrollTimer = null;
}
+ document.l10n.disconnectRoot(this.shadowRoot);
}
on_wheel(event) {
@@ -749,7 +754,7 @@
}
}
- on_touchend(event) {
+ on_touchend() {
this._touchStart = -1;
}
@@ -804,12 +809,12 @@
this._updateScrollButtonsDisabledState();
}
- on_scroll(event) {
+ on_scroll() {
this._isScrolling = true;
this._updateScrollButtonsDisabledState();
}
- on_scrollend(event) {
+ on_scrollend() {
this._isScrolling = false;
this._destination = 0;
this._direction = 0;
diff --git a/toolkit/content/widgets/autocomplete-input.js b/toolkit/content/widgets/autocomplete-input.js
index 36105ba4d7..a6fb4b5067 100644
--- a/toolkit/content/widgets/autocomplete-input.js
+++ b/toolkit/content/widgets/autocomplete-input.js
@@ -40,7 +40,7 @@
this.addEventListener(
"compositionstart",
- event => {
+ () => {
if (
this.mController.input.wrappedJSObject == this.nsIAutocompleteInput
) {
@@ -52,7 +52,7 @@
this.addEventListener(
"compositionend",
- event => {
+ () => {
if (
this.mController.input.wrappedJSObject == this.nsIAutocompleteInput
) {
@@ -64,7 +64,7 @@
this.addEventListener(
"focus",
- event => {
+ () => {
this.attachController();
if (
window.gBrowser &&
@@ -82,7 +82,7 @@
this.addEventListener(
"blur",
- event => {
+ () => {
if (!this._dontBlur) {
if (this.forceComplete && this.mController.matchCount >= 1) {
// If forceComplete is requested, we need to call the enter processing
@@ -625,7 +625,7 @@
return value;
}
- onInput(aEvent) {
+ onInput() {
if (
!this.mIgnoreInput &&
this.mController.input.wrappedJSObject == this.nsIAutocompleteInput
diff --git a/toolkit/content/widgets/autocomplete-popup.js b/toolkit/content/widgets/autocomplete-popup.js
index f033511e07..a13ba1bc62 100644
--- a/toolkit/content/widgets/autocomplete-popup.js
+++ b/toolkit/content/widgets/autocomplete-popup.js
@@ -572,7 +572,7 @@
}
setListeners() {
- this.addEventListener("popupshowing", event => {
+ 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.
@@ -584,14 +584,14 @@
this.mPopupOpen = true;
});
- this.addEventListener("popupshown", event => {
+ this.addEventListener("popupshown", () => {
if (this._adjustHeightOnPopupShown) {
this._adjustHeightOnPopupShown = false;
this.adjustHeight();
}
});
- this.addEventListener("popuphiding", event => {
+ this.addEventListener("popuphiding", () => {
var isListActive = true;
if (this.selectedIndex == -1) {
isListActive = false;
diff --git a/toolkit/content/widgets/autocomplete-richlistitem.js b/toolkit/content/widgets/autocomplete-richlistitem.js
index ccbd37e132..fddd5b4029 100644
--- a/toolkit/content/widgets/autocomplete-richlistitem.js
+++ b/toolkit/content/widgets/autocomplete-richlistitem.js
@@ -21,7 +21,7 @@
* 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", event => {
+ this.addEventListener("mousedown", () => {
// Call this.control only once since it's not a simple getter.
let control = this.control;
if (!control || control.disabled) {
@@ -587,7 +587,7 @@
/**
* Override _getSearchTokens to have the Learn More text emphasized
*/
- _getSearchTokens(aSearch) {
+ _getSearchTokens() {
return [this._learnMoreString.toLowerCase()];
}
}
diff --git a/toolkit/content/widgets/browser-custom-element.js b/toolkit/content/widgets/browser-custom-element.js
index e9b29034fa..887f59c742 100644
--- a/toolkit/content/widgets/browser-custom-element.js
+++ b/toolkit/content/widgets/browser-custom-element.js
@@ -872,7 +872,7 @@
this.webProgress.removeProgressListener(aListener);
}
- onPageHide(aEvent) {
+ 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);
@@ -1919,7 +1919,7 @@
// 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(browser, redirectLoadSwitchId) {
+ afterChangeRemoteness() {
/* no-op unless replaced */
return false;
}
diff --git a/toolkit/content/widgets/datetimebox.js b/toolkit/content/widgets/datetimebox.js
index 28b32fddfa..04ed398bd7 100644
--- a/toolkit/content/widgets/datetimebox.js
+++ b/toolkit/content/widgets/datetimebox.js
@@ -5,7 +5,7 @@
"use strict";
// This is a UA widget. It runs in per-origin UA widget scope,
-// to be loaded by UAWidgetsChild.jsm.
+// to be loaded by UAWidgetsChild.sys.mjs.
/*
* This is the class of entry. It will construct the actual implementation
diff --git a/toolkit/content/widgets/dialog.js b/toolkit/content/widgets/dialog.js
index 52eb2168f8..c4b25c2f48 100644
--- a/toolkit/content/widgets/dialog.js
+++ b/toolkit/content/widgets/dialog.js
@@ -146,7 +146,7 @@
if (document.readyState == "complete") {
this._postLoadInit();
} else {
- window.addEventListener("load", event => this._postLoadInit());
+ window.addEventListener("load", () => this._postLoadInit());
}
}
@@ -521,7 +521,7 @@
}
var btn = this.getButton(this.defaultButton);
- if (btn) {
+ if (btn && !btn.hidden) {
this._doButtonCommand(this.defaultButton);
}
}
diff --git a/toolkit/content/widgets/editor.js b/toolkit/content/widgets/editor.js
index 9e5ffb542e..8e014f77af 100644
--- a/toolkit/content/widgets/editor.js
+++ b/toolkit/content/widgets/editor.js
@@ -16,13 +16,13 @@
"nsIURIContentListener",
"nsISupportsWeakReference",
]),
- doContent(contentType, isContentPreferred, request, contentHandler) {
+ doContent() {
return false;
},
- isPreferred(contentType, desiredContentType) {
+ isPreferred() {
return false;
},
- canHandleContent(contentType, isContentPreferred, desiredContentType) {
+ canHandleContent() {
return false;
},
loadCookie: null,
diff --git a/toolkit/content/widgets/findbar.js b/toolkit/content/widgets/findbar.js
index 3cbce11771..cdfbd315a7 100644
--- a/toolkit/content/widgets/findbar.js
+++ b/toolkit/content/widgets/findbar.js
@@ -164,7 +164,7 @@
window.addEventListener("unload", this.destroy);
- this._findField.addEventListener("input", event => {
+ 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
@@ -230,17 +230,17 @@
}
});
- this._findField.addEventListener("blur", event => {
+ 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", event => {
+ this._findField.addEventListener("focus", () => {
this._updateBrowserWithState();
});
- this._findField.addEventListener("compositionstart", event => {
+ this._findField.addEventListener("compositionstart", () => {
// Don't close the find toolbar while IME is composing.
let findbar = this;
findbar._isIMEComposing = true;
@@ -251,7 +251,7 @@
}
});
- this._findField.addEventListener("compositionend", event => {
+ this._findField.addEventListener("compositionend", () => {
this._isIMEComposing = false;
if (this.findMode != this.FIND_NORMAL) {
this._setFindCloseTimeout();
@@ -1307,7 +1307,7 @@
}
}
- onHighlightFinished(result) {
+ onHighlightFinished() {
// Noop.
}
diff --git a/toolkit/content/widgets/menu.js b/toolkit/content/widgets/menu.js
index f787747a01..1a55d799b6 100644
--- a/toolkit/content/widgets/menu.js
+++ b/toolkit/content/widgets/menu.js
@@ -129,7 +129,7 @@
};
// The <menucaption> element is used for rendering <html:optgroup> inside of <html:select>,
- // See SelectParentHelper.jsm.
+ // See SelectParentHelper.sys.mjs.
class MozMenuCaption extends MozMenuBaseMixin(MozXULElement) {
static get inheritedAttributes() {
return {
diff --git a/toolkit/content/widgets/menulist.js b/toolkit/content/widgets/menulist.js
index 4e66c030f3..3672d4ccf1 100644
--- a/toolkit/content/widgets/menulist.js
+++ b/toolkit/content/widgets/menulist.js
@@ -289,6 +289,9 @@
}
setInitialSelection() {
+ if (this.getAttribute("noinitialselection") === "true") {
+ return;
+ }
var popup = this.menupopup;
if (popup) {
var arr = popup.getElementsByAttribute("selected", "true");
diff --git a/toolkit/content/widgets/menupopup.js b/toolkit/content/widgets/menupopup.js
index 31801d6a33..c7448d4f05 100644
--- a/toolkit/content/widgets/menupopup.js
+++ b/toolkit/content/widgets/menupopup.js
@@ -11,16 +11,12 @@
"resource://gre/modules/AppConstants.sys.mjs"
);
- // 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. 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.
- Services.els.addSystemEventListener(
- document,
+ 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([type=checkbox],[type=radio]${
@@ -33,7 +29,10 @@
e.target.toggleAttribute("needsgutter", haveCheckableChild);
}
},
- false
+ // 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) {
@@ -74,13 +73,13 @@
initShadowDOM() {
// Retarget events from shadow DOM arrowscrollbox to the host.
- this.scrollBox.addEventListener("scroll", ev =>
+ this.scrollBox.addEventListener("scroll", () =>
this.dispatchEvent(new Event("scroll"))
);
- this.scrollBox.addEventListener("overflow", ev =>
+ this.scrollBox.addEventListener("overflow", () =>
this.dispatchEvent(new Event("overflow"))
);
- this.scrollBox.addEventListener("underflow", ev =>
+ this.scrollBox.addEventListener("underflow", () =>
this.dispatchEvent(new Event("underflow"))
);
}
diff --git a/toolkit/content/widgets/moz-button-group/moz-button-group.mjs b/toolkit/content/widgets/moz-button-group/moz-button-group.mjs
index 0706f94762..8bf553c23d 100644
--- a/toolkit/content/widgets/moz-button-group/moz-button-group.mjs
+++ b/toolkit/content/widgets/moz-button-group/moz-button-group.mjs
@@ -44,7 +44,7 @@ export default class MozButtonGroup extends MozLitElement {
}
}
- onSlotchange(e) {
+ onSlotchange() {
for (let child of this.defaultSlotEl.assignedNodes()) {
if (!(child instanceof Element)) {
// Text nodes won't support classList or getAttribute.
diff --git a/toolkit/content/widgets/moz-button/moz-button.css b/toolkit/content/widgets/moz-button/moz-button.css
new file mode 100644
index 0000000000..47567df41d
--- /dev/null
+++ b/toolkit/content/widgets/moz-button/moz-button.css
@@ -0,0 +1,142 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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;
+}
+
+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%;
+
+ &[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);
+ }
+ }
+
+ &[type~=icon] {
+ background-size: var(--icon-size-default);
+ background-position: center;
+ background-repeat: no-repeat;
+ -moz-context-properties: fill, stroke;
+ fill: currentColor;
+ stroke: currentColor;
+ 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);
+ }
+ }
+}
diff --git a/toolkit/content/widgets/moz-button/moz-button.mjs b/toolkit/content/widgets/moz-button/moz-button.mjs
new file mode 100644
index 0000000000..3e7c151e61
--- /dev/null
+++ b/toolkit/content/widgets/moz-button/moz-button.mjs
@@ -0,0 +1,90 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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";
+
+/**
+ * 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} ariaLabelAttribute - Internal, map aria-label attribute to the ariaLabel JS property.
+ * @property {HTMLButtonElement} buttonEl - The internal button 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 },
+ 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 },
+ ariaLabelAttribute: {
+ type: String,
+ attribute: "aria-label",
+ reflect: true,
+ },
+ ariaLabel: { type: String, state: true },
+ };
+
+ static queries = {
+ buttonEl: "button",
+ };
+
+ constructor() {
+ super();
+ this.type = "default";
+ this.size = "default";
+ this.disabled = false;
+ }
+
+ willUpdate(changes) {
+ if (changes.has("titleAttribute")) {
+ this.title = this.titleAttribute;
+ this.titleAttribute = null;
+ }
+ if (changes.has("ariaLabelAttribute")) {
+ this.ariaLabel = this.ariaLabelAttribute;
+ this.ariaLabelAttribute = null;
+ }
+ }
+
+ 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"
+ >
+ <slot>${this.label}</slot>
+ </button>
+ `;
+ }
+}
+customElements.define("moz-button", MozButton);
diff --git a/toolkit/content/widgets/moz-button/moz-button.stories.mjs b/toolkit/content/widgets/moz-button/moz-button.stories.mjs
new file mode 100644
index 0000000000..52a459e807
--- /dev/null
+++ b/toolkit/content/widgets/moz-button/moz-button.stories.mjs
@@ -0,0 +1,100 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "../vendor/lit.all.mjs";
+// eslint-disable-next-line import/no-unassigned-import
+import "./moz-button.mjs";
+
+export default {
+ title: "UI Widgets/Moz Button",
+ component: "moz-button",
+ argTypes: {
+ l10nId: {
+ options: [
+ "moz-button-labelled",
+ "moz-button-titled",
+ "moz-button-aria-labelled",
+ ],
+ control: { type: "select" },
+ },
+ size: {
+ options: ["default", "small"],
+ control: { type: "radio" },
+ },
+ },
+ parameters: {
+ actions: {
+ handles: ["click"],
+ },
+ status: "in-development",
+ fluent: `
+moz-button-labelled = Button
+moz-button-primary = Primary
+moz-button-destructive = Destructive
+moz-button-titled =
+ .title = View logins
+moz-button-aria-labelled =
+ .aria-label = View logins
+`,
+ },
+};
+
+const Template = ({ type, size, l10nId, iconUrl, disabled }) => html`
+ <style>
+ moz-button[type~="icon"]::part(button) {
+ background-image: url("${iconUrl}");
+ }
+ </style>
+ <moz-button
+ data-l10n-id=${l10nId}
+ type=${type}
+ size=${size}
+ ?disabled=${disabled}
+ ></moz-button>
+`;
+
+export const Default = Template.bind({});
+Default.args = {
+ type: "default",
+ size: "default",
+ l10nId: "moz-button-labelled",
+ iconUrl: "chrome://global/skin/icons/more.svg",
+ disabled: false,
+};
+export const DefaultSmall = Template.bind({});
+DefaultSmall.args = {
+ type: "default",
+ size: "small",
+ l10nId: "moz-button-labelled",
+ iconUrl: "chrome://global/skin/icons/more.svg",
+ disabled: false,
+};
+export const Primary = Template.bind({});
+Primary.args = {
+ ...Default.args,
+ type: "primary",
+ l10nId: "moz-button-primary",
+};
+export const Destructive = Template.bind({});
+Destructive.args = {
+ ...Default.args,
+ type: "destructive",
+ l10nId: "moz-button-destructive",
+};
+export const Icon = Template.bind({});
+Icon.args = {
+ ...Default.args,
+ type: "icon",
+ l10nId: "moz-button-titled",
+};
+export const IconSmall = Template.bind({});
+IconSmall.args = {
+ ...Icon.args,
+ size: "small",
+};
+export const IconGhost = Template.bind({});
+IconGhost.args = {
+ ...Icon.args,
+ type: "icon ghost",
+};
diff --git a/toolkit/content/widgets/moz-input-box.js b/toolkit/content/widgets/moz-input-box.js
index 4704db6dc5..6e7b7b3f29 100644
--- a/toolkit/content/widgets/moz-input-box.js
+++ b/toolkit/content/widgets/moz-input-box.js
@@ -92,7 +92,7 @@
});
if (this.spellcheck) {
- this.menupopup.addEventListener("popuphiding", event => {
+ this.menupopup.addEventListener("popuphiding", () => {
if (this.spellCheckerUI) {
this.spellCheckerUI.clearSuggestionsFromMenu();
this.spellCheckerUI.clearDictionaryListFromMenu();
diff --git a/toolkit/content/widgets/moz-label/README.stories.md b/toolkit/content/widgets/moz-label/README.stories.md
index a3492ebefa..f5e4e2dd14 100644
--- a/toolkit/content/widgets/moz-label/README.stories.md
+++ b/toolkit/content/widgets/moz-label/README.stories.md
@@ -3,10 +3,10 @@
`moz-label` is an extension of the built-in `HTMLLabelElement` that provides accesskey styling and formatting as well as some click handling logic.
```html story
-<label is="moz-label" accesskey="c" for="check">
+<label is="moz-label" accesskey="c" for="check" style={{ display: "inline-block" }}>
This is a label with an accesskey:
</label>
-<input id="check" type="checkbox" defaultChecked />
+<input id="check" type="checkbox" defaultChecked style={{ display: "inline-block" }} />
```
Accesskey underlining is enabled by default on Windows and Linux. It is also enabled in Storybook on Mac for demonstrative purposes, but is usually controlled by the `ui.key.menuAccessKey` preference.
diff --git a/toolkit/content/widgets/moz-label/moz-label.mjs b/toolkit/content/widgets/moz-label/moz-label.mjs
index 7812436ecd..cd9144e7cc 100644
--- a/toolkit/content/widgets/moz-label/moz-label.mjs
+++ b/toolkit/content/widgets/moz-label/moz-label.mjs
@@ -103,7 +103,7 @@ class MozTextLabel extends HTMLLabelElement {
this.formatAccessKey();
}
- _onClick(event) {
+ _onClick() {
let controlElement = this.labeledControlElement;
if (!controlElement || this.disabled) {
return;
diff --git a/toolkit/content/widgets/moz-message-bar/moz-message-bar.mjs b/toolkit/content/widgets/moz-message-bar/moz-message-bar.mjs
index 58f41c28e4..d83de5d29f 100644
--- a/toolkit/content/widgets/moz-message-bar/moz-message-bar.mjs
+++ b/toolkit/content/widgets/moz-message-bar/moz-message-bar.mjs
@@ -69,7 +69,7 @@ export default class MozMessageBar extends MozLitElement {
this.dismissable = false;
}
- onSlotchange(e) {
+ onSlotchange() {
let actions = this.actionsSlotEl.assignedNodes();
this.actionsEl.classList.toggle("active", actions.length);
}
diff --git a/toolkit/content/widgets/moz-message-bar/moz-message-bar.stories.mjs b/toolkit/content/widgets/moz-message-bar/moz-message-bar.stories.mjs
index 65803eed9f..6f19c45aee 100644
--- a/toolkit/content/widgets/moz-message-bar/moz-message-bar.stories.mjs
+++ b/toolkit/content/widgets/moz-message-bar/moz-message-bar.stories.mjs
@@ -121,3 +121,9 @@ WithSupportLink.args = {
hasSupportLink: true,
hasActionButton: false,
};
+
+export const WithHeading = Template.bind({});
+WithHeading.args = {
+ ...Default.args,
+ l10nId: "moz-message-bar-message-heading",
+};
diff --git a/toolkit/content/widgets/moz-page-nav/moz-page-nav-button.css b/toolkit/content/widgets/moz-page-nav/moz-page-nav-button.css
new file mode 100644
index 0000000000..2975bb1a7c
--- /dev/null
+++ b/toolkit/content/widgets/moz-page-nav/moz-page-nav-button.css
@@ -0,0 +1,123 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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);
+ &:focus-visible {
+ outline-offset: var(--page-nav-focus-outline-inset);
+ }
+}
+
+button {
+ background-color: var(--page-nav-button-background-color);
+ border: 1px solid var(--page-nav-border-color);
+ -moz-context-properties: fill, fill-opacity;
+ fill: currentColor;
+ display: grid;
+ grid-template-columns: min-content 1fr;
+ gap: 12px;
+ 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);
+}
+
+button:hover {
+ cursor: pointer;
+}
+
+@media not (prefers-contrast) {
+ button {
+ border-inline-start: 2px solid transparent;
+ border-inline-end: none;
+ border-block: none;
+ }
+
+ button:hover,
+ button[selected]:hover {
+ background-color: var(--page-nav-button-background-color-hover);
+ }
+
+ button[selected]:hover {
+ border-inline-start-color: inherit;
+ }
+
+ button[selected],
+ button[selected]:hover {
+ border-inline-start: 2px solid;
+ }
+
+ button[selected]:not(:focus-visible) {
+ border-start-start-radius: 0;
+ border-end-start-radius: 0;
+ }
+
+ button[selected]:not(:hover) {
+ color: var(--color-accent-primary);
+ background-color: color-mix(in srgb, var(--page-nav-button-background-color-selected) 5%, transparent);
+ border-inline-start-color: var(--color-accent-primary);
+ }
+}
+
+@media (prefers-color-scheme: dark) {
+ button[selected] {
+ background-color: color-mix(in srgb, var(--page-nav-button-background-color-selected) 12%, transparent);
+ }
+}
+
+button:focus-visible,
+button[selected]:focus-visible {
+ outline: var(--focus-outline);
+ outline-offset: var(--focus-outline-offset);
+ border-radius: var(--border-radius-small);
+}
+
+.page-nav-icon {
+ height: 20px;
+ width: 20px;
+ -moz-context-properties: fill;
+ fill: currentColor;
+}
+
+@media (prefers-contrast) {
+ button {
+ transition: none;
+ border-color: ButtonText;
+ background-color: var(--button-background-color);
+ }
+
+ button:hover {
+ color: SelectedItem;
+ }
+
+ button[selected] {
+ color: SelectedItemText;
+ background-color: SelectedItem;
+ border-color: SelectedItem;
+ }
+}
+
+slot {
+ font-size: var(--font-size-large);
+ margin: 0;
+ padding-inline-start: 0;
+ user-select: none;
+}
+
+@media (max-width: 52rem) {
+ button {
+ grid-template-columns: min-content;
+ justify-content: center;
+ margin-inline: 0;
+ }
+
+ slot {
+ display: none;
+ }
+}
diff --git a/toolkit/content/widgets/moz-page-nav/moz-page-nav.css b/toolkit/content/widgets/moz-page-nav/moz-page-nav.css
new file mode 100644
index 0000000000..49000f622d
--- /dev/null
+++ b/toolkit/content/widgets/moz-page-nav/moz-page-nav.css
@@ -0,0 +1,76 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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;
+ 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(100vh - 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);
+ }
+}
diff --git a/toolkit/content/widgets/moz-page-nav/moz-page-nav.mjs b/toolkit/content/widgets/moz-page-nav/moz-page-nav.mjs
new file mode 100644
index 0000000000..f998ee735f
--- /dev/null
+++ b/toolkit/content/widgets/moz-page-nav/moz-page-nav.mjs
@@ -0,0 +1,170 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 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
+ );
+ }
+
+ 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();
+ }
+ }
+
+ 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" @keydown=${this.handleFocus}></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} iconSrc - The chrome:// url for the icon used for the button.
+ * @property {string} l10nId - The fluent ID for the button's text
+ * @property {boolean} selected - Whether or not the button is currently selected.
+ * @slot [default] - Used to append the l10n string to the button.
+ */
+export class MozPageNavButton extends MozLitElement {
+ static properties = {
+ iconSrc: { type: String },
+ l10nId: { type: String },
+ selected: { type: Boolean },
+ };
+
+ connectedCallback() {
+ super.connectedCallback();
+ this.setAttribute("role", "none");
+ }
+
+ static queries = {
+ buttonEl: "button",
+ };
+
+ get view() {
+ return this.getAttribute("view");
+ }
+
+ activate() {
+ this.dispatchEvent(
+ new CustomEvent("change-view", {
+ bubbles: true,
+ composed: true,
+ })
+ );
+ }
+
+ render() {
+ return html`
+ <link
+ rel="stylesheet"
+ href="chrome://global/content/elements/moz-page-nav-button.css"
+ />
+ <button
+ aria-selected=${this.selected}
+ tabindex=${this.selected ? 0 : -1}
+ role="tab"
+ ?selected=${this.selected}
+ @click=${this.activate}
+ >
+ <img class="page-nav-icon" src=${this.iconSrc} />
+ <slot></slot>
+ </button>
+ `;
+ }
+}
+customElements.define("moz-page-nav-button", MozPageNavButton);
diff --git a/toolkit/content/widgets/moz-page-nav/moz-page-nav.stories.mjs b/toolkit/content/widgets/moz-page-nav/moz-page-nav.stories.mjs
new file mode 100644
index 0000000000..4ac7b455cf
--- /dev/null
+++ b/toolkit/content/widgets/moz-page-nav/moz-page-nav.stories.mjs
@@ -0,0 +1,77 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. 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 "../vendor/lit.all.mjs";
+// eslint-disable-next-line import/no-unassigned-import
+import "./moz-page-nav.mjs";
+
+export default {
+ title: "UI Widgets/Page Nav",
+ component: "moz-page-nav",
+ parameters: {
+ status: "in-development",
+ actions: {
+ handles: ["change-view"],
+ },
+ fluent: `
+moz-page-nav-button-one = View 1
+ .title = View 1
+moz-page-nav-button-two = View 2
+ .title = View 2
+moz-page-nav-button-three = View 3
+ .title = View 3
+moz-page-link-one = Support Page
+ .title = Support Page
+moz-page-nav-heading =
+ .heading = Heading
+ `,
+ },
+};
+
+const Template = () => html`
+ <style>
+ #page {
+ height: 100%;
+ display: flex;
+
+ @media (max-width: 52rem) {
+ grid-template-columns: 82px 1fr;
+ }
+ }
+ moz-page-nav {
+ margin-inline-start: 10px;
+ --page-nav-margin-top: 10px;
+
+ @media (max-width: 52rem) {
+ margin-inline-start: 0;
+ }
+ }
+ </style>
+ <div id="page">
+ <moz-page-nav data-l10n-id="moz-page-nav-heading" data-l10n-attrs="heading">
+ <moz-page-nav-button
+ view="view-one"
+ data-l10n-id="moz-page-nav-button-one"
+ iconSrc="chrome://browser/skin/preferences/category-general.svg"
+ >
+ </moz-page-nav-button>
+ <moz-page-nav-button
+ view="view-two"
+ data-l10n-id="moz-page-nav-button-two"
+ iconSrc="chrome://browser/skin/preferences/category-general.svg"
+ >
+ </moz-page-nav-button>
+ <moz-page-nav-button
+ view="view-three"
+ data-l10n-id="moz-page-nav-button-three"
+ iconSrc="chrome://browser/skin/preferences/category-general.svg"
+ >
+ </moz-page-nav-button>
+ </moz-page-nav>
+ <main></main>
+ </div>
+`;
+
+export const Default = Template.bind({});
+Default.args = {};
diff --git a/toolkit/content/widgets/moz-support-link/moz-support-link.mjs b/toolkit/content/widgets/moz-support-link/moz-support-link.mjs
index 23f18ac434..9d2d6ffac2 100644
--- a/toolkit/content/widgets/moz-support-link/moz-support-link.mjs
+++ b/toolkit/content/widgets/moz-support-link/moz-support-link.mjs
@@ -82,7 +82,7 @@ export default class MozSupportLink extends HTMLAnchorElement {
}
}
- attributeChangedCallback(attrName, oldVal, newVal) {
+ attributeChangedCallback(attrName) {
if (attrName === "support-page" || attrName === "utm-content") {
this.#setHref();
}
diff --git a/toolkit/content/widgets/moz-toggle/moz-toggle.css b/toolkit/content/widgets/moz-toggle/moz-toggle.css
index 8b67a81878..2005544181 100644
--- a/toolkit/content/widgets/moz-toggle/moz-toggle.css
+++ b/toolkit/content/widgets/moz-toggle/moz-toggle.css
@@ -38,11 +38,15 @@
--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));
@@ -141,17 +145,6 @@
background-color: var(--toggle-background-color-disabled);
}
- .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-interactive-color-hover);
- --toggle-border-color-active: var(--border-interactive-color-active);
- --toggle-border-color-disabled: var(--border-interactive-color-disabled);
- }
-
.toggle-button:enabled:hover {
border-color: var(--toggle-border-color-hover);
}
@@ -175,6 +168,24 @@
border-color: var(--toggle-dot-background-color-hover);
}
+ .toggle-button:hover::before,
+ .toggle-button: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-interactive-color-hover);
+ --toggle-border-color-active: var(--border-interactive-color-active);
+ --toggle-border-color-disabled: var(--border-interactive-color-disabled);
+ }
+
.toggle-button[aria-pressed="true"]:enabled::after {
border: 1px solid var(--button-background-color);
content: '';
@@ -189,10 +200,4 @@
.toggle-button[aria-pressed="true"]:enabled:active::after {
border-color: var(--toggle-border-color-active);
}
-
- .toggle-button:hover::before,
- .toggle-button:hover:active::before,
- .toggle-button:active::before {
- background-color: var(--toggle-dot-background-color-hover);
- }
}
diff --git a/toolkit/content/widgets/named-deck.js b/toolkit/content/widgets/named-deck.js
index 6b3b7a8835..42c96e278d 100644
--- a/toolkit/content/widgets/named-deck.js
+++ b/toolkit/content/widgets/named-deck.js
@@ -156,7 +156,7 @@
this.getRootNode().removeEventListener("keypress", this);
}
- attributeChangedCallback(name, oldVal, newVal) {
+ attributeChangedCallback(name) {
if (name == "orientation") {
if (this.isVertical) {
this.setAttribute("aria-orientation", this.orientation);
diff --git a/toolkit/content/widgets/notificationbox.js b/toolkit/content/widgets/notificationbox.js
index f23fb03a74..0161588853 100644
--- a/toolkit/content/widgets/notificationbox.js
+++ b/toolkit/content/widgets/notificationbox.js
@@ -622,7 +622,7 @@
async function createNotificationMessageElement() {
await window.ensureCustomElements("moz-message-bar");
- let MozMessageBar = customElements.get("moz-message-bar");
+ let MozMessageBar = await customElements.whenDefined("moz-message-bar");
class NotificationMessage extends MozMessageBar {
static queries = {
...MozMessageBar.queries,
diff --git a/toolkit/content/widgets/panel-list/README.stories.md b/toolkit/content/widgets/panel-list/README.stories.md
index b8800e2b5f..3e8617958e 100644
--- a/toolkit/content/widgets/panel-list/README.stories.md
+++ b/toolkit/content/widgets/panel-list/README.stories.md
@@ -6,7 +6,7 @@ children and optional `hr` elements as separators. The `panel-list` will anchor
itself to the target of the initiating event when opened with
`panelList.toggle(event)`.
-Note: Nested menus are not currently supported. XUL is currently required to
+Note: XUL is currently required to
support accesskey underlining (although using `moz-label` could change that).
Shortcuts are not displayed automatically in the `panel-item`.
@@ -229,3 +229,23 @@ grow larger than its containing window if needed.
</html:panel-list>
</panel>
```
+
+### Submenus
+
+`panel-list` supports nested submenus. Submenus can be created by nesting a second `panel-list` in a `panel-item`'s `submenu` slot and specifying a `submenu` attribute on that `panel-item` that points to the nested list's ID. For example:
+
+```html
+<panel-list>
+ <panel-item>No submenu</panel-item>
+ <panel-item>No submenu</panel-item>
+ <panel-item submenu="example-submenu">
+ Has a submenu
+ <panel-list slot="submenu" id="example-submenu">
+ <panel-item>I'm a submenu item!</panel-item>
+ <panel-item>I'm also a submenu item!</panel-item>
+ </panel-list>
+ </panel-item>
+</panel-list>
+```
+
+As of February 2024 submenus are only in use in Firefox View and support for nesting beyond one submenu may be limited.
diff --git a/toolkit/content/widgets/panel-list/panel-list.css b/toolkit/content/widgets/panel-list/panel-list.css
index 4358fc0cf8..619e6919a3 100644
--- a/toolkit/content/widgets/panel-list/panel-list.css
+++ b/toolkit/content/widgets/panel-list/panel-list.css
@@ -26,6 +26,10 @@
box-sizing: border-box;
}
+:host([has-submenu]) {
+ overflow-y: visible;
+}
+
:host(:not([slot=submenu])) {
max-height: 100%;
}
diff --git a/toolkit/content/widgets/panel-list/panel-list.js b/toolkit/content/widgets/panel-list/panel-list.js
index 1cc1f865c3..2e93b4ddc3 100644
--- a/toolkit/content/widgets/panel-list/panel-list.js
+++ b/toolkit/content/widgets/panel-list/panel-list.js
@@ -308,7 +308,7 @@
}
addHideListeners() {
- if (this.hasAttribute("stay-open") && !this.lastAnchorNode.hasSubmenu) {
+ if (this.hasAttribute("stay-open") && !this.lastAnchorNode?.hasSubmenu) {
// This is intended for inspection in Storybook.
return;
}
@@ -631,31 +631,12 @@
this.#defaultSlot = document.createElement("slot");
this.#defaultSlot.style.display = "none";
- if (this.hasSubmenu) {
- 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(
- style,
- this.button,
- this.#defaultSlot,
- this.submenuSlot
- );
- } else {
- this.shadowRoot.append(
- style,
- this.button,
- supportLinkSlot,
- this.#defaultSlot
- );
- }
+ this.shadowRoot.append(
+ style,
+ this.button,
+ supportLinkSlot,
+ this.#defaultSlot
+ );
}
connectedCallback() {
@@ -664,6 +645,10 @@
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
@@ -683,18 +668,28 @@
});
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();
}
}
- this.panel =
- this.getRootNode()?.host?.closest("panel-list") ||
- this.closest("panel-list");
-
if (this.panel) {
this.panel.addEventListener("hidden", this);
this.panel.addEventListener("shown", this);
}
+
if (this.hasSubmenu) {
this.addEventListener("mouseenter", this);
this.addEventListener("mouseleave", this);
@@ -762,7 +757,9 @@
setSubmenuContents() {
this.submenuPanel = this.submenuSlot.assignedNodes()[0];
- this.shadowRoot.append(this.submenuPanel);
+ if (this.submenuPanel) {
+ this.shadowRoot.append(this.submenuPanel);
+ }
}
get disabled() {
diff --git a/toolkit/content/widgets/panel-list/panel-list.stories.mjs b/toolkit/content/widgets/panel-list/panel-list.stories.mjs
index 9c5a4cbe1f..db0ab7597c 100644
--- a/toolkit/content/widgets/panel-list/panel-list.stories.mjs
+++ b/toolkit/content/widgets/panel-list/panel-list.stories.mjs
@@ -22,6 +22,9 @@ panel-list-checked = Checked
panel-list-badged = Badged, look at me
panel-list-passwords = Passwords
panel-list-settings = Settings
+submenu-item-one = Submenu Item One
+submenu-item-two = Submenu Item Two
+submenu-item-three = Submenu Item Three
`,
},
};
@@ -36,7 +39,7 @@ function openMenu(event) {
}
}
-const Template = ({ isOpen, items, wideAnchor }) =>
+const Template = ({ isOpen, items, wideAnchor, hasSubMenu }) =>
html`
<style>
panel-item[icon="passwords"]::part(button) {
@@ -93,22 +96,36 @@ const Template = ({ isOpen, items, wideAnchor }) =>
?open=${isOpen}
?min-width-from-anchor=${wideAnchor}
>
- ${items.map(i =>
- i == "<hr>"
+ ${items.map((item, index) => {
+ // Always showing submenu on the first item for simplicity.
+ let showSubMenu = hasSubMenu && index == 0;
+ let subMenuId = showSubMenu ? "example-sub-menu" : undefined;
+ return item == "<hr>"
? html` <hr /> `
: html`
<panel-item
- icon=${i.icon ?? ""}
- ?checked=${i.checked}
- ?badged=${i.badged}
- accesskey=${ifDefined(i.accesskey)}
- data-l10n-id=${i.l10nId ?? i}
- ></panel-item>
- `
- )}
+ icon=${item.icon ?? ""}
+ ?checked=${item.checked}
+ ?badged=${item.badged}
+ accesskey=${ifDefined(item.accesskey)}
+ data-l10n-id=${item.l10nId ?? item}
+ submenu=${ifDefined(subMenuId)}
+ >
+ ${showSubMenu ? subMenuTemplate() : ""}
+ </panel-item>
+ `;
+ })}
</panel-list>
`;
+const subMenuTemplate = () => html`
+ <panel-list slot="submenu" id="example-sub-menu">
+ <panel-item data-l10n-id="submenu-item-one"></panel-item>
+ <panel-item data-l10n-id="submenu-item-two"></panel-item>
+ <panel-item data-l10n-id="submenu-item-three"></panel-item>
+ </panel-list>
+`;
+
export const Simple = Template.bind({});
Simple.args = {
isOpen: false,
@@ -145,3 +162,9 @@ Wide.args = {
...Simple.args,
wideAnchor: true,
};
+
+export const SubMenu = Template.bind({});
+SubMenu.args = {
+ ...Simple.args,
+ hasSubMenu: true,
+};
diff --git a/toolkit/content/widgets/radio.js b/toolkit/content/widgets/radio.js
index 482323acb9..41e8a945ba 100644
--- a/toolkit/content/widgets/radio.js
+++ b/toolkit/content/widgets/radio.js
@@ -197,7 +197,7 @@
* @param {DOMNode} child
* The <radio> element that got removed
*/
- radioUnattached(child) {
+ radioUnattached() {
// Just invalidate the cache, next time it's fetched it'll get rebuilt.
this._radioChildren = null;
}
@@ -481,13 +481,13 @@
constructor() {
super();
- this.addEventListener("click", event => {
+ this.addEventListener("click", () => {
if (!this.disabled) {
this.control.selectedItem = this;
}
});
- this.addEventListener("mousedown", event => {
+ this.addEventListener("mousedown", () => {
if (!this.disabled) {
this.control.focusedItem = this;
}
diff --git a/toolkit/content/widgets/richlistbox.js b/toolkit/content/widgets/richlistbox.js
index 904ef9ceec..01d970e6ed 100644
--- a/toolkit/content/widgets/richlistbox.js
+++ b/toolkit/content/widgets/richlistbox.js
@@ -126,7 +126,7 @@
}
});
- this.addEventListener("focus", event => {
+ this.addEventListener("focus", () => {
if (this.getRowCount() > 0) {
if (this.currentIndex == -1) {
this.currentIndex = this.getIndexOfFirstVisibleRow();
diff --git a/toolkit/content/widgets/search-textbox.js b/toolkit/content/widgets/search-textbox.js
index abdcfa2999..b254b2796d 100644
--- a/toolkit/content/widgets/search-textbox.js
+++ b/toolkit/content/widgets/search-textbox.js
@@ -214,7 +214,7 @@
}
}
- on_mousedown(event) {
+ on_mousedown() {
if (!this.hasAttribute("focused")) {
this.setSelectionRange(0, 0);
this.focus();
diff --git a/toolkit/content/widgets/tabbox.js b/toolkit/content/widgets/tabbox.js
index 997e8413f2..b1b2ddecce 100644
--- a/toolkit/content/widgets/tabbox.js
+++ b/toolkit/content/widgets/tabbox.js
@@ -24,15 +24,15 @@
}
connectedCallback() {
- Services.els.addSystemEventListener(document, "keydown", this, false);
+ 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);
- Services.els.removeSystemEventListener(document, "keydown", this, false);
}
set handleCtrlTab(val) {
@@ -729,7 +729,7 @@
direction = 1,
wrap = false,
startWithAdjacent = true,
- filter = tab => true,
+ filter = () => true,
} = opts;
let tab = startTab;
@@ -804,7 +804,7 @@
}
}
- _canAdvanceToTab(aTab) {
+ _canAdvanceToTab() {
return true;
}
diff --git a/toolkit/content/widgets/text.js b/toolkit/content/widgets/text.js
index ca10f1489e..7bbf6db4cc 100644
--- a/toolkit/content/widgets/text.js
+++ b/toolkit/content/widgets/text.js
@@ -67,7 +67,7 @@
this.formatAccessKey();
}
- _onClick(event) {
+ _onClick() {
let controlElement = this.labeledControlElement;
if (!controlElement || this.disabled) {
return;
diff --git a/toolkit/content/widgets/textrecognition.js b/toolkit/content/widgets/textrecognition.js
index 887d576770..c517f7bfb1 100644
--- a/toolkit/content/widgets/textrecognition.js
+++ b/toolkit/content/widgets/textrecognition.js
@@ -4,7 +4,7 @@
"use strict";
// This is a UA widget. It runs in per-origin UA widget scope,
-// to be loaded by UAWidgetsChild.jsm.
+// to be loaded by UAWidgetsChild.sys.mjs.
this.TextRecognitionWidget = class {
/**
diff --git a/toolkit/content/widgets/tree.js b/toolkit/content/widgets/tree.js
index 322e42586e..4993bef0c2 100644
--- a/toolkit/content/widgets/tree.js
+++ b/toolkit/content/widgets/tree.js
@@ -515,7 +515,7 @@
}
}
- _onDragMouseUp(aEvent) {
+ _onDragMouseUp() {
var col = document.treecolDragging;
if (!col) {
return;
@@ -786,7 +786,7 @@
}
});
- this.addEventListener("touchend", event => {
+ this.addEventListener("touchend", () => {
this._touchY = -1;
});
@@ -840,7 +840,7 @@
}
});
- this.addEventListener("focus", event => {
+ this.addEventListener("focus", () => {
this.focused = true;
if (this.currentIndex == -1 && this.view.rowCount > 0) {
this.currentIndex = this.getFirstVisibleRow();
@@ -1651,7 +1651,7 @@
this.ensureRowIsVisible(edge);
}
- _handleEnter(event) {
+ _handleEnter() {
if (this._editingColumn) {
this.stopEditing(true);
this.focus();
diff --git a/toolkit/content/widgets/videocontrols.js b/toolkit/content/widgets/videocontrols.js
index 21c8946e60..73a32164aa 100644
--- a/toolkit/content/widgets/videocontrols.js
+++ b/toolkit/content/widgets/videocontrols.js
@@ -5,7 +5,7 @@
"use strict";
// This is a UA widget. It runs in per-origin UA widget scope,
-// to be loaded by UAWidgetsChild.jsm.
+// to be loaded by UAWidgetsChild.sys.mjs.
/*
* This is the class of entry. It will construct the actual implementation
@@ -64,11 +64,8 @@ this.VideoControlsWidget = class {
// 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.elementStateMatches(this.element)
- ) {
+ if (this.impl && this.impl.constructor == newImpl) {
+ this.impl.onchange();
return;
}
if (this.impl) {
@@ -458,10 +455,10 @@ this.VideoControlsImplWidget = class {
this.statusIcon.setAttribute("type", "error");
this.updateErrorText();
this.setupStatusFader(true);
- } else if (VideoControlsWidget.isPictureInPictureVideo(this.video)) {
- this.setShowPictureInPictureMessage(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
@@ -934,6 +931,8 @@ this.VideoControlsImplWidget = class {
// 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;
@@ -1095,7 +1094,10 @@ this.VideoControlsImplWidget = class {
);
},
- setShowPictureInPictureMessage(showMessage) {
+ updatePictureInPictureMessage() {
+ let showMessage =
+ !this.hasError() &&
+ VideoControlsWidget.isPictureInPictureVideo(this.video);
this.pictureInPictureOverlay.hidden = !showMessage;
this.isShowingPictureInPictureMessage = showMessage;
},
@@ -1188,7 +1190,7 @@ this.VideoControlsImplWidget = class {
}
},
- onScrubberInput(e) {
+ onScrubberInput() {
const duration = Math.round(this.video.duration * 1000); // in ms
let time = this.scrubber.value;
@@ -1200,7 +1202,7 @@ this.VideoControlsImplWidget = class {
this.pauseVideoDuringDragging();
},
- onScrubberChange(e) {
+ onScrubberChange() {
this.scrubber.isDragging = false;
if (this.isPausedByDragging) {
@@ -1815,12 +1817,7 @@ this.VideoControlsImplWidget = class {
updateMuteButtonState() {
var muted = this.isEffectivelyMuted;
-
- if (muted) {
- this.muteButton.setAttribute("muted", "true");
- } else {
- this.muteButton.removeAttribute("muted");
- }
+ this.muteButton.toggleAttribute("muted", muted);
var id = muted
? "videocontrols-unmute-button"
@@ -2026,12 +2023,7 @@ this.VideoControlsImplWidget = class {
},
setCastingButtonState() {
- if (this.isCastingOn) {
- this.castingButton.setAttribute("enabled", "true");
- } else {
- this.castingButton.removeAttribute("enabled");
- }
-
+ this.castingButton.toggleAttribute("enabled", this.isCastingOn);
this.adjustControlSize();
},
@@ -2058,22 +2050,15 @@ this.VideoControlsImplWidget = class {
},
setClosedCaptionButtonState() {
- if (this.isClosedCaptionOn) {
- this.closedCaptionButton.setAttribute("enabled", "true");
- } else {
- this.closedCaptionButton.removeAttribute("enabled");
- }
-
+ this.closedCaptionButton.toggleAttribute(
+ "enabled",
+ this.isClosedCaptionOn
+ );
let ttItems = this.textTrackList.childNodes;
for (let tti of ttItems) {
const idx = +tti.getAttribute("index");
-
- if (idx == this.currentTextTrackIndex) {
- tti.setAttribute("aria-checked", "true");
- } else {
- tti.setAttribute("aria-checked", "false");
- }
+ tti.setAttribute("aria-checked", idx == this.currentTextTrackIndex);
}
this.adjustControlSize();
@@ -2804,10 +2789,6 @@ this.VideoControlsImplWidget = class {
if (this.Utils.isTouchControls) {
this.TouchUtils.init(this.shadowRoot, this.Utils);
}
- this.shadowRoot.firstChild.dispatchEvent(
- new this.window.CustomEvent("VideoBindingAttached")
- );
-
this._setupEventListeners();
}
@@ -2920,9 +2901,9 @@ this.VideoControlsImplWidget = class {
this.l10n.translateRoots();
}
- elementStateMatches(element) {
- let elementInPiP = VideoControlsWidget.isPictureInPictureVideo(element);
- return this.isShowingPictureInPictureMessage == elementInPiP;
+ onchange() {
+ this.Utils.updatePictureInPictureMessage();
+ this.shadowRoot.firstChild.removeAttribute("flipped");
}
teardown() {
@@ -3080,14 +3061,9 @@ this.NoControlsMobileImplWidget = class {
},
};
this.Utils.init(this.shadowRoot);
- this.Utils.video.dispatchEvent(
- new this.window.CustomEvent("MozNoControlsVideoBindingAttached")
- );
}
- elementStateMatches(element) {
- return true;
- }
+ onchange() {}
teardown() {
this.Utils.terminate();
@@ -3135,9 +3111,7 @@ this.NoControlsPictureInPictureImplWidget = class {
this.shadowRoot.firstElementChild.setAttribute("localedir", direction);
}
- elementStateMatches(element) {
- return true;
- }
+ onchange() {}
teardown() {}
@@ -3312,9 +3286,7 @@ this.NoControlsDesktopImplWidget = class {
this.Utils.init(this.shadowRoot, this.prefs);
}
- elementStateMatches(element) {
- return true;
- }
+ onchange() {}
teardown() {
this.Utils.terminate();
diff --git a/toolkit/content/widgets/wizard.js b/toolkit/content/widgets/wizard.js
index 6eb4bcb517..c4285fada5 100644
--- a/toolkit/content/widgets/wizard.js
+++ b/toolkit/content/widgets/wizard.js
@@ -359,7 +359,7 @@
this._wizardButtons.onPageChange();
}
- _advanceFocusToPage(aPage) {
+ _advanceFocusToPage() {
if (!this._hasLoaded) {
return;
}
diff --git a/toolkit/content/xul.css b/toolkit/content/xul.css
index e8635d4525..ead9997295 100644
--- a/toolkit/content/xul.css
+++ b/toolkit/content/xul.css
@@ -359,17 +359,15 @@ menubar > menu:empty {
/********* menupopup, panel, & tooltip ***********/
menupopup,
-panel {
- flex-direction: column;
-}
-
-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;
@@ -438,87 +436,6 @@ tooltip:not([position]) {
}
}
-@media (-moz-panel-animations) and (prefers-reduced-motion: no-preference) {
-@media (-moz-platform: macos) {
- /* 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. In fact, Gecko no longer triggers shadow shape recomputations
- for repaints.
- These properties are not implemented on other platforms. */
- panel[type="arrow"]:not([animate="false"]) {
- transition-property: -moz-window-transform, -moz-window-opacity;
- transition-duration: 0.18s, 0.18s;
- transition-timing-function:
- var(--animation-easing-function), ease-out;
- }
-
- /* Only do the fade-in animation on pre-Big Sur to avoid missing shadows on
- * Big Sur, see bug 1672091. */
- @media (-moz-mac-big-sur-theme: 0) {
- panel[type="arrow"]:not([animate="false"]) {
- -moz-window-opacity: 0;
- -moz-window-transform: translateY(-70px);
- }
-
- panel[type="arrow"][side="bottom"]:not([animate="false"]) {
- -moz-window-transform: translateY(70px);
- }
- }
-
- /* [animate] is here only so that this rule has greater specificity than the
- * rule right above */
- panel[type="arrow"][animate][animate="open"] {
- -moz-window-opacity: 1.0;
- transition-duration: 0.18s, 0.18s;
- -moz-window-transform: none;
- transition-timing-function:
- var(--animation-easing-function), ease-in-out;
- }
-
- panel[type="arrow"][animate][animate="cancel"] {
- -moz-window-opacity: 0;
- -moz-window-transform: none;
- }
-} /* end of macOS rules */
-
-@media not (-moz-platform: macos) {
- panel[type="arrow"]:not([animate="false"]) {
- opacity: 0;
- transform: translateY(-70px);
- transition-property: transform, opacity;
- transition-duration: 0.18s, 0.18s;
- transition-timing-function:
- var(--animation-easing-function), ease-out;
- will-change: transform, opacity;
- }
-
- panel[type="arrow"][side="bottom"]:not([animate="false"]) {
- transform: translateY(70px);
- }
-
- /* [animate] is here only so that this rule has greater specificity than the
- * rule right above */
- panel[type="arrow"][animate][animate="open"] {
- opacity: 1.0;
- transition-duration: 0.18s, 0.18s;
- transform: none;
- transition-timing-function:
- var(--animation-easing-function), ease-in-out;
- }
-
- panel[type="arrow"][animate][animate="cancel"] {
- transform: none;
- }
-} /* end of non-macOS rules */
-}
-
-panel[type="arrow"][animating] {
- pointer-events: none;
-}
-
/******** tree ******/
treecolpicker {
diff --git a/toolkit/crashreporter/CrashAnnotations.h.in b/toolkit/crashreporter/CrashAnnotations.h.in
index 7ea8c98bc1..2288498f36 100644
--- a/toolkit/crashreporter/CrashAnnotations.h.in
+++ b/toolkit/crashreporter/CrashAnnotations.h.in
@@ -50,6 +50,16 @@ ${skiplist}
};
/**
+ * Return the type of a crash annotation.
+ *
+ * @param aAnnotation a crash annotation
+ * @returns The type of this annotation
+ */
+static inline AnnotationType TypeOfAnnotation(Annotation aAnnotation) {
+ return kAnnotationTypes[static_cast<uint32_t>(aAnnotation)];
+}
+
+/**
* Return the string representation of a crash annotation.
*
* @param aAnnotation a crash annotation
@@ -97,9 +107,17 @@ class AnnotationWriter {
public:
virtual void Write(Annotation aAnnotation, const char* aValue,
size_t aLen = 0) = 0;
+ virtual void Write(Annotation aAnnotation, bool aValue) = 0;
virtual void Write(Annotation aAnnotation, uint64_t aValue) = 0;
};
+#ifdef XP_WIN
+
+extern void RecordDllAnnotations(bool* aBlocklistInitFailed,
+ bool* aUser32BeforeBlocklist);
+
+#endif // XP_WIN
+
} // namespace CrashReporter
#endif // CrashAnnotations_h
diff --git a/toolkit/crashreporter/breakpad-client/linux/microdump_writer/microdump_writer.cc b/toolkit/crashreporter/breakpad-client/linux/microdump_writer/microdump_writer.cc
index 8f25b7be02..2c2e24e071 100644
--- a/toolkit/crashreporter/breakpad-client/linux/microdump_writer/microdump_writer.cc
+++ b/toolkit/crashreporter/breakpad-client/linux/microdump_writer/microdump_writer.cc
@@ -466,8 +466,8 @@ class MicrodumpWriter {
char file_name[NAME_MAX];
char file_path[NAME_MAX];
- dumper_->GetMappingEffectiveNameAndPath(
- mapping, file_path, sizeof(file_path), file_name, sizeof(file_name));
+ dumper_->GetMappingEffectiveNamePathAndVersion(
+ mapping, file_path, sizeof(file_path), file_name, sizeof(file_name), nullptr);
LogAppend("M ");
LogAppend(static_cast<uintptr_t>(mapping.start_addr));
diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_dumper.cc b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_dumper.cc
index 3b400ce8ca..ef05583397 100644
--- a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_dumper.cc
+++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_dumper.cc
@@ -468,11 +468,12 @@ bool ElfFileSoName(const LinuxDumper& dumper,
} // namespace
-void LinuxDumper::GetMappingEffectiveNameAndPath(const MappingInfo& mapping,
+void LinuxDumper::GetMappingEffectiveNamePathAndVersion(const MappingInfo& mapping,
char* file_path,
size_t file_path_size,
char* file_name,
- size_t file_name_size) {
+ size_t file_name_size,
+ uint32_t* version) {
my_strlcpy(file_path, mapping.name, file_path_size);
// Tools such as minidump_stackwalk use the name of the module to look up
@@ -487,6 +488,9 @@ void LinuxDumper::GetMappingEffectiveNameAndPath(const MappingInfo& mapping,
const char* basename = my_strrchr(file_path, '/');
basename = basename == NULL ? file_path : (basename + 1);
my_strlcpy(file_name, basename, file_name_size);
+ if (version) {
+ ElfFileSoVersion(mapping.name, version);
+ }
return;
}
@@ -512,6 +516,10 @@ void LinuxDumper::GetMappingEffectiveNameAndPath(const MappingInfo& mapping,
my_strlcpy(file_path, file_name, file_path_size);
}
}
+
+ if (version) {
+ ElfFileSoVersion(mapping.name, version);
+ }
}
bool LinuxDumper::ReadAuxv() {
diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_dumper.h b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_dumper.h
index 7155524ffc..7fd0693968 100644
--- a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_dumper.h
+++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/linux_dumper.h
@@ -56,6 +56,10 @@
#include "common/memory_allocator.h"
#include "google_breakpad/common/minidump_format.h"
+#if defined(XP_LINUX)
+# include "linux_utils.h"
+#endif // defined(XP_LINUX)
+
namespace google_breakpad {
// Typedef for our parsing of the auxv variables in /proc/pid/auxv.
@@ -213,11 +217,12 @@ class LinuxDumper {
// other cases, however, a library can be mapped from an archive (e.g., when
// loading .so libs from an apk on Android) and this method is able to
// reconstruct the original file name.
- void GetMappingEffectiveNameAndPath(const MappingInfo& mapping,
+ void GetMappingEffectiveNamePathAndVersion(const MappingInfo& mapping,
char* file_path,
size_t file_path_size,
char* file_name,
- size_t file_name_size);
+ size_t file_name_size,
+ uint32_t* version);
protected:
bool ReadAuxv();
diff --git a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/minidump_writer.cc b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/minidump_writer.cc
index 03066e9110..60d1195949 100644
--- a/toolkit/crashreporter/breakpad-client/linux/minidump_writer/minidump_writer.cc
+++ b/toolkit/crashreporter/breakpad-client/linux/minidump_writer/minidump_writer.cc
@@ -717,8 +717,16 @@ class MinidumpWriter {
char file_name[NAME_MAX];
char file_path[NAME_MAX];
- dumper_->GetMappingEffectiveNameAndPath(
- mapping, file_path, sizeof(file_path), file_name, sizeof(file_name));
+ uint32_t version[4] = { 0, 0, 0, 0 };
+ dumper_->GetMappingEffectiveNamePathAndVersion(
+ mapping, file_path, sizeof(file_path), file_name, sizeof(file_name), version);
+
+ mod->version_info.signature = MD_VSFIXEDFILEINFO_SIGNATURE;
+ mod->version_info.struct_version |= MD_VSFIXEDFILEINFO_VERSION;
+ mod->version_info.file_version_hi = version[0];
+ mod->version_info.file_version_lo = version[1];
+ mod->version_info.product_version_hi = version[2];
+ mod->version_info.product_version_lo = version[3];
MDLocationDescriptor ld;
if (!minidump_writer_.WriteString(file_path, my_strlen(file_path), &ld))
diff --git a/toolkit/crashreporter/breakpad-client/mac/handler/minidump_generator.cc b/toolkit/crashreporter/breakpad-client/mac/handler/minidump_generator.cc
index e13e4509b0..64c01bdce4 100644
--- a/toolkit/crashreporter/breakpad-client/mac/handler/minidump_generator.cc
+++ b/toolkit/crashreporter/breakpad-client/mac/handler/minidump_generator.cc
@@ -83,7 +83,7 @@ MinidumpGenerator::MinidumpGenerator()
cpu_type_(DynamicImages::GetNativeCPUType()),
dyldImageLoadAddress_(NULL),
dyldSlide_(0),
- dyldPath_(),
+ dyldPath_(nullptr),
task_context_(NULL),
dynamic_images_(NULL),
memory_blocks_(&allocator_) {
@@ -105,7 +105,7 @@ MinidumpGenerator::MinidumpGenerator(mach_port_t crashing_task,
cpu_type_(DynamicImages::GetNativeCPUType()),
dyldImageLoadAddress_(NULL),
dyldSlide_(0),
- dyldPath_(),
+ dyldPath_(nullptr),
task_context_(NULL),
dynamic_images_(NULL),
memory_blocks_(&allocator_) {
@@ -250,7 +250,7 @@ void MinidumpGenerator::GatherCurrentProcessDyldInformation() {
return;
}
dyldImageLoadAddress_ = mh;
- dyldPath_ = string(aii->dyldPath);
+ dyldPath_ = aii->dyldPath;
dyldSlide_ = GetCurrentProcessModuleSlide(mh, aii->sharedCacheSlide);
}
@@ -1464,7 +1464,7 @@ bool MinidumpGenerator::WriteModuleStream(unsigned int index,
if (index == INT_MAX) {
dyld_or_in_dyld_shared_cache = true;
slide = dyldSlide_;
- name = dyldPath_.c_str();
+ name = dyldPath_;
} else {
dyld_or_in_dyld_shared_cache =
((header->flags & MH_SHAREDCACHE) != 0);
@@ -1993,7 +1993,7 @@ bool MinidumpGenerator::WriteCrashInfoStream(
bool dyld_or_in_dyld_shared_cache;
if (i == image_count - 1) {
slide = dyldSlide_;
- module_path = dyldPath_.c_str();
+ module_path = dyldPath_;
dyld_or_in_dyld_shared_cache = true;
} else {
slide = _dyld_get_image_vmaddr_slide(i);
@@ -2048,7 +2048,10 @@ bool MinidumpGenerator::WriteBootargsStream(
int rv = sysctlbyname("kern.bootargs", NULL, &size, NULL, 0);
if ((rv != 0) || (size == 0))
size = 1;
- vector<uint8_t> bootargs(size);
+
+ wasteful_vector<uint8_t> bootargs(&this->allocator_, size);
+ bootargs.resize(size, 0);
+
bootargs[0] = 0;
if (rv == 0)
sysctlbyname("kern.bootargs", &bootargs[0], &size, NULL, 0);
diff --git a/toolkit/crashreporter/breakpad-client/mac/handler/minidump_generator.h b/toolkit/crashreporter/breakpad-client/mac/handler/minidump_generator.h
index aba067cc04..77c250ccd5 100644
--- a/toolkit/crashreporter/breakpad-client/mac/handler/minidump_generator.h
+++ b/toolkit/crashreporter/breakpad-client/mac/handler/minidump_generator.h
@@ -266,7 +266,8 @@ class MinidumpGenerator {
// process has crashed.
breakpad_mach_header* dyldImageLoadAddress_;
ptrdiff_t dyldSlide_;
- string dyldPath_;
+ // We don't own this pointer, this is owned by macOS internal structures.
+ const char* dyldPath_;
// Context of the task to dump.
breakpad_ucontext_t *task_context_;
diff --git a/toolkit/crashreporter/docs/index.rst b/toolkit/crashreporter/docs/index.rst
index f2444a75c6..fe1af45d8f 100644
--- a/toolkit/crashreporter/docs/index.rst
+++ b/toolkit/crashreporter/docs/index.rst
@@ -66,7 +66,8 @@ a ``google_breakpad::ExceptionHandler`` instance and it's stored as
As the application runs, various other systems may write *annotations*
or *notes* to the crash reporter to indicate state of the application,
help with possible reasons for a current or future crash, etc. These are
-performed via ``CrashReporter::AnnotateCrashReport()`` and
+performed via ``CrashReporter::RecordAnnotation*()``,
+``CrashReporter::RegisterAnnotation*()`` functions and
``CrashReporter::AppendAppNotesToCrashReport()`` from
``nsExceptionHandler.h``.
@@ -154,7 +155,7 @@ with information about the crash.
Submission of child process crashes is handled by application code. This
code prompts the user to submit crashes in context-appropriate UI and then
-submits the crashes using ``CrashSubmit.jsm``.
+submits the crashes using ``CrashSubmit.sys.mjs``.
Memory Reports
==============
diff --git a/toolkit/crashreporter/linux_utils.cc b/toolkit/crashreporter/linux_utils.cc
new file mode 100644
index 0000000000..cfddecf084
--- /dev/null
+++ b/toolkit/crashreporter/linux_utils.cc
@@ -0,0 +1,59 @@
+/* -*- 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/. */
+
+#include "common/linux/linux_libc_support.h"
+#include "linux_utils.h"
+
+bool ElfFileSoVersion(const char* mapping_name, uint32_t* version_components) {
+ if (!version_components) {
+ return false;
+ }
+
+ // We found no version so just report 0
+ const char* so_version = my_strstr(mapping_name, ".so.");
+ if (so_version == nullptr) {
+ return true;
+ }
+
+ char tmp[12]; // 11 for maximum representation of UINT32T_MAX + \0 ?
+ size_t current_position = 0;
+ size_t next_tmp = 0;
+ tmp[0] = '\0';
+ for (size_t so_version_pos = 0; so_version_pos <= my_strlen(so_version);
+ ++so_version_pos) {
+ // We can't have more than four: MAJOR.minor.release.patch
+ if (current_position == 4) {
+ break;
+ }
+
+ char c = so_version[so_version_pos];
+ if (c != '.') {
+ if ((c <= '9' && c >= '0')) {
+ tmp[next_tmp] = c;
+ tmp[next_tmp + 1] = '\0';
+ ++next_tmp;
+ }
+
+ if (so_version[so_version_pos + 1] != '\0') {
+ continue;
+ }
+ }
+
+ if (my_strlen(tmp) > 0) {
+ int t;
+ if (!my_strtoui(&t, tmp)) {
+ return false;
+ }
+ uint32_t casted_tmp = (uint32_t)t;
+ version_components[current_position] = casted_tmp;
+ ++current_position;
+ }
+
+ tmp[0] = '\0';
+ next_tmp = 0;
+ }
+
+ return true;
+}
diff --git a/toolkit/crashreporter/linux_utils.h b/toolkit/crashreporter/linux_utils.h
new file mode 100644
index 0000000000..2dd4ff785c
--- /dev/null
+++ b/toolkit/crashreporter/linux_utils.h
@@ -0,0 +1,16 @@
+/* -*- 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/. */
+
+#ifndef toolkit_breakpad_linux_utils_h__
+#define toolkit_breakpad_linux_utils_h__
+
+#include <stdio.h>
+#include <stdint.h>
+#include <string>
+#include <vector>
+
+bool ElfFileSoVersion(const char* mapping_name, uint32_t* version);
+
+#endif /* toolkit_breakpad_linux_utils_h__ */
diff --git a/toolkit/crashreporter/moz.build b/toolkit/crashreporter/moz.build
index d6f49680e7..952ca405bb 100644
--- a/toolkit/crashreporter/moz.build
+++ b/toolkit/crashreporter/moz.build
@@ -55,6 +55,14 @@ if CONFIG["MOZ_CRASHREPORTER"]:
"google-breakpad/src/processor",
]
+ UNIFIED_SOURCES += [
+ "linux_utils.cc",
+ ]
+
+ EXPORTS += [
+ "linux_utils.h",
+ ]
+
if CONFIG["MOZ_OXIDIZED_BREAKPAD"]:
DIRS += ["rust_minidump_writer_linux"]
diff --git a/toolkit/crashreporter/mozannotation_client/src/lib.rs b/toolkit/crashreporter/mozannotation_client/src/lib.rs
index 17fe017095..df2bd5bf94 100644
--- a/toolkit/crashreporter/mozannotation_client/src/lib.rs
+++ b/toolkit/crashreporter/mozannotation_client/src/lib.rs
@@ -3,30 +3,53 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
use nsstring::nsCString;
-use std::{ffi::c_void, sync::Mutex};
+use std::{
+ alloc::{self, Layout},
+ cmp::min,
+ ffi::{c_char, c_void, CStr},
+ ptr::{copy_nonoverlapping, null_mut},
+ sync::Mutex,
+};
#[cfg(any(target_os = "linux", target_os = "android"))]
use std::arch::global_asm;
#[repr(C)]
-#[derive(Copy, Clone)]
+#[derive(Copy, Clone, Debug, PartialEq)]
pub enum AnnotationContents {
Empty,
- NSCString,
+ NSCStringPointer,
+ CStringPointer,
CString,
- CharBuffer,
- USize,
ByteBuffer(u32),
+ OwnedByteBuffer(u32),
}
-
#[repr(C)]
-#[derive(Clone, Copy)]
pub struct Annotation {
pub id: u32,
pub contents: AnnotationContents,
pub address: usize,
}
+impl Drop for Annotation {
+ fn drop(&mut self) {
+ match self.contents {
+ AnnotationContents::OwnedByteBuffer(len) => {
+ if (self.address != 0) && (len > 0) {
+ let align = min(usize::next_power_of_two(len as usize), 32);
+ unsafe {
+ let layout = Layout::from_size_align_unchecked(len as usize, align);
+ alloc::dealloc(self.address as *mut u8, layout);
+ }
+ }
+ }
+ _ => {
+ // Nothing to do
+ }
+ };
+ }
+}
+
pub struct AnnotationTable {
data: Vec<Annotation>,
magic_number: u32,
@@ -222,98 +245,126 @@ pub struct MozAnnotationNote {
pub ehdr: isize,
}
-/// Register an annotation containing an nsCString.
-/// Returns false if the annotation is already present (and doesn't change it).
+fn store_annotation<T>(id: u32, contents: AnnotationContents, address: *const T) -> *const T {
+ let address = match contents {
+ AnnotationContents::OwnedByteBuffer(len) => {
+ if !address.is_null() && (len > 0) {
+ // Copy the contents of this annotation, we'll own the copy
+ let len = len as usize;
+ let align = min(usize::next_power_of_two(len), 32);
+ unsafe {
+ let layout = Layout::from_size_align_unchecked(len as usize, align);
+ let src = address as *mut u8;
+ let dst = alloc::alloc(layout);
+ copy_nonoverlapping(src, dst, len);
+ dst
+ }
+ } else {
+ null_mut()
+ }
+ }
+ _ => address as *mut u8,
+ };
+
+ let annotations = &mut MOZANNOTATIONS.lock().unwrap().data;
+ let old = if let Some(existing) = annotations.iter_mut().find(|e| e.id == id) {
+ let old = match existing.contents {
+ AnnotationContents::OwnedByteBuffer(len) => {
+ // If we owned the previous value of this annotation we must free it.
+ if (existing.address != 0) && (len > 0) {
+ let len = len as usize;
+ let align = min(usize::next_power_of_two(len), 32);
+ unsafe {
+ let layout = Layout::from_size_align_unchecked(len, align);
+ alloc::dealloc(existing.address as *mut u8, layout);
+ }
+ }
+ null_mut::<T>()
+ }
+ _ => existing.address as *mut T,
+ };
+
+ existing.contents = contents;
+ existing.address = address as usize;
+ old
+ } else {
+ annotations.push(Annotation {
+ id,
+ contents,
+ address: address as usize,
+ });
+ null_mut::<T>()
+ };
+
+ old
+}
+
+/// Register a pointer to an nsCString string.
+///
+/// Returns the value of the previously registered annotation or null.
///
/// This function will be exposed to C++
#[no_mangle]
-pub extern "C" fn mozannotation_register_nscstring(id: u32, address: *const nsCString) -> bool {
- let annotations = &mut MOZANNOTATIONS.lock().unwrap().data;
-
- if annotations.iter().any(|e| e.id == id) {
- return false;
- }
-
- annotations.push(Annotation {
- id,
- contents: AnnotationContents::NSCString,
- address: address as usize,
- });
-
- true
+pub extern "C" fn mozannotation_register_nscstring(
+ id: u32,
+ address: *const nsCString,
+) -> *const nsCString {
+ store_annotation(id, AnnotationContents::NSCStringPointer, address)
}
-/// Register an annotation containing a null-terminated string.
-/// Returns false if the annotation is already present (and doesn't change it).
+/// Create a copy of the provided string with a specified size that will be
+/// owned by the crate, and register a pointer to it.
///
/// This function will be exposed to C++
#[no_mangle]
-pub extern "C" fn mozannotation_register_cstring(
+pub extern "C" fn mozannotation_record_nscstring_from_raw_parts(
id: u32,
- address: *const *const std::ffi::c_char,
-) -> bool {
- let annotations = &mut MOZANNOTATIONS.lock().unwrap().data;
-
- if annotations.iter().any(|e| e.id == id) {
- return false;
- }
-
- annotations.push(Annotation {
+ address: *const u8,
+ size: usize,
+) {
+ store_annotation(
id,
- contents: AnnotationContents::CString,
- address: address as usize,
- });
-
- true
+ AnnotationContents::OwnedByteBuffer(size as u32),
+ address,
+ );
}
-/// Register an annotation pointing to a buffer holding a null-terminated string.
-/// Returns false if the annotation is already present (and doesn't change it).
+/// Register a pointer to a pointer to a nul-terminated string.
+///
+/// Returns the value of the previously registered annotation or null.
///
/// This function will be exposed to C++
#[no_mangle]
-pub extern "C" fn mozannotation_register_char_buffer(
+pub extern "C" fn mozannotation_register_cstring_ptr(
id: u32,
- address: *const std::ffi::c_char,
-) -> bool {
- let annotations = &mut MOZANNOTATIONS.lock().unwrap().data;
-
- if annotations.iter().any(|e| e.id == id) {
- return false;
- }
-
- annotations.push(Annotation {
- id,
- contents: AnnotationContents::CharBuffer,
- address: address as usize,
- });
-
- true
+ address: *const *const c_char,
+) -> *const *const c_char {
+ store_annotation(id, AnnotationContents::CStringPointer, address)
}
-/// Register an annotation pointing to a variable of type usize.
-/// Returns false if the annotation is already present (and doesn't change it).
+/// Register a pointer to a nul-terminated string.
+///
+/// Returns the value of the previously registered annotation or null.
///
/// This function will be exposed to C++
#[no_mangle]
-pub extern "C" fn mozannotation_register_usize(id: u32, address: *const usize) -> bool {
- let annotations = &mut MOZANNOTATIONS.lock().unwrap().data;
-
- if annotations.iter().any(|e| e.id == id) {
- return false;
- }
-
- annotations.push(Annotation {
- id,
- contents: AnnotationContents::USize,
- address: address as usize,
- });
+pub extern "C" fn mozannotation_register_cstring(id: u32, address: *const c_char) -> *const c_char {
+ store_annotation(id, AnnotationContents::CString, address)
+}
- true
+/// Create a copy of the provided nul-terminated string which will be owned by
+/// the crate, and register a pointer to it.
+///
+/// This function will be exposed to C++
+#[no_mangle]
+pub extern "C" fn mozannotation_record_cstring(id: u32, address: *const c_char) {
+ let len = unsafe { CStr::from_ptr(address).to_bytes().len() };
+ store_annotation(id, AnnotationContents::OwnedByteBuffer(len as u32), address);
}
-/// Register an annotation pointing to a fixed size buffer.
-/// Returns false if the annotation is already present (and doesn't change it).
+/// Register a pointer to a fixed size buffer.
+///
+/// Returns the value of the previously registered annotation or null.
///
/// This function will be exposed to C++
#[no_mangle]
@@ -321,34 +372,50 @@ pub extern "C" fn mozannotation_register_bytebuffer(
id: u32,
address: *const c_void,
size: u32,
-) -> bool {
- let annotations = &mut MOZANNOTATIONS.lock().unwrap().data;
-
- if annotations.iter().any(|e| e.id == id) {
- return false;
- }
+) -> *const c_void {
+ store_annotation(id, AnnotationContents::ByteBuffer(size), address)
+}
- annotations.push(Annotation {
- id,
- contents: AnnotationContents::ByteBuffer(size),
- address: address as usize,
- });
+/// Create a copy of the provided buffer which will be owned by the crate, and
+/// register a pointer to it.
+///
+/// This function will be exposed to C++
+#[no_mangle]
+pub extern "C" fn mozannotation_record_bytebuffer(id: u32, address: *const c_void, size: u32) {
+ store_annotation(id, AnnotationContents::OwnedByteBuffer(size), address);
+}
- true
+/// Unregister a crash annotation. Returns the previously registered pointer or
+/// null if none was present. Return null also if the crate owned the
+/// annotations' buffer.
+///
+/// This function will be exposed to C++
+#[no_mangle]
+pub extern "C" fn mozannotation_unregister(id: u32) -> *const c_void {
+ store_annotation(id, AnnotationContents::Empty, null_mut())
}
-/// Unregister a crash annotation. Returns false if the annotation is not present.
+/// Returns the raw address of an annotation if it has been registered or NULL
+/// if it hasn't.
///
/// This function will be exposed to C++
#[no_mangle]
-pub extern "C" fn mozannotation_unregister(id: u32) -> bool {
- let annotations = &mut MOZANNOTATIONS.lock().unwrap().data;
- let index = annotations.iter().position(|e| e.id == id);
+pub extern "C" fn mozannotation_get_contents(id: u32, contents: *mut AnnotationContents) -> usize {
+ let annotations = &MOZANNOTATIONS.lock().unwrap().data;
+ if let Some(annotation) = annotations.iter().find(|e| e.id == id) {
+ if annotation.contents == AnnotationContents::Empty {
+ return 0;
+ }
- if let Some(index) = index {
- annotations.remove(index);
- return true;
+ unsafe { *contents = annotation.contents };
+ return annotation.address;
}
- false
+ return 0;
+}
+
+#[no_mangle]
+pub extern "C" fn mozannotation_clear_all() {
+ let annotations = &mut MOZANNOTATIONS.lock().unwrap().data;
+ annotations.clear();
}
diff --git a/toolkit/crashreporter/mozannotation_server/Cargo.toml b/toolkit/crashreporter/mozannotation_server/Cargo.toml
index 747aff791e..3a5834a645 100644
--- a/toolkit/crashreporter/mozannotation_server/Cargo.toml
+++ b/toolkit/crashreporter/mozannotation_server/Cargo.toml
@@ -11,7 +11,6 @@ license = "MPL-2.0"
goblin = { version = "0.7", features = ["elf32", "elf64", "pe32", "pe64"] }
memoffset = "0.8"
mozannotation_client = { path = "../mozannotation_client/" }
-nsstring = { path = "../../../xpcom/rust/nsstring/" }
thin-vec = { version = "0.2.7", features = ["gecko-ffi"] }
thiserror = "1.0.38"
diff --git a/toolkit/crashreporter/mozannotation_server/src/lib.rs b/toolkit/crashreporter/mozannotation_server/src/lib.rs
index 4ecb8ec919..6ed5b3cc41 100644
--- a/toolkit/crashreporter/mozannotation_server/src/lib.rs
+++ b/toolkit/crashreporter/mozannotation_server/src/lib.rs
@@ -9,22 +9,21 @@ use crate::errors::*;
use process_reader::ProcessReader;
use mozannotation_client::{Annotation, AnnotationContents, AnnotationMutex};
-use nsstring::nsCString;
use std::cmp::min;
use std::iter::FromIterator;
-use std::mem::size_of;
+use std::mem::{size_of, ManuallyDrop};
use std::ptr::null_mut;
use thin_vec::ThinVec;
#[repr(C)]
+#[derive(Debug)]
pub enum AnnotationData {
Empty,
- UsizeData(usize),
- NSCStringData(nsCString),
ByteBuffer(ThinVec<u8>),
}
#[repr(C)]
+#[derive(Debug)]
pub struct CAnnotation {
id: u32,
data: AnnotationData,
@@ -100,33 +99,33 @@ pub fn retrieve_annotations(
// Read an annotation from the given address
fn read_annotation(reader: &ProcessReader, address: usize) -> Result<CAnnotation, ReadError> {
- let raw_annotation = reader.copy_object::<Annotation>(address)?;
+ let raw_annotation = ManuallyDrop::new(reader.copy_object::<Annotation>(address)?);
let mut annotation = CAnnotation {
id: raw_annotation.id,
data: AnnotationData::Empty,
};
+ if raw_annotation.address == 0 {
+ return Ok(annotation);
+ }
+
match raw_annotation.contents {
AnnotationContents::Empty => {}
- AnnotationContents::NSCString => {
+ AnnotationContents::NSCStringPointer => {
let string = copy_nscstring(reader, raw_annotation.address)?;
- annotation.data = AnnotationData::NSCStringData(string);
- }
- AnnotationContents::CString => {
- let string = copy_null_terminated_string_pointer(reader, raw_annotation.address)?;
- annotation.data = AnnotationData::NSCStringData(string);
+ annotation.data = AnnotationData::ByteBuffer(string);
}
- AnnotationContents::CharBuffer => {
- let string = copy_null_terminated_string(reader, raw_annotation.address)?;
- annotation.data = AnnotationData::NSCStringData(string);
+ AnnotationContents::CStringPointer => {
+ let buffer = copy_null_terminated_string_pointer(reader, raw_annotation.address)?;
+ annotation.data = AnnotationData::ByteBuffer(buffer);
}
- AnnotationContents::USize => {
- let value = reader.copy_object::<usize>(raw_annotation.address)?;
- annotation.data = AnnotationData::UsizeData(value);
+ AnnotationContents::CString => {
+ let buffer = copy_null_terminated_string(reader, raw_annotation.address)?;
+ annotation.data = AnnotationData::ByteBuffer(buffer);
}
- AnnotationContents::ByteBuffer(size) => {
- let value = copy_bytebuffer(reader, raw_annotation.address, size)?;
- annotation.data = AnnotationData::ByteBuffer(value);
+ AnnotationContents::ByteBuffer(size) | AnnotationContents::OwnedByteBuffer(size) => {
+ let buffer = copy_bytebuffer(reader, raw_annotation.address, size)?;
+ annotation.data = AnnotationData::ByteBuffer(buffer);
}
};
@@ -136,7 +135,7 @@ fn read_annotation(reader: &ProcessReader, address: usize) -> Result<CAnnotation
fn copy_null_terminated_string_pointer(
reader: &ProcessReader,
address: usize,
-) -> Result<nsCString, ReadError> {
+) -> Result<ThinVec<u8>, ReadError> {
let buffer_address = reader.copy_object::<usize>(address)?;
copy_null_terminated_string(reader, buffer_address)
}
@@ -144,7 +143,7 @@ fn copy_null_terminated_string_pointer(
fn copy_null_terminated_string(
reader: &ProcessReader,
address: usize,
-) -> Result<nsCString, ReadError> {
+) -> Result<ThinVec<u8>, ReadError> {
// Try copying the string word-by-word first, this is considerably faster
// than one byte at a time.
if let Ok(string) = copy_null_terminated_string_word_by_word(reader, address) {
@@ -155,7 +154,7 @@ fn copy_null_terminated_string(
// at a time. It's slow but it might work in situations where the string
// alignment causes word-by-word access to straddle page boundaries.
let mut length = 0;
- let mut string = Vec::<u8>::new();
+ let mut string = ThinVec::<u8>::new();
loop {
let char = reader.copy_object::<u8>(address + length)?;
@@ -167,33 +166,34 @@ fn copy_null_terminated_string(
}
}
- Ok(nsCString::from(&string[..length]))
+ Ok(string)
}
fn copy_null_terminated_string_word_by_word(
reader: &ProcessReader,
address: usize,
-) -> Result<nsCString, ReadError> {
+) -> Result<ThinVec<u8>, ReadError> {
const WORD_SIZE: usize = size_of::<usize>();
let mut length = 0;
- let mut string = Vec::<u8>::new();
+ let mut string = ThinVec::<u8>::new();
loop {
- let mut array = reader.copy_array::<u8>(address + length, WORD_SIZE)?;
+ let array = reader.copy_array::<u8>(address + length, WORD_SIZE)?;
let null_terminator = array.iter().position(|&e| e == 0);
length += null_terminator.unwrap_or(WORD_SIZE);
- string.append(&mut array);
+ string.extend(array.into_iter());
if null_terminator.is_some() {
+ string.truncate(length);
break;
}
}
- Ok(nsCString::from(&string[..length]))
+ Ok(string)
}
-fn copy_nscstring(reader: &ProcessReader, address: usize) -> Result<nsCString, ReadError> {
- // HACK: This assumes the layout of the string
+fn copy_nscstring(reader: &ProcessReader, address: usize) -> Result<ThinVec<u8>, ReadError> {
+ // HACK: This assumes the layout of the nsCString object
let length_address = address + size_of::<usize>();
let length = reader.copy_object::<u32>(length_address)?;
@@ -201,9 +201,9 @@ fn copy_nscstring(reader: &ProcessReader, address: usize) -> Result<nsCString, R
let data_address = reader.copy_object::<usize>(address)?;
reader
.copy_array::<u8>(data_address, length as _)
- .map(nsCString::from)
+ .map(ThinVec::from)
} else {
- Ok(nsCString::new())
+ Ok(ThinVec::<u8>::new())
}
}
diff --git a/toolkit/crashreporter/mozannotation_server/src/process_reader/linux.rs b/toolkit/crashreporter/mozannotation_server/src/process_reader/linux.rs
index db6dbd3df2..34f8ef90d5 100644
--- a/toolkit/crashreporter/mozannotation_server/src/process_reader/linux.rs
+++ b/toolkit/crashreporter/mozannotation_server/src/process_reader/linux.rs
@@ -261,8 +261,10 @@ enum PTraceOperation {
PeekData,
}
-#[cfg(target_os = "linux")]
+#[cfg(all(target_os = "linux", target_env = "gnu"))]
type PTraceOperationNative = libc::c_uint;
+#[cfg(all(target_os = "linux", target_env = "musl"))]
+type PTraceOperationNative = libc::c_int;
#[cfg(target_os = "android")]
type PTraceOperationNative = c_int;
diff --git a/toolkit/crashreporter/nsDummyExceptionHandler.cpp b/toolkit/crashreporter/nsDummyExceptionHandler.cpp
index 7ccede695f..cd76630f3c 100644
--- a/toolkit/crashreporter/nsDummyExceptionHandler.cpp
+++ b/toolkit/crashreporter/nsDummyExceptionHandler.cpp
@@ -4,10 +4,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-#include <functional>
-
#include "nsExceptionHandler.h"
-#include "nsExceptionHandlerUtils.h"
namespace CrashReporter {
@@ -38,41 +35,71 @@ nsresult SetupExtraData(nsIFile* aAppDataDirectory,
nsresult UnsetExceptionHandler() { return NS_ERROR_NOT_IMPLEMENTED; }
-nsresult AnnotateCrashReport(Annotation key, bool data) {
- return NS_ERROR_NOT_IMPLEMENTED;
+const bool* RegisterAnnotationBool(Annotation aKey, const bool* aData) {
+ return nullptr;
}
-nsresult AnnotateCrashReport(Annotation key, int data) {
- return NS_ERROR_NOT_IMPLEMENTED;
+const uint32_t* RegisterAnnotationU32(Annotation aKey, const uint32_t* aData) {
+ return nullptr;
}
-nsresult AnnotateCrashReport(Annotation key, unsigned int data) {
- return NS_ERROR_NOT_IMPLEMENTED;
+const uint64_t* RegisterAnnotationU64(Annotation aKey, const uint64_t* aData) {
+ return nullptr;
}
-nsresult AnnotateCrashReport(Annotation key, const nsACString& data) {
- return NS_ERROR_NOT_IMPLEMENTED;
+const size_t* RegisterAnnotationUSize(Annotation aKey, const size_t* aData) {
+ return nullptr;
}
-nsresult AppendToCrashReportAnnotation(Annotation key, const nsACString& data) {
- return NS_ERROR_NOT_IMPLEMENTED;
+const char* RegisterAnnotationCString(Annotation aKey, const char* aData) {
+ return nullptr;
}
-nsresult RemoveCrashReportAnnotation(Annotation key) {
- return NS_ERROR_NOT_IMPLEMENTED;
+const nsCString* RegisterAnnotationNSCString(Annotation aKey,
+ const nsCString* aData) {
+ return nullptr;
+}
+
+nsresult RecordAnnotationBool(Annotation aKey, bool aData) {
+ return NS_ERROR_FAILURE;
+}
+
+nsresult RecordAnnotationU32(Annotation aKey, uint32_t aData) {
+ return NS_ERROR_FAILURE;
}
-AutoAnnotateCrashReport::AutoAnnotateCrashReport(Annotation key, bool data) {}
+nsresult RecordAnnotationU64(Annotation aKey, uint64_t aData) {
+ return NS_ERROR_FAILURE;
+}
+
+nsresult RecordAnnotationUSize(Annotation aKey, size_t aData) {
+ return NS_ERROR_FAILURE;
+}
+
+nsresult RecordAnnotationCString(Annotation aKey, const char* aData) {
+ return NS_ERROR_FAILURE;
+}
+
+nsresult RecordAnnotationNSCString(Annotation aKey, const nsACString& aData) {
+ return NS_ERROR_FAILURE;
+}
+
+nsresult RecordAnnotationNSString(Annotation aKey, const nsAString& aData) {
+ return NS_ERROR_FAILURE;
+}
+
+nsresult UnrecordAnnotation(Annotation aKey) { return NS_ERROR_FAILURE; }
+
+AutoRecordAnnotation::AutoRecordAnnotation(Annotation key, bool data) {}
-AutoAnnotateCrashReport::AutoAnnotateCrashReport(Annotation key, int data) {}
+AutoRecordAnnotation::AutoRecordAnnotation(Annotation key, int data) {}
-AutoAnnotateCrashReport::AutoAnnotateCrashReport(Annotation key,
- unsigned data) {}
+AutoRecordAnnotation::AutoRecordAnnotation(Annotation key, unsigned data) {}
-AutoAnnotateCrashReport::AutoAnnotateCrashReport(Annotation key,
- const nsACString& data) {}
+AutoRecordAnnotation::AutoRecordAnnotation(Annotation key,
+ const nsACString& data) {}
-AutoAnnotateCrashReport::~AutoAnnotateCrashReport() {}
+AutoRecordAnnotation::~AutoRecordAnnotation() {}
void MergeCrashAnnotations(AnnotationTable& aDst, const AnnotationTable& aSrc) {
}
diff --git a/toolkit/crashreporter/nsExceptionHandler.cpp b/toolkit/crashreporter/nsExceptionHandler.cpp
index ceb021f7f3..aca266749a 100644
--- a/toolkit/crashreporter/nsExceptionHandler.cpp
+++ b/toolkit/crashreporter/nsExceptionHandler.cpp
@@ -201,6 +201,7 @@ static const char kCrashMainID[] = "crash.main.3\n";
static google_breakpad::ExceptionHandler* gExceptionHandler = nullptr;
static mozilla::Atomic<bool> gEncounteredChildException(false);
+static nsCString gServerURL;
static xpstring pendingDirectory;
static xpstring crashReporterPath;
@@ -232,9 +233,7 @@ static const char* androidStartServiceCommand = nullptr;
#endif
// this holds additional data sent via the API
-static Mutex* crashReporterAPILock;
static Mutex* notesFieldLock;
-static AnnotationTable crashReporterAPIData_Table;
static nsCString* notesField = nullptr;
static bool isGarbageCollecting;
static uint32_t eventloopNestingLevel = 0;
@@ -656,11 +655,17 @@ class JSONAnnotationWriter : public AnnotationWriter {
size_t len = aLen ? aLen : my_strlen(aValue);
const char* annotationStr = AnnotationToString(aAnnotation);
- WritePrefix();
- mWriter.WriteBuffer(annotationStr, my_strlen(annotationStr));
- WriteSeparator();
- WriteEscapedString(aValue, len);
- WriteSuffix();
+ if (len && CrashReporter::ShouldIncludeAnnotation(aAnnotation, aValue)) {
+ WritePrefix();
+ mWriter.WriteBuffer(annotationStr, my_strlen(annotationStr));
+ WriteSeparator();
+ WriteEscapedString(aValue, len);
+ WriteSuffix();
+ }
+ };
+
+ void Write(Annotation aAnnotation, bool aValue) override {
+ Write(aAnnotation, aValue ? "1" : "0", 1);
};
void Write(Annotation aAnnotation, uint64_t aValue) override {
@@ -789,8 +794,10 @@ static void WritePHCAddrInfo(AnnotationWriter& writer,
break;
}
writer.Write(Annotation::PHCKind, kindString);
- writer.Write(Annotation::PHCBaseAddress, uintptr_t(aAddrInfo->mBaseAddr));
- writer.Write(Annotation::PHCUsableSize, aAddrInfo->mUsableSize);
+ writer.Write(Annotation::PHCBaseAddress,
+ reinterpret_cast<uint64_t>(aAddrInfo->mBaseAddr));
+ writer.Write(Annotation::PHCUsableSize,
+ static_cast<uint64_t>(aAddrInfo->mUsableSize));
WritePHCStackTrace(writer, Annotation::PHCAllocStack,
aAddrInfo->mAllocStack);
@@ -879,7 +886,8 @@ static void AnnotateMemoryStatus(AnnotationWriter& aWriter) {
MEMORYSTATUSEX statex;
statex.dwLength = sizeof(statex);
if (GlobalMemoryStatusEx(&statex)) {
- aWriter.Write(Annotation::SystemMemoryUsePercentage, statex.dwMemoryLoad);
+ aWriter.Write(Annotation::SystemMemoryUsePercentage,
+ static_cast<uint64_t>(statex.dwMemoryLoad));
aWriter.Write(Annotation::TotalVirtualMemory, statex.ullTotalVirtual);
aWriter.Write(Annotation::AvailableVirtualMemory, statex.ullAvailVirtual);
aWriter.Write(Annotation::TotalPhysicalMemory, statex.ullTotalPhys);
@@ -888,9 +896,11 @@ static void AnnotateMemoryStatus(AnnotationWriter& aWriter) {
PERFORMANCE_INFORMATION info;
if (K32GetPerformanceInfo(&info, sizeof(info))) {
- aWriter.Write(Annotation::TotalPageFile, info.CommitLimit * info.PageSize);
+ aWriter.Write(Annotation::TotalPageFile,
+ static_cast<uint64_t>(info.CommitLimit * info.PageSize));
aWriter.Write(Annotation::AvailablePageFile,
- (info.CommitLimit - info.CommitTotal) * info.PageSize);
+ static_cast<uint64_t>((info.CommitLimit - info.CommitTotal) *
+ info.PageSize));
}
}
#elif XP_MACOSX
@@ -916,9 +926,9 @@ static void WriteAvailableMemoryStatus(AnnotationWriter& aWriter) {
if (host_statistics64(host, HOST_VM_INFO64, (host_info64_t)&stats, &count) ==
KERN_SUCCESS) {
aWriter.Write(Annotation::AvailablePhysicalMemory,
- stats.free_count * vm_page_size);
+ static_cast<uint64_t>(stats.free_count * vm_page_size));
aWriter.Write(Annotation::PurgeablePhysicalMemory,
- stats.purgeable_count * vm_page_size);
+ static_cast<uint64_t>(stats.purgeable_count * vm_page_size));
}
}
@@ -1199,7 +1209,8 @@ static void AnnotateMemoryStatus(AnnotationWriter& aWriter) {
pointOfInterest.dest->value = value;
}
if (pointOfInterest.annotation != Annotation::Count) {
- aWriter.Write(pointOfInterest.annotation, value);
+ aWriter.Write(pointOfInterest.annotation,
+ static_cast<uint64_t>(value));
}
}
break;
@@ -1224,7 +1235,8 @@ static void AnnotateMemoryStatus(AnnotationWriter& aWriter) {
}
if (memTotal.found && swapTotal.found) {
// If available, attempt to determine the available virtual memory.
- aWriter.Write(Annotation::TotalPageFile, memTotal.value + swapTotal.value);
+ aWriter.Write(Annotation::TotalPageFile,
+ static_cast<uint64_t>(memTotal.value + swapTotal.value));
}
}
@@ -1356,40 +1368,6 @@ static bool LaunchCrashHandlerService(const XP_CHAR* aProgramPath,
#endif
-static void WriteMainThreadRunnableName(AnnotationWriter& aWriter) {
-#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
- // Only try to collect this information if the main thread is crashing.
- if (!SignalSafeIsMainThread()) {
- return;
- }
-
- // NOTE: Use `my_memchr` over `strlen` to ensure we don't run off the end of
- // the buffer if it contains no null bytes. This is used instead of `strnlen`,
- // as breakpad's linux support library doesn't export a `my_strnlen` function.
- const char* buf = nsThread::sMainThreadRunnableName.begin();
- size_t len = nsThread::kRunnableNameBufSize;
- if (const void* end = my_memchr(buf, '\0', len)) {
- len = static_cast<const char*>(end) - buf;
- }
-
- if (len > 0) {
- aWriter.Write(Annotation::MainThreadRunnableName, buf, len);
- }
-#endif
-}
-
-static void WriteOOMAllocationSize(AnnotationWriter& aWriter) {
- if (gOOMAllocationSize) {
- aWriter.Write(Annotation::OOMAllocationSize, gOOMAllocationSize);
- }
-}
-
-static void WriteMozCrashReason(AnnotationWriter& aWriter) {
- if (gMozCrashReason != nullptr) {
- aWriter.Write(Annotation::MozCrashReason, gMozCrashReason);
- }
-}
-
static void WriteAnnotations(AnnotationWriter& aWriter,
const AnnotationTable& aAnnotations) {
for (auto key : MakeEnumeratedRange(Annotation::Count)) {
@@ -1408,13 +1386,65 @@ static void WriteAnnotationsForMainProcessCrash(PlatformWriter& pw,
const phc::AddrInfo* addrInfo,
time_t crashTime) {
JSONAnnotationWriter writer(pw);
- WriteAnnotations(writer, crashReporterAPIData_Table);
+
+ for (auto key : MakeEnumeratedRange(Annotation::Count)) {
+ AnnotationContents contents = {};
+ size_t address =
+ mozannotation_get_contents(static_cast<uint32_t>(key), &contents);
+ if (address != 0) {
+ switch (TypeOfAnnotation(key)) {
+ case AnnotationType::String:
+ switch (contents.tag) {
+ case AnnotationContents::Tag::NSCStringPointer: {
+ const nsCString* string =
+ reinterpret_cast<const nsCString*>(address);
+ writer.Write(key, string->Data(), string->Length());
+ } break;
+ case AnnotationContents::Tag::CStringPointer:
+ address = *(reinterpret_cast<size_t*>(address));
+ if (address == 0) {
+ break;
+ }
+ // FALLTHROUGH
+ case AnnotationContents::Tag::CString: {
+ writer.Write(key, reinterpret_cast<const char*>(address));
+ } break;
+ case AnnotationContents::Tag::ByteBuffer:
+ writer.Write(key, reinterpret_cast<const char*>(address),
+ static_cast<size_t>(contents.byte_buffer._0));
+ break;
+ case AnnotationContents::Tag::OwnedByteBuffer:
+ writer.Write(key, reinterpret_cast<const char*>(address),
+ static_cast<size_t>(contents.owned_byte_buffer._0));
+ break;
+ case AnnotationContents::Tag::Empty:
+ break;
+ }
+ break;
+ case AnnotationType::Boolean:
+ writer.Write(key, *reinterpret_cast<const bool*>(address));
+ break;
+ case AnnotationType::U32:
+ writer.Write(key, static_cast<uint64_t>(
+ *reinterpret_cast<uint32_t*>(address)));
+ break;
+ case AnnotationType::U64:
+ writer.Write(key, *reinterpret_cast<uint64_t*>(address));
+ break;
+ case AnnotationType::USize:
+ writer.Write(
+ key, static_cast<uint64_t>(*reinterpret_cast<size_t*>(address)));
+ break;
+ }
+ }
+ }
+
WriteSynthesizedAnnotations(writer);
writer.Write(Annotation::CrashTime, uint64_t(crashTime));
if (inactiveStateStart) {
writer.Write(Annotation::LastInteractionDuration,
- crashTime - inactiveStateStart);
+ static_cast<uint64_t>(crashTime - inactiveStateStart));
}
double uptimeTS = (TimeStamp::NowLoRes() - TimeStamp::ProcessCreation())
@@ -1427,34 +1457,14 @@ static void WriteAnnotationsForMainProcessCrash(PlatformWriter& pw,
if (lastCrashTime != 0) {
uint64_t timeSinceLastCrash = crashTime - lastCrashTime;
- if (timeSinceLastCrash != 0) {
- writer.Write(Annotation::SecondsSinceLastCrash, timeSinceLastCrash);
- }
- }
-
- if (isGarbageCollecting) {
- writer.Write(Annotation::IsGarbageCollecting, "1");
- }
-
- if (eventloopNestingLevel > 0) {
- writer.Write(Annotation::EventLoopNestingLevel, eventloopNestingLevel);
+ writer.Write(Annotation::SecondsSinceLastCrash, timeSinceLastCrash);
}
#if defined(XP_WIN) && defined(HAS_DLL_BLOCKLIST)
// HACK: The DLL blocklist code will manually write its annotations as JSON
- DllBlocklist_WriteNotes(writer);
+ DllBlocklist_WriteNotes();
#endif // defined(XP_WIN) && defined(HAS_DLL_BLOCKLIST)
- WriteMozCrashReason(writer);
-
- WriteMainThreadRunnableName(writer);
-
- WriteOOMAllocationSize(writer);
-
- if (gTexturesSize) {
- writer.Write(Annotation::TextureUsage, gTexturesSize);
- }
-
#ifdef MOZ_PHC
WritePHCAddrInfo(writer, addrInfo);
#endif
@@ -1889,19 +1899,50 @@ static nsresult LocateExecutable(nsIFile* aXREDirectory, const nsAString& aName,
#endif // !defined(MOZ_WIDGET_ANDROID)
-static void InitializeAnnotationFacilities() {
- crashReporterAPILock = new Mutex("crashReporterAPILock");
+static void InitializeAppNotes() {
notesFieldLock = new Mutex("notesFieldLock");
notesField = new nsCString();
}
-static void TeardownAnnotationFacilities() {
- std::fill(crashReporterAPIData_Table.begin(),
- crashReporterAPIData_Table.end(), ""_ns);
-
- delete crashReporterAPILock;
- crashReporterAPILock = nullptr;
+// Register crash annotations that are present in both main and child processes
+static void RegisterAnnotations() {
+ mozannotation_register_cstring_ptr(
+ static_cast<uint32_t>(Annotation::MozCrashReason), &gMozCrashReason);
+#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
+ mozannotation_register_cstring(
+ static_cast<uint32_t>(Annotation::MainThreadRunnableName),
+ &nsThread::sMainThreadRunnableName[0]);
+#endif
+ mozannotation_register_bytebuffer(
+ static_cast<uint32_t>(Annotation::EventLoopNestingLevel),
+ &eventloopNestingLevel, sizeof(uint32_t));
+ mozannotation_register_nscstring(static_cast<uint32_t>(Annotation::Notes),
+ notesField);
+ mozannotation_register_bytebuffer(
+ static_cast<uint32_t>(Annotation::OOMAllocationSize), &gOOMAllocationSize,
+ sizeof(size_t));
+ mozannotation_register_bytebuffer(
+ static_cast<uint32_t>(Annotation::IsGarbageCollecting),
+ &isGarbageCollecting, sizeof(bool));
+ mozannotation_register_nscstring(static_cast<uint32_t>(Annotation::ServerURL),
+ &gServerURL);
+ mozannotation_register_bytebuffer(
+ static_cast<uint32_t>(Annotation::TextureUsage), &gTexturesSize,
+ sizeof(size_t));
+#if defined(XP_WIN) && defined(HAS_DLL_BLOCKLIST)
+ mozannotation_register_bytebuffer(
+ static_cast<uint32_t>(Annotation::BlocklistInitFailed),
+ DllBlocklist_GetBlocklistInitFailedPointer(), sizeof(bool));
+ mozannotation_register_bytebuffer(
+ static_cast<uint32_t>(Annotation::User32BeforeBlocklist),
+ DllBlocklist_GetUser32BeforeBlocklistPointer(), sizeof(bool));
+ mozannotation_register_cstring(
+ static_cast<uint32_t>(Annotation::BlockedDllList),
+ DllBlocklist_GetBlocklistWriterData());
+#endif // defined(XP_WIN) && defined(HAS_DLL_BLOCKLIST)
+}
+static void TeardownAppNotes() {
delete notesFieldLock;
notesFieldLock = nullptr;
@@ -1929,7 +1970,8 @@ nsresult SetExceptionHandler(nsIFile* aXREDirectory, bool force /*=false*/) {
doReport = ShouldReport();
RegisterRuntimeExceptionModule();
- InitializeAnnotationFacilities();
+ InitializeAppNotes();
+ RegisterAnnotations();
#if !defined(MOZ_WIDGET_ANDROID)
// Locate the crash reporter executable
@@ -2055,10 +2097,8 @@ nsresult SetExceptionHandler(nsIFile* aXREDirectory, bool force /*=false*/) {
#endif
// store application start time
- char timeString[32];
- time_t startupTime = time(nullptr);
- XP_TTOA(startupTime, timeString);
- AnnotateCrashReport(Annotation::StartupTime, nsDependentCString(timeString));
+ RecordAnnotationU64(Annotation::StartupTime,
+ static_cast<uint64_t>(time(nullptr)));
#if defined(XP_MACOSX)
// On OS X, many testers like to see the OS crash reporting dialog
@@ -2259,8 +2299,9 @@ nsresult SetupExtraData(nsIFile* aAppDataDirectory,
nsAutoCString data;
if (NS_SUCCEEDED(GetOrInit(dataDirectory, "InstallTime"_ns + aBuildID, data,
- InitInstallTime)))
- AnnotateCrashReport(Annotation::InstallTime, data);
+ InitInstallTime))) {
+ RecordAnnotationNSCString(Annotation::InstallTime, data);
+ }
// this is a little different, since we can't init it with anything,
// since it's stored at crash time, and we can't annotate the
@@ -2314,7 +2355,8 @@ nsresult UnsetExceptionHandler() {
delete gExceptionHandler;
- TeardownAnnotationFacilities();
+ gServerURL = "";
+ TeardownAppNotes();
if (!gExceptionHandler) return NS_ERROR_NOT_INITIALIZED;
@@ -2330,73 +2372,201 @@ nsresult UnsetExceptionHandler() {
return NS_OK;
}
-nsresult AnnotateCrashReport(Annotation key, bool data) {
- return AnnotateCrashReport(key, data ? "1"_ns : "0"_ns);
+const bool* RegisterAnnotationBool(Annotation aKey, const bool* aData) {
+ MOZ_ASSERT(TypeOfAnnotation(aKey) == AnnotationType::Boolean,
+ "Wrong annotation type");
+
+ if (!GetEnabled()) {
+ return nullptr;
+ }
+
+ return reinterpret_cast<const bool*>(mozannotation_register_bytebuffer(
+ static_cast<uint32_t>(aKey), aData, sizeof(bool)));
}
-nsresult AnnotateCrashReport(Annotation key, int data) {
- nsAutoCString dataString;
- dataString.AppendInt(data);
+const uint32_t* RegisterAnnotationU32(Annotation aKey, const uint32_t* aData) {
+ MOZ_ASSERT(TypeOfAnnotation(aKey) == AnnotationType::U32,
+ "Wrong annotation type");
- return AnnotateCrashReport(key, dataString);
+ if (!GetEnabled()) {
+ return nullptr;
+ }
+
+ return reinterpret_cast<const uint32_t*>(mozannotation_register_bytebuffer(
+ static_cast<uint32_t>(aKey), aData, sizeof(uint32_t)));
}
-nsresult AnnotateCrashReport(Annotation key, unsigned int data) {
- nsAutoCString dataString;
- dataString.AppendInt(data);
+const uint64_t* RegisterAnnotationU64(Annotation aKey, const uint64_t* aData) {
+ MOZ_ASSERT(TypeOfAnnotation(aKey) == AnnotationType::U64,
+ "Wrong annotation type");
- return AnnotateCrashReport(key, dataString);
+ if (!GetEnabled()) {
+ return nullptr;
+ }
+
+ return reinterpret_cast<const uint64_t*>(mozannotation_register_bytebuffer(
+ static_cast<uint32_t>(aKey), aData, sizeof(uint64_t)));
}
-nsresult AnnotateCrashReport(Annotation key, const nsACString& data) {
- if (!GetEnabled()) return NS_ERROR_NOT_INITIALIZED;
+const size_t* RegisterAnnotationUSize(Annotation aKey, const size_t* aData) {
+ MOZ_ASSERT(TypeOfAnnotation(aKey) == AnnotationType::USize,
+ "Wrong annotation type");
+
+ if (!GetEnabled()) {
+ return nullptr;
+ }
+
+ return reinterpret_cast<const size_t*>(mozannotation_register_bytebuffer(
+ static_cast<uint32_t>(aKey), aData, sizeof(size_t)));
+}
+
+const char* RegisterAnnotationCString(Annotation aKey, const char* aData) {
+ MOZ_ASSERT(TypeOfAnnotation(aKey) == AnnotationType::String,
+ "Wrong annotation type");
- MutexAutoLock lock(*crashReporterAPILock);
- crashReporterAPIData_Table[key] = data;
+ if (!GetEnabled()) {
+ return nullptr;
+ }
+ return mozannotation_register_cstring(static_cast<uint32_t>(aKey), aData);
+}
+
+const nsCString* RegisterAnnotationNSCString(Annotation aKey,
+ const nsCString* aData) {
+ MOZ_ASSERT(TypeOfAnnotation(aKey) == AnnotationType::String,
+ "Wrong annotation type");
+
+ if (!GetEnabled()) {
+ return nullptr;
+ }
+
+ return mozannotation_register_nscstring(static_cast<uint32_t>(aKey), aData);
+}
+
+nsresult RecordAnnotationBool(Annotation aKey, bool aData) {
+ MOZ_ASSERT(TypeOfAnnotation(aKey) == AnnotationType::Boolean,
+ "Wrong annotation type");
+
+ if (!GetEnabled()) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ mozannotation_record_bytebuffer(static_cast<uint32_t>(aKey), &aData,
+ sizeof(bool));
return NS_OK;
}
-nsresult AppendToCrashReportAnnotation(Annotation key, const nsACString& data) {
- if (!GetEnabled()) return NS_ERROR_NOT_INITIALIZED;
+nsresult RecordAnnotationU32(Annotation aKey, uint32_t aData) {
+ MOZ_ASSERT(TypeOfAnnotation(aKey) == AnnotationType::U32,
+ "Wrong annotation type");
- MutexAutoLock lock(*crashReporterAPILock);
- nsAutoCString newString(crashReporterAPIData_Table[key]);
- newString.Append(" - "_ns);
- newString.Append(data);
- crashReporterAPIData_Table[key] = newString;
+ if (!GetEnabled()) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ mozannotation_record_bytebuffer(static_cast<uint32_t>(aKey), &aData,
+ sizeof(uint32_t));
return NS_OK;
}
-nsresult RemoveCrashReportAnnotation(Annotation key) {
- return AnnotateCrashReport(key, ""_ns);
+nsresult RecordAnnotationU64(Annotation aKey, uint64_t aData) {
+ MOZ_ASSERT(TypeOfAnnotation(aKey) == AnnotationType::U64,
+ "Wrong annotation type");
+
+ if (!GetEnabled()) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ mozannotation_record_bytebuffer(static_cast<uint64_t>(aKey), &aData,
+ sizeof(uint64_t));
+ return NS_OK;
}
-AutoAnnotateCrashReport::AutoAnnotateCrashReport(Annotation key, bool data)
- : AutoAnnotateCrashReport(key, data ? "1"_ns : "0"_ns) {}
+nsresult RecordAnnotationUSize(Annotation aKey, size_t aData) {
+ MOZ_ASSERT(TypeOfAnnotation(aKey) == AnnotationType::USize,
+ "Wrong annotation type");
-AutoAnnotateCrashReport::AutoAnnotateCrashReport(Annotation key, int data)
- : AutoAnnotateCrashReport(key, nsPrintfCString("%d", data)) {}
+ if (!GetEnabled()) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
-AutoAnnotateCrashReport::AutoAnnotateCrashReport(Annotation key, unsigned data)
- : AutoAnnotateCrashReport(key, nsPrintfCString("%u", data)) {}
+ mozannotation_record_bytebuffer(static_cast<size_t>(aKey), &aData,
+ sizeof(size_t));
+ return NS_OK;
+}
-AutoAnnotateCrashReport::AutoAnnotateCrashReport(Annotation key,
- const nsACString& data)
- : mKey(key) {
+nsresult RecordAnnotationCString(Annotation aKey, const char* aData) {
+ MOZ_ASSERT(TypeOfAnnotation(aKey) == AnnotationType::String,
+ "Wrong annotation type");
+
+ if (!GetEnabled()) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ mozannotation_record_cstring(static_cast<uint32_t>(aKey), aData);
+ return NS_OK;
+}
+
+nsresult RecordAnnotationNSCString(Annotation aKey, const nsACString& aData) {
+ MOZ_ASSERT(TypeOfAnnotation(aKey) == AnnotationType::String,
+ "Wrong annotation type");
+
+ if (!GetEnabled()) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ mozannotation_record_nscstring_from_raw_parts(
+ static_cast<uint32_t>(aKey),
+ reinterpret_cast<const uint8_t*>(aData.Data()), aData.Length());
+ return NS_OK;
+}
+
+nsresult RecordAnnotationNSString(Annotation aKey, const nsAString& aData) {
+ MOZ_ASSERT(TypeOfAnnotation(aKey) == AnnotationType::String,
+ "Wrong annotation type");
+
+ if (!GetEnabled()) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ nsAutoCString data = NS_ConvertUTF16toUTF8(aData);
+ RecordAnnotationNSCString(aKey, data);
+ return NS_OK;
+}
+
+nsresult UnrecordAnnotation(Annotation aKey) {
+ if (!GetEnabled()) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ mozannotation_unregister(static_cast<uint32_t>(aKey));
+ return NS_OK;
+}
+
+// TODO: The first three methods here should be migrated to use native
+// types instead of turning the values into strings. They're not currently
+// used in mozilla-central so it doesn't really matter in the short term.
+AutoRecordAnnotation::AutoRecordAnnotation(Annotation key, bool data)
+ : AutoRecordAnnotation(key, data ? "1"_ns : "0"_ns) {}
+
+AutoRecordAnnotation::AutoRecordAnnotation(Annotation key, int data)
+ : AutoRecordAnnotation(key, nsPrintfCString("%d", data)) {}
+
+AutoRecordAnnotation::AutoRecordAnnotation(Annotation key, unsigned data)
+ : AutoRecordAnnotation(key, nsPrintfCString("%u", data)) {}
+
+AutoRecordAnnotation::AutoRecordAnnotation(Annotation key,
+ const nsACString& data)
+ : mKey(key), mCurrent(data) {
if (GetEnabled()) {
- MutexAutoLock lock(*crashReporterAPILock);
- auto& entry = crashReporterAPIData_Table[mKey];
- mPrevious = std::move(entry);
- entry = data;
+ mPrevious =
+ mozannotation_register_nscstring(static_cast<uint32_t>(key), &mCurrent);
}
}
-AutoAnnotateCrashReport::~AutoAnnotateCrashReport() {
+AutoRecordAnnotation::~AutoRecordAnnotation() {
if (GetEnabled()) {
- MutexAutoLock lock(*crashReporterAPILock);
- crashReporterAPIData_Table[mKey] = std::move(mPrevious);
+ Unused << mozannotation_register_nscstring(static_cast<uint32_t>(mKey),
+ mPrevious);
}
}
@@ -2409,11 +2579,6 @@ void MergeCrashAnnotations(AnnotationTable& aDst, const AnnotationTable& aSrc) {
}
}
-static void MergeContentCrashAnnotations(AnnotationTable& aDst) {
- MutexAutoLock lock(*crashReporterAPILock);
- MergeCrashAnnotations(aDst, crashReporterAPIData_Table);
-}
-
// Adds crash time, uptime and memory report annotations
static void AddCommonAnnotations(AnnotationTable& aAnnotations) {
const time_t crashTime = time(nullptr);
@@ -2461,23 +2626,8 @@ nsresult AppendAppNotesToCrashReport(const nsACString& data) {
if (!GetEnabled()) return NS_ERROR_NOT_INITIALIZED;
MutexAutoLock lock(*notesFieldLock);
-
notesField->Append(data);
- return AnnotateCrashReport(Annotation::Notes, *notesField);
-}
-
-// Returns true if found, false if not found.
-static bool GetAnnotation(CrashReporter::Annotation key, nsACString& data) {
- if (!gExceptionHandler) return false;
-
- MutexAutoLock lock(*crashReporterAPILock);
- const nsCString& entry = crashReporterAPIData_Table[key];
- if (entry.IsEmpty()) {
- return false;
- }
-
- data = entry;
- return true;
+ return NS_OK;
}
nsresult RegisterAppMemory(void* ptr, size_t length) {
@@ -2513,15 +2663,19 @@ void SetIncludeContextHeap(bool aValue) {
}
bool GetServerURL(nsACString& aServerURL) {
- if (!gExceptionHandler) return false;
+ if (!gExceptionHandler) {
+ return false;
+ }
- return GetAnnotation(CrashReporter::Annotation::ServerURL, aServerURL);
+ aServerURL = gServerURL;
+ return true;
}
nsresult SetServerURL(const nsACString& aServerURL) {
- // store server URL with the API data
- // the client knows to handle this specially
- return AnnotateCrashReport(Annotation::ServerURL, aServerURL);
+ // Store the server URL as an annotation, the crash reporter client knows how
+ // to handle this specially.
+ gServerURL = aServerURL;
+ return NS_OK;
}
nsresult SetRestartArgs(int argc, char** argv) {
@@ -3107,12 +3261,89 @@ bool WriteExtraFile(const nsAString& id, const AnnotationTable& annotations) {
return WriteExtraFile(pw, annotations);
}
+template <typename T>
+static bool IsFixedSizeAnnotation(AnnotationContents& contents) {
+ return ((contents.tag == AnnotationContents::Tag::ByteBuffer) &&
+ (contents.byte_buffer._0 == sizeof(T))) ||
+ ((contents.tag == AnnotationContents::Tag::OwnedByteBuffer) &&
+ (contents.owned_byte_buffer._0 == sizeof(T)));
+}
+
// This adds annotations that were populated in the main process but are not
// present among the ones that were passed in. Additionally common annotations
// which are present in every crash report are added, including crash time,
// uptime, etc...
static void AddSharedAnnotations(AnnotationTable& aAnnotations) {
- MergeContentCrashAnnotations(aAnnotations);
+ for (auto key : MakeEnumeratedRange(Annotation::Count)) {
+ AnnotationContents contents = {};
+ nsAutoCString value;
+ size_t address =
+ mozannotation_get_contents(static_cast<uint32_t>(key), &contents);
+
+ if (address) {
+ switch (TypeOfAnnotation(key)) {
+ case AnnotationType::String:
+ switch (contents.tag) {
+ case AnnotationContents::Tag::Empty:
+ break;
+ case AnnotationContents::Tag::CStringPointer:
+ address = *reinterpret_cast<size_t*>(address);
+ if (address == 0) {
+ break;
+ }
+ // FALLTHROUGH
+ case AnnotationContents::Tag::CString:
+ value.Assign(reinterpret_cast<const char*>(address));
+ break;
+ case AnnotationContents::Tag::NSCStringPointer:
+ value.Assign(*reinterpret_cast<nsCString*>(address));
+ break;
+ case AnnotationContents::Tag::ByteBuffer:
+ value.Assign(reinterpret_cast<const char*>(address),
+ contents.byte_buffer._0);
+ break;
+ case AnnotationContents::Tag::OwnedByteBuffer:
+ value.Assign(reinterpret_cast<const char*>(address),
+ contents.owned_byte_buffer._0);
+ break;
+ }
+
+ break;
+ case AnnotationType::Boolean:
+ if (IsFixedSizeAnnotation<bool>(contents)) {
+ value.Assign(*reinterpret_cast<const bool*>(address) ? "1" : "0");
+ }
+ break;
+ case AnnotationType::U32:
+ if (IsFixedSizeAnnotation<uint32_t>(contents)) {
+ value.AppendInt(*reinterpret_cast<const uint32_t*>(address));
+ }
+ break;
+ case AnnotationType::U64:
+ if (IsFixedSizeAnnotation<uint64_t>(contents)) {
+ value.AppendInt(*reinterpret_cast<const uint64_t*>(address));
+ }
+ break;
+ case AnnotationType::USize:
+ if (IsFixedSizeAnnotation<size_t>(contents)) {
+#ifdef XP_MACOSX
+ // macOS defines size_t as unsigned long, which causes ambiguity
+ // when it comes to function overload, use a 64-bit integer instead
+ value.AppendInt(*reinterpret_cast<const uint64_t*>(address));
+#else
+ value.AppendInt(*reinterpret_cast<const size_t*>(address));
+#endif
+ }
+ break;
+ }
+
+ if (!value.IsEmpty() && aAnnotations[key].IsEmpty() &&
+ ShouldIncludeAnnotation(key, value.get())) {
+ aAnnotations[key] = value;
+ }
+ }
+ }
+
AddCommonAnnotations(aAnnotations);
}
@@ -3126,54 +3357,71 @@ static void AddChildProcessAnnotations(
}
for (const auto& annotation : *aChildAnnotations) {
- switch (annotation.data.tag) {
- case AnnotationData::Tag::Empty:
- break;
+ Annotation id = static_cast<Annotation>(annotation.id);
+ const AnnotationData& data = annotation.data;
- case AnnotationData::Tag::UsizeData:
- if (annotation.id ==
- static_cast<uint32_t>(Annotation::OOMAllocationSize)) {
- // We need to special-case OOMAllocationSize here because it should
- // not be added if its value is 0. We'll come up with a more general
- // method of skipping ignored values for crash annotations in the
- // follow-ups.
- if (annotation.data.usize_data._0 != 0) {
- aAnnotations[static_cast<Annotation>(annotation.id)] =
- nsPrintfCString("%zu", annotation.data.usize_data._0);
- }
- } else {
- aAnnotations[static_cast<Annotation>(annotation.id)] =
- nsPrintfCString("%zu", annotation.data.usize_data._0);
+ if ((id == Annotation::PHCBaseAddress) &&
+ (data.tag == AnnotationData::Tag::ByteBuffer)) {
+ // PHC is special for now, let's deal with it here
+#ifdef MOZ_PHC
+ const auto& buffer = data.byte_buffer._0;
+ mozilla::phc::AddrInfo addr_info;
+ memcpy(&addr_info, buffer.Elements(), sizeof(addr_info));
+ PopulatePHCAnnotations(aAnnotations, &addr_info);
+#endif
+ continue;
+ }
+
+ if (data.tag == AnnotationData::Tag::Empty) {
+ continue;
+ }
+
+ nsAutoCString value;
+ const uint8_t* buffer = data.byte_buffer._0.Elements();
+ const size_t length = data.byte_buffer._0.Length();
+
+ switch (TypeOfAnnotation(id)) {
+ case AnnotationType::String:
+ value.Assign(reinterpret_cast<const char*>(buffer), length);
+ break;
+ case AnnotationType::Boolean:
+ if (length == sizeof(bool)) {
+ value.Assign(*reinterpret_cast<const bool*>(buffer) ? "1" : "0");
}
break;
-
- case AnnotationData::Tag::NSCStringData: {
- const auto& string = annotation.data.nsc_string_data._0;
- if (!string.IsEmpty()) {
- aAnnotations[static_cast<Annotation>(annotation.id)] =
- annotation.data.nsc_string_data._0;
+ case AnnotationType::U32:
+ if (length == sizeof(uint32_t)) {
+ value.AppendInt(*reinterpret_cast<const uint32_t*>(buffer));
}
- } break;
-
- case AnnotationData::Tag::ByteBuffer: {
- if (annotation.id ==
- static_cast<uint32_t>(Annotation::PHCBaseAddress)) {
-#ifdef MOZ_PHC
- const auto& buffer = annotation.data.byte_buffer._0;
- mozilla::phc::AddrInfo addr_info;
- memcpy(&addr_info, buffer.Elements(), sizeof(addr_info));
- PopulatePHCAnnotations(aAnnotations, &addr_info);
+ break;
+ case AnnotationType::U64:
+ if (length == sizeof(uint64_t)) {
+ value.AppendInt(*reinterpret_cast<const uint64_t*>(buffer));
+ }
+ break;
+ case AnnotationType::USize:
+ if (length == sizeof(size_t)) {
+#ifdef XP_MACOSX
+ // macOS defines size_t as unsigned long, which causes ambiguity
+ // when it comes to function overload, use a 64-bit integer instead
+ value.AppendInt(*reinterpret_cast<const uint64_t*>(buffer));
+#else
+ value.AppendInt(*reinterpret_cast<const size_t*>(buffer));
#endif
}
- } break;
+ break;
+ }
+
+ if (!value.IsEmpty() && ShouldIncludeAnnotation(id, value.get())) {
+ aAnnotations[id] = value;
}
}
}
// It really only makes sense to call this function when
// ShouldReport() is true.
-// Uses dumpFile's filename to generate memoryReport's filename (same name with
-// a different extension)
+// Uses dumpFile's filename to generate memoryReport's filename (same name
+// with a different extension)
static bool MoveToPending(nsIFile* dumpFile, nsIFile* extraFile,
nsIFile* memoryReport) {
nsCOMPtr<nsIFile> pendingDir;
@@ -3480,44 +3728,15 @@ bool CreateNotificationPipeForChild(int* childCrashFd, int* childCrashRemapFd) {
bool SetRemoteExceptionHandler(const char* aCrashPipe) {
MOZ_ASSERT(!gExceptionHandler, "crash client already init'd");
RegisterRuntimeExceptionModule();
- InitializeAnnotationFacilities();
- for (auto key : MakeEnumeratedRange(Annotation::Count)) {
- switch (key) {
- case Annotation::MozCrashReason:
-#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
- case Annotation::MainThreadRunnableName:
-#endif
- case Annotation::OOMAllocationSize:
-#ifdef MOZ_PHC
- case Annotation::PHCBaseAddress:
-#endif
- break;
-
- default:
- mozannotation_register_nscstring(static_cast<uint32_t>(key),
- &crashReporterAPIData_Table[key]);
- }
- }
-
- mozannotation_register_cstring(
- static_cast<uint32_t>(Annotation::MozCrashReason), &gMozCrashReason);
-
-#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
- mozannotation_register_char_buffer(
- static_cast<uint32_t>(Annotation::MainThreadRunnableName),
- &nsThread::sMainThreadRunnableName[0]);
-#endif
-
- mozannotation_register_usize(
- static_cast<uint32_t>(Annotation::OOMAllocationSize),
- &gOOMAllocationSize);
+ InitializeAppNotes();
+ RegisterAnnotations();
#ifdef MOZ_PHC
// HACK: We're using the PHCBaseAddress annotation to point to the actual
// PHC address information object. This is because we currently have no
// difference between the internal representation of annotations and their
- // external representation. Once we remove the old annotation API this will
- // be properly addressed.
+ // external representation. Once we remove the old annotation API this
+ // will be properly addressed.
mozannotation_register_bytebuffer(
static_cast<uint32_t>(Annotation::PHCBaseAddress),
&mozilla::phc::gAddrInfo, sizeof(mozilla::phc::gAddrInfo));
@@ -3876,7 +4095,8 @@ bool UnsetRemoteExceptionHandler(bool wasSet) {
gExceptionHandler = nullptr;
}
#endif
- TeardownAnnotationFacilities();
+ gServerURL = "";
+ TeardownAppNotes();
return true;
}
@@ -3894,9 +4114,9 @@ void SetNotificationPipeForChild(int childCrashFd) {
// Bionic introduced support for getgrgid_r() and getgrnam_r() only in version
// 24 (that is Android Nougat / 7.1.2). Since GeckoView is built by version 16
// (32-bit) or 21 (64-bit), those functions aren't defined, but nix needs them
-// and minidump-writer relies on nix. These functions should never be called in
-// practice hence we implement them only to satisfy nix linking requirements but
-// we crash if we accidentally enter them.
+// and minidump-writer relies on nix. These functions should never be called
+// in practice hence we implement them only to satisfy nix linking
+// requirements but we crash if we accidentally enter them.
extern "C" {
diff --git a/toolkit/crashreporter/nsExceptionHandler.h b/toolkit/crashreporter/nsExceptionHandler.h
index f81cc07c8c..2564737137 100644
--- a/toolkit/crashreporter/nsExceptionHandler.h
+++ b/toolkit/crashreporter/nsExceptionHandler.h
@@ -18,12 +18,12 @@
#include "CrashAnnotations.h"
-#include <stddef.h>
-#include <stdint.h>
#include "nsError.h"
#include "nsString.h"
#include "nsXULAppAPI.h"
#include "prio.h"
+#include <stddef.h>
+#include <stdint.h>
#if defined(XP_WIN)
# ifdef WIN32_LEAN_AND_MEAN
@@ -95,30 +95,42 @@ nsresult SetMinidumpPath(const nsAString& aPath);
// child processes. Annotations added in the main process will be included in
// child process crashes too unless the child process sets its own annotations.
// If it does the child-provided annotation overrides the one set in the parent.
-nsresult AnnotateCrashReport(Annotation key, bool data);
-nsresult AnnotateCrashReport(Annotation key, int data);
-nsresult AnnotateCrashReport(Annotation key, unsigned int data);
-nsresult AnnotateCrashReport(Annotation key, const nsACString& data);
-nsresult AppendToCrashReportAnnotation(Annotation key, const nsACString& data);
-nsresult RemoveCrashReportAnnotation(Annotation key);
+const bool* RegisterAnnotationBool(Annotation aKey, const bool* aData);
+const uint32_t* RegisterAnnotationU32(Annotation aKey, const uint32_t* aData);
+const uint64_t* RegisterAnnotationU64(Annotation aKey, const uint64_t* aData);
+const size_t* RegisterAnnotationUSize(Annotation aKey, const size_t* aData);
+const char* RegisterAnnotationCString(Annotation aKey, const char* aData);
+const nsCString* RegisterAnnotationNSCString(Annotation aKey,
+ const nsCString* aData);
+
+nsresult RecordAnnotationBool(Annotation aKey, bool aData);
+nsresult RecordAnnotationU32(Annotation aKey, uint32_t aData);
+nsresult RecordAnnotationU64(Annotation aKey, uint64_t aData);
+nsresult RecordAnnotationUSize(Annotation aKey, size_t aData);
+nsresult RecordAnnotationCString(Annotation aKey, const char* aData);
+nsresult RecordAnnotationNSCString(Annotation aKey, const nsACString& aData);
+nsresult RecordAnnotationNSString(Annotation aKey, const nsAString& aData);
+nsresult UnrecordAnnotation(Annotation aKey);
+
nsresult AppendAppNotesToCrashReport(const nsACString& data);
// RAII class for setting a crash annotation during a limited scope of time.
// Will reset the named annotation to its previous value when destroyed.
//
-// This type's behavior is identical to that of AnnotateCrashReport().
-class MOZ_RAII AutoAnnotateCrashReport final {
+// This type's behavior is identical to that of RecordAnnotation().
+class MOZ_RAII AutoRecordAnnotation final {
public:
- AutoAnnotateCrashReport(Annotation key, bool data);
- AutoAnnotateCrashReport(Annotation key, int data);
- AutoAnnotateCrashReport(Annotation key, unsigned int data);
- AutoAnnotateCrashReport(Annotation key, const nsACString& data);
- ~AutoAnnotateCrashReport();
+ AutoRecordAnnotation(Annotation key, bool data);
+ AutoRecordAnnotation(Annotation key, int data);
+ AutoRecordAnnotation(Annotation key, unsigned int data);
+ AutoRecordAnnotation(Annotation key, const nsACString& data);
+ ~AutoRecordAnnotation();
#ifdef MOZ_CRASHREPORTER
private:
Annotation mKey;
- nsCString mPrevious;
+ const nsCString mCurrent;
+ const nsCString* mPrevious;
#endif
};
@@ -143,7 +155,8 @@ void GetAnnotation(uint32_t childPid, Annotation annotation,
nsACString& outStr);
// Functions for working with minidumps and .extras
-typedef mozilla::EnumeratedArray<Annotation, Annotation::Count, nsCString>
+typedef mozilla::EnumeratedArray<Annotation, nsCString,
+ size_t(Annotation::Count)>
AnnotationTable;
void DeleteMinidumpFilesForID(
const nsAString& aId,
diff --git a/toolkit/crashreporter/test/gtest/TestElfSoVersion.cpp b/toolkit/crashreporter/test/gtest/TestElfSoVersion.cpp
new file mode 100644
index 0000000000..a6c91a8dff
--- /dev/null
+++ b/toolkit/crashreporter/test/gtest/TestElfSoVersion.cpp
@@ -0,0 +1,143 @@
+/* -*- 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/. */
+
+#include "gtest/gtest.h"
+#include "mozilla/SpinEventLoopUntil.h"
+
+#include "linux_utils.h"
+
+#define ASSERT_EQ_UNSIGNED(v, e) ASSERT_EQ((v), (uint32_t)(e))
+
+using namespace mozilla;
+
+class CrashReporter : public ::testing::Test {};
+
+TEST_F(CrashReporter, ElfSoNoVersion) {
+ uint32_t version[4] = {0, 0, 0, 0};
+ bool rv = ElfFileSoVersion("libdbus1.so", version);
+ ASSERT_TRUE(rv);
+}
+
+TEST_F(CrashReporter, ElfSo6) {
+ uint32_t version[4] = {0, 0, 0, 0};
+ bool rv = ElfFileSoVersion("libm.so.6", version);
+ ASSERT_TRUE(rv);
+ ASSERT_EQ_UNSIGNED(version[0], 6);
+}
+
+TEST_F(CrashReporter, ElfSoNormalShort) {
+ uint32_t version[4] = {0, 0, 0, 0};
+ bool rv = ElfFileSoVersion("libdbus1.so.1.2", version);
+ ASSERT_TRUE(rv);
+ ASSERT_EQ_UNSIGNED(version[0], 1);
+ ASSERT_EQ_UNSIGNED(version[1], 2);
+}
+
+TEST_F(CrashReporter, ElfSoNormalComplete) {
+ uint32_t version[4] = {0, 0, 0, 0};
+ bool rv = ElfFileSoVersion("libdbus1.so.1.2.3", version);
+ ASSERT_TRUE(rv);
+ ASSERT_EQ_UNSIGNED(version[0], 1);
+ ASSERT_EQ_UNSIGNED(version[1], 2);
+ ASSERT_EQ_UNSIGNED(version[2], 3);
+}
+
+TEST_F(CrashReporter, ElfSoNormalPrerelease) {
+ uint32_t version[4] = {0, 0, 0, 0};
+ bool rv = ElfFileSoVersion("libdbus1.so.1.2.3.98", version);
+ ASSERT_TRUE(rv);
+ ASSERT_EQ_UNSIGNED(version[0], 1);
+ ASSERT_EQ_UNSIGNED(version[1], 2);
+ ASSERT_EQ_UNSIGNED(version[2], 3);
+ ASSERT_EQ_UNSIGNED(version[3], 98);
+}
+
+TEST_F(CrashReporter, ElfSoNormalPrereleaseToomuch) {
+ uint32_t version[4] = {0, 0, 0, 0};
+ bool rv = ElfFileSoVersion("libdbus1.so.1.2.3.98.9.2.3", version);
+ ASSERT_TRUE(rv);
+ ASSERT_EQ_UNSIGNED(version[0], 1);
+ ASSERT_EQ_UNSIGNED(version[1], 2);
+ ASSERT_EQ_UNSIGNED(version[2], 3);
+ ASSERT_EQ_UNSIGNED(version[3], 98);
+}
+
+TEST_F(CrashReporter, ElfSoBig) {
+ uint32_t version[4] = {0, 0, 0, 0};
+ bool rv = ElfFileSoVersion("libatk-1.0.so.0.25009.1", version);
+ ASSERT_TRUE(rv);
+ ASSERT_EQ_UNSIGNED(version[0], 0);
+ ASSERT_EQ_UNSIGNED(version[1], 25009);
+ ASSERT_EQ_UNSIGNED(version[2], 1);
+}
+
+TEST_F(CrashReporter, ElfSoCairo) {
+ uint32_t version[4] = {0, 0, 0, 0};
+ bool rv = ElfFileSoVersion("libcairo.so.2.11800.3", version);
+ ASSERT_TRUE(rv);
+ ASSERT_EQ_UNSIGNED(version[0], 2);
+ ASSERT_EQ_UNSIGNED(version[1], 11800);
+ ASSERT_EQ_UNSIGNED(version[2], 3);
+}
+
+TEST_F(CrashReporter, ElfSoMax) {
+ uint32_t version[4] = {0, 0, 0, 0};
+ bool rv = ElfFileSoVersion(
+ "libcairo.so.2147483647.2147483647.2147483647.2147483647", version);
+ ASSERT_TRUE(rv);
+ ASSERT_EQ_UNSIGNED(version[0], INT32_MAX);
+ ASSERT_EQ_UNSIGNED(version[1], INT32_MAX);
+ ASSERT_EQ_UNSIGNED(version[2], INT32_MAX);
+ ASSERT_EQ_UNSIGNED(version[3], INT32_MAX);
+}
+
+TEST_F(CrashReporter, ElfSoTimestamp) {
+ uint32_t version[4] = {0, 0, 0, 0};
+ bool rv = ElfFileSoVersion("libabsl_time_zone.so.20220623.0.0", version);
+ ASSERT_TRUE(rv);
+ ASSERT_EQ_UNSIGNED(version[0], 20220623);
+ ASSERT_EQ_UNSIGNED(version[1], 0);
+ ASSERT_EQ_UNSIGNED(version[2], 0);
+}
+
+TEST_F(CrashReporter, ElfSoChars) {
+ uint32_t version[4] = {0, 0, 0, 0};
+ bool rv = ElfFileSoVersion("libabsl_time_zone.so.1.2.3rc4", version);
+ ASSERT_TRUE(rv);
+ ASSERT_EQ_UNSIGNED(version[0], 1);
+ ASSERT_EQ_UNSIGNED(version[1], 2);
+ ASSERT_EQ_UNSIGNED(version[2], 34);
+}
+
+TEST_F(CrashReporter, ElfSoCharsMore) {
+ uint32_t version[4] = {0, 0, 0, 0};
+ bool rv = ElfFileSoVersion("libabsl_time_zone.so.1.2.3rc4.9", version);
+ ASSERT_TRUE(rv);
+ ASSERT_EQ_UNSIGNED(version[0], 1);
+ ASSERT_EQ_UNSIGNED(version[1], 2);
+ ASSERT_EQ_UNSIGNED(version[2], 34);
+ ASSERT_EQ_UNSIGNED(version[3], 9);
+}
+
+TEST_F(CrashReporter, ElfSoCharsOnly) {
+ uint32_t version[4] = {0, 0, 0, 0};
+ bool rv = ElfFileSoVersion("libabsl_time_zone.so.final", version);
+ ASSERT_TRUE(rv);
+}
+
+TEST_F(CrashReporter, ElfSoNullVersion) {
+ bool rv = ElfFileSoVersion("libabsl_time_zone.so.1", nullptr);
+ ASSERT_FALSE(rv);
+}
+
+TEST_F(CrashReporter, ElfSoFullPath) {
+ uint32_t version[4] = {0, 0, 0, 0};
+ bool rv = ElfFileSoVersion(
+ "/usr/lib/x86_64-linux-gnu/libabsl_time_zone.so.20220623.0.0", version);
+ ASSERT_TRUE(rv);
+ ASSERT_EQ_UNSIGNED(version[0], 20220623);
+ ASSERT_EQ_UNSIGNED(version[1], 0);
+ ASSERT_EQ_UNSIGNED(version[2], 0);
+}
diff --git a/toolkit/crashreporter/test/gtest/moz.build b/toolkit/crashreporter/test/gtest/moz.build
new file mode 100644
index 0000000000..9aa1c0a0db
--- /dev/null
+++ b/toolkit/crashreporter/test/gtest/moz.build
@@ -0,0 +1,16 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at http://mozilla.org/MPL/2.0/.
+
+Library("crashreportertest")
+
+if CONFIG["OS_ARCH"] == "Linux":
+ UNIFIED_SOURCES = [
+ "TestElfSoVersion.cpp",
+ ]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul-gtest"
diff --git a/toolkit/crashreporter/test/moz.build b/toolkit/crashreporter/test/moz.build
index 1ebd9b9029..254d232e0e 100644
--- a/toolkit/crashreporter/test/moz.build
+++ b/toolkit/crashreporter/test/moz.build
@@ -9,6 +9,10 @@ XPCSHELL_TESTS_MANIFESTS += ["unit/xpcshell.toml", "unit_ipc/xpcshell.toml"]
if CONFIG["MOZ_PHC"]:
XPCSHELL_TESTS_MANIFESTS += ["unit/xpcshell-phc.toml", "unit_ipc/xpcshell-phc.toml"]
+TEST_DIRS += [
+ "gtest",
+]
+
BROWSER_CHROME_MANIFESTS += ["browser/browser.toml"]
UNIFIED_SOURCES += [
diff --git a/toolkit/crashreporter/test/unit/head_crashreporter.js b/toolkit/crashreporter/test/unit/head_crashreporter.js
index c37c8acf8c..34602e2107 100644
--- a/toolkit/crashreporter/test/unit/head_crashreporter.js
+++ b/toolkit/crashreporter/test/unit/head_crashreporter.js
@@ -156,6 +156,9 @@ async function handleMinidump(callback) {
registerCleanupFunction(cleanup);
Assert.ok(extrafile.exists());
+ let data = await IOUtils.read(extrafile.path);
+ let decoder = new TextDecoder("ascii");
+ console.log("data = " + decoder.decode(data));
let extra = await IOUtils.readJSON(extrafile.path);
if (callback) {
diff --git a/toolkit/crashreporter/test/unit/test_crash_modules_linux.js b/toolkit/crashreporter/test/unit/test_crash_modules_linux.js
new file mode 100644
index 0000000000..9fcf587308
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crash_modules_linux.js
@@ -0,0 +1,33 @@
+add_task(async function run_test() {
+ if (!("@mozilla.org/toolkit/crash-reporter;1" in Cc)) {
+ dump(
+ "INFO | test_crash_modules.js | Can't test crashreporter in a non-libxul build.\n"
+ );
+ return;
+ }
+
+ await do_crash(
+ function () {
+ crashType = CrashTestUtils.CRASH_ABORT;
+ },
+ async function (mdump, extra, extraFile) {
+ runMinidumpAnalyzer(mdump);
+
+ // Refresh updated extra data
+ extra = await IOUtils.readJSON(extraFile.path);
+
+ // Check modules' versions
+ const modules = extra.StackTraces.modules;
+ const version_regexp = /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/;
+
+ for (let module of modules) {
+ console.debug("module", module);
+ console.debug("version => ", module.version);
+ console.debug("version regex => ", version_regexp.exec(module.version));
+ Assert.notEqual(version_regexp.exec(module.version), null);
+ }
+ },
+ // process will exit with a zero exit status
+ true
+ );
+});
diff --git a/toolkit/crashreporter/test/unit/xpcshell-phc.toml b/toolkit/crashreporter/test/unit/xpcshell-phc.toml
index 278cf28193..1bb182d852 100644
--- a/toolkit/crashreporter/test/unit/xpcshell-phc.toml
+++ b/toolkit/crashreporter/test/unit/xpcshell-phc.toml
@@ -1,8 +1,8 @@
[DEFAULT]
head = "head_crashreporter.js"
skip-if = [
- "toolkit == 'android'", # 1536217
- "os == 'win' && msix", # https://bugzilla.mozilla.org/show_bug.cgi?id=1807922
+ "os == 'android'", # 1536217
+ "win11_2009 && msix", # https://bugzilla.mozilla.org/show_bug.cgi?id=1807922
]
support-files = [
"crasher_subprocess_head.js",
diff --git a/toolkit/crashreporter/test/unit/xpcshell.toml b/toolkit/crashreporter/test/unit/xpcshell.toml
index ffa631d0a1..6b1676ac32 100644
--- a/toolkit/crashreporter/test/unit/xpcshell.toml
+++ b/toolkit/crashreporter/test/unit/xpcshell.toml
@@ -40,6 +40,10 @@ run-if = ["os == 'win'"]
reason = "Test covering Windows-specific module handling"
run-sequentially = "very high failure rate in parallel"
+["test_crash_modules_linux.js"]
+run-if = ["os == 'linux'"]
+reason = "Test covering Linux-specific module handling"
+
["test_crash_moz_crash.js"]
["test_crash_oom.js"]
@@ -51,7 +55,7 @@ run-sequentially = "very high failure rate in parallel"
["test_crash_rust_panic_multiline.js"]
["test_crash_stack_overflow.js"]
-skip-if = ["os != 'linux'"]
+run-if = ["os == 'linux'"]
reason = "Still broken on macOS and not yet supported on Windows"
["test_crash_terminator.js"]
@@ -60,71 +64,71 @@ reason = "Still broken on macOS and not yet supported on Windows"
["test_crash_win64cfi_alloc_large.js"]
head = "head_crashreporter.js head_win64cfi.js"
-run-if = ["os == 'win' && bits == 64 && processor == 'x86_64'"]
+run-if = ["os == 'win' && bits == 64"]
reason = "Windows test specific to the x86-64 architecture"
["test_crash_win64cfi_alloc_small.js"]
head = "head_crashreporter.js head_win64cfi.js"
-run-if = ["os == 'win' && bits == 64 && processor == 'x86_64'"]
+run-if = ["os == 'win' && bits == 64"]
reason = "Windows test specific to the x86-64 architecture"
["test_crash_win64cfi_epilog.js"]
head = "head_crashreporter.js head_win64cfi.js"
-run-if = ["os == 'win' && bits == 64 && processor == 'x86_64'"]
+run-if = ["os == 'win' && bits == 64"]
reason = "Windows test specific to the x86-64 architecture"
["test_crash_win64cfi_infinite_code_chain.js"]
head = "head_crashreporter.js head_win64cfi.js"
-run-if = ["os == 'win' && bits == 64 && processor == 'x86_64'"]
+run-if = ["os == 'win' && bits == 64"]
reason = "Windows test specific to the x86-64 architecture"
support-files = ["test_crash_win64cfi_infinite_code_chain.exe"]
["test_crash_win64cfi_infinite_entry_chain.js"]
head = "head_crashreporter.js head_win64cfi.js"
-run-if = ["os == 'win' && bits == 64 && processor == 'x86_64'"]
+run-if = ["os == 'win' && bits == 64"]
reason = "Windows test specific to the x86-64 architecture"
support-files = ["test_crash_win64cfi_infinite_entry_chain.exe"]
["test_crash_win64cfi_invalid_exception_rva.js"]
head = "head_crashreporter.js head_win64cfi.js"
-run-if = ["os == 'win' && bits == 64 && processor == 'x86_64'"]
+run-if = ["os == 'win' && bits == 64"]
reason = "Windows test specific to the x86-64 architecture"
support-files = ["test_crash_win64cfi_invalid_exception_rva.exe"]
["test_crash_win64cfi_not_a_pe.js"]
head = "head_crashreporter.js head_win64cfi.js"
-run-if = ["os == 'win' && bits == 64 && processor == 'x86_64'"]
+run-if = ["os == 'win' && bits == 64"]
reason = "Windows test specific to the x86-64 architecture"
support-files = ["test_crash_win64cfi_not_a_pe.exe"]
["test_crash_win64cfi_push_nonvol.js"]
head = "head_crashreporter.js head_win64cfi.js"
-run-if = ["os == 'win' && bits == 64 && processor == 'x86_64'"]
+run-if = ["os == 'win' && bits == 64"]
reason = "Windows test specific to the x86-64 architecture"
["test_crash_win64cfi_save_nonvol.js"]
head = "head_crashreporter.js head_win64cfi.js"
-run-if = ["os == 'win' && bits == 64 && processor == 'x86_64'"]
+run-if = ["os == 'win' && bits == 64"]
reason = "Windows test specific to the x86-64 architecture"
["test_crash_win64cfi_save_nonvol_far.js"]
head = "head_crashreporter.js head_win64cfi.js"
-run-if = ["os == 'win' && bits == 64 && processor == 'x86_64'"]
+run-if = ["os == 'win' && bits == 64"]
reason = "Windows test specific to the x86-64 architecture"
["test_crash_win64cfi_save_xmm128.js"]
head = "head_crashreporter.js head_win64cfi.js"
-run-if = ["os == 'win' && bits == 64 && processor == 'x86_64'"]
+run-if = ["os == 'win' && bits == 64"]
reason = "Windows test specific to the x86-64 architecture"
["test_crash_win64cfi_save_xmm128_far.js"]
head = "head_crashreporter.js head_win64cfi.js"
-run-if = ["os == 'win' && bits == 64 && processor == 'x86_64'"]
+run-if = ["os == 'win' && bits == 64"]
reason = "Windows test specific to the x86-64 architecture"
["test_crash_win64cfi_unknown_op.js"]
head = "head_crashreporter.js head_win64cfi.js"
-run-if = ["os == 'win' && bits == 64 && processor == 'x86_64'"]
+run-if = ["os == 'win' && bits == 64"]
reason = "Windows test specific to the x86-64 architecture"
["test_crash_with_memory_report.js"]
@@ -134,8 +138,10 @@ reason = "Windows test specific to the x86-64 architecture"
["test_crashreporter_appmem.js"]
# we need to skip this due to bug 838613
skip-if = [
- "os != 'win' && os != 'linux'",
- "os=='linux' && bits==32",
+ "os == 'android'",
+ "apple_silicon",
+ "apple_catalina",
+ "os == 'linux' && os_version == '18.04' && bits == 32",
]
["test_crashreporter_crash.js"]
@@ -149,4 +155,4 @@ run-sequentially = "very high failure rate in parallel"
run-sequentially = "very high failure rate in parallel"
["test_override_exception_handler.js"]
-skip-if = ["os != 'win'"]
+run-if = ["os == 'win'"]
diff --git a/toolkit/crashreporter/tools/symbolstore.py b/toolkit/crashreporter/tools/symbolstore.py
index bc16002503..8bc7a7120a 100755
--- a/toolkit/crashreporter/tools/symbolstore.py
+++ b/toolkit/crashreporter/tools/symbolstore.py
@@ -578,7 +578,7 @@ class Dumper:
# MODULE os cpu guid debug_file
(guid, debug_file) = (module_line.split())[3:5]
# strip off .pdb extensions, and append .sym
- sym_file = re.sub("\.pdb$", "", debug_file) + ".sym"
+ sym_file = re.sub(r"\.pdb$", "", debug_file) + ".sym"
# we do want forward slashes here
rel_path = os.path.join(debug_file, guid, sym_file).replace("\\", "/")
full_path = os.path.normpath(os.path.join(self.symbol_path, rel_path))
diff --git a/toolkit/library/moz.build b/toolkit/library/moz.build
index 76746f3080..185d5f74d0 100644
--- a/toolkit/library/moz.build
+++ b/toolkit/library/moz.build
@@ -245,6 +245,7 @@ if CONFIG["MOZ_WIDGET_TOOLKIT"] == "uikit":
"-framework CoreVideo",
"-framework OpenGLES",
"-framework QuartzCore",
+ "-framework Security",
]
if CONFIG["MOZ_WMF"]:
diff --git a/toolkit/library/rust/moz.build b/toolkit/library/rust/moz.build
index eb2db8f81b..0935410268 100644
--- a/toolkit/library/rust/moz.build
+++ b/toolkit/library/rust/moz.build
@@ -31,6 +31,9 @@ RUST_TESTS = [
"gkrust",
]
+if CONFIG["TARGET_OS"] in ("WINNT", "OSX"):
+ RUST_TESTS += ["nmhproxy"]
+
# Code coverage builds link a bunch of Gecko bindings code from the style
# crate, which is not used by our tests but would cause link errors.
#
diff --git a/toolkit/library/rust/shared/Cargo.toml b/toolkit/library/rust/shared/Cargo.toml
index a3c5bef5f6..4e7ab800a2 100644
--- a/toolkit/library/rust/shared/Cargo.toml
+++ b/toolkit/library/rust/shared/Cargo.toml
@@ -101,7 +101,7 @@ processtools = { path = "../../../components/processtools" }
qcms = { path = "../../../../gfx/qcms", features = ["c_bindings", "neon"], default-features = false }
wpf-gpu-raster = { git = "https://github.com/FirefoxGraphics/wpf-gpu-raster", rev = "99979da091fd58fba8477e7fcdf5ec0727102916" }
-aa-stroke = { git = "https://github.com/FirefoxGraphics/aa-stroke", rev = "96e66f91bb8e8efb80ff144eabd668002aa89650" }
+aa-stroke = { git = "https://github.com/FirefoxGraphics/aa-stroke", rev = "d94278ed9c7020f50232689a26d1277eb0eb74d2" }
url = "2.5.0"
diff --git a/toolkit/locales/en-US/chrome/global/narrate.properties b/toolkit/locales/en-US/chrome/global/narrate.properties
index 712cbcde51..c622049a81 100644
--- a/toolkit/locales/en-US/chrome/global/narrate.properties
+++ b/toolkit/locales/en-US/chrome/global/narrate.properties
@@ -7,14 +7,16 @@
# of the feature and it is the label for the popup button.
# %S is the keyboard shortcut for the listen command
listen-label = Listen (%S)
-back = Back
+# %S is the keyboard shortcut for the skip back command
+previous-label = Back (%S)
# %S is the keyboard shortcut for the start command
start-label = Start (%S)
# %S is the keyboard shortcut for the stop command
stop-label = Stop (%S)
# Keyboard shortcut to toggle the narrate feature
narrate-key-shortcut = N
-forward = Forward
+# %S is the keyboard shortcut for the skip forward command
+next-label = Forward (%S)
speed = Speed
selectvoicelabel = Voice:
# Default voice is determined by the language of the document.
diff --git a/toolkit/locales/en-US/toolkit/contentanalysis/contentanalysis.ftl b/toolkit/locales/en-US/toolkit/contentanalysis/contentanalysis.ftl
index 2bf5d0cf19..fbd9cd2ff8 100644
--- a/toolkit/locales/en-US/toolkit/contentanalysis/contentanalysis.ftl
+++ b/toolkit/locales/en-US/toolkit/contentanalysis/contentanalysis.ftl
@@ -7,13 +7,23 @@ contentanalysis-alert-title = Content Analysis
# Variables:
# $content - Description of the content being warned about, such as "clipboard" or "aFile.txt"
contentanalysis-slow-agent-notification = The Content Analysis tool is taking a long time to respond for resource “{ $content }”
-contentanalysis-slow-agent-dialog-title = Content analysis in progress
+contentanalysis-slow-agent-dialog-header = Scan in progress
# Variables:
-# $content - Description of the content being warned about, such as "clipboard" or "aFile.txt"
-contentanalysis-slow-agent-dialog-body = Content Analysis is analyzing resource “{ $content }”
+# $agent - The name of the DLP agent doing the analysis
+# $filename - Name of the file being analyzed, such as "aFile.txt"
+contentanalysis-slow-agent-dialog-body-file = { $agent } is reviewing “{ $filename }” against your organization’s data policies. This may take a moment.
+# Variables:
+# $agent - The name of the DLP agent doing the analysis
+contentanalysis-slow-agent-dialog-body-clipboard = { $agent } is reviewing what you pasted against your organization’s data policies. This may take a moment.
+# Note that this is shown when the user drag and drops text into the browser.
+# Variables:
+# $agent - The name of the DLP agent doing the analysis
+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-operationtype-clipboard = clipboard
contentanalysis-operationtype-dropped-text = dropped text
+# $filename - The filename associated with the request, such as "aFile.txt"
+contentanalysis-customdisplaystring-description = upload of “{ $filename }”
contentanalysis-warndialogtitle = This content may be unsafe
@@ -34,3 +44,7 @@ contentanalysis-block-message = Your organization uses data-loss prevention soft
# Variables:
# $content - Description of the content being blocked, such as "clipboard" or "aFile.txt"
contentanalysis-error-message = An error occurred in communicating with the data-loss prevention software. Transfer denied for resource: { $content }.
+
+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
diff --git a/toolkit/locales/en-US/toolkit/formautofill/formAutofill.ftl b/toolkit/locales/en-US/toolkit/formautofill/formAutofill.ftl
index 2dbf721213..34489e5b66 100644
--- a/toolkit/locales/en-US/toolkit/formautofill/formAutofill.ftl
+++ b/toolkit/locales/en-US/toolkit/formautofill/formAutofill.ftl
@@ -52,3 +52,21 @@ credit-card-capture-save-new-button =
credit-card-capture-update-button =
.label = Update existing card
.accessKey = U
+
+# Used as a label for the button, displayed at the bottom of the dropdown suggestion, to open Form Autofill browser preferences.
+autofill-manage-addresses-label = Manage addresses
+
+# Used as a label for the button, displayed at the bottom of the dropdown suggestion, to open Form Autofill browser preferences.
+autofill-manage-payment-methods-label = Manage payment methods
+
+## These are brand names and should only be translated when a locale-specific name for that brand is in common use
+
+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
diff --git a/toolkit/locales/en-US/toolkit/global/arrowscrollbox.ftl b/toolkit/locales/en-US/toolkit/global/arrowscrollbox.ftl
new file mode 100644
index 0000000000..f1a1dad700
--- /dev/null
+++ b/toolkit/locales/en-US/toolkit/global/arrowscrollbox.ftl
@@ -0,0 +1,20 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.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 the entities needed to use the Arrowscrollbox component.
+### For example, Arrowscrollboxes are used in Tabs Toolbar when there are
+### multiple tabs opened and in the overflowing menus.
+
+# This button is shown at the beginning of the overflowing list of elements.
+# For example, in LTR language like English, on the Tabs Toolbar it would look
+# like "<" and scroll the tab list to the left, and in the overflowing menu it
+# would look like "^" and scroll the list of menuitems up.
+overflow-scroll-button-up =
+ .tooltiptext = Scroll up
+# This button is shown at the end of the overflowing list of elements. For
+# example, in LTR language like English, on the Tabs Toolbar it would look like
+# ">" and scroll the tab list to the right, and in the overflowing menu it
+# would look like "v" and scroll the list of menuitems down.
+overflow-scroll-button-down =
+ .tooltiptext = Scroll down
diff --git a/toolkit/locales/en-US/toolkit/global/processTypes.ftl b/toolkit/locales/en-US/toolkit/global/processTypes.ftl
index db48ec250b..0c37076410 100644
--- a/toolkit/locales/en-US/toolkit/global/processTypes.ftl
+++ b/toolkit/locales/en-US/toolkit/global/processTypes.ftl
@@ -51,6 +51,13 @@ process-type-rdd = RDD
# process used to run some IPC actor in their own sandbox
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
##
## Other
diff --git a/toolkit/locales/en-US/toolkit/global/textActions.ftl b/toolkit/locales/en-US/toolkit/global/textActions.ftl
index 4b5334e57e..aae40157df 100644
--- a/toolkit/locales/en-US/toolkit/global/textActions.ftl
+++ b/toolkit/locales/en-US/toolkit/global/textActions.ftl
@@ -83,3 +83,6 @@ text-action-spell-dictionaries =
text-action-search-text-box-clear =
.title = Clear
+
+text-action-highlight-selection =
+ .label = Highlight Selection
diff --git a/toolkit/locales/en-US/toolkit/pdfviewer/viewer.ftl b/toolkit/locales/en-US/toolkit/pdfviewer/viewer.ftl
index 9242a298a2..3804a3bd4d 100644
--- a/toolkit/locales/en-US/toolkit/pdfviewer/viewer.ftl
+++ b/toolkit/locales/en-US/toolkit/pdfviewer/viewer.ftl
@@ -58,14 +58,6 @@ pdfjs-bookmark-button =
.title = Current Page (View URL from Current Page)
pdfjs-bookmark-button-label = Current Page
-# Used in Firefox for Android.
-pdfjs-open-in-app-button =
- .title = Open in app
-
-# Used in Firefox for Android.
-# Length of the translation matters since we are in a mobile context, with limited screen estate.
-pdfjs-open-in-app-button-label = Open in app
-
## Secondary toolbar and context menu
pdfjs-tools-button =
@@ -326,6 +318,8 @@ pdfjs-editor-stamp-button-label = Add or edit images
pdfjs-editor-highlight-button =
.title = Highlight
pdfjs-editor-highlight-button-label = Highlight
+pdfjs-highlight-floating-button =
+ .title = Highlight
## Remove button for the various kind of editor.
@@ -413,3 +407,10 @@ pdfjs-editor-colorpicker-pink =
.title = Pink
pdfjs-editor-colorpicker-red =
.title = Red
+
+## Show all highlights
+## This is a toggle button to show/hide all the highlights.
+
+pdfjs-editor-highlight-show-all-button-label = Show all
+pdfjs-editor-highlight-show-all-button =
+ .title = Show all
diff --git a/toolkit/modules/ActorManagerParent.sys.mjs b/toolkit/modules/ActorManagerParent.sys.mjs
index 3bf7299abe..5b31421ec6 100644
--- a/toolkit/modules/ActorManagerParent.sys.mjs
+++ b/toolkit/modules/ActorManagerParent.sys.mjs
@@ -317,6 +317,17 @@ let JSWINDOWACTORS = {
child: {
esModuleURI: "resource://gre/actors/FormHistoryChild.sys.mjs",
events: {
+ "form-submission-detected": {},
+ },
+ },
+
+ allFrames: true,
+ },
+
+ FormHandler: {
+ child: {
+ esModuleURI: "resource://gre/actors/FormHandlerChild.sys.mjs",
+ events: {
DOMFormBeforeSubmit: {},
},
},
@@ -355,7 +366,7 @@ let JSWINDOWACTORS = {
child: {
esModuleURI: "resource://gre/modules/LoginManagerChild.sys.mjs",
events: {
- DOMFormBeforeSubmit: {},
+ "form-submission-detected": {},
DOMFormHasPassword: {},
DOMFormHasPossibleUsername: {},
DOMInputPasswordAdded: {},
@@ -372,6 +383,22 @@ let JSWINDOWACTORS = {
},
},
+ // 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",
diff --git a/toolkit/modules/AppConstants.sys.mjs b/toolkit/modules/AppConstants.sys.mjs
index 36b26056ec..bfc87fa622 100644
--- a/toolkit/modules/AppConstants.sys.mjs
+++ b/toolkit/modules/AppConstants.sys.mjs
@@ -5,10 +5,10 @@
* License, v. 2.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.defineModuleGetter(lazy, "AddonManager", "resource://gre/modules/AddonManager.jsm");
-
-// Immutable for export.
+/**
+ * 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
@@ -292,16 +292,19 @@ export var AppConstants = Object.freeze({
false,
#endif
- get MOZ_UNSIGNED_SCOPES() {
- let result = 0;
+ MOZ_UNSIGNED_APP_SCOPE:
#ifdef MOZ_UNSIGNED_APP_SCOPE
- result |= lazy.AddonManager.SCOPE_APPLICATION;
+ true,
+#else
+ false,
#endif
+
+ MOZ_UNSIGNED_SYSTEM_SCOPE:
#ifdef MOZ_UNSIGNED_SYSTEM_SCOPE
- result |= lazy.AddonManager.SCOPE_SYSTEM;
+ true,
+#else
+ false,
#endif
- return result;
- },
MOZ_ALLOW_ADDON_SIDELOAD:
#ifdef MOZ_ALLOW_ADDON_SIDELOAD
diff --git a/toolkit/modules/BrowserUtils.sys.mjs b/toolkit/modules/BrowserUtils.sys.mjs
index 1963b9728e..db9ef425c5 100644
--- a/toolkit/modules/BrowserUtils.sys.mjs
+++ b/toolkit/modules/BrowserUtils.sys.mjs
@@ -112,6 +112,7 @@ export var BrowserUtils = {
canFindInPage(location) {
return (
!location.startsWith("about:preferences") &&
+ !location.startsWith("about:settings") &&
!location.startsWith("about:logins") &&
!(location.startsWith("about:firefoxview") && lazy.FXVIEW_SEARCH_ENABLED)
);
diff --git a/toolkit/modules/ClipboardContextMenu.sys.mjs b/toolkit/modules/ClipboardContextMenu.sys.mjs
index 011bfe64d7..d66a2f466d 100644
--- a/toolkit/modules/ClipboardContextMenu.sys.mjs
+++ b/toolkit/modules/ClipboardContextMenu.sys.mjs
@@ -182,12 +182,18 @@ export var ClipboardContextMenu = {
_startWatchingForSpammyActivation() {
let doc = this._menuitem.ownerDocument;
- Services.els.addSystemEventListener(doc, "keydown", this, true);
+ doc.addEventListener("keydown", this, {
+ capture: true,
+ mozSystemGroup: true,
+ });
},
_stopWatchingForSpammyActivation() {
let doc = this._menuitem.ownerDocument;
- Services.els.removeSystemEventListener(doc, "keydown", this, true);
+ doc.removeEventListener("keydown", this, {
+ capture: true,
+ mozSystemGroup: true,
+ });
},
_delayTimer: null,
diff --git a/toolkit/modules/Console.sys.mjs b/toolkit/modules/Console.sys.mjs
index 5fb4f750f4..c3964fa840 100644
--- a/toolkit/modules/Console.sys.mjs
+++ b/toolkit/modules/Console.sys.mjs
@@ -6,7 +6,7 @@
* 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.jsm for an implementation of other web constants to help
+ * 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:
diff --git a/toolkit/modules/DateTimePickerPanel.sys.mjs b/toolkit/modules/DateTimePickerPanel.sys.mjs
index 8d67cb0d8e..f20f2c1668 100644
--- a/toolkit/modules/DateTimePickerPanel.sys.mjs
+++ b/toolkit/modules/DateTimePickerPanel.sys.mjs
@@ -68,8 +68,6 @@ export var DateTimePickerPanel = class {
closePicker(clear) {
if (clear) {
this.element.dispatchEvent(new CustomEvent("DateTimePickerValueCleared"));
- } else {
- this.setInputBoxValue(true);
}
this.pickerState = {};
this.type = undefined;
diff --git a/toolkit/modules/Deprecated.sys.mjs b/toolkit/modules/Deprecated.sys.mjs
deleted file mode 100644
index c8f5aeba01..0000000000
--- a/toolkit/modules/Deprecated.sys.mjs
+++ /dev/null
@@ -1,81 +0,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/. */
-
-const PREF_DEPRECATION_WARNINGS = "devtools.errorconsole.deprecation_warnings";
-
-// A flag that indicates whether deprecation warnings should be logged.
-var logWarnings = Services.prefs.getBoolPref(PREF_DEPRECATION_WARNINGS);
-
-Services.prefs.addObserver(
- PREF_DEPRECATION_WARNINGS,
- function (aSubject, aTopic, aData) {
- logWarnings = Services.prefs.getBoolPref(PREF_DEPRECATION_WARNINGS);
- }
-);
-
-/**
- * Build a callstack log message.
- *
- * @param nsIStackFrame aStack
- * A callstack to be converted into a string log message.
- */
-function stringifyCallstack(aStack) {
- // If aStack is invalid, use Components.stack (ignoring the last frame).
- if (!aStack || !(aStack instanceof Ci.nsIStackFrame)) {
- aStack = Components.stack.caller;
- }
-
- let frame = aStack.caller;
- let msg = "";
- // Get every frame in the callstack.
- while (frame) {
- if (frame.filename || frame.lineNumber || frame.name) {
- msg += frame.filename + " " + frame.lineNumber + " " + frame.name + "\n";
- }
- frame = frame.caller;
- }
- return msg;
-}
-
-export var Deprecated = {
- /**
- * Log a deprecation warning.
- *
- * @param string aText
- * Deprecation warning text.
- * @param string aUrl
- * A URL pointing to documentation describing deprecation
- * and the way to address it.
- * @param nsIStackFrame aStack
- * An optional callstack. If it is not provided a
- * snapshot of the current JavaScript callstack will be
- * logged.
- */
- warning(aText, aUrl, aStack) {
- if (!logWarnings) {
- return;
- }
-
- // If URL is not provided, report an error.
- if (!aUrl) {
- console.error(
- "Error in Deprecated.warning: warnings must " +
- "provide a URL documenting this deprecation."
- );
- return;
- }
-
- let textMessage =
- "DEPRECATION WARNING: " +
- aText +
- "\nYou may find more details about this deprecation at: " +
- aUrl +
- "\n" +
- // Append a callstack part to the deprecation message.
- stringifyCallstack(aStack);
-
- // Report deprecation warning.
- console.error(textMessage);
- },
-};
diff --git a/toolkit/modules/FindBarContent.sys.mjs b/toolkit/modules/FindBarContent.sys.mjs
index 8b34d93f9d..ef3a197d50 100644
--- a/toolkit/modules/FindBarContent.sys.mjs
+++ b/toolkit/modules/FindBarContent.sys.mjs
@@ -27,12 +27,9 @@ export class FindBarContent {
startQuickFind(event, autostart = false) {
if (!this.addedEventListener) {
this.addedEventListener = true;
- Services.els.addSystemEventListener(
- this.actor.document.defaultView,
- "mouseup",
- this,
- false
- );
+ this.actor.document.defaultView.addEventListener("mouseup", this, {
+ mozSystemGroup: true,
+ });
}
let mode = FIND_TYPEAHEAD;
diff --git a/toolkit/modules/FirstStartup.sys.mjs b/toolkit/modules/FirstStartup.sys.mjs
index b31e7ffa07..c09885abe9 100644
--- a/toolkit/modules/FirstStartup.sys.mjs
+++ b/toolkit/modules/FirstStartup.sys.mjs
@@ -36,8 +36,25 @@ export var FirstStartup = {
* 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() {
+ 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();
@@ -106,4 +123,11 @@ export var FirstStartup = {
get state() {
return this._state;
},
+
+ /**
+ * For testing only. This puts us back into the initial NOT_STARTED state.
+ */
+ resetForTesting() {
+ this._state = this.NOT_STARTED;
+ },
};
diff --git a/toolkit/modules/ObjectUtils.sys.mjs b/toolkit/modules/ObjectUtils.sys.mjs
index e0fbeead12..2c927477b7 100644
--- a/toolkit/modules/ObjectUtils.sys.mjs
+++ b/toolkit/modules/ObjectUtils.sys.mjs
@@ -142,6 +142,22 @@ function objEquiv(a, b) {
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)) {
diff --git a/toolkit/modules/ProcessType.sys.mjs b/toolkit/modules/ProcessType.sys.mjs
index 4c30068755..f8dcfb6fae 100644
--- a/toolkit/modules/ProcessType.sys.mjs
+++ b/toolkit/modules/ProcessType.sys.mjs
@@ -15,6 +15,17 @@ export const ProcessType = Object.freeze({
socket: "process-type-socket",
utility: "process-type-utility",
+ // 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",
diff --git a/toolkit/modules/RemotePageAccessManager.sys.mjs b/toolkit/modules/RemotePageAccessManager.sys.mjs
index d2ab2fb805..61c00880cb 100644
--- a/toolkit/modules/RemotePageAccessManager.sys.mjs
+++ b/toolkit/modules/RemotePageAccessManager.sys.mjs
@@ -89,7 +89,10 @@ export let RemotePageAccessManager = {
"OpenTRRPreferences",
],
RPMCheckAlternateHostAvailable: ["*"],
- RPMRecordTelemetryEvent: ["security.doh.neterror"],
+ RPMRecordTelemetryEvent: [
+ "security.doh.neterror",
+ "security.ui.tlserror",
+ ],
RPMAddMessageListener: ["*"],
RPMRemoveMessageListener: ["*"],
RPMGetFormatURLPref: [
diff --git a/toolkit/modules/Sqlite.sys.mjs b/toolkit/modules/Sqlite.sys.mjs
index 2c9de0f438..b1f48c28be 100644
--- a/toolkit/modules/Sqlite.sys.mjs
+++ b/toolkit/modules/Sqlite.sys.mjs
@@ -23,11 +23,14 @@ 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",
- Log: "resource://gre/modules/Log.sys.mjs",
-});
+ChromeUtils.defineESModuleGetters(
+ lazy,
+ {
+ AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs",
+ FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
+ },
+ { global: "contextual" }
+);
XPCOMUtils.defineLazyServiceGetter(
lazy,
@@ -307,6 +310,18 @@ function unregisterVacuumParticipant(connectionData) {
}
/**
+ * 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
@@ -325,12 +340,8 @@ function unregisterVacuumParticipant(connectionData) {
* dispatch its method calls here.
*/
function ConnectionData(connection, identifier, options = {}) {
- this._log = lazy.Log.repository.getLoggerWithMessagePrefix(
- "Sqlite.sys.mjs",
- `Connection ${identifier}: `
- );
- this._log.manageLevelFromPref("toolkit.sqlitejsm.loglevel");
- this._log.debug("Opened");
+ this._logger = createLoggerWithPrefix(`Connection ${identifier}`);
+ this._logger.debug("Opened");
this._dbConn = connection;
@@ -410,25 +421,27 @@ function ConnectionData(connection, identifier, options = {}) {
this._useIncrementalVacuum = !!options.incrementalVacuum;
if (this._useIncrementalVacuum) {
- this._log.debug("Set auto_vacuum INCREMENTAL");
+ this._logger.debug("Set auto_vacuum INCREMENTAL");
this.execute("PRAGMA auto_vacuum = 2").catch(ex => {
- this._log.error("Setting auto_vacuum to INCREMENTAL failed.");
+ this._logger.error("Setting auto_vacuum to INCREMENTAL failed.");
console.error(ex);
});
}
this._expectedPageSize = options.pageSize ?? 0;
if (this._expectedPageSize) {
- this._log.debug("Set page_size to " + this._expectedPageSize);
+ this._logger.debug("Set page_size to " + this._expectedPageSize);
this.execute("PRAGMA page_size = " + this._expectedPageSize).catch(ex => {
- this._log.error(`Setting page_size to ${this._expectedPageSize} failed.`);
+ this._logger.error(
+ `Setting page_size to ${this._expectedPageSize} failed.`
+ );
console.error(ex);
});
}
this._vacuumOnIdle = options.vacuumOnIdle;
if (this._vacuumOnIdle) {
- this._log.debug("Register as vacuum participant");
+ this._logger.debug("Register as vacuum participant");
this.QueryInterface = ChromeUtils.generateQI([
Ci.mozIStorageVacuumParticipant,
]);
@@ -470,12 +483,12 @@ ConnectionData.prototype = Object.freeze({
onBeginVacuum() {
let granted = !this.transactionInProgress;
- this._log.debug("Begin Vacuum - " + granted ? "granted" : "denied");
+ this._logger.debug("Begin Vacuum - " + granted ? "granted" : "denied");
return granted;
},
onEndVacuum(succeeded) {
- this._log.debug("End Vacuum - " + succeeded ? "success" : "failure");
+ this._logger.debug("End Vacuum - " + succeeded ? "success" : "failure");
},
/**
@@ -597,11 +610,11 @@ ConnectionData.prototype = Object.freeze({
return this._deferredClose.promise;
}
- this._log.debug("Request to close connection.");
+ this._logger.debug("Request to close connection.");
this._clearIdleShrinkTimer();
if (this._vacuumOnIdle) {
- this._log.debug("Unregister as vacuum participant");
+ this._logger.debug("Unregister as vacuum participant");
unregisterVacuumParticipant(this);
}
@@ -616,7 +629,7 @@ ConnectionData.prototype = Object.freeze({
clone(readOnly = false) {
this.ensureOpen();
- this._log.debug("Request to clone connection.");
+ this._logger.debug("Request to clone connection.");
let options = {
connection: this._dbConn,
@@ -632,7 +645,7 @@ ConnectionData.prototype = Object.freeze({
return this._operationsCounter++;
},
_finalize() {
- this._log.debug("Finalizing connection.");
+ this._logger.debug("Finalizing connection.");
// Cancel any pending statements.
for (let [, /* k */ statement] of this._pendingStatements) {
statement.cancel();
@@ -660,7 +673,7 @@ ConnectionData.prototype = Object.freeze({
// We must always close the connection at the Sqlite.sys.mjs-level, not
// necessarily at the mozStorage-level.
let markAsClosed = () => {
- this._log.debug("Closed");
+ this._logger.debug("Closed");
// Now that the connection is closed, no need to keep
// a blocker for Barriers.connections.
lazy.Barriers.connections.client.removeBlocker(
@@ -673,7 +686,7 @@ ConnectionData.prototype = Object.freeze({
this._dbConn = null;
markAsClosed();
} else {
- this._log.debug("Calling asyncClose().");
+ this._logger.debug("Calling asyncClose().");
try {
this._dbConn.asyncClose(markAsClosed);
} catch (ex) {
@@ -770,7 +783,7 @@ ConnectionData.prototype = Object.freeze({
.pop()
.match(/^([^@]*@).*\/([^\/:]+)[:0-9]*$/);
caller = caller[1] + caller[2];
- this._log.debug(`Transaction (type ${type}) requested by: ${caller}`);
+ this._logger.debug(`Transaction (type ${type}) requested by: ${caller}`);
if (type == OpenedConnection.prototype.TRANSACTION_DEFAULT) {
type = this.defaultTransactionType;
@@ -794,7 +807,7 @@ ConnectionData.prototype = Object.freeze({
// At this point we should never have an in progress transaction, since
// they are enqueued.
if (this._initiatedTransaction) {
- this._log.error(
+ this._logger.error(
"Unexpected transaction in progress when trying to start a new one."
);
}
@@ -802,7 +815,7 @@ ConnectionData.prototype = Object.freeze({
// We catch errors in statement execution to detect nested transactions.
try {
await this.execute("BEGIN " + type + " TRANSACTION");
- this._log.debug(`Begin transaction`);
+ this._logger.debug(`Begin transaction`);
this._initiatedTransaction = true;
} catch (ex) {
// Unfortunately, if we are wrapping an existing connection, a
@@ -811,12 +824,12 @@ ConnectionData.prototype = Object.freeze({
// The best we can do is proceed without a transaction and hope
// things won't break.
if (wrappedConnections.has(this._identifier)) {
- this._log.warn(
+ this._logger.warn(
"A new transaction could not be started cause the wrapped connection had one in progress",
ex
);
} else {
- this._log.warn(
+ this._logger.warn(
"A transaction was already in progress, likely a nested transaction",
ex
);
@@ -831,7 +844,7 @@ ConnectionData.prototype = Object.freeze({
// 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._log.warn(
+ this._logger.warn(
"Connection closed while performing a transaction",
ex
);
@@ -845,12 +858,12 @@ ConnectionData.prototype = Object.freeze({
caller_module,
1
);
- this._log.error(
+ this._logger.error(
`The transaction requested by ${caller} timed out. Rolling back`,
ex
);
} else {
- this._log.error(
+ this._logger.error(
`Error during transaction requested by ${caller}. Rolling back`,
ex
);
@@ -860,9 +873,9 @@ ConnectionData.prototype = Object.freeze({
try {
await this.execute("ROLLBACK TRANSACTION");
this._initiatedTransaction = false;
- this._log.debug(`Roll back transaction`);
+ this._logger.debug(`Roll back transaction`);
} catch (inner) {
- this._log.error("Could not roll back transaction", inner);
+ this._logger.error("Could not roll back transaction", inner);
}
}
}
@@ -872,7 +885,7 @@ ConnectionData.prototype = Object.freeze({
// See comment above about connection being closed during transaction.
if (this._closeRequested) {
- this._log.warn(
+ this._logger.warn(
"Connection closed before committing the transaction."
);
throw new Error(
@@ -884,9 +897,9 @@ ConnectionData.prototype = Object.freeze({
if (this._initiatedTransaction) {
try {
await this.execute("COMMIT TRANSACTION");
- this._log.debug(`Commit transaction`);
+ this._logger.debug(`Commit transaction`);
} catch (ex) {
- this._log.warn("Error committing transaction", ex);
+ this._logger.warn("Error committing transaction", ex);
throw ex;
}
}
@@ -902,7 +915,7 @@ ConnectionData.prototype = Object.freeze({
// Atomically update the queue before anyone else has a chance to enqueue
// further transactions.
this._transactionQueue = promise.catch(ex => {
- this._log.error(ex);
+ this._logger.error(ex);
});
// Make sure that we do not shutdown the connection during a transaction.
@@ -914,7 +927,7 @@ ConnectionData.prototype = Object.freeze({
},
shrinkMemory() {
- this._log.debug("Shrinking memory usage.");
+ this._logger.debug("Shrinking memory usage.");
return this.execute("PRAGMA shrink_memory").finally(() => {
this._clearIdleShrinkTimer();
});
@@ -927,12 +940,12 @@ ConnectionData.prototype = Object.freeze({
statement.finalize();
}
this._cachedStatements.clear();
- this._log.debug("Discarded " + count + " cached statements.");
+ this._logger.debug("Discarded " + count + " cached statements.");
return count;
},
interrupt() {
- this._log.debug("Trying to interrupt.");
+ this._logger.debug("Trying to interrupt.");
this.ensureOpen();
this._dbConn.interrupt();
},
@@ -1018,15 +1031,15 @@ ConnectionData.prototype = Object.freeze({
// Don't incur overhead for serializing params unless the messages go
// somewhere.
- if (this._log.level <= lazy.Log.Level.Trace) {
+ if (this._logger.shouldLog("Trace")) {
let msg = "Stmt #" + index + " " + sql;
if (params) {
msg += " - " + JSON.stringify(params);
}
- this._log.trace(msg);
+ this._logger.trace(msg);
} else {
- this._log.debug("Stmt #" + index + " starting");
+ this._logger.debug("Stmt #" + index + " starting");
}
let self = this;
@@ -1052,20 +1065,20 @@ ConnectionData.prototype = Object.freeze({
pending.cancel();
});
} catch (e) {
- self._log.warn("Exception when calling onRow callback", e);
+ self._logger.warn("Exception when calling onRow callback", e);
}
}
},
handleError(error) {
- self._log.warn(
+ self._logger.warn(
"Error when executing SQL (" + error.result + "): " + error.message
);
errors.push(error);
},
handleCompletion(reason) {
- self._log.debug("Stmt #" + index + " finished.");
+ self._logger.debug("Stmt #" + index + " finished.");
self._pendingStatements.delete(index);
switch (reason) {
@@ -1256,11 +1269,7 @@ ConnectionData.prototype = Object.freeze({
* @return Promise<OpenedConnection>
*/
function openConnection(options) {
- let log = lazy.Log.repository.getLoggerWithMessagePrefix(
- "Sqlite.sys.mjs",
- `ConnectionOpener: `
- );
- log.manageLevelFromPref("toolkit.sqlitejsm.loglevel");
+ let logger = createLoggerWithPrefix("ConnectionOpener");
if (!options.path) {
throw new Error("path not specified in connection options.");
@@ -1350,7 +1359,7 @@ function openConnection(options) {
let identifier = getIdentifierByFileName(PathUtils.filename(path));
- log.debug("Opening database: " + path + " (" + identifier + ")");
+ logger.debug("Opening database: " + path + " (" + identifier + ")");
return new Promise((resolve, reject) => {
let dbOpenOptions = Ci.mozIStorageService.OPEN_DEFAULT;
@@ -1373,7 +1382,7 @@ function openConnection(options) {
dbConnectionOptions,
async (status, connection) => {
if (!connection) {
- log.error(`Could not open connection to ${path}: ${status}`);
+ logger.error(`Could not open connection to ${path}: ${status}`);
let error = new Components.Exception(
`Could not open connection to ${path}: ${status}`,
status
@@ -1381,7 +1390,7 @@ function openConnection(options) {
reject(error);
return;
}
- log.debug("Connection opened");
+ logger.debug("Connection opened");
if (options.testDelayedOpenPromise) {
await options.testDelayedOpenPromise;
@@ -1407,7 +1416,7 @@ function openConnection(options) {
)
);
} catch (ex) {
- log.error("Could not open database", ex);
+ logger.error("Could not open database", ex);
connection.asyncClose();
reject(ex);
}
@@ -1446,11 +1455,7 @@ function openConnection(options) {
* @return Promise<OpenedConnection>
*/
function cloneStorageConnection(options) {
- let log = lazy.Log.repository.getLoggerWithMessagePrefix(
- "Sqlite.sys.mjs",
- `ConnectionCloner: `
- );
- log.manageLevelFromPref("toolkit.sqlitejsm.loglevel");
+ let logger = createLoggerWithPrefix("ConnectionCloner");
let source = options && options.connection;
if (!source) {
@@ -1484,16 +1489,16 @@ function cloneStorageConnection(options) {
let path = source.databaseFile.path;
let identifier = getIdentifierByFileName(PathUtils.filename(path));
- log.debug("Cloning database: " + path + " (" + identifier + ")");
+ logger.debug("Cloning database: " + path + " (" + identifier + ")");
return new Promise((resolve, reject) => {
source.asyncClone(!!options.readOnly, (status, connection) => {
if (!connection) {
- log.error("Could not clone connection: " + status);
+ logger.error("Could not clone connection: " + status);
reject(new Error("Could not clone connection: " + status));
return;
}
- log.debug("Connection cloned");
+ logger.debug("Connection cloned");
if (isClosed()) {
connection.QueryInterface(Ci.mozIStorageAsyncConnection).asyncClose();
@@ -1510,7 +1515,7 @@ function cloneStorageConnection(options) {
let conn = connection.QueryInterface(Ci.mozIStorageAsyncConnection);
resolve(new OpenedConnection(conn, identifier, openedOptions));
} catch (ex) {
- log.error("Could not clone database", ex);
+ logger.error("Could not clone database", ex);
connection.asyncClose();
reject(ex);
}
@@ -1537,11 +1542,7 @@ function cloneStorageConnection(options) {
* @return Promise<OpenedConnection>
*/
function wrapStorageConnection(options) {
- let log = lazy.Log.repository.getLoggerWithMessagePrefix(
- "Sqlite.sys.mjs",
- `ConnectionCloner: `
- );
- log.manageLevelFromPref("toolkit.sqlitejsm.loglevel");
+ let logger = createLoggerWithPrefix("ConnectionWrapper");
let connection = options && options.connection;
if (!connection || !(connection instanceof Ci.mozIStorageAsyncConnection)) {
@@ -1557,7 +1558,7 @@ function wrapStorageConnection(options) {
let identifier = getIdentifierByFileName(connection.databaseFile.leafName);
- log.debug("Wrapping database: " + identifier);
+ logger.debug("Wrapping database: " + identifier);
return new Promise(resolve => {
try {
let conn = connection.QueryInterface(Ci.mozIStorageAsyncConnection);
@@ -1567,7 +1568,7 @@ function wrapStorageConnection(options) {
wrappedConnections.add(identifier);
resolve(wrapper);
} catch (ex) {
- log.error("Could not wrap database", ex);
+ logger.error("Could not wrap database", ex);
throw ex;
}
});
@@ -1687,13 +1688,24 @@ OpenedConnection.prototype = Object.freeze({
* Returns the maximum number of bound parameters for statements executed
* on this connection.
*
- * @type {number}
+ * @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.
diff --git a/toolkit/modules/Troubleshoot.sys.mjs b/toolkit/modules/Troubleshoot.sys.mjs
index 53259bcb67..43b8c1ca51 100644
--- a/toolkit/modules/Troubleshoot.sys.mjs
+++ b/toolkit/modules/Troubleshoot.sys.mjs
@@ -52,6 +52,7 @@ const PREFS_FOR_DISPLAY = [
"browser.startup.homepage",
"browser.startup.page",
"browser.tabs.",
+ "browser.toolbars.",
"browser.urlbar.",
"browser.zoom.",
"doh-rollout.",
@@ -412,6 +413,11 @@ var dataProviders = {
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) {
@@ -425,6 +431,20 @@ var dataProviders = {
}
}
+ 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) {
diff --git a/toolkit/modules/docs/AsyncShutdown.rst b/toolkit/modules/docs/AsyncShutdown.rst
index 8d9e0d4388..4b0c5ef0f9 100644
--- a/toolkit/modules/docs/AsyncShutdown.rst
+++ b/toolkit/modules/docs/AsyncShutdown.rst
@@ -58,8 +58,8 @@ The following snippet presents an example of a client of FooService that has a s
// Some client of FooService called FooClient
- const { FooService } = ChromeUtils.import(
- "resource://gre/modules/FooService.jsm"
+ const { FooService } = ChromeUtils.importESModule(
+ "resource://gre/modules/FooService.sys.mjs"
);
// FooService.shutdown is the `client` capability of a `Barrier`.
@@ -117,8 +117,8 @@ The following snippet presents FooClient2, a more sophisticated client of FooSer
// Some client of FooService called FooClient2
- const { FooService } = ChromeUtils.import(
- "resource://gre/modules/FooService.jsm"
+ const { FooService } = ChromeUtils.importESModule(
+ "resource://gre/modules/FooService.sys.mjs"
);
FooService.shutdown.addBlocker(
diff --git a/toolkit/modules/metrics.yaml b/toolkit/modules/metrics.yaml
index a60fa077f5..9c7cedfe14 100644
--- a/toolkit/modules/metrics.yaml
+++ b/toolkit/modules/metrics.yaml
@@ -83,3 +83,23 @@ first_startup:
expires: never
send_in_pings:
- first-startup
+
+ new_profile:
+ type: boolean
+ description: >
+ True if FirstStartup was initted after a new profile was just created. If
+ false, this means that FirstStartup was initted with a pre-existing
+ profile, which is a no-op.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1877545
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1749345
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1877545
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - rhelmer@mozilla.com
+ - mconley@mozilla.com
+ expires: never
+ send_in_pings:
+ - first-startup
diff --git a/toolkit/modules/moz.build b/toolkit/modules/moz.build
index 60e3ae20f9..07924cedba 100644
--- a/toolkit/modules/moz.build
+++ b/toolkit/modules/moz.build
@@ -133,6 +133,7 @@ with Files("WindowsRegistry.sys.mjs"):
XPCSHELL_TESTS_MANIFESTS += ["tests/xpcshell/xpcshell.toml"]
BROWSER_CHROME_MANIFESTS += ["tests/browser/browser.toml"]
MOCHITEST_CHROME_MANIFESTS += ["tests/chrome/chrome.toml"]
+MARIONETTE_MANIFESTS += ["tests/marionette/manifest.toml"]
TESTING_JS_MODULES += [
"tests/modules/MockDocument.sys.mjs",
@@ -163,7 +164,6 @@ EXTRA_JS_MODULES += [
"CreditCard.sys.mjs",
"DateTimePickerPanel.sys.mjs",
"DeferredTask.sys.mjs",
- "Deprecated.sys.mjs",
"E10SUtils.sys.mjs",
"EventEmitter.sys.mjs",
"FileUtils.sys.mjs",
diff --git a/toolkit/modules/subprocess/.eslintrc.js b/toolkit/modules/subprocess/.eslintrc.js
index 7640781589..ccec3c9d1b 100644
--- a/toolkit/modules/subprocess/.eslintrc.js
+++ b/toolkit/modules/subprocess/.eslintrc.js
@@ -6,8 +6,4 @@
module.exports = {
extends: "../../components/extensions/.eslintrc.js",
-
- rules: {
- "no-console": "off",
- },
};
diff --git a/toolkit/modules/tests/browser/browser.toml b/toolkit/modules/tests/browser/browser.toml
index 932b06d19e..b3c2b9cfda 100644
--- a/toolkit/modules/tests/browser/browser.toml
+++ b/toolkit/modules/tests/browser/browser.toml
@@ -13,8 +13,6 @@ support-files = [
["browser_CreditCard.js"]
skip-if = ["apple_silicon"] # Disabled due to bleedover with other tests when run in regular suites; passes in "failures" jobs
-["browser_Deprecated.js"]
-
["browser_Finder.js"]
["browser_FinderHighlighter.js"]
diff --git a/toolkit/modules/tests/browser/browser_Deprecated.js b/toolkit/modules/tests/browser/browser_Deprecated.js
deleted file mode 100644
index b718ba37e7..0000000000
--- a/toolkit/modules/tests/browser/browser_Deprecated.js
+++ /dev/null
@@ -1,140 +0,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/. */
-
-const PREF_DEPRECATION_WARNINGS = "devtools.errorconsole.deprecation_warnings";
-
-// Using this named functions to test deprecation and the properly logged
-// callstacks.
-function basicDeprecatedFunction() {
- Deprecated.warning("this method is deprecated.", "https://example.com");
- return true;
-}
-
-function deprecationFunctionBogusCallstack() {
- Deprecated.warning("this method is deprecated.", "https://example.com", {
- caller: {},
- });
- return true;
-}
-
-function deprecationFunctionCustomCallstack() {
- // Get the nsIStackFrame that will contain the name of this function.
- function getStack() {
- return Components.stack;
- }
- Deprecated.warning(
- "this method is deprecated.",
- "https://example.com",
- getStack()
- );
- return true;
-}
-
-var tests = [
- // Test deprecation warning without passing the callstack.
- {
- deprecatedFunction: basicDeprecatedFunction,
- expectedObservation(aMessage) {
- testAMessage(aMessage);
- Assert.greater(
- aMessage.indexOf("basicDeprecatedFunction"),
- 0,
- "Callstack is correctly logged."
- );
- },
- },
- // Test a reported error when URL to documentation is not passed.
- {
- deprecatedFunction() {
- Deprecated.warning("this method is deprecated.");
- return true;
- },
- expectedObservation(aMessage) {
- Assert.greater(
- aMessage.indexOf("must provide a URL"),
- 0,
- "Deprecation warning logged an empty URL argument."
- );
- },
- },
- // Test deprecation with a bogus callstack passed as an argument (it will be
- // replaced with the current call stack).
- {
- deprecatedFunction: deprecationFunctionBogusCallstack,
- expectedObservation(aMessage) {
- testAMessage(aMessage);
- Assert.greater(
- aMessage.indexOf("deprecationFunctionBogusCallstack"),
- 0,
- "Callstack is correctly logged."
- );
- },
- },
- // Test deprecation with a valid custom callstack passed as an argument.
- {
- deprecatedFunction: deprecationFunctionCustomCallstack,
- expectedObservation(aMessage) {
- testAMessage(aMessage);
- Assert.greater(
- aMessage.indexOf("deprecationFunctionCustomCallstack"),
- 0,
- "Callstack is correctly logged."
- );
- },
- // Set pref to true.
- logWarnings: true,
- },
-];
-
-// Test Console Message attributes.
-function testAMessage(aMessage) {
- Assert.strictEqual(
- aMessage.indexOf("DEPRECATION WARNING: this method is deprecated."),
- 0,
- "Deprecation is correctly logged."
- );
- Assert.greater(
- aMessage.indexOf("https://example.com"),
- 0,
- "URL is correctly logged."
- );
-}
-
-add_task(async function test_setup() {
- Services.prefs.setBoolPref(PREF_DEPRECATION_WARNINGS, true);
-
- // Check if Deprecated is loaded.
- ok(Deprecated, "Deprecated object exists");
-});
-
-add_task(async function test_pref_enabled() {
- for (let [idx, test] of tests.entries()) {
- info("Running test #" + idx);
-
- let promiseObserved = TestUtils.consoleMessageObserved(subject => {
- let msg = subject.wrappedJSObject.arguments?.[0];
- return (
- msg.includes("DEPRECATION WARNING: ") ||
- msg.includes("must provide a URL")
- );
- });
-
- test.deprecatedFunction();
-
- let msg = await promiseObserved;
-
- test.expectedObservation(msg.wrappedJSObject.arguments?.[0]);
- }
-});
-
-add_task(async function test_pref_disabled() {
- // Deprecation warnings will be logged only when the preference is set.
- Services.prefs.setBoolPref(PREF_DEPRECATION_WARNINGS, false);
-
- let endFn = TestUtils.listenForConsoleMessages();
- basicDeprecatedFunction();
-
- let messages = await endFn();
- Assert.equal(messages.length, 0, "Should not have received any messages");
-});
diff --git a/toolkit/modules/tests/marionette/manifest.toml b/toolkit/modules/tests/marionette/manifest.toml
new file mode 100644
index 0000000000..08a103c289
--- /dev/null
+++ b/toolkit/modules/tests/marionette/manifest.toml
@@ -0,0 +1,4 @@
+[DEFAULT]
+run-if = ["buildapp == 'browser'"]
+
+["test_first_startup.py"]
diff --git a/toolkit/modules/tests/marionette/test_first_startup.py b/toolkit/modules/tests/marionette/test_first_startup.py
new file mode 100644
index 0000000000..6d8bbce02d
--- /dev/null
+++ b/toolkit/modules/tests/marionette/test_first_startup.py
@@ -0,0 +1,54 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from marionette_harness import MarionetteTestCase
+
+
+class TestFirstStartup(MarionetteTestCase):
+ def setUp(self):
+ MarionetteTestCase.setUp(self)
+ self.marionette.quit()
+ self.marionette.instance.prefs = {
+ "browser.startup.homepage_override.mstone": ""
+ }
+ self.marionette.instance.app_args = ["-first-startup"]
+
+ def test_new_profile(self):
+ """Test launching with --first-startup when a new profile was created.
+
+ Launches the browser with --first-startup on a freshly created profile
+ and then ensures that FirstStartup.init ran successfully.
+ """
+
+ self.marionette.instance.switch_profile()
+ self.marionette.start_session()
+ self.marionette.set_context("chrome")
+ firstStartupInittedSuccessfully = self.marionette.execute_script(
+ """
+ const { FirstStartup } = ChromeUtils.importESModule("resource://gre/modules/FirstStartup.sys.mjs");
+ return FirstStartup.state == FirstStartup.SUCCESS
+ """
+ )
+
+ self.assertTrue(
+ firstStartupInittedSuccessfully, "FirstStartup initted successfully"
+ )
+
+ def test_existing_profile(self):
+ """Test launching with --first-startup with a pre-existing profile.
+
+ Launches the browser with --first-startup on a profile that has been
+ run before. Ensures that FirstStartup.init was never run.
+ """
+
+ self.marionette.start_session()
+ self.marionette.set_context("chrome")
+ firstStartupSkipped = self.marionette.execute_script(
+ """
+ const { FirstStartup } = ChromeUtils.importESModule("resource://gre/modules/FirstStartup.sys.mjs");
+ return FirstStartup.state == FirstStartup.NOT_STARTED
+ """
+ )
+
+ self.assertTrue(firstStartupSkipped, "FirstStartup init skipped")
diff --git a/toolkit/modules/tests/xpcshell/test_GMPInstallManager.js b/toolkit/modules/tests/xpcshell/test_GMPInstallManager.js
index 8f725fc78d..a0d12b8a6a 100644
--- a/toolkit/modules/tests/xpcshell/test_GMPInstallManager.js
+++ b/toolkit/modules/tests/xpcshell/test_GMPInstallManager.js
@@ -1257,7 +1257,7 @@ add_task(async function test_GMPExtractor_paths() {
if (AppConstants.platform == "macosx") {
await Assert.rejects(
IOUtils.getMacXAttr(extractedFile, "com.apple.quarantine"),
- /NotFoundError: The file `.+' does not have an extended attribute `com.apple.quarantine'/,
+ /NotFoundError: Could not get extended attribute `com.apple.quarantine' from `.+': the file does not have the attribute/,
"The 'com.apple.quarantine' attribute should not be present"
);
}
diff --git a/toolkit/modules/tests/xpcshell/test_Services.js b/toolkit/modules/tests/xpcshell/test_Services.js
index 55c762fdad..8169a23bbd 100644
--- a/toolkit/modules/tests/xpcshell/test_Services.js
+++ b/toolkit/modules/tests/xpcshell/test_Services.js
@@ -28,7 +28,6 @@ function run_test() {
checkService("cookies", Ci.nsICookieManager);
checkService("dirsvc", Ci.nsIDirectoryService);
checkService("dirsvc", Ci.nsIProperties);
- checkService("DOMRequest", Ci.nsIDOMRequestService);
checkService("domStorageManager", Ci.nsIDOMStorageManager);
checkService("droppedLinkHandler", Ci.nsIDroppedLinkHandler);
checkService("eTLD", Ci.nsIEffectiveTLDService);
diff --git a/toolkit/modules/tests/xpcshell/test_firstStartup.js b/toolkit/modules/tests/xpcshell/test_firstStartup.js
index 02f126d66f..445d3a0c72 100644
--- a/toolkit/modules/tests/xpcshell/test_firstStartup.js
+++ b/toolkit/modules/tests/xpcshell/test_firstStartup.js
@@ -24,11 +24,13 @@ add_task(async function test_success() {
updateAppInfo();
let submissionPromise;
+ FirstStartup.resetForTesting();
if (AppConstants.MOZ_NORMANDY || AppConstants.MOZ_UPDATE_AGENT) {
submissionPromise = new Promise(resolve => {
GleanPings.firstStartup.testBeforeNextSubmit(() => {
Assert.equal(FirstStartup.state, FirstStartup.SUCCESS);
+ Assert.ok(Glean.firstStartup.newProfile.testGetValue());
Assert.equal(
Glean.firstStartup.statusCode.testGetValue(),
FirstStartup.SUCCESS
@@ -49,6 +51,7 @@ add_task(async function test_success() {
submissionPromise = new Promise(resolve => {
GleanPings.firstStartup.testBeforeNextSubmit(() => {
Assert.equal(FirstStartup.state, FirstStartup.UNSUPPORTED);
+ Assert.ok(Glean.firstStartup.newProfile.testGetValue());
Assert.equal(
Glean.firstStartup.statusCode.testGetValue(),
FirstStartup.UNSUPPORTED
@@ -58,13 +61,14 @@ add_task(async function test_success() {
});
}
- FirstStartup.init();
+ FirstStartup.init(true /* newProfile */);
await submissionPromise;
});
add_task(async function test_timeout() {
updateAppInfo();
Services.prefs.setIntPref(PREF_TIMEOUT, 0);
+ FirstStartup.resetForTesting();
let submissionPromise;
@@ -73,6 +77,7 @@ add_task(async function test_timeout() {
GleanPings.firstStartup.testBeforeNextSubmit(() => {
Assert.equal(FirstStartup.state, FirstStartup.TIMED_OUT);
Assert.ok(Glean.firstStartup.elapsed.testGetValue() > 0);
+ Assert.ok(Glean.firstStartup.newProfile.testGetValue());
if (AppConstants.MOZ_NORMANDY) {
Assert.ok(Glean.firstStartup.normandyInitTime.testGetValue() > 0);
@@ -90,11 +95,27 @@ add_task(async function test_timeout() {
GleanPings.firstStartup.testBeforeNextSubmit(() => {
Assert.equal(FirstStartup.state, FirstStartup.UNSUPPORTED);
Assert.equal(Glean.firstStartup.elapsed.testGetValue(), 0);
+ Assert.ok(Glean.firstStartup.newProfile.testGetValue());
resolve();
});
});
}
- FirstStartup.init();
+ FirstStartup.init(true /* newProfile */);
+ await submissionPromise;
+});
+
+add_task(async function test_existing_profile() {
+ FirstStartup.resetForTesting();
+
+ let submissionPromise = new Promise(resolve => {
+ GleanPings.firstStartup.testBeforeNextSubmit(() => {
+ Assert.equal(FirstStartup.state, FirstStartup.NOT_STARTED);
+ Assert.ok(!Glean.firstStartup.newProfile.testGetValue());
+ resolve();
+ });
+ });
+
+ FirstStartup.init(false /* newProfile */);
await submissionPromise;
});
diff --git a/toolkit/modules/tests/xpcshell/test_sqlite.js b/toolkit/modules/tests/xpcshell/test_sqlite.js
index 2c3ede46d6..e0a79e137d 100644
--- a/toolkit/modules/tests/xpcshell/test_sqlite.js
+++ b/toolkit/modules/tests/xpcshell/test_sqlite.js
@@ -118,9 +118,8 @@ add_task(async function test_open_normal_error() {
// Ensure that our database doesn't already exist.
let path = PathUtils.join(PROFILE_DIR, "corrupt.sqlite");
- await Assert.rejects(
- IOUtils.stat(path),
- /Could not stat file\(.*\) because it does not exist/,
+ Assert.ok(
+ !(await IOUtils.exists(path)),
"Database file should not exist yet"
);
diff --git a/toolkit/modules/third_party/fathom/README b/toolkit/modules/third_party/fathom/README
index 5f7ba3b4cb..7c2f0980fa 100644
--- a/toolkit/modules/third_party/fathom/README
+++ b/toolkit/modules/third_party/fathom/README
@@ -14,4 +14,4 @@ In order to regenerate this file, do the following:
$ make bundleESModule
$ export MOZ_FATHOM="../../mozilla-central/toolkit/modules/third_party/fathom"
- $ cat $MOZ_FATHOM/fx-header dist/fathom.js > $MOZ_FATHOM/fathom.jsm
+ $ cat $MOZ_FATHOM/fx-header dist/fathom.js > $MOZ_FATHOM/fathom.mjs
diff --git a/toolkit/modules/third_party/fathom/fathom.mjs b/toolkit/modules/third_party/fathom/fathom.mjs
index c1d984a9e3..be60013261 100644
--- a/toolkit/modules/third_party/fathom/fathom.mjs
+++ b/toolkit/modules/third_party/fathom/fathom.mjs
@@ -1,5 +1,5 @@
/*
-DO NOT TOUCH fathom.jsm DIRECTLY. See the README for instructions.
+DO NOT TOUCH fathom.mjs DIRECTLY. See the README for instructions.
*/
/* This Source Code Form is subject to the terms of the Mozilla Public
diff --git a/toolkit/moz.configure b/toolkit/moz.configure
index b616109b1f..693492a4bd 100644
--- a/toolkit/moz.configure
+++ b/toolkit/moz.configure
@@ -60,7 +60,7 @@ set_config("MOZ_CONFIGURE_OPTIONS", all_configure_options)
@depends(target)
def fold_libs(target):
- return target.os in ("WINNT", "OSX", "Android")
+ return target.os in ("WINNT", "OSX", "iOS", "Android")
set_config("MOZ_FOLD_LIBS", fold_libs)
@@ -87,16 +87,21 @@ set_config("MOZ_JPROF", jprof)
set_define("MOZ_JPROF", jprof)
imply_option("--enable-profiling", jprof)
+option("--disable-gecko-profiler", help="Disable the Gecko profiler")
+
+
+@depends("--disable-gecko-profiler", target)
+def gecko_profiler(enable_gecko_profiler, target):
+ if not enable_gecko_profiler:
+ return False
-@depends(target)
-def gecko_profiler(target):
if target.os == "Android":
return target.cpu in ("aarch64", "arm", "x86", "x86_64")
elif target.kernel == "Linux":
return target.cpu in ("aarch64", "arm", "x86", "x86_64", "mips64")
elif target.kernel == "FreeBSD":
return target.cpu in ("aarch64", "x86_64")
- return target.os in ("OSX", "WINNT")
+ return target.kernel in ("Darwin", "WINNT")
@depends(gecko_profiler)
@@ -202,6 +207,8 @@ def audio_backends_default(target):
return ("sndio",)
elif target.os == "OSX":
return ("audiounit",)
+ elif target.os == "iOS":
+ return None
elif target.os == "NetBSD":
return ("sunaudio",)
elif target.os == "SunOS":
@@ -276,7 +283,7 @@ def imply_opensl(values, target):
@depends("--enable-audio-backends", target)
def imply_oss(values, target):
if any("oss" in value for value in values) and (
- target.os == "Android" or target.os == "OSX" or target.os == "WINNT"
+ target.os in ("Android", "OSX", "iOS", "WINNT")
):
die("Cannot enable OSS on %s", target.os)
return any("oss" in value for value in values) or None
@@ -285,7 +292,7 @@ def imply_oss(values, target):
@depends("--enable-audio-backends", target)
def imply_pulseaudio(values, target):
if any("pulseaudio" in value for value in values) and (
- target.os == "Android" or target.os == "OSX" or target.os == "WINNT"
+ target.os in ("Android", "OSX", "iOS", "WINNT")
):
die("Cannot enable PulseAudio on %s", target.os)
return any("pulseaudio" in value for value in values) or None
@@ -294,7 +301,7 @@ def imply_pulseaudio(values, target):
@depends("--enable-audio-backends", target)
def imply_sndio(values, target):
if any("sndio" in value for value in values) and (
- target.os == "Android" or target.os == "OSX" or target.os == "WINNT"
+ target.os in ("Android", "OSX", "iOS", "WINNT")
):
die("Cannot enable sndio on %s", target.os)
return any("sndio" in value for value in values) or None
@@ -430,6 +437,8 @@ def toolkit_choices(target):
return ("cairo-windows",)
elif target.os == "OSX":
return ("cairo-cocoa",)
+ elif target.os == "iOS":
+ return ("cairo-uikit",)
elif target.os == "Android":
return ("cairo-android",)
else:
@@ -979,6 +988,8 @@ project_flag(
def check_places_and_android_history(places, android_history):
if places and android_history:
die("Cannot use MOZ_ANDROID_HISTORY alongside MOZ_PLACES.")
+ if not places and not android_history:
+ die("One of MOZ_ANDROID_HISTORY or MOZ_PLACES must be set.")
option(
@@ -1067,9 +1078,19 @@ def app_system_headers(value):
set_config("MOZ_APP_SYSTEM_HEADERS", app_system_headers)
set_define("MOZ_APP_SYSTEM_HEADERS", app_system_headers)
+
# Printing
# ==============================================================
-option("--disable-printing", help="Disable printing support")
+@depends(target)
+def printing_default(target):
+ return target.os != "iOS"
+
+
+option(
+ "--disable-printing",
+ default=printing_default,
+ help="{Enable|Disable} printing support",
+)
@depends("--disable-printing")
@@ -2377,89 +2398,77 @@ with only_when(compile_environment):
# FFmpeg's ffvpx configuration
# ==============================================================
-with only_when(compile_environment):
-
- @depends(target)
- def libav_fft(target):
- if target.os == "Android" and target.cpu != "arm":
- return True
- return target.kernel in ("WINNT", "Darwin") or target.cpu == "x86_64"
- set_config("MOZ_LIBAV_FFT", depends(when=libav_fft)(lambda: True))
- set_define("MOZ_LIBAV_FFT", depends(when=libav_fft)(lambda: True))
+@depends(target)
+def ffvpx(target):
+ use_nasm = True
+ audio_only = False
+ flags = []
+
+ # This enables audio and video codecs paths on Windows x86 and x86_64,
+ # macOS (all arch), and Linux x86_64. On other arch / OS combinations,
+ # only audio codecs are enabled.
+ if target.kernel == "WINNT":
+ if target.cpu == "x86":
+ # 32-bit windows need to prefix symbols with an underscore.
+ flags = ["-DPIC", "-DWIN32", "-DPREFIX", "-Pconfig_win32.asm"]
+ elif target.cpu == "x86_64":
+ flags = [
+ "-D__x86_64__",
+ "-DPIC",
+ "-DWIN64",
+ "-DMSVC",
+ "-Pconfig_win64.asm",
+ ]
+ elif target.cpu == "aarch64":
+ flags = ["-DPIC", "-DWIN64"]
+ use_nasm = False
+ elif target.kernel == "Darwin":
+ # 32/64-bit macosx assemblers need to prefix symbols with an
+ # underscore.
+ flags = ["-DPIC", "-DMACHO", "-DPREFIX"]
+ if target.cpu == "x86_64":
+ flags += [
+ "-D__x86_64__",
+ "-Pconfig_darwin64.asm",
+ ]
+ elif target.cpu == "aarch64":
+ use_nasm = False
+ elif target.cpu == "x86_64":
+ flags = ["-D__x86_64__", "-DPIC", "-DELF", "-Pconfig_unix64.asm"]
+ else:
+ audio_only = True
-# Artifact builds need MOZ_FFVPX defined as if compilation happened.
-with only_when(compile_environment | artifact_builds):
+ if audio_only:
+ use_nasm = False
- @depends(target)
- def ffvpx(target):
- enable = use_nasm = True
- flac_only = False
- flags = []
+ return namespace(
+ use_nasm=use_nasm,
+ audio_only=audio_only,
+ flags=flags,
+ )
- if target.kernel == "WINNT":
- if target.cpu == "x86":
- # 32-bit windows need to prefix symbols with an underscore.
- flags = ["-DPIC", "-DWIN32", "-DPREFIX", "-Pconfig_win32.asm"]
- elif target.cpu == "x86_64":
- flags = [
- "-D__x86_64__",
- "-DPIC",
- "-DWIN64",
- "-DMSVC",
- "-Pconfig_win64.asm",
- ]
- elif target.cpu == "aarch64":
- flags = ["-DPIC", "-DWIN64"]
- use_nasm = False
- elif target.kernel == "Darwin":
- # 32/64-bit macosx assemblers need to prefix symbols with an
- # underscore.
- flags = ["-DPIC", "-DMACHO", "-DPREFIX"]
- if target.cpu == "x86_64":
- flags += [
- "-D__x86_64__",
- "-Pconfig_darwin64.asm",
- ]
- elif target.cpu == "aarch64":
- use_nasm = False
- elif target.cpu == "x86_64":
- flags = ["-D__x86_64__", "-DPIC", "-DELF", "-Pconfig_unix64.asm"]
- elif target.cpu in ("x86", "arm", "aarch64"):
- flac_only = True
- else:
- enable = False
- if flac_only or not enable:
- use_nasm = False
+@depends(when=ffvpx.use_nasm)
+def ffvpx_nasm():
+ # nasm 2.10 for AVX-2 support.
+ return namespace(version="2.10", what="FFVPX")
- return namespace(
- enable=enable,
- use_nasm=use_nasm,
- flac_only=flac_only,
- flags=flags,
- )
- @depends(when=ffvpx.use_nasm)
- def ffvpx_nasm():
- # nasm 2.10 for AVX-2 support.
- return namespace(version="2.10", what="FFVPX")
+# ffvpx_nasm can't indirectly depend on vpx_as_flags, because it depends
+# on a compiler test, so we have to do a little bit of dance here.
+@depends(ffvpx, vpx_as_flags, target)
+def ffvpx(ffvpx, vpx_as_flags, target):
+ if ffvpx and vpx_as_flags and target.cpu in ("arm", "aarch64"):
+ ffvpx.flags.extend(vpx_as_flags)
+ return ffvpx
- # ffvpx_nasm can't indirectly depend on vpx_as_flags, because it depends
- # on a compiler test, so we have to do a little bit of dance here.
- @depends(ffvpx, vpx_as_flags, target)
- def ffvpx(ffvpx, vpx_as_flags, target):
- if ffvpx and vpx_as_flags and target.cpu in ("arm", "aarch64"):
- ffvpx.flags.extend(vpx_as_flags)
- return ffvpx
- set_config("MOZ_FFVPX", True, when=ffvpx.enable)
- set_define("MOZ_FFVPX", True, when=ffvpx.enable)
- set_config("MOZ_FFVPX_AUDIOONLY", True, when=ffvpx.flac_only)
- set_define("MOZ_FFVPX_AUDIOONLY", True, when=ffvpx.flac_only)
- set_config("FFVPX_ASFLAGS", ffvpx.flags)
- set_config("FFVPX_USE_NASM", True, when=ffvpx.use_nasm)
+set_config("MOZ_FFVPX_AUDIOONLY", True, when=ffvpx.audio_only)
+set_define("MOZ_FFVPX_AUDIOONLY", True, when=ffvpx.audio_only)
+set_config("FFVPX_ASFLAGS", ffvpx.flags)
+set_config("FFVPX_USE_NASM", True, when=ffvpx.use_nasm)
# nasm detection
@@ -2520,11 +2529,11 @@ def check_nasm_version(nasm_version, versioned):
@depends(target, when=check_nasm_version)
def nasm_asflags(target):
asflags = {
- ("OSX", "x86"): ["-f", "macho32"],
- ("OSX", "x86_64"): ["-f", "macho64"],
+ ("Darwin", "x86"): ["-f", "macho32"],
+ ("Darwin", "x86_64"): ["-f", "macho64"],
("WINNT", "x86"): ["-f", "win32"],
("WINNT", "x86_64"): ["-f", "win64"],
- }.get((target.os, target.cpu), None)
+ }.get((target.kernel, target.cpu), None)
if asflags is None:
# We're assuming every x86 platform we support that's
# not Windows or Mac is ELF.
@@ -2846,29 +2855,29 @@ def pdbstr_paths(valid_windows_sdk_dir, host):
]
+@depends("MOZ_AUTOMATION", c_compiler)
+def allow_missing_wintools(automation, c_compiler):
+ if not automation:
+ return True
+ if c_compiler and c_compiler.type != "clang-cl":
+ return True
+
+
check_prog(
"PDBSTR",
["pdbstr.exe"],
- allow_missing=True,
+ allow_missing=allow_missing_wintools,
when=compile_environment & target_is_windows,
paths=pdbstr_paths,
allow_spaces=True,
)
-@depends("MOZ_AUTOMATION", c_compiler)
-def allow_missing_winchecksec(automation, c_compiler):
- if not automation:
- return True
- if c_compiler and c_compiler.type != "clang-cl":
- return True
-
-
check_prog(
"WINCHECKSEC",
["winchecksec.exe", "winchecksec"],
bootstrap="winchecksec",
- allow_missing=allow_missing_winchecksec,
+ allow_missing=allow_missing_wintools,
when=compile_environment & target_is_windows,
)
@@ -3092,7 +3101,7 @@ with only_when(compile_environment):
# ==============================================================
@depends(target, developer_options, artifact_builds)
def crashreporter_default(target, developer_options, artifacts):
- if target.kernel in ("WINNT", "Darwin"):
+ if target.os in ("WINNT", "OSX"):
return True
if target.kernel == "Linux" and target.cpu in ("x86", "x86_64", "arm", "aarch64"):
# The crash reporter prevents crash stacktraces to be logged in the
diff --git a/toolkit/mozapps/defaultagent/BackgroundTask_defaultagent.sys.mjs b/toolkit/mozapps/defaultagent/BackgroundTask_defaultagent.sys.mjs
index c727a55997..691f76c319 100644
--- a/toolkit/mozapps/defaultagent/BackgroundTask_defaultagent.sys.mjs
+++ b/toolkit/mozapps/defaultagent/BackgroundTask_defaultagent.sys.mjs
@@ -17,8 +17,6 @@ const EXIT_CODE = {
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
setTimeout: "resource://gre/modules/Timer.sys.mjs",
- BackgroundTasksUtils: "resource://gre/modules/BackgroundTasksUtils.sys.mjs",
- NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
// eslint-disable-next-line mozilla/no-browser-refs-in-toolkit
ShellService: "resource:///modules/ShellService.sys.mjs",
});
@@ -37,11 +35,20 @@ ChromeUtils.defineLazyGetter(lazy, "log", () => {
return new ConsoleAPI(consoleOptions);
});
-// Should be slightly longer than NOTIFICATION_WAIT_TIMEOUT_MS in
-// Notification.cpp (divided by 1000 to convert millseconds to seconds) to not
-// cause race between timeouts. Currently 12 hours + 5 additional minutes.
-export const backgroundTaskTimeoutSec = 12 * 60 * 60 + 60 * 5;
-const kNotificationTimeoutMs = 12 * 60 * 60 * 1000;
+// Should be slightly longer than kNotificationTimeoutMs and kGleanSendWait below
+// (divided by 1000 to convert millseconds to seconds) to not cause race
+// between timeouts.
+//
+// Additionally, should be less than the Windows Scheduled Task timeout
+// execTimeLimitBStr in ScheduledTask.cpp.
+//
+// Current bounds are 11 hours 55 minutes 10 seconds and 12 hours 5 minutes.
+export const backgroundTaskTimeoutSec = 12 * 60 * 60;
+
+// 11 hours 55 minutes in milliseconds.
+const kNotificationTimeoutMs = 11 * 60 * 60 * 1000 + 55 * 60 * 1000;
+// 10 seconds in milliseconds.
+const kGleanSendWait = 10000;
const kNotificationShown = Object.freeze({
notShown: "not-shown",
@@ -148,25 +155,11 @@ export async function runBackgroundTask(commandLine) {
lazy.log.info(`Running do-task with AUMID "${aumid}"`);
- let cppFallback = false;
try {
- await lazy.BackgroundTasksUtils.enableNimbus(commandLine);
- cppFallback =
- lazy.NimbusFeatures.defaultAgent.getVariable("cppFallback");
- } catch (e) {
- lazy.log.error(`Error enabling nimbus: ${e}`);
- }
-
- try {
- if (!cppFallback) {
- lazy.log.info("Running JS do-task.");
- await runWithRegistryLocked(async () => {
- await doTask(defaultAgent, force);
- });
- } else {
- lazy.log.info("Running C++ do-task.");
- defaultAgent.doTask(aumid, force);
- }
+ lazy.log.info("Running JS do-task.");
+ await runWithRegistryLocked(async () => {
+ await doTask(defaultAgent, force);
+ });
} catch (e) {
if (e.message) {
lazy.log.error(e.message);
@@ -181,7 +174,7 @@ export async function runBackgroundTask(commandLine) {
// Bug 1857333: We wait for arbitrary time for Glean to submit telemetry.
lazy.log.info("Pinged glean, waiting for submission.");
- await new Promise(resolve => lazy.setTimeout(resolve, 5000));
+ await new Promise(resolve => lazy.setTimeout(resolve, kGleanSendWait));
return EXIT_CODE.SUCCESS;
}
diff --git a/toolkit/mozapps/defaultagent/DefaultAgent.cpp b/toolkit/mozapps/defaultagent/DefaultAgent.cpp
index 2ebb5e466e..3bff6e3243 100644
--- a/toolkit/mozapps/defaultagent/DefaultAgent.cpp
+++ b/toolkit/mozapps/defaultagent/DefaultAgent.cpp
@@ -7,11 +7,8 @@
#include <windows.h>
#include <shlwapi.h>
#include <objbase.h>
-#include <string.h>
-#include <vector>
#include "nsAutoRef.h"
-#include "nsDebug.h"
#include "nsProxyRelease.h"
#include "nsWindowsHelpers.h"
#include "nsString.h"
@@ -304,62 +301,6 @@ DefaultAgent::Uninstall(const nsAString& aUniqueToken) {
}
NS_IMETHODIMP
-DefaultAgent::DoTask(const nsAString& aUniqueToken, const bool aForce) {
- // Acquire() has a short timeout. Since this runs in the background, we
- // could use a longer timeout in this situation. However, if another
- // installation's agent is already running, it will update CurrentDefault,
- // possibly send a ping, and possibly show a notification.
- // Once all that has happened, there is no real reason to do it again. We
- // only send one ping per day, so we aren't going to do that again. And
- // the only time we ever show a second notification is 7 days after the
- // first one, so we aren't going to do that again either.
- // If the other process didn't take those actions, there is no reason that
- // this process would take them.
- // If the other process fails, this one will most likely fail for the same
- // reason.
- // So we'll just bail if we can't get the mutex quickly.
- RegistryMutex regMutex;
- if (!regMutex.Acquire()) {
- return NS_ERROR_NOT_AVAILABLE;
- }
-
- // Check that Firefox ran recently, if not then stop here.
- // Also stop if no timestamp was found, which most likely indicates
- // that Firefox was not yet run.
- bool ranRecently = false;
- if (!aForce && (!CheckIfAppRanRecently(&ranRecently) || !ranRecently)) {
- return NS_ERROR_FAILURE;
- }
-
- DefaultBrowserResult defaultBrowserResult = GetDefaultBrowserInfo();
- DefaultBrowserInfo browserInfo{};
- if (defaultBrowserResult.isOk()) {
- browserInfo = defaultBrowserResult.unwrap();
- } else {
- browserInfo.currentDefaultBrowser = Browser::Error;
- browserInfo.previousDefaultBrowser = Browser::Error;
- }
-
- DefaultPdfResult defaultPdfResult = GetDefaultPdfInfo();
- DefaultPdfInfo pdfInfo{};
- if (defaultPdfResult.isOk()) {
- pdfInfo = defaultPdfResult.unwrap();
- } else {
- pdfInfo.currentDefaultPdf = PDFHandler::Error;
- }
-
- NotificationActivities activitiesPerformed;
- // We block while waiting for the notification which prevents STA thread
- // callbacks from running as the event loop won't run. Moving notification
- // handling to an MTA thread prevents this conflict.
- activitiesPerformed = MaybeShowNotification(
- browserInfo, PromiseFlatString(aUniqueToken).get(), aForce);
-
- HRESULT hr = SendDefaultAgentPing(browserInfo, pdfInfo, activitiesPerformed);
- return SUCCEEDED(hr) ? NS_OK : NS_ERROR_FAILURE;
-}
-
-NS_IMETHODIMP
DefaultAgent::AppRanRecently(bool* aRanRecently) {
bool ranRecently = false;
*aRanRecently = CheckIfAppRanRecently(&ranRecently) && ranRecently;
diff --git a/toolkit/mozapps/defaultagent/DefaultBrowser.cpp b/toolkit/mozapps/defaultagent/DefaultBrowser.cpp
index 87d3f62632..d9543665b7 100644
--- a/toolkit/mozapps/defaultagent/DefaultBrowser.cpp
+++ b/toolkit/mozapps/defaultagent/DefaultBrowser.cpp
@@ -184,17 +184,6 @@ BrowserResult TryGetReplacePreviousDefaultBrowser(Browser currentDefault) {
return GetBrowserFromString(previousDefault);
}
-DefaultBrowserResult GetDefaultBrowserInfo() {
- DefaultBrowserInfo browserInfo;
-
- MOZ_TRY_VAR(browserInfo.currentDefaultBrowser, TryGetDefaultBrowser());
- MOZ_TRY_VAR(
- browserInfo.previousDefaultBrowser,
- TryGetReplacePreviousDefaultBrowser(browserInfo.currentDefaultBrowser));
-
- return browserInfo;
-}
-
// We used to prefix this key with the installation directory, but that causes
// problems with our new "only one ping per day across installs" restriction.
// To make sure all installations use consistent data, the value's name is
diff --git a/toolkit/mozapps/defaultagent/DefaultBrowser.h b/toolkit/mozapps/defaultagent/DefaultBrowser.h
index f1b940959f..081f3b9ccf 100644
--- a/toolkit/mozapps/defaultagent/DefaultBrowser.h
+++ b/toolkit/mozapps/defaultagent/DefaultBrowser.h
@@ -10,7 +10,6 @@
#include <string>
#include "mozilla/DefineEnum.h"
-#include "mozilla/WinHeaderOnlyUtils.h"
namespace mozilla::default_agent {
@@ -24,9 +23,6 @@ struct DefaultBrowserInfo {
Browser previousDefaultBrowser;
};
-using DefaultBrowserResult = mozilla::WindowsErrorResult<DefaultBrowserInfo>;
-
-DefaultBrowserResult GetDefaultBrowserInfo();
Browser GetDefaultBrowser();
Browser GetReplacePreviousDefaultBrowser(Browser currentBrowser);
diff --git a/toolkit/mozapps/defaultagent/Notification.cpp b/toolkit/mozapps/defaultagent/Notification.cpp
index 961e57c9b3..39236eac15 100644
--- a/toolkit/mozapps/defaultagent/Notification.cpp
+++ b/toolkit/mozapps/defaultagent/Notification.cpp
@@ -7,616 +7,22 @@
#include "Notification.h"
#include <shlwapi.h>
-#include <wchar.h>
#include <windows.h>
#include <winnt.h>
-#include "mozilla/ArrayUtils.h"
-#include "mozilla/CmdLineAndEnvUtils.h"
-#include "mozilla/ErrorResult.h"
-#include "mozilla/mscom/EnsureMTA.h"
-#include "mozilla/intl/FileSource.h"
-#include "mozilla/intl/Localization.h"
-#include "mozilla/ShellHeaderOnlyUtils.h"
-#include "mozilla/UniquePtr.h"
-#include "mozilla/Unused.h"
-#include "mozilla/WinHeaderOnlyUtils.h"
-#include "nsString.h"
-#include "nsTArray.h"
-#include "nsWindowsHelpers.h"
-#include "readstrings.h"
-#include "updatererrors.h"
-#include "WindowsDefaultBrowser.h"
-
-#include "common.h"
-#include "DefaultBrowser.h"
-#include "EventLog.h"
-#include "Registry.h"
-#include "SetDefaultBrowser.h"
-
-#include "wintoastlib.h"
-
-using mozilla::intl::Localization;
+#include "nsLiteralString.h"
#define SEVEN_DAYS_IN_SECONDS (7 * 24 * 60 * 60)
-// If the notification hasn't been activated or dismissed within 12 hours,
-// stop waiting for it.
-#define NOTIFICATION_WAIT_TIMEOUT_MS (12 * 60 * 60 * 1000)
+// If the notification hasn't been activated or dismissed within 11 hours 55
+// minutes, stop waiting for it.
+#define NOTIFICATION_WAIT_TIMEOUT_MS (11 * 60 * 60 * 1000 + 55 * 60 * 1000)
// If the mutex hasn't been released within a few minutes, something is wrong
// and we should give up on it
#define MUTEX_TIMEOUT_MS (10 * 60 * 1000)
namespace mozilla::default_agent {
-bool FirefoxInstallIsEnglish();
-
-static bool SetInitialNotificationShown(bool wasShown) {
- return !RegistrySetValueBool(IsPrefixed::Unprefixed,
- L"InitialNotificationShown", wasShown)
- .isErr();
-}
-
-static bool GetInitialNotificationShown() {
- return RegistryGetValueBool(IsPrefixed::Unprefixed,
- L"InitialNotificationShown")
- .unwrapOr(mozilla::Some(false))
- .valueOr(false);
-}
-
-static bool ResetInitialNotificationShown() {
- return RegistryDeleteValue(IsPrefixed::Unprefixed,
- L"InitialNotificationShown")
- .isOk();
-}
-
-static bool SetFollowupNotificationShown(bool wasShown) {
- return !RegistrySetValueBool(IsPrefixed::Unprefixed,
- L"FollowupNotificationShown", wasShown)
- .isErr();
-}
-
-static bool GetFollowupNotificationShown() {
- return RegistryGetValueBool(IsPrefixed::Unprefixed,
- L"FollowupNotificationShown")
- .unwrapOr(mozilla::Some(false))
- .valueOr(false);
-}
-
-static bool SetFollowupNotificationSuppressed(bool value) {
- return !RegistrySetValueBool(IsPrefixed::Unprefixed,
- L"FollowupNotificationSuppressed", value)
- .isErr();
-}
-
-static bool GetFollowupNotificationSuppressed() {
- return RegistryGetValueBool(IsPrefixed::Unprefixed,
- L"FollowupNotificationSuppressed")
- .unwrapOr(mozilla::Some(false))
- .valueOr(false);
-}
-
-// Returns 0 if no value is set.
-static ULONGLONG GetFollowupNotificationRequestTime() {
- return RegistryGetValueQword(IsPrefixed::Unprefixed, L"FollowupRequestTime")
- .unwrapOr(mozilla::Some(0))
- .valueOr(0);
-}
-
-// Returns false if no value is set.
-static bool GetPrefSetDefaultBrowserUserChoice() {
- return RegistryGetValueBool(IsPrefixed::Prefixed,
- L"SetDefaultBrowserUserChoice")
- .unwrapOr(mozilla::Some(false))
- .valueOr(false);
-}
-
-struct ToastStrings {
- mozilla::UniquePtr<wchar_t[]> text1;
- mozilla::UniquePtr<wchar_t[]> text2;
- mozilla::UniquePtr<wchar_t[]> action1;
- mozilla::UniquePtr<wchar_t[]> action2;
- mozilla::UniquePtr<wchar_t[]> relImagePath;
-};
-
-struct Strings {
- // Toast notification button text is hard to localize because it tends to
- // overflow. Thus, we have 3 different toast notifications:
- // - The initial notification, which includes a button with text like
- // "Ask me later". Since we cannot easily localize this, we will display
- // it only in English.
- // - The followup notification, to be shown if the user clicked "Ask me
- // later". Since we only have that button in English, we only need this
- // notification in English.
- // - The localized notification, which has much shorter button text to
- // (hopefully) prevent overflow: just "Yes" and "No". Since we no longer
- // have an "Ask me later" button, a followup localized notification is not
- // needed.
- ToastStrings initialToast;
- ToastStrings followupToast;
- ToastStrings localizedToast;
-
- // Returned pointer points within this struct and should not be freed.
- const ToastStrings* GetToastStrings(NotificationType whichToast,
- bool englishStrings) const {
- if (!englishStrings) {
- return &localizedToast;
- }
- if (whichToast == NotificationType::Initial) {
- return &initialToast;
- }
- return &followupToast;
- }
-};
-
-// Gets all strings out of the relevant INI files.
-// Returns true on success, false on failure
-static bool GetStrings(Strings& strings) {
- mozilla::UniquePtr<wchar_t[]> installPath;
- bool success = GetInstallDirectory(installPath);
- if (!success) {
- LOG_ERROR_MESSAGE(L"Failed to get install directory when getting strings");
- return false;
- }
- nsTArray<nsCString> resIds = {"branding/brand.ftl"_ns,
- "browser/backgroundtasks/defaultagent.ftl"_ns};
- RefPtr<Localization> l10n = Localization::Create(resIds, true);
- nsAutoCString daHeaderText, daBodyText, daYesButton, daNoButton;
- mozilla::ErrorResult daRv;
- l10n->FormatValueSync("default-browser-notification-header-text"_ns, {},
- daHeaderText, daRv);
- ENSURE_SUCCESS(daRv, false);
- l10n->FormatValueSync("default-browser-notification-body-text"_ns, {},
- daBodyText, daRv);
- ENSURE_SUCCESS(daRv, false);
- l10n->FormatValueSync("default-browser-notification-yes-button-text"_ns, {},
- daYesButton, daRv);
- ENSURE_SUCCESS(daRv, false);
- l10n->FormatValueSync("default-browser-notification-no-button-text"_ns, {},
- daNoButton, daRv);
- ENSURE_SUCCESS(daRv, false);
-
- NS_ConvertUTF8toUTF16 daHeaderTextW(daHeaderText), daBodyTextW(daBodyText),
- daYesButtonW(daYesButton), daNoButtonW(daNoButton);
- strings.localizedToast.text1 =
- mozilla::MakeUnique<wchar_t[]>(daHeaderTextW.Length() + 1);
- wcsncpy(strings.localizedToast.text1.get(), daHeaderTextW.get(),
- daHeaderTextW.Length() + 1);
- strings.localizedToast.text2 =
- mozilla::MakeUnique<wchar_t[]>(daBodyTextW.Length() + 1);
- wcsncpy(strings.localizedToast.text2.get(), daBodyTextW.get(),
- daBodyTextW.Length() + 1);
- strings.localizedToast.action1 =
- mozilla::MakeUnique<wchar_t[]>(daYesButtonW.Length() + 1);
- wcsncpy(strings.localizedToast.action1.get(), daYesButtonW.get(),
- daYesButtonW.Length() + 1);
- strings.localizedToast.action2 =
- mozilla::MakeUnique<wchar_t[]>(daNoButtonW.Length() + 1);
- wcsncpy(strings.localizedToast.action2.get(), daNoButtonW.get(),
- daNoButtonW.Length() + 1);
- const wchar_t* iniFormat = L"%s\\defaultagent.ini";
- int bufferSize = _scwprintf(iniFormat, installPath.get());
- ++bufferSize; // Extra character for terminating null
- mozilla::UniquePtr<wchar_t[]> iniPath =
- mozilla::MakeUnique<wchar_t[]>(bufferSize);
- _snwprintf_s(iniPath.get(), bufferSize, _TRUNCATE, iniFormat,
- installPath.get());
-
- IniReader nonlocalizedReader(iniPath.get(), "Nonlocalized");
- nonlocalizedReader.AddKey("InitialToastRelativeImagePath",
- &strings.initialToast.relImagePath);
- nonlocalizedReader.AddKey("FollowupToastRelativeImagePath",
- &strings.followupToast.relImagePath);
- nonlocalizedReader.AddKey("LocalizedToastRelativeImagePath",
- &strings.localizedToast.relImagePath);
- int result = nonlocalizedReader.Read();
- if (result != OK) {
- LOG_ERROR_MESSAGE(L"Unable to read non-localized strings: %d", result);
- return false;
- }
-
- return true;
-}
-
-static mozilla::WindowsError LaunchFirefoxToHandleDefaultBrowserAgent() {
- // Could also be `MOZ_APP_NAME.exe`, but there's no generality to be gained:
- // the WDBA is Firefox-only.
- FilePathResult firefoxPathResult = GetRelativeBinaryPath(L"firefox.exe");
- if (firefoxPathResult.isErr()) {
- return firefoxPathResult.unwrapErr();
- }
- std::wstring firefoxPath = firefoxPathResult.unwrap();
-
- _bstr_t cmd = firefoxPath.c_str();
- // Omit argv[0] because ShellExecute doesn't need it.
- _variant_t args(L"-to-handle-default-browser-agent");
- _variant_t operation(L"open");
- _variant_t directory;
- _variant_t showCmd(SW_SHOWNORMAL);
-
- // To prevent inheriting environment variables from the background task, we
- // run Firefox via Explorer instead of our own process. This mimics the
- // implementation of the Windows Launcher Process.
- auto result =
- ShellExecuteByExplorer(cmd, args, operation, directory, showCmd);
- NS_ENSURE_TRUE(result.isOk(), result.unwrapErr());
-
- return mozilla::WindowsError::CreateSuccess();
-}
-
-/*
- * Set the default browser.
- *
- * First check if we can directly write UserChoice, if so attempt that.
- * If we can't write UserChoice, or if the attempt fails, fall back to
- * showing the Default Apps page of Settings.
- *
- * @param aAumi The AUMI of the installation to set as default.
- */
-static void SetDefaultBrowserFromNotification(const wchar_t* aumi) {
- nsresult rv = NS_ERROR_FAILURE;
- if (GetPrefSetDefaultBrowserUserChoice()) {
- rv = SetDefaultBrowserUserChoice(aumi);
- }
-
- if (NS_SUCCEEDED(rv)) {
- mozilla::Unused << LaunchFirefoxToHandleDefaultBrowserAgent();
- } else {
- LOG_ERROR_MESSAGE(L"Failed to SetDefaultBrowserUserChoice: %#X",
- GetLastError());
- LaunchModernSettingsDialogDefaultApps();
- }
-}
-
-// This encapsulates the data that needs to be protected by a mutex because it
-// will be shared by the main thread and the handler thread.
-// To ensure the data is only written once, handlerDataHasBeenSet should be
-// initialized to false, then set to true when the handler writes data into the
-// structure.
-struct HandlerData {
- NotificationActivities activitiesPerformed;
- bool handlerDataHasBeenSet;
-};
-
-// The value that ToastHandler writes into should be a global. We can't control
-// when ToastHandler is called, and if this value isn't a global, ToastHandler
-// may be called and attempt to access this after it has been deconstructed.
-// Since this value is accessed by the handler thread and the main thread, it
-// is protected by a mutex (gHandlerMutex).
-// Since ShowNotification deconstructs the mutex, it might seem like once
-// ShowNotification exits, we can just rely on the inability to wait on an
-// invalid mutex to protect the deconstructed data, but it's possible that
-// we could deconstruct the mutex while the handler is holding it and is
-// already accessing the protected data.
-static HandlerData gHandlerReturnData;
-static HANDLE gHandlerMutex = INVALID_HANDLE_VALUE;
-
-class ToastHandler : public WinToastLib::IWinToastHandler {
- private:
- NotificationType mWhichNotification;
- HANDLE mEvent;
- const std::wstring mAumiStr;
-
- public:
- ToastHandler(NotificationType whichNotification, HANDLE event,
- const wchar_t* aumi)
- : mWhichNotification(whichNotification), mEvent(event), mAumiStr(aumi) {}
-
- void FinishHandler(NotificationActivities& returnData) const {
- SetReturnData(returnData);
-
- BOOL success = SetEvent(mEvent);
- if (!success) {
- LOG_ERROR_MESSAGE(L"Event could not be set: %#X", GetLastError());
- }
- }
-
- void SetReturnData(NotificationActivities& toSet) const {
- DWORD result = WaitForSingleObject(gHandlerMutex, MUTEX_TIMEOUT_MS);
- if (result == WAIT_TIMEOUT) {
- LOG_ERROR_MESSAGE(L"Unable to obtain mutex ownership");
- return;
- } else if (result == WAIT_FAILED) {
- LOG_ERROR_MESSAGE(L"Failed to wait on mutex: %#X", GetLastError());
- return;
- } else if (result == WAIT_ABANDONED) {
- LOG_ERROR_MESSAGE(L"Found abandoned mutex");
- ReleaseMutex(gHandlerMutex);
- return;
- }
-
- // Only set this data once
- if (!gHandlerReturnData.handlerDataHasBeenSet) {
- gHandlerReturnData.activitiesPerformed = toSet;
- gHandlerReturnData.handlerDataHasBeenSet = true;
- }
-
- BOOL success = ReleaseMutex(gHandlerMutex);
- if (!success) {
- LOG_ERROR_MESSAGE(L"Unable to release mutex ownership: %#X",
- GetLastError());
- }
- }
-
- void toastActivated() const override {
- NotificationActivities activitiesPerformed;
- activitiesPerformed.type = mWhichNotification;
- activitiesPerformed.shown = NotificationShown::Shown;
- activitiesPerformed.action = NotificationAction::ToastClicked;
-
- // Notification strings are written to indicate the default browser is
- // restored to Firefox when the notification body is clicked to prevent
- // ambiguity when buttons aren't pressed.
- SetDefaultBrowserFromNotification(mAumiStr.c_str());
-
- FinishHandler(activitiesPerformed);
- }
-
- void toastActivated(int actionIndex) const override {
- NotificationActivities activitiesPerformed;
- activitiesPerformed.type = mWhichNotification;
- activitiesPerformed.shown = NotificationShown::Shown;
- // Override this below
- activitiesPerformed.action = NotificationAction::NoAction;
-
- if (actionIndex == 0) {
- // "Make Firefox the default" button, on both the initial and followup
- // notifications. "Yes" button on the localized notification.
- activitiesPerformed.action = NotificationAction::MakeFirefoxDefaultButton;
-
- SetDefaultBrowserFromNotification(mAumiStr.c_str());
- } else if (actionIndex == 1) {
- // Do nothing. As long as we don't call
- // SetFollowupNotificationRequestTime, there will be no followup
- // notification.
- activitiesPerformed.action = NotificationAction::DismissedByButton;
- }
-
- FinishHandler(activitiesPerformed);
- }
-
- void toastDismissed(WinToastDismissalReason state) const override {
- NotificationActivities activitiesPerformed;
- activitiesPerformed.type = mWhichNotification;
- activitiesPerformed.shown = NotificationShown::Shown;
- // Override this below
- activitiesPerformed.action = NotificationAction::NoAction;
-
- if (state == WinToastDismissalReason::TimedOut) {
- activitiesPerformed.action = NotificationAction::DismissedByTimeout;
- } else if (state == WinToastDismissalReason::ApplicationHidden) {
- activitiesPerformed.action =
- NotificationAction::DismissedByApplicationHidden;
- } else if (state == WinToastDismissalReason::UserCanceled) {
- activitiesPerformed.action = NotificationAction::DismissedToActionCenter;
- }
-
- FinishHandler(activitiesPerformed);
- }
-
- void toastFailed() const override {
- NotificationActivities activitiesPerformed;
- activitiesPerformed.type = mWhichNotification;
- activitiesPerformed.shown = NotificationShown::Error;
- activitiesPerformed.action = NotificationAction::NoAction;
-
- LOG_ERROR_MESSAGE(L"Toast notification failed to display");
- FinishHandler(activitiesPerformed);
- }
-};
-
-// This function blocks until the shown notification is activated or dismissed.
-static NotificationActivities ShowNotification(
- NotificationType whichNotification, const wchar_t* aumi) {
- // Initially set the value that will be returned to error. If the notification
- // is shown successfully, we'll update it.
- NotificationActivities activitiesPerformed = {whichNotification,
- NotificationShown::Error,
- NotificationAction::NoAction};
-
- bool isEnglishInstall = FirefoxInstallIsEnglish();
-
- Strings strings;
- if (!GetStrings(strings)) {
- return activitiesPerformed;
- }
- const ToastStrings* toastStrings =
- strings.GetToastStrings(whichNotification, isEnglishInstall);
-
- mozilla::mscom::EnsureMTA([&] {
- using namespace WinToastLib;
-
- if (!WinToast::isCompatible()) {
- LOG_ERROR_MESSAGE(L"System is not compatible with WinToast");
- return;
- }
- WinToast::instance()->setAppName(L"" MOZ_APP_DISPLAYNAME);
- std::wstring aumiStr = aumi;
- WinToast::instance()->setAppUserModelId(aumiStr);
- WinToast::instance()->setShortcutPolicy(
- WinToastLib::WinToast::SHORTCUT_POLICY_REQUIRE_NO_CREATE);
- WinToast::WinToastError error;
- if (!WinToast::instance()->initialize(&error)) {
- LOG_ERROR_MESSAGE(WinToast::strerror(error).c_str());
- return;
- }
-
- // This event object will let the handler notify us when it has handled the
- // notification.
- nsAutoHandle event(CreateEventW(nullptr, TRUE, FALSE, nullptr));
- if (event.get() == nullptr) {
- LOG_ERROR_MESSAGE(L"Unable to create event object: %#X", GetLastError());
- return;
- }
-
- bool success = false;
- if (whichNotification == NotificationType::Initial) {
- success = SetInitialNotificationShown(true);
- } else {
- success = SetFollowupNotificationShown(true);
- }
- if (!success) {
- // Return early in this case to prevent the notification from being shown
- // on every run.
- LOG_ERROR_MESSAGE(L"Unable to set notification as displayed");
- return;
- }
-
- // We need the absolute image path, not the relative path.
- mozilla::UniquePtr<wchar_t[]> installPath;
- success = GetInstallDirectory(installPath);
- if (!success) {
- LOG_ERROR_MESSAGE(L"Failed to get install directory for the image path");
- return;
- }
- const wchar_t* absPathFormat = L"%s\\%s";
- int bufferSize = _scwprintf(absPathFormat, installPath.get(),
- toastStrings->relImagePath.get());
- ++bufferSize; // Extra character for terminating null
- mozilla::UniquePtr<wchar_t[]> absImagePath =
- mozilla::MakeUnique<wchar_t[]>(bufferSize);
- _snwprintf_s(absImagePath.get(), bufferSize, _TRUNCATE, absPathFormat,
- installPath.get(), toastStrings->relImagePath.get());
-
- // This is used to protect gHandlerReturnData.
- gHandlerMutex = CreateMutexW(nullptr, TRUE, nullptr);
- if (gHandlerMutex == nullptr) {
- LOG_ERROR_MESSAGE(L"Unable to create mutex: %#X", GetLastError());
- return;
- }
- // Automatically close this mutex when this function exits.
- nsAutoHandle autoMutex(gHandlerMutex);
- // No need to initialize gHandlerReturnData.activitiesPerformed, since it
- // will be set by the handler. But we do need to initialize
- // gHandlerReturnData.handlerDataHasBeenSet so the handler knows that no
- // data has been set yet.
- gHandlerReturnData.handlerDataHasBeenSet = false;
- success = ReleaseMutex(gHandlerMutex);
- if (!success) {
- LOG_ERROR_MESSAGE(L"Unable to release mutex ownership: %#X",
- GetLastError());
- }
-
- // Finally ready to assemble the notification and dispatch it.
- WinToastTemplate toastTemplate =
- WinToastTemplate(WinToastTemplate::ImageAndText02);
- toastTemplate.setTextField(toastStrings->text1.get(),
- WinToastTemplate::FirstLine);
- toastTemplate.setTextField(toastStrings->text2.get(),
- WinToastTemplate::SecondLine);
- toastTemplate.addAction(toastStrings->action1.get());
- toastTemplate.addAction(toastStrings->action2.get());
- toastTemplate.setImagePath(absImagePath.get());
- toastTemplate.setScenario(WinToastTemplate::Scenario::Reminder);
- ToastHandler* handler =
- new ToastHandler(whichNotification, event.get(), aumi);
- INT64 id = WinToast::instance()->showToast(toastTemplate, handler, &error);
- if (id < 0) {
- LOG_ERROR_MESSAGE(WinToast::strerror(error).c_str());
- return;
- }
-
- DWORD result =
- WaitForSingleObject(event.get(), NOTIFICATION_WAIT_TIMEOUT_MS);
- // Don't return after these errors. Attempt to hide the notification.
- if (result == WAIT_FAILED) {
- LOG_ERROR_MESSAGE(L"Unable to wait on event object: %#X", GetLastError());
- } else if (result == WAIT_TIMEOUT) {
- LOG_ERROR_MESSAGE(L"Timed out waiting for event object");
- } else {
- result = WaitForSingleObject(gHandlerMutex, MUTEX_TIMEOUT_MS);
- if (result == WAIT_TIMEOUT) {
- LOG_ERROR_MESSAGE(L"Unable to obtain mutex ownership");
- // activitiesPerformed is already set to error. No change needed.
- } else if (result == WAIT_FAILED) {
- LOG_ERROR_MESSAGE(L"Failed to wait on mutex: %#X", GetLastError());
- // activitiesPerformed is already set to error. No change needed.
- } else if (result == WAIT_ABANDONED) {
- LOG_ERROR_MESSAGE(L"Found abandoned mutex");
- ReleaseMutex(gHandlerMutex);
- // activitiesPerformed is already set to error. No change needed.
- } else {
- // Mutex is being held. It is safe to access gHandlerReturnData.
- // If gHandlerReturnData.handlerDataHasBeenSet is false, the handler
- // never ran. Use the error value activitiesPerformed already contains.
- if (gHandlerReturnData.handlerDataHasBeenSet) {
- activitiesPerformed = gHandlerReturnData.activitiesPerformed;
- }
-
- success = ReleaseMutex(gHandlerMutex);
- if (!success) {
- LOG_ERROR_MESSAGE(L"Unable to release mutex ownership: %#X",
- GetLastError());
- }
- }
- }
-
- if (!WinToast::instance()->hideToast(id)) {
- LOG_ERROR_MESSAGE(L"Failed to hide notification");
- }
- });
- return activitiesPerformed;
-}
-
-// Previously this function checked that the Firefox build was using English.
-// This was checked because of the peculiar way we were localizing toast
-// notifications where we used a completely different set of strings in English.
-//
-// We've since unified the notification flows but need to clean up unused code
-// and config files - Bug 1826375.
-bool FirefoxInstallIsEnglish() { return false; }
-
-// If a notification is shown, this function will block until the notification
-// is activated or dismissed.
-// aumi is the App User Model ID.
-NotificationActivities MaybeShowNotification(
- const DefaultBrowserInfo& browserInfo, const wchar_t* aumi, bool force) {
- // Default to not showing a notification. Any other value will be returned
- // directly from ShowNotification.
- NotificationActivities activitiesPerformed = {NotificationType::Initial,
- NotificationShown::NotShown,
- NotificationAction::NoAction};
-
- // Reset notification state machine, user setting default browser to Firefox
- // is a strong signal that they intend to have it as the default browser.
- if (browserInfo.currentDefaultBrowser == Browser::Firefox) {
- ResetInitialNotificationShown();
- }
-
- bool initialNotificationShown = GetInitialNotificationShown();
- if (!initialNotificationShown || force) {
- if ((browserInfo.currentDefaultBrowser == Browser::EdgeWithBlink &&
- browserInfo.previousDefaultBrowser == Browser::Firefox) ||
- force) {
- return ShowNotification(NotificationType::Initial, aumi);
- }
- return activitiesPerformed;
- }
- activitiesPerformed.type = NotificationType::Followup;
-
- ULONGLONG followupNotificationRequestTime =
- GetFollowupNotificationRequestTime();
- bool followupNotificationRequested = followupNotificationRequestTime != 0;
- bool followupNotificationShown = GetFollowupNotificationShown();
- if (followupNotificationRequested && !followupNotificationShown &&
- !GetFollowupNotificationSuppressed()) {
- ULONGLONG secondsSinceRequestTime =
- SecondsPassedSince(followupNotificationRequestTime);
-
- if (secondsSinceRequestTime >= SEVEN_DAYS_IN_SECONDS) {
- // If we go to show the followup notification and the user has already
- // changed the default browser, permanently suppress the followup since
- // it's no longer relevant.
- if (browserInfo.currentDefaultBrowser == Browser::EdgeWithBlink) {
- return ShowNotification(NotificationType::Followup, aumi);
- } else {
- SetFollowupNotificationSuppressed(true);
- }
- }
- }
- return activitiesPerformed;
-}
-
std::string GetStringForNotificationType(NotificationType type) {
switch (type) {
case NotificationType::Initial:
diff --git a/toolkit/mozapps/defaultagent/Notification.h b/toolkit/mozapps/defaultagent/Notification.h
index 210c55f559..e4e007088d 100644
--- a/toolkit/mozapps/defaultagent/Notification.h
+++ b/toolkit/mozapps/defaultagent/Notification.h
@@ -7,7 +7,9 @@
#ifndef __DEFAULT_BROWSER_NOTIFICATION_H__
#define __DEFAULT_BROWSER_NOTIFICATION_H__
-#include "DefaultBrowser.h"
+#include <string>
+
+#include "nsStringFwd.h"
namespace mozilla::default_agent {
@@ -39,9 +41,6 @@ struct NotificationActivities {
NotificationAction action;
};
-NotificationActivities MaybeShowNotification(
- const DefaultBrowserInfo& browserInfo, const wchar_t* aumi, bool force);
-
// These take enum values and get strings suitable for telemetry
std::string GetStringForNotificationType(NotificationType type);
std::string GetStringForNotificationShown(NotificationShown shown);
diff --git a/toolkit/mozapps/defaultagent/ScheduledTask.cpp b/toolkit/mozapps/defaultagent/ScheduledTask.cpp
index a9cd647c03..c867e59b76 100644
--- a/toolkit/mozapps/defaultagent/ScheduledTask.cpp
+++ b/toolkit/mozapps/defaultagent/ScheduledTask.cpp
@@ -13,14 +13,11 @@
#include <comutil.h>
#include <taskschd.h>
-#include "readstrings.h"
-#include "updatererrors.h"
#include "EventLog.h"
#include "mozilla/RefPtr.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/WinHeaderOnlyUtils.h"
-#include "WindowsDefaultBrowser.h"
#include "DefaultBrowser.h"
diff --git a/toolkit/mozapps/defaultagent/SetDefaultBrowser.cpp b/toolkit/mozapps/defaultagent/SetDefaultBrowser.cpp
index 8bc0889e67..6d829ff045 100644
--- a/toolkit/mozapps/defaultagent/SetDefaultBrowser.cpp
+++ b/toolkit/mozapps/defaultagent/SetDefaultBrowser.cpp
@@ -6,17 +6,16 @@
#include <windows.h>
#include <appmodel.h>
#include <shlobj.h> // for SHChangeNotify and IApplicationAssociationRegistration
-#include <functional>
#include <timeapi.h>
#include "mozilla/ArrayUtils.h"
#include "mozilla/CmdLineAndEnvUtils.h"
#include "mozilla/RefPtr.h"
+#include "mozilla/Result.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/WindowsVersion.h"
#include "mozilla/WinHeaderOnlyUtils.h"
#include "WindowsUserChoice.h"
-#include "nsThreadUtils.h"
#include "EventLog.h"
#include "SetDefaultBrowser.h"
@@ -70,6 +69,77 @@ static bool AddMillisecondsToSystemTime(SYSTEMTIME& aSystemTime,
return true;
}
+/*
+ * Takes two times: the start time and the current time, and returns the number
+ * of seconds left before the current time hits the next minute from the start
+ * time. Used to check if we are within the same minute as the start time and
+ * how much time we have left to perform an operation in the same minute.
+ *
+ * Used with user choice hashes, which have to be written to the registry
+ * in the same minute as they are generated.
+ *
+ * Example 1:
+ * operationStartTime - 10m 20s 800ms
+ * currentTime - 10m 22s 0ms
+ * The next minute is 11, so the return value is 11m - 10m 22s 0ms, converted to
+ * milliseconds.
+ *
+ * Example 2:
+ * operationStartTime - 10m 59s 800ms
+ * currentTime - 11m 0s 0ms
+ * The next minute is 11, but the minute the operation started on was 10, so the
+ * time to the next minute is 0 (because the current time is already at the next
+ * minute).
+ *
+ * @param operationStartTime
+ * @param currentTime
+ *
+ * @returns the number of milliseconds left from the current time to the
+ * next minute from operationStartTime, or zero if the currentTime is already at
+ * the next minute or greater
+ */
+static WORD GetMillisecondsToNextMinute(SYSTEMTIME operationStartTime,
+ SYSTEMTIME currentTime) {
+ SYSTEMTIME operationStartTimeMinute = operationStartTime;
+ SYSTEMTIME currentTimeMinute = currentTime;
+
+ // Zero out the seconds and milliseconds so that we can confirm they are the
+ // same minutes
+ operationStartTimeMinute.wSecond = 0;
+ operationStartTimeMinute.wMilliseconds = 0;
+
+ currentTimeMinute.wSecond = 0;
+ currentTimeMinute.wMilliseconds = 0;
+
+ // Convert to a 64 bit value so we can compare them directly
+ FILETIME fileTime1;
+ FILETIME fileTime2;
+ if (!::SystemTimeToFileTime(&operationStartTimeMinute, &fileTime1) ||
+ !::SystemTimeToFileTime(&currentTimeMinute, &fileTime2)) {
+ // Error: report that there is 0 milliseconds till the next minute
+ return 0;
+ }
+
+ // The minutes for both times have to be the same, so confirm that they are,
+ // and if they aren't, return 0 milliseconds to indicate that we're already
+ // not on the minute that operationStartTime was on
+ if ((fileTime1.dwLowDateTime != fileTime2.dwLowDateTime) ||
+ (fileTime1.dwHighDateTime != fileTime2.dwHighDateTime)) {
+ return 0;
+ }
+
+ // The minutes are the same; determine the number of milliseconds left until
+ // the next minute
+ const WORD secondsToMilliseconds = 1000;
+ const WORD minutesToSeconds = 60;
+
+ // 1 minute converted to milliseconds - (the current second converted to
+ // milliseconds + the current milliseconds)
+ return (1 * minutesToSeconds * secondsToMilliseconds) -
+ ((currentTime.wSecond * secondsToMilliseconds) +
+ currentTime.wMilliseconds);
+}
+
// Compare two SYSTEMTIMEs as FILETIME after clearing everything
// below minutes.
static bool CheckEqualMinutes(SYSTEMTIME aSystemTime1,
@@ -149,6 +219,75 @@ static bool SetUserChoiceRegistry(const wchar_t* aExt, const wchar_t* aProgID,
return true;
}
+struct LaunchExeErr {};
+using LaunchExeResult =
+ mozilla::Result<std::tuple<DWORD, mozilla::UniquePtr<wchar_t[]>>,
+ LaunchExeErr>;
+
+static LaunchExeResult LaunchExecutable(const wchar_t* exePath, int aArgsLength,
+ const wchar_t* const* aArgs) {
+ const wchar_t* args[] = {exePath};
+ mozilla::UniquePtr<wchar_t[]> cmdLine(mozilla::MakeCommandLine(
+ mozilla::ArrayLength(args), const_cast<wchar_t**>(args), aArgsLength,
+ const_cast<wchar_t**>(aArgs)));
+
+ PROCESS_INFORMATION pi;
+ STARTUPINFOW si = {sizeof(si)};
+ si.dwFlags = STARTF_USESHOWWINDOW;
+ si.wShowWindow = SW_HIDE;
+
+ if (!::CreateProcessW(exePath, cmdLine.get(), nullptr, nullptr, FALSE, 0,
+ nullptr, nullptr, &si, &pi)) {
+ HRESULT hr = HRESULT_FROM_WIN32(GetLastError());
+ LOG_ERROR(hr);
+ return Err(LaunchExeErr());
+ }
+
+ nsAutoHandle process(pi.hProcess);
+ nsAutoHandle mainThread(pi.hThread);
+
+ DWORD exitCode;
+ if (::WaitForSingleObject(process.get(), INFINITE) == WAIT_OBJECT_0 &&
+ ::GetExitCodeProcess(process.get(), &exitCode)) {
+ return std::make_tuple(exitCode, std::move(cmdLine));
+ }
+
+ return Err(LaunchExeErr());
+}
+
+static bool LaunchPowershell(const wchar_t* command,
+ const wchar_t* powershellPath) {
+ const wchar_t* args[] = {
+ L"-NoProfile", // ensure nothing is monkeying with powershell
+ L"-c",
+ command,
+ };
+
+ return LaunchExecutable(powershellPath, mozilla::ArrayLength(args), args)
+ .map([](auto result) {
+ auto& [exitCode, regCmdLine] = result;
+ bool success = (exitCode == 0);
+ if (!success) {
+ LOG_ERROR_MESSAGE(L"%s returned failure exitCode %d",
+ regCmdLine.get(), exitCode);
+ }
+ return success;
+ })
+ .unwrapOr(false);
+}
+
+static bool FindPowershell(mozilla::UniquePtr<wchar_t[]>& powershellPath) {
+ auto exePath = mozilla::MakeUnique<wchar_t[]>(MAX_PATH + 1);
+ if (!ConstructSystem32Path(L"WindowsPowershell\\v1.0\\powershell.exe",
+ exePath.get(), MAX_PATH + 1)) {
+ LOG_ERROR_MESSAGE(L"Failed to construct path to powershell.exe");
+ return false;
+ }
+
+ powershellPath.swap(exePath);
+ return true;
+}
+
/*
* Set an association with a UserChoice key
*
@@ -165,7 +304,9 @@ static bool SetUserChoiceRegistry(const wchar_t* aExt, const wchar_t* aProgID,
static bool SetUserChoice(const wchar_t* aExt, const wchar_t* aSid,
const wchar_t* aProgID, bool inMsix) {
if (inMsix) {
- LOG_ERROR_MESSAGE(L"SetUserChoice does not work on MSIX builds.");
+ LOG_ERROR_MESSAGE(
+ L"SetUserChoice should not be called on MSIX builds. Call "
+ L"SetDefaultExtensionHandlersUserChoiceImplMsix instead.");
return false;
}
@@ -298,6 +439,247 @@ nsresult SetDefaultExtensionHandlersUserChoice(
return rv;
}
+/*
+ * Takes the list of file extension pairs and fills a list of program ids for
+ * each pair.
+ *
+ * @param aFileExtensions array of file association pairs to
+ * set as default, like `[ ".pdf", "FirefoxPDF" ]`.
+ */
+static nsresult GenerateProgramIDs(const nsTArray<nsString>& aFileExtensions,
+ nsTArray<nsString>& progIDs) {
+ for (size_t i = 0; i + 1 < aFileExtensions.Length(); i += 2) {
+ const wchar_t* fileExtension = aFileExtensions[i].get();
+
+ // Formatting the ProgID here prevents using this helper to target arbitrary
+ // ProgIDs.
+ mozilla::UniquePtr<wchar_t[]> progID;
+ nsresult rv = GetMsixProgId(fileExtension, progID);
+ if (NS_FAILED(rv)) {
+ LOG_ERROR_MESSAGE(L"Failed to retrieve MSIX progID for %s",
+ fileExtension);
+ return rv;
+ }
+
+ progIDs.AppendElement(nsString(progID.get()));
+ }
+
+ return NS_OK;
+}
+
+/*
+ * Takes the list of file extension pairs and a matching list of program ids for
+ * each of those pairs and verifies that the system is successfully to match
+ * each.
+ *
+ * @param aFileExtensions array of file association pairs to set as default,
+ * like `[ ".pdf", "FirefoxPDF" ]`.
+ * @param aProgIDs array of program ids. The order of this array matches the
+ * file extensions parameter.
+ */
+static nsresult VerifyUserDefaults(const nsTArray<nsString>& aFileExtensions,
+ const nsTArray<nsString>& aProgIDs) {
+ for (size_t i = 0; i + 1 < aFileExtensions.Length(); i += 2) {
+ const wchar_t* fileExtension = aFileExtensions[i].get();
+
+ if (!VerifyUserDefault(fileExtension, aProgIDs[i / 2].get())) {
+ return NS_ERROR_WDBA_REJECTED;
+ }
+ }
+
+ return NS_OK;
+}
+
+/*
+ * Queries the system for the minimum resolution (or tick time) in milliseconds
+ * that can be used with ::Sleep. If a Sleep is not triggered with a time
+ * divisible by the tick time, control can return to the thread before the time
+ * specified to Sleep.
+ *
+ * @param defaultValue what to return if the system query fails.
+ */
+static UINT GetSystemSleepIntervalInMilliseconds(UINT defaultValue) {
+ TIMECAPS timeCapabilities;
+ bool timeCapsFetchSuccessful =
+ (MMSYSERR_NOERROR ==
+ timeGetDevCaps(&timeCapabilities, sizeof(timeCapabilities)));
+ if (!timeCapsFetchSuccessful) {
+ return defaultValue;
+ }
+
+ return timeCapabilities.wPeriodMin > 0 ? timeCapabilities.wPeriodMin
+ : defaultValue;
+}
+
+/*
+ * MSIX implementation for SetDefaultExtensionHandlersUserChoice.
+ *
+ * Due to the fact that MSIX builds run in a virtual, walled off environment,
+ * calling into the Win32 registry APIs doesn't work to set registry keys.
+ * MSIX builds access a virtual registry.
+ *
+ * Sends a "script" via command line to Powershell to modify the registry
+ * in an executable that is outside of the walled garden MSIX FX package
+ * environment, which is the only way to do that so far. Only works
+ * on slightly older versions of Windows 11 (older than 23H2).
+ *
+ * This was originally done using calls to Reg.exe, but permissions can't
+ * be changed that way, requiring using Powershell to call into .Net functions
+ * directly. Launching Powershell is slow (on the order of a second on some
+ * systems) so this method jams the calls to do everything into one launch of
+ * Powershell, to make it as quick as possible.
+ *
+ */
+static nsresult SetDefaultExtensionHandlersUserChoiceImplMsix(
+ const wchar_t* aAumi, const wchar_t* const aSid,
+ const nsTArray<nsString>& aFileExtensions) {
+ mozilla::UniquePtr<wchar_t[]> exePath;
+ if (!FindPowershell(exePath)) {
+ LOG_ERROR_MESSAGE(L"Could not locate Powershell");
+ return NS_ERROR_FAILURE;
+ }
+
+ // The following is the start of the script that will be sent to Powershell.
+ // In includes a function; calls to the function get appended per file
+ // extension, done below.
+ nsString startScript(
+ uR"(
+# Force exceptions to stop execution
+$ErrorActionPreference = 'Stop'
+
+function Set-DefaultHandlerRegistry($Path, $ProgID, $Hash) {
+ $Path = "$Path\UserChoice"
+ $CurrentUser = [Microsoft.Win32.Registry]::CurrentUser
+
+ # DeleteSubKey throws if we don't have sufficient permissions to delete key,
+ # signaling failure to launching process.
+ #
+ # Note: DeleteSubKeyTree fails when DENY permissions are set on key, whereas
+ # DeleteSubKey succeeds.
+ $CurrentUser.DeleteSubKey($Path, $false)
+ $key = $CurrentUser.CreateSubKey($Path)
+
+ $StringType = [Microsoft.Win32.RegistryValueKind]::String
+ $key.SetValue('ProgID', $ProgID, $StringType)
+ $key.SetValue('Hash', $Hash, $StringType)
+}
+
+)"); // Newlines in the above powershell script at the end are important!!!
+
+ // NOTE!!!! CreateProcess / calling things on the command line has a character
+ // count limit. For CMD.exe, it's 8K. For CreateProcessW, it's technically
+ // 32K. I can't find documentation about Powershell, but think 8K is safe to
+ // assume as a good maximum. The above script is about 1000 characters, and we
+ // will append another 100 characters at most per extension, so we'd need to
+ // be setting about 70 handlers at once to worry about the theoretical limit.
+ // Which we won't do. So the length shouldn't be a problem.
+ if (aFileExtensions.Length() >= 70) {
+ LOG_ERROR_MESSAGE(
+ L"SetDefaultExtensionHandlersUserChoiceImplMsix can't cope with 70 or "
+ L"more file extensions at once. Please break it up into multiple calls "
+ L"with fewer extensions per call.");
+ return NS_ERROR_FAILURE;
+ }
+
+ // NOTE!!!!
+ // User choice hashes have to be generated and written to the registry in the
+ // same minute. So we do everything we can upfront before we get to the hash
+ // generation, to ensure that hash generation and the call to Powershell to
+ // write to the registry has as much time as possible to run.
+
+ // Program ID fetch / generation might be slow, so do that ahead of time.
+ nsTArray<nsString> progIDs;
+ nsresult rv = GenerateProgramIDs(aFileExtensions, progIDs);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString scriptBuffer;
+
+ // Everyting in the loop below should succeed or fail reasonably fast (within
+ // 20 milliseconds or something well under a second) besides the Powershell
+ // call. The Powershell call itself will either fail or succeed and break out
+ // of the loop, so repeating 10 times should be fine and is mostly to allow
+ // for getting the timing right with the user choice hash generation happening
+ // in the same minute - meaning this should likely only happen twice through
+ // the loop at most.
+ for (int i = 0; i < 10; i++) {
+ // Pre-allocate the memory for the scriptBuffer upfront so that we don't
+ // have to keep allocating every time Append is called.
+ const int scriptBufferCapacity = 16 * 1024;
+ scriptBuffer = startScript;
+ scriptBuffer.SetCapacity(scriptBufferCapacity);
+
+ SYSTEMTIME hashTimestamp;
+ ::GetSystemTime(&hashTimestamp);
+
+ // Time critical stuff starts here:
+
+ for (size_t i = 0; i + 1 < aFileExtensions.Length(); i += 2) {
+ const wchar_t* fileExtension = aFileExtensions[i].get();
+
+ nsAutoString keyPath;
+ AppendAssociationKeyPath(fileExtension, keyPath);
+
+ auto hashWchar = GenerateUserChoiceHash(
+ fileExtension, aSid, progIDs[i / 2].get(), hashTimestamp);
+ if (!hashWchar) {
+ return NS_ERROR_FAILURE;
+ }
+ auto hash = nsDependentString(hashWchar.get());
+
+ // Append a line to the script buffer in the form:
+ // Set-DefaultHandlerRegistry $RegistryKeyPath $ProgID $UserChoiceHash
+ scriptBuffer += u"Set-DefaultHandlerRegistry "_ns + keyPath + u" "_ns +
+ progIDs[i / 2] + u" "_ns + hash + u"\n"_ns;
+ }
+
+ // The hash changes at the end of each minute, so check that the hash should
+ // be the same by the time we're done writing.
+ const ULONGLONG kWriteTimingThresholdMilliseconds = 2000;
+
+ // Generating the hash could have taken some time, so figure out what time
+ // we are at right now.
+ SYSTEMTIME writeEndTimestamp;
+ ::GetSystemTime(&writeEndTimestamp);
+
+ // Check if we have enough time to launch Powershell or if we should sleep
+ // to the next minute and try again
+ auto millisecondsLeftUntilNextMinute =
+ GetMillisecondsToNextMinute(hashTimestamp, writeEndTimestamp);
+ if (millisecondsLeftUntilNextMinute >= kWriteTimingThresholdMilliseconds) {
+ break;
+ }
+
+ LOG_ERROR_MESSAGE(
+ L"Hash is too close to next minute, sleeping until next minute to "
+ L"ensure that hash generation matches write to registry.");
+
+ UINT sleepUntilNextMinuteBufferMilliseconds =
+ GetSystemSleepIntervalInMilliseconds(
+ 50); // Default to 50ms if we can't figure out the right interval
+ // to sleep for
+ ::Sleep(millisecondsLeftUntilNextMinute +
+ (sleepUntilNextMinuteBufferMilliseconds * 2));
+
+ // Try again, if we have any attempts left
+ }
+
+ // Call Powershell to set the registry keys now - this is the really time
+ // consuming thing 250ms to launch on a VM in a reasonably fast case, possibly
+ // a lot slower on other systems
+ bool powershellSuccessful =
+ LaunchPowershell(scriptBuffer.get(), exePath.get());
+ if (!powershellSuccessful) {
+ // If powershell failed, it likely means that something got mucked with
+ // the registry and that Windows is popping up notifications to the user,
+ // so don't try again right now, so as to not overwhelm the user and annoy
+ // them.
+ return NS_ERROR_FAILURE;
+ }
+
+ // Validate now
+ return VerifyUserDefaults(aFileExtensions, progIDs);
+}
+
nsresult SetDefaultExtensionHandlersUserChoiceImpl(
const wchar_t* aAumi, const wchar_t* const aSid,
const nsTArray<nsString>& aFileExtensions) {
@@ -306,9 +688,8 @@ nsresult SetDefaultExtensionHandlersUserChoiceImpl(
GetCurrentPackageFullName(&pfnLen, nullptr) != APPMODEL_ERROR_NO_PACKAGE;
if (inMsix) {
- // MSIX packages can not meaningfully modify the registry keys related to
- // default handlers
- return NS_ERROR_FAILURE;
+ return SetDefaultExtensionHandlersUserChoiceImplMsix(aAumi, aSid,
+ aFileExtensions);
}
for (size_t i = 0; i + 1 < aFileExtensions.Length(); i += 2) {
diff --git a/toolkit/mozapps/defaultagent/defaultagent.ini b/toolkit/mozapps/defaultagent/defaultagent.ini
deleted file mode 100644
index 9300b20c46..0000000000
--- a/toolkit/mozapps/defaultagent/defaultagent.ini
+++ /dev/null
@@ -1,9 +0,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/.
-
-; This file is in the UTF-8 encoding
-[Nonlocalized]
-InitialToastRelativeImagePath=browser/VisualElements/VisualElements_150.png
-FollowupToastRelativeImagePath=browser/VisualElements/VisualElements_150.png
-LocalizedToastRelativeImagePath=browser/VisualElements/VisualElements_150.png
diff --git a/toolkit/mozapps/defaultagent/moz.build b/toolkit/mozapps/defaultagent/moz.build
index 86b68c6371..f8ae506d9c 100644
--- a/toolkit/mozapps/defaultagent/moz.build
+++ b/toolkit/mozapps/defaultagent/moz.build
@@ -15,6 +15,7 @@ UNIFIED_SOURCES += [
"DefaultBrowser.cpp",
"DefaultPDF.cpp",
"EventLog.cpp",
+ "Notification.cpp",
"Policy.cpp",
"Registry.cpp",
"ScheduledTask.cpp",
@@ -25,21 +26,6 @@ UNIFIED_SOURCES += [
"WindowsMutex.cpp",
]
-SOURCES += [
- "/third_party/WinToast/wintoastlib.cpp",
- "/toolkit/mozapps/update/common/readstrings.cpp",
- "Notification.cpp",
-]
-
-# Suppress warnings from third-party code.
-SOURCES["/third_party/WinToast/wintoastlib.cpp"].flags += [
- "-Wno-implicit-fallthrough",
- "-Wno-nonportable-include-path", # Needed for wintoastlib.h including "Windows.h"
-]
-SOURCES["Notification.cpp"].flags += [
- "-Wno-nonportable-include-path", # Needed for wintoastlib.h including "Windows.h"
-]
-
EXPORTS.mozilla += [
"DefaultAgent.h",
"WindowsMutex.h",
@@ -52,9 +38,7 @@ USE_LIBS += [
LOCAL_INCLUDES += [
"/browser/components/shell/",
"/other-licenses/nsis/Contrib/CityHash/cityhash",
- "/third_party/WinToast",
"/toolkit/components/jsoncpp/include",
- "/toolkit/mozapps/update/common",
]
OS_LIBS += [
@@ -98,8 +82,6 @@ for var in ("MOZ_APP_BASENAME", "MOZ_APP_DISPLAYNAME", "MOZ_APP_VENDOR"):
DEFINES["UNICODE"] = True
DEFINES["_UNICODE"] = True
-FINAL_TARGET_FILES += ["defaultagent.ini"]
-
FINAL_LIBRARY = "xul"
if CONFIG["ENABLE_TESTS"]:
diff --git a/toolkit/mozapps/defaultagent/nsIDefaultAgent.idl b/toolkit/mozapps/defaultagent/nsIDefaultAgent.idl
index 7e78e1b30d..8472dea3af 100644
--- a/toolkit/mozapps/defaultagent/nsIDefaultAgent.idl
+++ b/toolkit/mozapps/defaultagent/nsIDefaultAgent.idl
@@ -52,20 +52,6 @@ interface nsIDefaultAgent : nsISupports
void uninstall(in AString aUniqueToken);
/**
- * Actually performs the default agent task, which currently means generating
- * and sending our telemetry ping and possibly showing a notification to the
- * user if their browser has switched from Firefox to Edge with Blink.
- *
- * @param {AString} aUniqueToken
- * A unique identifier for this installation; the same one provided when
- * the task was registered.
- * @param {boolean} aForce
- * For debugging, forces the task to run even if it has run in the last
- * 24 hours, and forces the notification to show.
- */
- void doTask(in AString aUniqueToken, in boolean aForce);
-
- /**
* Checks that the main app ran recently.
*
* @return {boolean} true if the app ran recently.
diff --git a/toolkit/mozapps/defaultagent/proxy/main.cpp b/toolkit/mozapps/defaultagent/proxy/main.cpp
index 53efdcb9ae..7ba63c2095 100644
--- a/toolkit/mozapps/defaultagent/proxy/main.cpp
+++ b/toolkit/mozapps/defaultagent/proxy/main.cpp
@@ -7,10 +7,8 @@
#include <windows.h>
#include <shlwapi.h>
#include <objbase.h>
-#include <string.h>
#include <filesystem>
-#include "../ScheduledTask.h"
#include "../ScheduledTaskRemove.h"
#include "mozilla/CmdLineAndEnvUtils.h"
diff --git a/toolkit/mozapps/defaultagent/proxy/moz.build b/toolkit/mozapps/defaultagent/proxy/moz.build
index 40d7655dfa..3ff6932ac2 100644
--- a/toolkit/mozapps/defaultagent/proxy/moz.build
+++ b/toolkit/mozapps/defaultagent/proxy/moz.build
@@ -17,7 +17,6 @@ UNIFIED_SOURCES += [
SOURCES += [
"/browser/components/shell/WindowsDefaultBrowser.cpp",
"/other-licenses/nsis/Contrib/CityHash/cityhash/city.cpp",
- "/toolkit/mozapps/update/common/readstrings.cpp",
]
LOCAL_INCLUDES += [
diff --git a/toolkit/mozapps/downloads/DownloadLastDir.sys.mjs b/toolkit/mozapps/downloads/DownloadLastDir.sys.mjs
index da0439dcc5..dfd5d2cd26 100644
--- a/toolkit/mozapps/downloads/DownloadLastDir.sys.mjs
+++ b/toolkit/mozapps/downloads/DownloadLastDir.sys.mjs
@@ -48,7 +48,7 @@ var observer = {
"nsISupportsWeakReference",
]),
- observe(aSubject, aTopic, aData) {
+ observe(aSubject, aTopic) {
switch (aTopic) {
case "last-pb-context-exited":
gDownloadLastDirFile = null;
diff --git a/toolkit/mozapps/downloads/DownloadUtils.sys.mjs b/toolkit/mozapps/downloads/DownloadUtils.sys.mjs
index 3bf97f3c9e..6684a73699 100644
--- a/toolkit/mozapps/downloads/DownloadUtils.sys.mjs
+++ b/toolkit/mozapps/downloads/DownloadUtils.sys.mjs
@@ -610,7 +610,7 @@ function convertTimeUnitsUnits(timeValue, aIndex) {
* Error message to log or an array of strings to concat
*/
// function log(aMsg) {
-// let msg = "DownloadUtils.jsm: " + (aMsg.join ? aMsg.join("") : aMsg);
+// let msg = "DownloadUtils.sys.mjs: " + (aMsg.join ? aMsg.join("") : aMsg);
// Services.console.logStringMessage(msg);
// dump(msg + "\n");
// }
diff --git a/toolkit/mozapps/downloads/HelperAppDlg.sys.mjs b/toolkit/mozapps/downloads/HelperAppDlg.sys.mjs
index 66f77d38e4..01d1a518d8 100644
--- a/toolkit/mozapps/downloads/HelperAppDlg.sys.mjs
+++ b/toolkit/mozapps/downloads/HelperAppDlg.sys.mjs
@@ -72,33 +72,19 @@ nsUnknownContentTypeDialogProgressListener.prototype = {
},
// Ignore onProgressChange, onProgressChange64, onStateChange, onLocationChange, onSecurityChange, onContentBlockingEvent and onRefreshAttempted notifications.
- onProgressChange(
- aWebProgress,
- aRequest,
- aCurSelfProgress,
- aMaxSelfProgress,
- aCurTotalProgress,
- aMaxTotalProgress
- ) {},
+ onProgressChange() {},
- onProgressChange64(
- aWebProgress,
- aRequest,
- aCurSelfProgress,
- aMaxSelfProgress,
- aCurTotalProgress,
- aMaxTotalProgress
- ) {},
+ onProgressChange64() {},
- onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {},
+ onStateChange() {},
- onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {},
+ onLocationChange() {},
- onSecurityChange(aWebProgress, aRequest, aState) {},
+ onSecurityChange() {},
- onContentBlockingEvent(aWebProgress, aRequest, aEvent) {},
+ onContentBlockingEvent() {},
- onRefreshAttempted(aWebProgress, aURI, aDelay, aSameURI) {
+ onRefreshAttempted() {
return true;
},
};
@@ -309,7 +295,7 @@ nsUnknownContentTypeDialog.prototype = {
var picker =
Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
var windowTitle = bundle.GetStringFromName("saveDialogTitle");
- picker.init(parent, windowTitle, nsIFilePicker.modeSave);
+ picker.init(parent.browsingContext, windowTitle, nsIFilePicker.modeSave);
if (aDefaultFileName) {
picker.defaultString = this.getFinalLeafName(aDefaultFileName);
}
@@ -1270,7 +1256,7 @@ nsUnknownContentTypeDialog.prototype = {
var nsIFilePicker = Ci.nsIFilePicker;
var fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
fp.init(
- this.mDialog,
+ this.mDialog.browsingContext,
this.dialogElement("strings").getString("chooseAppFilePickerTitle"),
nsIFilePicker.modeOpen
);
diff --git a/toolkit/mozapps/downloads/tests/browser/browser_save_wrongextension.js b/toolkit/mozapps/downloads/tests/browser/browser_save_wrongextension.js
index 83e668076a..ad83bb3689 100644
--- a/toolkit/mozapps/downloads/tests/browser/browser_save_wrongextension.js
+++ b/toolkit/mozapps/downloads/tests/browser/browser_save_wrongextension.js
@@ -8,7 +8,7 @@ let url =
"data:text/html,<a id='link' href='http://localhost:8000/thefile.js'>Link</a>";
let MockFilePicker = SpecialPowers.MockFilePicker;
-MockFilePicker.init(window);
+MockFilePicker.init(window.browsingContext);
let httpServer = null;
diff --git a/toolkit/mozapps/downloads/tests/browser/browser_unknownContentType_delayedbutton.js b/toolkit/mozapps/downloads/tests/browser/browser_unknownContentType_delayedbutton.js
index 06a5a9d238..9bd494e245 100644
--- a/toolkit/mozapps/downloads/tests/browser/browser_unknownContentType_delayedbutton.js
+++ b/toolkit/mozapps/downloads/tests/browser/browser_unknownContentType_delayedbutton.js
@@ -13,14 +13,14 @@ let UCTObserver = {
opened: Promise.withResolvers(),
closed: Promise.withResolvers(),
- observe(aSubject, aTopic, aData) {
+ observe(aSubject, aTopic) {
let win = aSubject;
switch (aTopic) {
case "domwindowopened":
win.addEventListener(
"load",
- function onLoad(event) {
+ function onLoad() {
// Let the dialog initialize
SimpleTest.executeSoon(function () {
UCTObserver.opened.resolve(win);
@@ -40,7 +40,7 @@ let UCTObserver = {
};
function waitDelay(delay) {
- return new Promise((resolve, reject) => {
+ return new Promise(resolve => {
/* eslint-disable mozilla/no-arbitrary-setTimeout */
window.setTimeout(resolve, delay);
});
diff --git a/toolkit/mozapps/downloads/tests/browser/browser_unknownContentType_dialog_layout.js b/toolkit/mozapps/downloads/tests/browser/browser_unknownContentType_dialog_layout.js
index 6755c1aadb..e42951696d 100644
--- a/toolkit/mozapps/downloads/tests/browser/browser_unknownContentType_dialog_layout.js
+++ b/toolkit/mozapps/downloads/tests/browser/browser_unknownContentType_dialog_layout.js
@@ -38,14 +38,14 @@ add_task(async function test_unknownContentType_dialog_layout() {
opened: Promise.withResolvers(),
closed: Promise.withResolvers(),
- observe(aSubject, aTopic, aData) {
+ observe(aSubject, aTopic) {
let win = aSubject;
switch (aTopic) {
case "domwindowopened":
win.addEventListener(
"load",
- function onLoad(event) {
+ function onLoad() {
// Let the dialog initialize
SimpleTest.executeSoon(function () {
UCTObserver.opened.resolve(win);
diff --git a/toolkit/mozapps/downloads/tests/unit/test_lowMinutes.js b/toolkit/mozapps/downloads/tests/unit/test_lowMinutes.js
index 786da28b75..9076fa7c76 100644
--- a/toolkit/mozapps/downloads/tests/unit/test_lowMinutes.js
+++ b/toolkit/mozapps/downloads/tests/unit/test_lowMinutes.js
@@ -20,7 +20,7 @@ const { DownloadUtils } = ChromeUtils.importESModule(
* @usage _("Hello World") -> prints "Hello World"
* @usage _(1, 2, 3) -> prints "1 2 3"
*/
-var _ = function (some, debug, text, to) {
+var _ = function () {
print(Array.from(arguments).join(" "));
};
diff --git a/toolkit/mozapps/extensions/AddonManager.sys.mjs b/toolkit/mozapps/extensions/AddonManager.sys.mjs
index e4e51885cf..c69cf4029e 100644
--- a/toolkit/mozapps/extensions/AddonManager.sys.mjs
+++ b/toolkit/mozapps/extensions/AddonManager.sys.mjs
@@ -1610,10 +1610,10 @@ var AddonManagerInternal = {
// 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.jsm) to
+ // 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.jsm. Thankfully, this is the only use
+ // 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.
diff --git a/toolkit/mozapps/extensions/content/aboutaddons.css b/toolkit/mozapps/extensions/content/aboutaddons.css
index 3f4e80797a..2f5157bea3 100644
--- a/toolkit/mozapps/extensions/content/aboutaddons.css
+++ b/toolkit/mozapps/extensions/content/aboutaddons.css
@@ -643,12 +643,18 @@ panel-item[action="report"]::part(button) {
text-decoration: none;
}
-.inline-options-stack {
- /* If the options browser triggers an alert we need room to show it. */
- min-height: 250px;
+.addon-inline-options {
width: 100%;
background-color: white;
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 {
diff --git a/toolkit/mozapps/extensions/content/aboutaddons.js b/toolkit/mozapps/extensions/content/aboutaddons.js
index 37687a8be7..39c4656210 100644
--- a/toolkit/mozapps/extensions/content/aboutaddons.js
+++ b/toolkit/mozapps/extensions/content/aboutaddons.js
@@ -1423,10 +1423,23 @@ class SidebarFooter extends HTMLElement {
labelL10nId: "addons-settings-button",
onClick: e => {
e.preventDefault();
- windowRoot.ownerGlobal.switchToTabHavingURI("about:preferences", true, {
- ignoreFragment: "whenComparing",
- triggeringPrincipal: systemPrincipal,
- });
+ let hasAboutSettings = windowRoot.ownerGlobal.switchToTabHavingURI(
+ "about:settings",
+ false,
+ {
+ ignoreFragment: "whenComparing",
+ }
+ );
+ if (!hasAboutSettings) {
+ windowRoot.ownerGlobal.switchToTabHavingURI(
+ "about:preferences",
+ true,
+ {
+ ignoreFragment: "whenComparing",
+ triggeringPrincipal: systemPrincipal,
+ }
+ );
+ }
},
});
@@ -1695,6 +1708,7 @@ class InlineOptionsBrowser extends HTMLElement {
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");
@@ -1731,10 +1745,7 @@ class InlineOptionsBrowser extends HTMLElement {
readyPromise = promiseEvent("load", browser, true);
}
- let stack = document.createXULElement("stack");
- stack.classList.add("inline-options-stack");
- stack.appendChild(browser);
- this.appendChild(stack);
+ this.appendChild(browser);
this.browser = browser;
// Force bindings to apply synchronously.
diff --git a/toolkit/mozapps/extensions/content/aboutaddonsCommon.js b/toolkit/mozapps/extensions/content/aboutaddonsCommon.js
index 739e7629d7..9315e35861 100644
--- a/toolkit/mozapps/extensions/content/aboutaddonsCommon.js
+++ b/toolkit/mozapps/extensions/content/aboutaddonsCommon.js
@@ -237,7 +237,11 @@ async function installAddonsFromFilePicker() {
]);
const nsIFilePicker = Ci.nsIFilePicker;
var fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
- fp.init(window, dialogTitle.value, nsIFilePicker.modeOpenMultiple);
+ fp.init(
+ window.browsingContext,
+ dialogTitle.value,
+ nsIFilePicker.modeOpenMultiple
+ );
try {
fp.appendFilter(filterName.value, "*.xpi;*.jar;*.zip");
fp.appendFilters(nsIFilePicker.filterAll);
diff --git a/toolkit/mozapps/extensions/internal/AddonRepository.sys.mjs b/toolkit/mozapps/extensions/internal/AddonRepository.sys.mjs
index e854e04b3c..e53e4af7a4 100644
--- a/toolkit/mozapps/extensions/internal/AddonRepository.sys.mjs
+++ b/toolkit/mozapps/extensions/internal/AddonRepository.sys.mjs
@@ -78,7 +78,7 @@ 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.jsm)
+// (Requires AddonManager.sys.mjs)
var logger = Log.repository.getLogger(LOGGER_ID);
function convertHTMLToPlainText(html) {
diff --git a/toolkit/mozapps/extensions/internal/AddonTestUtils.sys.mjs b/toolkit/mozapps/extensions/internal/AddonTestUtils.sys.mjs
index 7b30daa0e2..f53d32092d 100644
--- a/toolkit/mozapps/extensions/internal/AddonTestUtils.sys.mjs
+++ b/toolkit/mozapps/extensions/internal/AddonTestUtils.sys.mjs
@@ -298,7 +298,7 @@ export var AddonTestUtils = {
// And scan for changes at startup
Services.prefs.setIntPref("extensions.startupScanScopes", 15);
- // By default, don't cache add-ons in AddonRepository.jsm
+ // By default, don't cache add-ons in AddonRepository.sys.mjs
Services.prefs.setBoolPref("extensions.getAddons.cache.enabled", false);
// Point update checks to the local machine for fast failures
@@ -567,7 +567,7 @@ export var AddonTestUtils = {
},
overrideCertDB() {
- let verifyCert = async (file, result, cert, callback) => {
+ let verifyCert = async (file, result, signatureInfos, callback) => {
if (
result == Cr.NS_ERROR_SIGNED_JAR_NOT_SIGNED &&
!this.useRealCertChecks &&
@@ -606,7 +606,16 @@ export var AddonTestUtils = {
};
}
- return [callback, Cr.NS_OK, fakeCert];
+ return [
+ callback,
+ Cr.NS_OK,
+ [
+ {
+ signerCert: fakeCert,
+ signatureAlgorithm: Ci.nsIAppSignatureInfo.COSE_WITH_SHA256,
+ },
+ ],
+ ];
} catch (e) {
// If there is any error then just pass along the original results
} finally {
@@ -621,7 +630,7 @@ export var AddonTestUtils = {
}
}
- return [callback, result, cert];
+ return [callback, result, signatureInfos];
};
let FakeCertDB = {
@@ -644,10 +653,14 @@ export var AddonTestUtils = {
this._genuine.openSignedAppFileAsync(
root,
file,
- (result, zipReader, cert) => {
- verifyCert(file.clone(), result, cert, callback).then(
- ([callback, result, cert]) => {
- callback.openSignedAppFileFinished(result, zipReader, cert);
+ (result, zipReader, signatureInfos) => {
+ verifyCert(file.clone(), result, signatureInfos, callback).then(
+ ([callback, result, signatureInfos]) => {
+ callback.openSignedAppFileFinished(
+ result,
+ zipReader,
+ signatureInfos
+ );
}
);
}
@@ -1730,7 +1743,7 @@ export var AddonTestUtils = {
* @param {object} extension
* The return value of ExtensionTestUtils.loadExtension.
* For browser tests, see mochitest/tests/SimpleTest/ExtensionTestUtils.js
- * For xpcshell tests, see toolkit/components/extensions/ExtensionXPCShellUtils.jsm
+ * For xpcshell tests, see toolkit/components/extensions/ExtensionXPCShellUtils.sys.mjs
* @param {object} [options]
* Optional options.
* @param {boolean} [options.expectPending = false]
diff --git a/toolkit/mozapps/extensions/internal/AddonUpdateChecker.sys.mjs b/toolkit/mozapps/extensions/internal/AddonUpdateChecker.sys.mjs
index a3935a26f9..5b7b10a764 100644
--- a/toolkit/mozapps/extensions/internal/AddonUpdateChecker.sys.mjs
+++ b/toolkit/mozapps/extensions/internal/AddonUpdateChecker.sys.mjs
@@ -26,7 +26,7 @@ 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.jsm)
+// (Requires AddonManager.sys.mjs)
var logger = Log.repository.getLogger(LOGGER_ID);
/**
diff --git a/toolkit/mozapps/extensions/internal/SitePermsAddonProvider.sys.mjs b/toolkit/mozapps/extensions/internal/SitePermsAddonProvider.sys.mjs
index ccac484a1e..7ca952dc8a 100644
--- a/toolkit/mozapps/extensions/internal/SitePermsAddonProvider.sys.mjs
+++ b/toolkit/mozapps/extensions/internal/SitePermsAddonProvider.sys.mjs
@@ -148,20 +148,30 @@ class SitePermsAddonWrapper {
});
}
- get creator() {}
+ get creator() {
+ return undefined;
+ }
- get homepageURL() {}
+ get homepageURL() {
+ return undefined;
+ }
- get description() {}
+ get description() {
+ return undefined;
+ }
- get fullDescription() {}
+ 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() {}
+ get updateDate() {
+ return undefined;
+ }
get isActive() {
return true;
@@ -273,8 +283,6 @@ class SitePermsAddonWrapper {
}
class SitePermsAddonInstalling extends SitePermsAddonWrapper {
- #install = null;
-
/**
* @param {string} siteOriginNoSuffix: The origin this addon is installed
* for, WITHOUT the suffix generated from
@@ -293,7 +301,6 @@ class SitePermsAddonInstalling extends SitePermsAddonWrapper {
};
super(siteOriginNoSuffix, [permission]);
- this.#install = install;
}
get existingAddon() {
diff --git a/toolkit/mozapps/extensions/internal/XPIDatabase.sys.mjs b/toolkit/mozapps/extensions/internal/XPIDatabase.sys.mjs
index 5d1d2c1970..af0b02444a 100644
--- a/toolkit/mozapps/extensions/internal/XPIDatabase.sys.mjs
+++ b/toolkit/mozapps/extensions/internal/XPIDatabase.sys.mjs
@@ -117,7 +117,7 @@ const nsIFile = Components.Constructor(
);
// Create a new logger for use by the Addons XPI Provider Utils
-// (Requires AddonManager.jsm)
+// (Requires AddonManager.sys.mjs)
var logger = Log.repository.getLogger(LOGGER_ID);
const FILE_JSON_DB = "extensions.json";
diff --git a/toolkit/mozapps/extensions/internal/XPIInstall.sys.mjs b/toolkit/mozapps/extensions/internal/XPIInstall.sys.mjs
index 1a80407ad2..0402c2f2ca 100644
--- a/toolkit/mozapps/extensions/internal/XPIInstall.sys.mjs
+++ b/toolkit/mozapps/extensions/internal/XPIInstall.sys.mjs
@@ -99,6 +99,17 @@ 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.
@@ -168,7 +179,7 @@ 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.jsm)
+// (Requires AddonManager.sys.mjs)
var logger = Log.repository.getLogger(LOGGER_ID);
// Stores the ID of the theme which was selected during the last session,
@@ -316,13 +327,22 @@ XPIPackage = class XPIPackage extends Package {
verifySignedStateForRoot(addonId, root) {
return new Promise(resolve => {
let callback = {
- openSignedAppFileFinished(aRv, aZipReader, aCert) {
+ 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({
- signedState: getSignedStatus(aRv, aCert, addonId),
- cert: aCert,
+ signedState: getSignedStatus(aRv, cert, addonId),
+ cert,
});
},
};
@@ -872,10 +892,7 @@ function shouldVerifySignedState(aAddonType, aLocation) {
return true;
}
- if (
- aLocation.isBuiltin ||
- aLocation.scope & AppConstants.MOZ_UNSIGNED_SCOPES
- ) {
+ if (aLocation.isBuiltin || aLocation.scope & lazy.MOZ_UNSIGNED_SCOPES) {
return false;
}
diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.sys.mjs b/toolkit/mozapps/extensions/internal/XPIProvider.sys.mjs
index d5ffd06d11..12d4fa1172 100644
--- a/toolkit/mozapps/extensions/internal/XPIProvider.sys.mjs
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.sys.mjs
@@ -185,7 +185,7 @@ 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.jsm)
+// (Requires AddonManager.sys.mjs)
var logger = Log.repository.getLogger(LOGGER_ID);
/**
diff --git a/toolkit/mozapps/extensions/test/browser/browser.toml b/toolkit/mozapps/extensions/test/browser/browser.toml
index 1daf6211f8..0b4a2d8f0a 100644
--- a/toolkit/mozapps/extensions/test/browser/browser.toml
+++ b/toolkit/mozapps/extensions/test/browser/browser.toml
@@ -156,6 +156,8 @@ https_first_disabled = true
["browser_sidebar_hidden_categories.js"]
+["browser_sidebar_preferences_button.js"]
+
["browser_sidebar_restore_category.js"]
["browser_subframe_install.js"]
diff --git a/toolkit/mozapps/extensions/test/browser/browser_html_options_ui.js b/toolkit/mozapps/extensions/test/browser/browser_html_options_ui.js
index c5bfa1022f..efe28cf218 100644
--- a/toolkit/mozapps/extensions/test/browser/browser_html_options_ui.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_html_options_ui.js
@@ -115,13 +115,7 @@ add_task(async function testInlineOptions() {
"The browser has the expected options URL"
);
is(url, card.addon.optionsURL, "Browser has the expected options URL loaded");
- let stack = browser.closest("stack");
- is(
- browser.clientWidth,
- stack.clientWidth,
- "Browser should be the same width as its direct parent"
- );
- Assert.greater(stack.clientWidth, 0, "The stack has a width");
+ Assert.greater(browser.clientWidth, 0, "The browser has a width");
ok(
card.querySelector('[action="preferences"]').hidden,
"The preferences option is hidden now"
@@ -163,8 +157,7 @@ add_task(async function testInlineOptions() {
info("Switch back, check browser is shown");
prefsBtn.click();
- is(browser.clientWidth, stack.clientWidth, "The browser width is set again");
- Assert.greater(stack.clientWidth, 0, "The stack has a width");
+ Assert.greater(browser.clientWidth, 0, "The browser has a width");
await closeView(win);
await extension.unload();
diff --git a/toolkit/mozapps/extensions/test/browser/browser_page_options_install_addon.js b/toolkit/mozapps/extensions/test/browser/browser_page_options_install_addon.js
index 5007731927..7bc7c08345 100644
--- a/toolkit/mozapps/extensions/test/browser/browser_page_options_install_addon.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_page_options_install_addon.js
@@ -5,7 +5,7 @@
// Tests bug 567127 - Add install button to the add-ons manager
var MockFilePicker = SpecialPowers.MockFilePicker;
-MockFilePicker.init(window);
+MockFilePicker.init(window.browsingContext);
async function checkInstallConfirmation(...names) {
let notificationCount = 0;
diff --git a/toolkit/mozapps/extensions/test/browser/browser_sidebar_preferences_button.js b/toolkit/mozapps/extensions/test/browser/browser_sidebar_preferences_button.js
new file mode 100644
index 0000000000..91a631bca2
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/browser/browser_sidebar_preferences_button.js
@@ -0,0 +1,73 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function testNoOtherTabsPresent() {
+ let addonsWin = await loadInitialView("extension");
+ let preferencesButton =
+ addonsWin.document.querySelector("#preferencesButton");
+
+ let preferencesPromise = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ "about:preferences"
+ );
+
+ preferencesButton.click();
+
+ let preferencesTab = await preferencesPromise;
+
+ is(
+ gBrowser.currentURI.spec,
+ "about:preferences",
+ "about:preferences should open if neither it nor about:settings are present"
+ );
+
+ gBrowser.removeTab(preferencesTab);
+
+ await closeView(addonsWin);
+});
+
+async function ensurePreferencesButtonFocusesTab(expectedUri) {
+ let addonsWin = await loadInitialView("extension");
+ let preferencesButton =
+ addonsWin.document.querySelector("#preferencesButton");
+
+ let tabCountBeforeClick = gBrowser.tabCount;
+ preferencesButton.click();
+ let tabCountAfterClick = gBrowser.tabCount;
+
+ is(
+ tabCountAfterClick,
+ tabCountBeforeClick,
+ "preferences button should not open new tabs"
+ );
+ is(
+ gBrowser.currentURI.spec,
+ expectedUri,
+ "the correct tab should be focused"
+ );
+
+ addonsWin.focus();
+ await closeView(addonsWin);
+}
+
+add_task(async function testAboutPreferencesPresent() {
+ await BrowserTestUtils.withNewTab("about:preferences", async () => {
+ await ensurePreferencesButtonFocusesTab("about:preferences");
+ });
+});
+
+add_task(async function testAboutSettingsPresent() {
+ await BrowserTestUtils.withNewTab("about:settings", async () => {
+ await ensurePreferencesButtonFocusesTab("about:settings");
+ });
+});
+
+add_task(async function testAboutSettingsAndPreferencesPresent() {
+ await BrowserTestUtils.withNewTab("about:settings", async () => {
+ await BrowserTestUtils.withNewTab("about:preferences", async () => {
+ await ensurePreferencesButtonFocusesTab("about:settings");
+ });
+ });
+});
diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_corrupt.js b/toolkit/mozapps/extensions/test/xpcshell/test_corrupt.js
index 727c643763..33e81c13bd 100644
--- a/toolkit/mozapps/extensions/test/xpcshell/test_corrupt.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_corrupt.js
@@ -196,7 +196,7 @@ add_task(async function test_after_corruption() {
await Assert.rejects(
promiseShutdownManager(),
- /NotAllowedError: Could not open the file at .+ for writing/
+ /NotAllowedError: Could not write to `.+'/
);
});
@@ -211,6 +211,6 @@ add_task(async function test_after_second_restart() {
await Assert.rejects(
promiseShutdownManager(),
- /NotAllowedError: Could not open the file at .+ for writing/
+ /NotAllowedError: Could not write to `.+'/
);
});
diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_system_update_enterprisepolicy.js b/toolkit/mozapps/extensions/test/xpcshell/test_system_update_enterprisepolicy.js
index d36b97a3cc..e25890515f 100644
--- a/toolkit/mozapps/extensions/test/xpcshell/test_system_update_enterprisepolicy.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_system_update_enterprisepolicy.js
@@ -44,12 +44,32 @@ const TEST_CONDITIONS = {
add_task(async function test_update_disabled_by_policy() {
await setupSystemAddonConditions(TEST_CONDITIONS, distroDir);
+ const TEST_POLICY_DATA = {
+ DisableSystemAddonUpdate: true,
+ };
await EnterprisePolicyTesting.setupPolicyEngineWithJson({
- policies: {
- DisableSystemAddonUpdate: true,
- },
+ policies: TEST_POLICY_DATA,
});
+ Assert.deepEqual(
+ Services.policies.getActivePolicies(),
+ TEST_POLICY_DATA,
+ "Got the expected test policy data as the active policy " +
+ "(if this assertions fails, check your system for enterprise policies installed at system level)"
+ );
+
+ Assert.equal(
+ Services.policies.isAllowed("SysAddonUpdate"),
+ false,
+ "Expected SysAddonUpdate feature to be disabled by policies"
+ );
+
+ Assert.equal(
+ Services.prefs.getBoolPref("extensions.systemAddon.update.enabled"),
+ true,
+ "Expected system addon updates to not be already disabled through prefs"
+ );
+
await updateAllSystemAddons(
buildSystemAddonUpdates([
{
diff --git a/toolkit/mozapps/handling/content/appChooser.js b/toolkit/mozapps/handling/content/appChooser.js
index 2958ad68b4..6c04717783 100644
--- a/toolkit/mozapps/handling/content/appChooser.js
+++ b/toolkit/mozapps/handling/content/appChooser.js
@@ -225,7 +225,7 @@ let dialog = {
let title = await this.getChooseAppWindowTitle();
var fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
- fp.init(window, title, Ci.nsIFilePicker.modeOpen);
+ fp.init(window.browsingContext, title, Ci.nsIFilePicker.modeOpen);
fp.appendFilters(Ci.nsIFilePicker.filterApps);
fp.open(rv => {
diff --git a/toolkit/mozapps/installer/packager.py b/toolkit/mozapps/installer/packager.py
index 29364045dd..f9d28d9c0c 100644
--- a/toolkit/mozapps/installer/packager.py
+++ b/toolkit/mozapps/installer/packager.py
@@ -63,7 +63,7 @@ class RemovedFiles(GeneratedFile):
return
if self.copier.contains(f):
errors.error("Removal of packaged file(s): %s" % f)
- self.content = f + "\n"
+ self.content += f.encode("utf-8") + b"\n"
def split_define(define):
diff --git a/toolkit/mozapps/update/AppUpdater.sys.mjs b/toolkit/mozapps/update/AppUpdater.sys.mjs
index 6133fb613b..f837982b84 100644
--- a/toolkit/mozapps/update/AppUpdater.sys.mjs
+++ b/toolkit/mozapps/update/AppUpdater.sys.mjs
@@ -411,20 +411,6 @@ export class AppUpdater {
return Services.sysinfo.getProperty("isPackagedApp");
}
- // true when updating in background is enabled.
- get #updateStagingEnabled() {
- LOG(
- "AppUpdater:#updateStagingEnabled" +
- "canStageUpdates: " +
- this.aus.canStageUpdates
- );
- return (
- !this.aus.disabled &&
- !this.#updateDisabledByPackage &&
- this.aus.canStageUpdates
- );
- }
-
/**
* Downloads an update mar or connects to an in-progress download.
* Doesn't resolve until the update is ready to install, or a failure state
diff --git a/toolkit/mozapps/update/BackgroundUpdate.sys.mjs b/toolkit/mozapps/update/BackgroundUpdate.sys.mjs
index 28d0fc8538..3022c9ffde 100644
--- a/toolkit/mozapps/update/BackgroundUpdate.sys.mjs
+++ b/toolkit/mozapps/update/BackgroundUpdate.sys.mjs
@@ -570,7 +570,7 @@ export var BackgroundUpdate = {
};
try {
- // Interacting with `TaskScheduler.jsm` can throw, so we'll catch.
+ // Interacting with `TaskScheduler.sys.mjs` can throw, so we'll catch.
if (!enabled) {
lazy.log.info(
`${SLUG}: not scheduling background update: '${JSON.stringify(
diff --git a/toolkit/mozapps/update/UpdateService.sys.mjs b/toolkit/mozapps/update/UpdateService.sys.mjs
index 16ebf64ef3..34bf1b9e19 100644
--- a/toolkit/mozapps/update/UpdateService.sys.mjs
+++ b/toolkit/mozapps/update/UpdateService.sys.mjs
@@ -193,7 +193,7 @@ const WRITE_ERROR_DIR_ACCESS_DENIED = 68;
const WRITE_ERROR_DELETE_BACKUP = 69;
const WRITE_ERROR_EXTRACT = 70;
-// Error codes 80 through 99 are reserved for UpdateService.jsm and are not
+// Error codes 80 through 99 are reserved for UpdateService.sys.mjs and are not
// defined in common/updatererrors.h
const ERR_UPDATER_CRASHED = 89;
const ERR_OLDER_VERSION_OR_SAME_BUILD = 90;
@@ -440,7 +440,7 @@ let gOtherInstancePollPromise;
*
* @return true if at least one other instance is running, false if not
*/
-function isOtherInstanceRunning(callback) {
+function isOtherInstanceRunning() {
const checkEnabled = Services.prefs.getBoolPref(
PREF_APP_UPDATE_CHECK_ONLY_INSTANCE_ENABLED,
true
@@ -497,7 +497,7 @@ function waitForOtherInstances() {
let iterations = 0;
const maxIterations = Math.ceil(timeout / interval);
- gOtherInstancePollPromise = new Promise(function (resolve, reject) {
+ gOtherInstancePollPromise = new Promise(function (resolve) {
let poll = function () {
iterations++;
if (!isOtherInstanceRunning()) {
@@ -1907,7 +1907,7 @@ function pingStateAndStatusCodes(aUpdate, aStartup, aStatus) {
stateCode = 13;
break;
// Note: Do not use stateCode 14 here. It is defined in
- // UpdateTelemetry.jsm
+ // UpdateTelemetry.sys.mjs
default:
stateCode = 1;
}
@@ -3352,10 +3352,10 @@ UpdateService.prototype = {
/**
* Notified when a timer fires
- * @param timer
+ * @param _timer
* The timer that fired
*/
- notify: function AUS_notify(timer) {
+ notify: function AUS_notify(_timer) {
this._checkForBackgroundUpdates(true);
},
@@ -3399,7 +3399,7 @@ UpdateService.prototype = {
// See Bug 1599590.
// Note that we exit unconditionally here if we are only doing manual
// update checks, because manual update checking uses a completely
- // different code path (AppUpdater.jsm creates its own nsIUpdateChecker),
+ // different code path (AppUpdater.sys.mjs creates its own nsIUpdateChecker),
// bypassing this function completely.
AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_DISABLED_BY_POLICY);
return false;
@@ -4907,7 +4907,7 @@ UpdateManager.prototype = {
/**
* See nsIUpdateService.idl
*/
- doInstallCleanup: async function UM_doInstallCleanup(isUninstall) {
+ doInstallCleanup: async function UM_doInstallCleanup() {
LOG("UpdateManager:doInstallCleanup - cleaning up");
let completionPromises = [];
@@ -4949,7 +4949,7 @@ UpdateManager.prototype = {
/**
* See nsIUpdateService.idl
*/
- doUninstallCleanup: async function UM_doUninstallCleanup(isUninstall) {
+ doUninstallCleanup: async function UM_doUninstallCleanup() {
LOG("UpdateManager:doUninstallCleanup - cleaning up.");
let completionPromises = [];
@@ -5272,13 +5272,13 @@ export class CheckerService {
return;
}
- let onLoad = event => {
+ let onLoad = _event => {
request.removeEventListener("load", onLoad);
LOG("CheckerService:#updateCheck - request got 'load' event");
resolve(UPDATE_CHECK_LOAD_SUCCESS);
};
request.addEventListener("load", onLoad);
- let onError = event => {
+ let onError = _event => {
request.removeEventListener("error", onLoad);
LOG("CheckerService:#updateCheck - request got 'error' event");
resolve(UPDATE_CHECK_LOAD_ERROR);
@@ -5702,11 +5702,9 @@ Downloader.prototype = {
* set of update patches.
* @param update
* A nsIUpdate object to select a patch from
- * @param updateDir
- * A nsIFile representing the update directory
* @return A nsIUpdatePatch object to download
*/
- _selectPatch: function Downloader__selectPatch(update, updateDir) {
+ _selectPatch: function Downloader__selectPatch(update) {
// Given an update to download, we will always try to download the patch
// for a partial update over the patch for a full update.
@@ -5934,7 +5932,7 @@ Downloader.prototype = {
// This function may return null, which indicates that there are no patches
// to download.
- this._patch = this._selectPatch(update, updateDir);
+ this._patch = this._selectPatch(update);
if (!this._patch) {
LOG("Downloader:downloadUpdate - no patch to download");
AUSTLMY.pingDownloadCode(undefined, AUSTLMY.DWNLD_ERR_NO_UPDATE_PATCH);
diff --git a/toolkit/mozapps/update/UpdateTelemetry.sys.mjs b/toolkit/mozapps/update/UpdateTelemetry.sys.mjs
index 20fb0ab4a4..f3c350267c 100644
--- a/toolkit/mozapps/update/UpdateTelemetry.sys.mjs
+++ b/toolkit/mozapps/update/UpdateTelemetry.sys.mjs
@@ -316,7 +316,7 @@ export var AUSTLMY = {
* This value is also used to determine the key for the keyed scalar
* update.bitshresult (key is either "COMPLETE" or "PARTIAL")
* @param aError
- * The BitsError that occurred. See Bits.jsm for details on BitsError.
+ * The BitsError that occurred. See Bits.sys.mjs for details on BitsError.
*/
pingBitsError: function UT_pingBitsError(aIsComplete, aError) {
if (AppConstants.platform != "win") {
diff --git a/toolkit/mozapps/update/common/updatererrors.h b/toolkit/mozapps/update/common/updatererrors.h
index f2663d5b57..d3abf14ffe 100644
--- a/toolkit/mozapps/update/common/updatererrors.h
+++ b/toolkit/mozapps/update/common/updatererrors.h
@@ -10,7 +10,7 @@
#define OK 0
// Error codes that are no longer used should not be used again unless they
-// aren't used in client code (e.g. UpdateService.jsm, updates.js, etc.).
+// aren't used in client code (e.g. UpdateService.sys.mjs, updates.js, etc.).
#define MAR_ERROR_EMPTY_ACTION_LIST 1
#define LOADSOURCE_ERROR_WRONG_SIZE 2
@@ -26,8 +26,8 @@
// Error codes 10-14 are related to memory allocation failures.
// Note: If more memory allocation error codes are added, the implementation of
-// isMemoryAllocationErrorCode in UpdateService.jsm should be updated to account
-// for them.
+// isMemoryAllocationErrorCode in UpdateService.sys.mjs should be updated to
+// account for them.
#define READ_STRINGS_MEM_ERROR 10
#define ARCHIVE_READER_MEM_ERROR 11
#define BSPATCH_MEM_ERROR 12
@@ -49,8 +49,8 @@
// Error codes 24-33 and 49-58 are for the Windows maintenance service.
// Note: If more maintenance service error codes are added, the implementations
-// of IsServiceSpecificErrorCode in updater.cpp and UpdateService.jsm should be
-// updated to account for them.
+// of IsServiceSpecificErrorCode in updater.cpp and UpdateService.sys.mjs should
+// be updated to account for them.
#define SERVICE_UPDATER_COULD_NOT_BE_STARTED 24
#define SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS 25
#define SERVICE_UPDATER_SIGN_ERROR 26
@@ -79,8 +79,8 @@
// Error codes 24-33 and 49-58 are for the Windows maintenance service.
// Note: If more maintenance service error codes are added, the implementations
-// of IsServiceSpecificErrorCode in updater.cpp and UpdateService.jsm should be
-// updated to account for them.
+// of IsServiceSpecificErrorCode in updater.cpp and UpdateService.sys.mjs should
+// be updated to account for them.
#define SERVICE_COULD_NOT_COPY_UPDATER 49
#define SERVICE_STILL_APPLYING_TERMINATED 50
#define SERVICE_STILL_APPLYING_NO_EXIT_CODE 51
@@ -112,7 +112,7 @@
#define INVALID_CALLBACK_DIR_ERROR 78
#define UPDATE_STATUS_UNCHANGED 79
-// Error codes 80 through 99 are reserved for UpdateService.jsm
+// Error codes 80 through 99 are reserved for UpdateService.sys.mjs
// The following error codes are only used by updater.exe
// when a fallback key exists for tests.
@@ -125,6 +125,6 @@
#define SILENT_UPDATE_NEEDED_ELEVATION_ERROR 105
#define WRITE_ERROR_BACKGROUND_TASK_SHARING_VIOLATION 106
-// Error codes 110 and 111 are reserved for UpdateService.jsm
+// Error codes 110 and 111 are reserved for UpdateService.sys.mjs
#endif // UPDATEERRORS_H
diff --git a/toolkit/mozapps/update/docs/BackgroundUpdates.rst b/toolkit/mozapps/update/docs/BackgroundUpdates.rst
index 7f97f58c74..d8a154cb39 100644
--- a/toolkit/mozapps/update/docs/BackgroundUpdates.rst
+++ b/toolkit/mozapps/update/docs/BackgroundUpdates.rst
@@ -65,7 +65,7 @@ observe and control these settings.
But there are some other pieces of state which absolutely must come from a
profile, such as the telemetry client ID and logging level settings (see
-`BackgroundTasksUtils.jsm <https://searchfox.org/mozilla-central/source/toolkit/components/backgroundtasks/BackgroundTasksUtils.jsm>`__).
+`BackgroundTasksUtils.sys.mjs <https://searchfox.org/mozilla-central/source/toolkit/components/backgroundtasks/BackgroundTasksUtils.sys.mjs>`__).
This means that, in addition to our per-installation prefs, we also need
to be able to identify and load a profile. To do that, we leverage `the profile
@@ -137,7 +137,7 @@ API <https://docs.microsoft.com/en-us/windows/win32/taskschd/task-scheduler-star
on macOS this will use
`launchd <https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html>`__.
For platform-specific scheduling details, see the
-`TaskScheduler.jsm <https://searchfox.org/mozilla-central/source/toolkit/components/taskscheduler/TaskScheduler.jsm>`__
+`TaskScheduler.sys.mjs <https://searchfox.org/mozilla-central/source/toolkit/components/taskscheduler/TaskScheduler.sys.mjs>`__
module.
These background tasks are scheduled per OS user and run with that user’s
@@ -177,7 +177,7 @@ visible to users: see `bug 1775132
After setting up this profile and reading all the configuration we need
into it, the regular
-`UpdateService.jsm <https://searchfox.org/mozilla-central/source/toolkit/mozapps/update/UpdateService.jsm>`__
+`UpdateService.sys.mjs <https://searchfox.org/mozilla-central/source/toolkit/mozapps/update/UpdateService.sys.mjs>`__
check process is initiated. To the greatest extent possible, this process is
identical to what happens during any regular browsing session.
diff --git a/toolkit/mozapps/update/tests/browser/browser_elevationDialog.js b/toolkit/mozapps/update/tests/browser/browser_elevationDialog.js
index 6aa32a7fc9..639af1ffff 100644
--- a/toolkit/mozapps/update/tests/browser/browser_elevationDialog.js
+++ b/toolkit/mozapps/update/tests/browser/browser_elevationDialog.js
@@ -13,7 +13,7 @@ add_task(async function elevation_dialog() {
let { startup } = Services;
let appStartup = {
QueryInterface: ChromeUtils.generateQI(["nsIAppStartup"]),
- quit(mode) {
+ quit(_mode) {
if (elevationDialog) {
elevationDialog.close();
elevationDialog = null;
@@ -120,7 +120,7 @@ function waitForElevationDialog() {
var domwindow = aXULWindow.docShell.domWindow;
domwindow.addEventListener("load", elevationDialogOnLoad, true);
},
- onCloseWindow: aXULWindow => {},
+ onCloseWindow: _aXULWindow => {},
};
Services.wm.addListener(listener);
diff --git a/toolkit/mozapps/update/tests/browser/head.js b/toolkit/mozapps/update/tests/browser/head.js
index a18a72e581..c5acdad8e4 100644
--- a/toolkit/mozapps/update/tests/browser/head.js
+++ b/toolkit/mozapps/update/tests/browser/head.js
@@ -487,7 +487,7 @@ function waitForAboutDialog() {
var domwindow = aXULWindow.docShell.domWindow;
domwindow.addEventListener("load", aboutDialogOnLoad, true);
},
- onCloseWindow: aXULWindow => {},
+ onCloseWindow: _aXULWindow => {},
};
Services.wm.addListener(listener);
diff --git a/toolkit/mozapps/update/tests/data/app_update.sjs b/toolkit/mozapps/update/tests/data/app_update.sjs
index 2081118547..1d5d23bfb5 100644
--- a/toolkit/mozapps/update/tests/data/app_update.sjs
+++ b/toolkit/mozapps/update/tests/data/app_update.sjs
@@ -79,7 +79,7 @@ function handleRequest(aRequest, aResponse) {
let retries = 0;
gSlowDownloadTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
gSlowDownloadTimer.initWithCallback(
- function (aTimer) {
+ function (_aTimer) {
let continueFile = getTestDataFile(CONTINUE_DOWNLOAD);
retries++;
if (continueFile.exists() || retries == MAX_SLOW_RESPONSE_RETRIES) {
@@ -197,7 +197,7 @@ function respond(aResponse, aParams, aResponseString) {
aResponse.processAsync();
gSlowCheckTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
gSlowCheckTimer.initWithCallback(
- function (aTimer) {
+ function (_aTimer) {
retries++;
let continueFile = getTestDataFile(CONTINUE_CHECK);
if (continueFile.exists() || retries == MAX_SLOW_RESPONSE_RETRIES) {
diff --git a/toolkit/mozapps/update/tests/data/shared.js b/toolkit/mozapps/update/tests/data/shared.js
index fc3d358586..60de6feeb8 100644
--- a/toolkit/mozapps/update/tests/data/shared.js
+++ b/toolkit/mozapps/update/tests/data/shared.js
@@ -922,7 +922,7 @@ async function continueFileHandler(leafName) {
"Waiting for file to be deleted, path: " + continueFile.path,
interval,
retries
- ).catch(e => {
+ ).catch(_e => {
logTestInfo(
"Continue file was not removed after checking " +
retries +
diff --git a/toolkit/mozapps/update/tests/data/xpcshellUtilsAUS.js b/toolkit/mozapps/update/tests/data/xpcshellUtilsAUS.js
index e88a4418cb..4b12edf6f0 100644
--- a/toolkit/mozapps/update/tests/data/xpcshellUtilsAUS.js
+++ b/toolkit/mozapps/update/tests/data/xpcshellUtilsAUS.js
@@ -3766,7 +3766,7 @@ function checkFilesAfterUpdateSuccess(
"xattr value changed"
);
},
- reason => {
+ _reason => {
Assert.fail(MAC_APP_XATTR_KEY + " xattr is missing!");
}
);
@@ -4233,9 +4233,9 @@ async function waitForUpdateDownload(aUpdates, aExpectedStatus) {
}
return new Promise(resolve =>
gAUS.addDownloadListener({
- onStartRequest: aRequest => {},
- onProgress: (aRequest, aContext, aProgress, aMaxProgress) => {},
- onStatus: (aRequest, aStatus, aStatusText) => {},
+ onStartRequest: _aRequest => {},
+ onProgress: (_aRequest, _aContext, _aProgress, _aMaxProgress) => {},
+ onStatus: (_aRequest, _aStatus, _aStatusText) => {},
onStopRequest(request, status) {
gAUS.removeDownloadListener(this);
Assert.equal(
@@ -4552,7 +4552,7 @@ function adjustGeneralPaths() {
* The timer callback to kill the process if it takes too long.
*/
const gAppTimerCallback = {
- notify: function TC_notify(aTimer) {
+ notify: function TC_notify(_aTimer) {
gAppTimer = null;
if (gProcess.isRunning) {
logTestInfo("attempting to kill process");
@@ -4662,7 +4662,7 @@ function IncrementalDownload() {
IncrementalDownload.prototype = {
/* nsIIncrementalDownload */
- init(uri, file, chunkSize, intervalInSeconds) {
+ init(uri, file, _chunkSize, _intervalInSeconds) {
this._destination = file;
this._URI = uri;
this._finalURI = uri;
@@ -4729,7 +4729,7 @@ IncrementalDownload.prototype = {
},
/* nsIRequest */
- cancel(aStatus) {
+ cancel(_aStatus) {
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
},
suspend() {
diff --git a/toolkit/mozapps/update/tests/marionette/test_no_window_update_restart.py b/toolkit/mozapps/update/tests/marionette/test_no_window_update_restart.py
index a8495064ef..9452a4fcd7 100644
--- a/toolkit/mozapps/update/tests/marionette/test_no_window_update_restart.py
+++ b/toolkit/mozapps/update/tests/marionette/test_no_window_update_restart.py
@@ -162,7 +162,9 @@ class TestNoWindowUpdateRestart(MarionetteTestCase):
let UM = Cc["@mozilla.org/updates/update-manager;1"].getService(Ci.nsIUpdateManager);
UM.QueryInterface(Ci.nsIObserver).observe(null, "um-reload-update-data", "skip-files");
- let { UpdateListener } = ChromeUtils.import("resource://gre/modules/UpdateListener.jsm");
+ let { UpdateListener } = ChromeUtils.importESModule(
+ "resource://gre/modules/UpdateListener.sys.mjs"
+ );
UpdateListener.reset();
let { AppMenuNotifications } = ChromeUtils.importESModule(
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/languagePackUpdates.js b/toolkit/mozapps/update/tests/unit_aus_update/languagePackUpdates.js
index 0ae35ef77a..9e23fab5e0 100644
--- a/toolkit/mozapps/update/tests/unit_aus_update/languagePackUpdates.js
+++ b/toolkit/mozapps/update/tests/unit_aus_update/languagePackUpdates.js
@@ -243,9 +243,9 @@ add_task(async function testRedownload() {
let downloadCount = 0;
let listener = {
- onStartRequest: aRequest => {},
- onProgress: (aRequest, aContext, aProgress, aMaxProgress) => {},
- onStatus: (aRequest, aStatus, aStatusText) => {},
+ onStartRequest: _aRequest => {},
+ onProgress: (_aRequest, _aContext, _aProgress, _aMaxProgress) => {},
+ onStatus: (_aRequest, _aStatus, _aStatusText) => {},
onStopRequest: (request, status) => {
Assert.equal(
status,
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/multiUpdate.js b/toolkit/mozapps/update/tests/unit_aus_update/multiUpdate.js
index 3767a44feb..411b303331 100644
--- a/toolkit/mozapps/update/tests/unit_aus_update/multiUpdate.js
+++ b/toolkit/mozapps/update/tests/unit_aus_update/multiUpdate.js
@@ -73,14 +73,14 @@ async function downloadUpdate(appUpdateAuto, onDownloadStartCallback) {
}
let waitToStartPromise = new Promise(resolve => {
let listener = {
- onStartRequest: aRequest => {
+ onStartRequest: _aRequest => {
gAUS.removeDownloadListener(listener);
onDownloadStartCallback();
resolve();
},
- onProgress: (aRequest, aContext, aProgress, aMaxProgress) => {},
- onStatus: (aRequest, aStatus, aStatusText) => {},
- onStopRequest(request, status) {},
+ onProgress: (_aRequest, _aContext, _aProgress, _aMaxProgress) => {},
+ onStatus: (_aRequest, _aStatus, _aStatusText) => {},
+ onStopRequest(_request, _status) {},
QueryInterface: ChromeUtils.generateQI([
"nsIRequestObserver",
"nsIProgressEventSink",
@@ -149,7 +149,7 @@ async function testUpdateDoesNotDownload() {
);
let updateAvailableObserved = false;
- let observer = (subject, topic, status) => {
+ let observer = (_subject, _topic, _status) => {
updateAvailableObserved = true;
};
Services.obs.addObserver(observer, "update-available");
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/updateSyncManager.js b/toolkit/mozapps/update/tests/unit_aus_update/updateSyncManager.js
index 9123f9e1e3..c705d536a4 100644
--- a/toolkit/mozapps/update/tests/unit_aus_update/updateSyncManager.js
+++ b/toolkit/mozapps/update/tests/unit_aus_update/updateSyncManager.js
@@ -79,7 +79,7 @@ add_task(async function () {
await TestUtils.waitForCondition(
() => syncManager.isOtherInstanceRunning(),
"waiting for child process to take the lock"
- ).catch(e => {
+ ).catch(_e => {
// Rather than throwing out of waitForCondition(), catch and log the failure
// manually so that we get output that's a bit more readable.
Assert.ok(
@@ -93,7 +93,7 @@ add_task(async function () {
await TestUtils.waitForCondition(
() => !syncManager.isOtherInstanceRunning(),
"waiting for child process to release the lock"
- ).catch(e => {
+ ).catch(_e => {
Assert.ok(
!syncManager.isOtherInstanceRunning(),
"child process has released the lock"
diff --git a/toolkit/mozapps/update/tests/unit_background_update/test_backgroundupdate_glean.js b/toolkit/mozapps/update/tests/unit_background_update/test_backgroundupdate_glean.js
index 0d9c06b2d0..0ca74e4764 100644
--- a/toolkit/mozapps/update/tests/unit_background_update/test_backgroundupdate_glean.js
+++ b/toolkit/mozapps/update/tests/unit_background_update/test_backgroundupdate_glean.js
@@ -140,7 +140,7 @@ async function do_readTargeting(content, beforeNextSubmitCallback) {
// Missing targeting is anticipated.
add_task(async function test_targeting_missing() {
- await do_readTargeting(null, reason => {
+ await do_readTargeting(null, _reason => {
Assert.equal(false, Glean.backgroundUpdate.targetingExists.testGetValue());
Assert.equal(
@@ -152,7 +152,7 @@ add_task(async function test_targeting_missing() {
// Malformed JSON yields an exception.
add_task(async function test_targeting_exception() {
- await do_readTargeting("{", reason => {
+ await do_readTargeting("{", _reason => {
Assert.equal(false, Glean.backgroundUpdate.targetingExists.testGetValue());
Assert.equal(
@@ -193,7 +193,7 @@ add_task(async function test_targeting_exists() {
targets: [manager.createTargetingContext(), target],
});
- await do_readTargeting(JSON.stringify(targetSnapshot), reason => {
+ await do_readTargeting(JSON.stringify(targetSnapshot), _reason => {
Assert.equal(true, Glean.backgroundUpdate.targetingExists.testGetValue());
Assert.equal(
diff --git a/toolkit/mozapps/update/updater/updater.cpp b/toolkit/mozapps/update/updater/updater.cpp
index 947e84aac3..6b01450b31 100644
--- a/toolkit/mozapps/update/updater/updater.cpp
+++ b/toolkit/mozapps/update/updater/updater.cpp
@@ -3734,7 +3734,7 @@ int NS_main(int argc, NS_tchar** argv) {
}
// Set an error so we don't get into an update loop when the callback
// runs. This will be reset to pending by handleUpdateFailure in
- // UpdateService.jsm.
+ // UpdateService.sys.mjs.
WriteStatusFile(SILENT_UPDATE_NEEDED_ELEVATION_ERROR);
LOG(("Skipping update to avoid UAC prompt from background task."));
output_finish();
diff --git a/toolkit/profile/content/createProfileWizard.js b/toolkit/profile/content/createProfileWizard.js
index 9e87fb4220..ece321e6e1 100644
--- a/toolkit/profile/content/createProfileWizard.js
+++ b/toolkit/profile/content/createProfileWizard.js
@@ -127,7 +127,7 @@ function chooseProfileFolder() {
I.nsIFilePicker
);
dirChooser.init(
- window,
+ window.browsingContext,
gProfileManagerBundle.getString("chooseFolder"),
I.nsIFilePicker.modeGetFolder
);
diff --git a/toolkit/system/androidproxy/nsAndroidSystemProxySettings.cpp b/toolkit/system/androidproxy/nsAndroidSystemProxySettings.cpp
index e60d0f3821..15dabfe14f 100644
--- a/toolkit/system/androidproxy/nsAndroidSystemProxySettings.cpp
+++ b/toolkit/system/androidproxy/nsAndroidSystemProxySettings.cpp
@@ -43,6 +43,12 @@ nsresult nsAndroidSystemProxySettings::GetProxyForURI(const nsACString& aSpec,
aPort, aResult);
}
+NS_IMETHODIMP
+nsAndroidSystemProxySettings::GetSystemWPADSetting(bool* aSystemWPADSetting) {
+ *aSystemWPADSetting = false;
+ return NS_OK;
+}
+
void test(){};
NS_IMPL_COMPONENT_FACTORY(nsAndroidSystemProxySettings) {
diff --git a/toolkit/system/osxproxy/nsOSXSystemProxySettings.mm b/toolkit/system/osxproxy/nsOSXSystemProxySettings.mm
index b1ad41ccc1..27c7b47148 100644
--- a/toolkit/system/osxproxy/nsOSXSystemProxySettings.mm
+++ b/toolkit/system/osxproxy/nsOSXSystemProxySettings.mm
@@ -68,6 +68,12 @@ nsOSXSystemProxySettings::GetMainThreadOnly(bool* aMainThreadOnly) {
return NS_OK;
}
+NS_IMETHODIMP
+nsOSXSystemProxySettings::GetSystemWPADSetting(bool* aSystemWPADSetting) {
+ *aSystemWPADSetting = false;
+ return NS_OK;
+}
+
// Mapping of URI schemes to SystemConfiguration keys
const nsOSXSystemProxySettings::SchemeMapping
nsOSXSystemProxySettings::gSchemeMappingList[] = {
@@ -400,6 +406,11 @@ OSXSystemProxySettingsAsync::GetPACURI(nsACString& aResult) {
}
NS_IMETHODIMP
+OSXSystemProxySettingsAsync::GetSystemWPADSetting(bool* aSystemWPADSetting) {
+ return nsOSXSystemProxySettings::GetSystemWPADSetting(aSystemWPADSetting);
+}
+
+NS_IMETHODIMP
OSXSystemProxySettingsAsync::GetProxyForURI(const nsACString& aSpec,
const nsACString& aScheme,
const nsACString& aHost,
diff --git a/toolkit/system/unixproxy/nsUnixSystemProxySettings.cpp b/toolkit/system/unixproxy/nsUnixSystemProxySettings.cpp
index 1b6d6223c4..4c84041ef3 100644
--- a/toolkit/system/unixproxy/nsUnixSystemProxySettings.cpp
+++ b/toolkit/system/unixproxy/nsUnixSystemProxySettings.cpp
@@ -398,6 +398,12 @@ nsresult nsUnixSystemProxySettings::GetProxyForURI(const nsACString& aSpec,
return GetProxyFromEnvironment(aScheme, aHost, aPort, aResult);
}
+NS_IMETHODIMP
+nsUnixSystemProxySettings::GetSystemWPADSetting(bool* aSystemWPADSetting) {
+ *aSystemWPADSetting = false;
+ return NS_OK;
+}
+
NS_IMPL_COMPONENT_FACTORY(nsUnixSystemProxySettings) {
auto result = MakeRefPtr<nsUnixSystemProxySettings>();
result->Init();
diff --git a/toolkit/system/windowsproxy/nsWindowsSystemProxySettings.cpp b/toolkit/system/windowsproxy/nsWindowsSystemProxySettings.cpp
index 7921cb60a2..94487505c3 100644
--- a/toolkit/system/windowsproxy/nsWindowsSystemProxySettings.cpp
+++ b/toolkit/system/windowsproxy/nsWindowsSystemProxySettings.cpp
@@ -241,6 +241,19 @@ nsresult nsWindowsSystemProxySettings::GetProxyForURI(const nsACString& aSpec,
return NS_OK;
}
+NS_IMETHODIMP nsWindowsSystemProxySettings::GetSystemWPADSetting(
+ bool* aSystemWPADSetting) {
+ nsresult rv;
+ uint32_t flags = 0;
+ nsAutoString buf;
+
+ rv = ReadInternetOption(INTERNET_PER_CONN_AUTOCONFIG_URL, flags, buf);
+ *aSystemWPADSetting =
+ (flags & (PROXY_TYPE_AUTO_PROXY_URL | PROXY_TYPE_AUTO_DETECT)) ==
+ PROXY_TYPE_AUTO_DETECT;
+ return rv;
+}
+
NS_IMPL_COMPONENT_FACTORY(nsWindowsSystemProxySettings) {
return mozilla::MakeAndAddRef<nsWindowsSystemProxySettings>()
.downcast<nsISupports>();
diff --git a/toolkit/themes/linux/global/global.css b/toolkit/themes/linux/global/global.css
index 4194e877b2..594bc81285 100644
--- a/toolkit/themes/linux/global/global.css
+++ b/toolkit/themes/linux/global/global.css
@@ -78,10 +78,6 @@ xul|separator.groove[orient="vertical"] {
/* Other margins */
-html|input {
- margin: 2px 4px;
-}
-
xul|notification > xul|hbox > xul|button {
margin-block: 0;
}
diff --git a/toolkit/themes/osx/global/global.css b/toolkit/themes/osx/global/global.css
index 34f40dfc9c..174ed4dfe9 100644
--- a/toolkit/themes/osx/global/global.css
+++ b/toolkit/themes/osx/global/global.css
@@ -62,9 +62,3 @@ xul|separator.groove[orient="vertical"] {
width: 0;
margin-inline: 0.4em;
}
-
-/* Input margins */
-
-html|input {
- margin: 4px;
-}
diff --git a/toolkit/themes/shared/design-system/README.design-tokens.stories.md b/toolkit/themes/shared/design-system/README.design-tokens.stories.md
index 6afe185d87..156ab35e19 100644
--- a/toolkit/themes/shared/design-system/README.design-tokens.stories.md
+++ b/toolkit/themes/shared/design-system/README.design-tokens.stories.md
@@ -451,17 +451,6 @@ The `--font-size-root` token was created for specific use under the document's `
</div>
</div>
-Just like the specific HTML term 'root', we tend to include the term 'default' on scales in order to identify values that are used as a default, or at the global level (`:root` or `body` tag):
-
-<div class="box">
- <div class="post-it blue big">
- font-weight
- </div>
- <div class="post-it big">
- <strong>default</strong>
- </div>
-</div>
-
It's okay to include intentional terms within scales that better represent the meaning of a value and when to use it. For example, our border radius collection, which uses t-shirt sizing, also mixes the 'circle' option within its scale in order to describe a border radius that will create a circular effect:
<div class="box">
@@ -589,28 +578,31 @@ Shared tokens ([tokens-shared.css](https://searchfox.org/mozilla-central/source/
</div>
</div>
-#### Brand
-Tokens specific to the brand, such as colors, and typographical styles. Used within domains that rely on brand values.
+#### `tokens-brand.css`
+This file is for token values specific to the brand, such as colors and
+typographical styles. This stylesheet should be loaded in domains that rely on
+brand values.
-For example, we use the brand's accent color under brand contexts (in-content/about: pages):
+For example, we re-map the accent color token in `tokens-brand.css` to the
+value we want to use in brand contexts (in-content/about: pages):
```css
/* tokens-brand.css */
---color-accent-primary: var(--brand-color-accent);
---brand-color-accent: light-dark(var(--color-blue-50), var(--color-cyan-50));
+--color-accent-primary: light-dark(var(--color-blue-50), var(--color-cyan-50));
```
-#### Platform
-Tokens used within the browser chrome that come from the user’s operating system, such as colors and fonts.
+#### `tokens-platform.css`
+This file is for token values used the browser chrome that come from the user’s
+operating system, such as colors and fonts.
-For example, we use the system's accent color under platform contexts (chrome):
+For example, we re-map the accent color token in `tokens-platform.css` to the
+value we want to use in platform contexts (chrome):
```css
/* tokens-platform.css */
---color-accent-primary: var(--platform-color-accent);
---platform-color-accent: AccentColor;
+--color-accent-primary: var(--button-primary-bgcolor, AccentColor);
```
-#### Shared
-Tokens used and shared between brand and platform contexts.
+#### `tokens-shared.css`
+This file is for tokens that are shared between brand and platform contexts.
For example, both the chrome and in-content pages make use of the same border-radius patterns:
```css
@@ -634,7 +626,7 @@ Application design tokens represent the collection of semantic design tokens tha
```css
/* tokens-brand.css */
---brand-color-accent: var(--color-blue-50);
+--color-accent-primary: light-dark(var(--color-blue-50), var(--color-cyan-50));
```
#### Component
@@ -644,7 +636,7 @@ Component-level tokens should live at the component-level file (e.g. [moz-toggle
```css
/* moz-toggle.css */
---toggle-background-color-pressed: var(--brand-color-accent);
+--toggle-background-color-pressed: var(--color-accent-primary);
```
### File structure
@@ -721,7 +713,7 @@ We rely on the [light-dark()](https://developer.mozilla.org/en-US/docs/Web/CSS/c
### High contrast mode
We rely on two queries for assigning HCM counterpart variables, @media (prefers-contrast) and @media (forced-colors). They are found at the bottom of [tokens-shared.css](https://searchfox.org/mozilla-central/rev/6eb2ebcafb1b4a8576eb513e6cd2c61e3f3ae6dc/toolkit/themes/shared/design-system/tokens-shared.css#109).
-<!-- This part of the documentation will link to Bug 1863436 once it lands -->
+/* This part of the documentation will link to Bug 1863436 once it lands */
## Help and support
If you have any questions, concerns, or feedback, and if this document has not answered something specific, please reach out to Desktop Theme Reviewers or Reusable Components Reviewers.
diff --git a/toolkit/themes/shared/design-system/tokens-brand.css b/toolkit/themes/shared/design-system/tokens-brand.css
index ac105d9767..0ab73834d0 100644
--- a/toolkit/themes/shared/design-system/tokens-brand.css
+++ b/toolkit/themes/shared/design-system/tokens-brand.css
@@ -4,17 +4,7 @@
@import url("chrome://global/skin/design-system/tokens-shared.css");
-:root,
-:host(.anonymous-content-host) {
- /** Font size **/
- --font-size-root: 15px; /* Set at the `:root`. Do not use */
- --font-size-small: 0.867rem; /* 13px */
- --font-size-large: 1.133rem; /* 17px */
- --font-size-xlarge: 1.467rem; /* 22px */
- --font-size-xxlarge: 1.6rem; /* 24px */
-}
-
-@media not (prefers-contrast) {
+@layer tokens-foundation {
:root,
:host(.anonymous-content-host) {
/** Border **/
@@ -25,28 +15,43 @@
--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);
+ --button-border-color-primary: 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));
/** Link **/
- --link-color: var(--brand-color-accent);
- --link-color-hover: var(--brand-color-accent-hover);
- --link-color-active: var(--brand-color-accent-active);
+ --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);
/** Color **/
- --color-accent-primary: var(--brand-color-accent);
- --color-accent-primary-hover: var(--brand-color-accent-hover);
- --color-accent-primary-active: var(--brand-color-accent-active);
-
- --brand-color-accent: light-dark(var(--color-blue-50), var(--color-cyan-50));
- --brand-color-accent-hover: light-dark(var(--color-blue-60), var(--color-cyan-30));
- --brand-color-accent-active: light-dark(var(--color-blue-70), var(--color-cyan-20));
+ --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));
--color-canvas: light-dark(var(--color-white), var(--color-gray-90));
+ /** Font size **/
+ --font-size-root: 15px; /* Set at the `:root`. Do not use */
+ --font-size-small: 0.867rem; /* 13px */
+ --font-size-large: 1.133rem; /* 17px */
+ --font-size-xlarge: 1.467rem; /* 22px */
+ --font-size-xxlarge: 1.6rem; /* 24px */
+
/** Text **/
--text-color: light-dark(var(--color-gray-100), var(--color-gray-05));
- --text-color-deemphasized: light-dark(
- color-mix(in srgb, currentColor 69%, transparent),
- color-mix(in srgb, currentColor 75%, transparent)
- );
+ }
+}
+
+@layer tokens-prefers-contrast {
+ @media (prefers-contrast) {
+ :root,
+ :host(.anonymous-content-host) {
+ /* Border */
+ --border-interactive-color: var(--text-color);
+ --border-interactive-color-hover: var(--border-interactive-color);
+ --border-interactive-color-active: var(--border-interactive-color);
+ --border-interactive-color-disabled: var(--border-interactive-color);
+ }
}
}
diff --git a/toolkit/themes/shared/design-system/tokens-platform.css b/toolkit/themes/shared/design-system/tokens-platform.css
index e7897d11b0..921f5fd860 100644
--- a/toolkit/themes/shared/design-system/tokens-platform.css
+++ b/toolkit/themes/shared/design-system/tokens-platform.css
@@ -4,7 +4,7 @@
@import url("chrome://global/skin/design-system/tokens-shared.css");
-@media not (prefers-contrast) {
+@layer tokens-foundation {
:root,
:host(.anonymous-content-host) {
/** Border **/
@@ -14,14 +14,13 @@
--button-background-color: var(--button-bgcolor);
--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(--platform-color-accent);
- --color-accent-primary-hover: var(--platform-color-accent-hover);
- --color-accent-primary-active: var(--platform-color-accent-active);
- --platform-color-accent: var(--button-primary-bgcolor, AccentColor);
- --platform-color-accent-hover: var(--button-primary-hover-bgcolor);
- --platform-color-accent-active: var(--button-primary-active-bgcolor);
+ --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);
--color-canvas: Canvas;
/** Font size **/
diff --git a/toolkit/themes/shared/design-system/tokens-shared.css b/toolkit/themes/shared/design-system/tokens-shared.css
index 5d62fb8bbe..0c074ca20e 100644
--- a/toolkit/themes/shared/design-system/tokens-shared.css
+++ b/toolkit/themes/shared/design-system/tokens-shared.css
@@ -2,105 +2,63 @@
* License, v. 2.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,
-:host(.anonymous-content-host) {
- /* Base tokens */
- /* Do not use base tokens directly as they don't carry any meaning and are used to set our base. Refer to Application tokens below. */
- /** Color **/
- --color-white: #ffffff;
- --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-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-20: #ff9aa2;
- --color-red-30: #f37f98;
- --color-red-50: #d7264c;
- --color-red-80: #690f22;
- --color-yellow-05: #ffebcd;
- --color-yellow-30: #e49c49;
- --color-yellow-50: #cd411e;
- --color-yellow-80: #5a3100;
-
- /* Application tokens */
- /** Border **/
- --border-radius-circle: 9999px;
- --border-radius-small: 4px;
- --border-radius-medium: 8px;
- --border-width: 1px;
-
- /** Box **/
- --box-background-color: light-dark(var(--color-white), var(--color-gray-80));
- --box-shadow-10: 0 1px 4px var(--color-black-a10);
-
- /** Font weight **/
- --font-weight-default: normal;
- --font-weight-bold: 600;
-
- /** 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;
-
- /** Input **/
- /*** Button ***/
- --button-border-radius: var(--border-radius-small);
- --button-font-weight: var(--font-weight-bold);
- --button-line-height: var(--line-height-small);
- --button-min-height: var(--size-item-large);
-
- /*** Checkbox ***/
- --checkbox-margin-inline: var(--space-small);
- /* TODO Bug 1876537: Make this em-based, probably? */
- --checkbox-size: var(--size-item-small);
-
- /*** Text ***/
- --input-text-line-height: var(--button-line-height);
- --input-text-min-height: var(--button-min-height);
-
- /** Link **/
- /* Not using --focus-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;
-
- /** Text **/
- --text-color-deemphasized: color-mix(in srgb, currentColor 60%, transparent);
-
- /** 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-xlarge: calc(8 * var(--space-xsmall));
-}
+@layer tokens-foundation, tokens-prefers-contrast, tokens-forced-colors;
-@media not (prefers-contrast) {
+@layer tokens-foundation {
:root,
:host(.anonymous-content-host) {
+ /* Base tokens */
+ /* Do not use base tokens directly as they don't carry any meaning and are used to set our base. Refer to Application tokens below. */
+ /** Color **/
+ --color-white: #ffffff;
+ --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-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-yellow-05: #ffebcd;
+ --color-yellow-30: #e49c49;
+ --color-yellow-50: #cd411e;
+ --color-yellow-80: #5a3100;
+
+ /* Application tokens */
+ /** Border **/
+ --border-radius-circle: 9999px;
+ --border-radius-small: 4px;
+ --border-radius-medium: 8px;
+ --border-width: 1px;
+ --border-interactive-color-hover: var(--border-interactive-color);
+ --border-interactive-color-active: var(--border-interactive-color);
+ --border-interactive-color-disabled: var(--border-interactive-color);
+
+ /** Box **/
+ --box-background-color: light-dark(var(--color-white), var(--color-gray-80));
+ --box-shadow-10: 0 1px 4px var(--color-black-a10);
+
/** Color **/
--color-background-information: light-dark(var(--color-blue-05), var(--color-blue-80));
--color-background-success: light-dark(var(--color-green-05), var(--color-green-80));
@@ -108,71 +66,176 @@
--color-background-critical: light-dark(var(--color-red-05), var(--color-red-80));
--color-error-outline: light-dark(var(--color-red-50), var(--color-red-20));
+ /** Font weight **/
+ --font-weight: normal;
+ --font-weight-bold: 600;
+
+ /** 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;
+
/** Icon **/
--icon-color: light-dark(var(--color-gray-70), var(--color-gray-05));
--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-color-critical: light-dark(var(--color-red-50), var(--color-red-30));
+ --icon-size-default: var(--size-item-small);
+
+ /** Input **/
+ /*** Button ***/
+ --button-border: var(--border-width) solid var(--button-border-color);
+ --button-border-radius: var(--border-radius-small);
+ --button-font-weight: var(--font-weight-bold);
+ --button-font-size: var(--font-size-root);
+ --button-font-size-small: var(--font-size-small);
+ --button-min-height: var(--size-item-large);
+ --button-min-height-small: var(--size-item-medium);
+ --button-size-icon: var(--button-min-height);
+ --button-size-icon-small: var(--button-min-height-small);
+ --button-padding: var(--space-xsmall) var(--space-large);
+ --button-padding-icon: 0;
+
+ --button-text-color: var(--text-color);
+ --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-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-background-color-disabled: var(--button-background-color);
+ --button-opacity-disabled: 0.5;
+
+ --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-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);
+ --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-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-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-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-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-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-ghost-active);
+ --button-text-color-ghost-disabled: var(--button-text-color);
+ --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);
+
+ /*** Checkbox ***/
+ --checkbox-margin-inline: var(--space-small);
+ /* TODO Bug 1876537: Make this em-based, probably? */
+ --checkbox-size: var(--size-item-small);
+
+ /*** Text ***/
+ --input-text-min-height: var(--button-min-height);
+
+ /** Link **/
+ /* Not using --focus-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;
/** Text **/
+ --text-color-deemphasized: color-mix(in srgb, currentColor 69%, transparent);
--text-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));
}
}
-@media (prefers-contrast) {
- :root,
- :host(.anonymous-content-host) {
- /* Border */
- --border-color: var(--text-color);
- --border-interactive-color: AccentColor;
- --border-interactive-color-hover: ButtonText;
- --border-interactive-color-active: AccentColor;
- --border-interactive-color-disabled: GrayText;
+/* 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) {
+ /* Border */
+ --border-color: var(--text-color);
+ --border-interactive-color: AccentColor;
+ --border-interactive-color-hover: ButtonText;
+ --border-interactive-color-active: AccentColor;
+ --border-interactive-color-disabled: GrayText;
- /** Box **/
- --box-background-color: var(--color-canvas);
+ /** Box **/
+ --box-background-color: var(--color-canvas);
- /* Button */
- --button-background-color: ButtonFace;
- --button-background-color-hover: ButtonFace;
- --button-background-color-active: ButtonFace;
- --button-background-color-disabled: GrayText;
+ /* Button */
+ --button-border-color: var(--button-text-color);
+ --button-background-color-ghost: var(--button-background-color);
- /** Link **/
- --link-color: LinkText;
- --link-color-hover: LinkText;
- --link-color-active: ActiveText;
- --link-color-visited: var(--link-color);
+ /** Link **/
+ --link-color: LinkText;
+ --link-color-hover: LinkText;
+ --link-color-active: ActiveText;
+ --link-color-visited: var(--link-color);
- /** Color **/
- --color-canvas: Canvas;
- --color-background-information: var(--color-canvas);
- --color-background-success: var(--color-canvas);
- --color-background-warning: var(--color-canvas);
- --color-background-critical: var(--color-canvas);
- --color-accent-primary: AccentColor;
- /* FIXME(emilio): These seem rather sketchy */
- --color-accent-primary-hover: ButtonText;
- --color-accent-primary-active: ButtonText;
- --color-error-outline: var(--border-color);
+ /** Color **/
+ --color-canvas: Canvas;
+ --color-background-information: var(--color-canvas);
+ --color-background-success: var(--color-canvas);
+ --color-background-warning: var(--color-canvas);
+ --color-background-critical: var(--color-canvas);
+ --color-error-outline: var(--border-color);
- /** Icon **/
- --icon-color: var(--text-color);
- --icon-color-information: var(--icon-color);
- --icon-color-success: var(--icon-color);
- --icon-color-warning: var(--icon-color);
- --icon-color-critical: var(--icon-color);
+ /** Icon **/
+ --icon-color: var(--text-color);
+ --icon-color-information: var(--icon-color);
+ --icon-color-success: var(--icon-color);
+ --icon-color-warning: var(--icon-color);
+ --icon-color-critical: var(--icon-color);
- /** Text **/
- --text-color: CanvasText;
- --text-color-error: inherit;
- --text-color-deemphasized: inherit;
+ /** Text **/
+ --text-color: CanvasText;
+ --text-color-error: inherit;
+ --text-color-deemphasized: inherit;
+ }
}
+}
+/* Bug 1879900: Can't nest media queries inside of :host, :root selector
+ until Bug 1879349 lands */
+/* NOTE: These do not apply in the browser chrome (bug 1878919). */
+@layer tokens-forced-colors {
@media (forced-colors) {
- /* Applies to Windows HCM only, by default.
- TODO(emilio): These seem rather sketchy */
:root,
:host(.anonymous-content-host) {
/** Border **/
@@ -181,10 +244,57 @@
--border-interactive-color-active: ButtonText;
--border-interactive-color-disabled: GrayText;
+ /** Button **/
+ --button-border-color: var(--border-interactive-color);
+ --button-border-color-hover: var(--border-interactive-color-hover);
+ --button-border-color-active: var(--border-interactive-color-active);
+ --button-border-color-disabled: var(--border-interactive-color-disabled);
+ --button-background-color: ButtonFace;
+ --button-background-color-hover: SelectedItemText;
+ --button-background-color-active: SelectedItemText;
+ --button-background-color-disabled: ButtonFace;
+ --button-text-color: ButtonText;
+ --button-text-color-hover: SelectedItem;
+ --button-text-color-active: SelectedItem;
+ --button-text-color-disabled: GrayText;
+ --button-opacity-disabled: 1;
+
+ --button-background-color-primary-disabled: var(--button-text-color-disabled);
+ --button-border-color-primary: ButtonFace;
+ --button-border-color-primary-hover: SelectedItemText;
+ --button-border-color-primary-active: ButtonText;
+ --button-text-color-primary: ButtonFace;
+ --button-text-color-primary-hover: SelectedItemText;
+
+ --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-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-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-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-background-color-ghost: var(--button-background-color);
+ --button-background-color-ghost-disabled: var(--button-background-color-disabled);
+ --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);
+
/** Color **/
--color-accent-primary: ButtonText;
--color-accent-primary-hover: SelectedItem;
- --color-accent-primary-active: SelectedItem;
+ --color-accent-primary-active: var(--color-accent-primary-hover);
+ --color-error-outline: var(--border-color);
}
}
}
diff --git a/toolkit/themes/shared/desktop-jar.inc.mn b/toolkit/themes/shared/desktop-jar.inc.mn
index 6f504132c2..6afb075ea2 100644
--- a/toolkit/themes/shared/desktop-jar.inc.mn
+++ b/toolkit/themes/shared/desktop-jar.inc.mn
@@ -100,6 +100,7 @@
skin/classic/global/icons/performance.svg (../../shared/icons/performance.svg)
skin/classic/global/icons/plugin.svg (../../shared/icons/plugin.svg)
skin/classic/global/icons/plus.svg (../../shared/icons/plus.svg)
+ skin/classic/global/icons/plus-20.svg (../../shared/icons/plus-20.svg)
skin/classic/global/icons/pocket.svg (../../shared/icons/pocket.svg)
skin/classic/global/icons/pocket-outline.svg (../../shared/icons/pocket-outline.svg)
skin/classic/global/icons/pocket-favicon.ico (../../shared/icons/pocket-favicon.ico)
diff --git a/toolkit/themes/shared/global-shared.css b/toolkit/themes/shared/global-shared.css
index c478b2745e..320245afbe 100644
--- a/toolkit/themes/shared/global-shared.css
+++ b/toolkit/themes/shared/global-shared.css
@@ -129,12 +129,74 @@ 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, ThreeDShadow);
+ 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(--color-error-outline);
+ }
+
+ &: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);
}
@@ -227,7 +289,7 @@ button.text-link .button-text {
.footer-button {
appearance: none;
border: 0;
- border-radius: 4px;
+ 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;
@@ -298,7 +360,7 @@ button.text-link .button-text {
--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.jsm. */
+ /* Set pointer-events: none; so that mousemove events can be handled by AutoScrollChild.sys.mjs. */
pointer-events: none;
}
diff --git a/toolkit/themes/shared/icons/plus-20.svg b/toolkit/themes/shared/icons/plus-20.svg
new file mode 100644
index 0000000000..2522d5d274
--- /dev/null
+++ b/toolkit/themes/shared/icons/plus-20.svg
@@ -0,0 +1,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/. -->
+<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>
diff --git a/toolkit/themes/shared/in-content/common-shared.css b/toolkit/themes/shared/in-content/common-shared.css
index d123d3c3b0..c8f4f3efd2 100644
--- a/toolkit/themes/shared/in-content/common-shared.css
+++ b/toolkit/themes/shared/in-content/common-shared.css
@@ -323,7 +323,6 @@ xul|tab[selected]:hover:active {
html|button {
font: inherit;
- line-height: var(--button-line-height);
}
/* xul buttons and menulists */
@@ -339,7 +338,6 @@ xul|menulist {
border-radius: 4px;
background-color: var(--in-content-button-background);
font-weight: 400;
- line-height: var(--button-line-height);
padding: .45em 1em;
text-decoration: none;
margin: 4px 8px;
@@ -614,12 +612,15 @@ html|textarea {
box-sizing: border-box;
font-family: inherit;
font-size: inherit;
- line-height: var(--input-text-line-height);
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],
diff --git a/toolkit/themes/shared/media/pause-fill.svg b/toolkit/themes/shared/media/pause-fill.svg
index 8dc2dea140..b35793b5d5 100644
--- a/toolkit/themes/shared/media/pause-fill.svg
+++ b/toolkit/themes/shared/media/pause-fill.svg
@@ -1,7 +1,6 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.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="m4.5 14-1 0A1.5 1.5 0 0 1 2 12.5l0-9A1.5 1.5 0 0 1 3.5 2l1 0A1.5 1.5 0 0 1 6 3.5l0 9A1.5 1.5 0 0 1 4.5 14z"/>
- <path d="m11.5 14-1 0A1.5 1.5 0 0 1 9 12.5l0-9A1.5 1.5 0 0 1 10.5 2l1 0A1.5 1.5 0 0 1 13 3.5l0 9a1.5 1.5 0 0 1-1.5 1.5z"/>
+<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>
diff --git a/toolkit/themes/shared/media/videocontrols.css b/toolkit/themes/shared/media/videocontrols.css
index c50ccda9a3..b1ae620fa0 100644
--- a/toolkit/themes/shared/media/videocontrols.css
+++ b/toolkit/themes/shared/media/videocontrols.css
@@ -24,7 +24,7 @@
white-space: normal !important;
}
-.videocontrols[flipped="true"] {
+.videocontrols[flipped] {
transform: scaleX(-1);
}
diff --git a/toolkit/themes/shared/popup.css b/toolkit/themes/shared/popup.css
index ddc41a66ed..1426f9c4f6 100644
--- a/toolkit/themes/shared/popup.css
+++ b/toolkit/themes/shared/popup.css
@@ -83,17 +83,6 @@ panel {
&[orient=vertical]::part(content) {
flex-direction: column;
}
-
- /* ::::: arrow panel ::::: */
-
- &:where([type="arrow"]) {
- appearance: none;
- background-color: transparent;
-
- &.panel-no-padding::part(content) {
- padding: 0;
- }
- }
}
menupopup {
@@ -129,3 +118,81 @@ menulist > menupopup {
}
}
}
+
+/* ::::: 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;
+ }
+}
diff --git a/toolkit/themes/windows/global/global.css b/toolkit/themes/windows/global/global.css
index aa4cd2b7b3..7fadeb31ec 100644
--- a/toolkit/themes/windows/global/global.css
+++ b/toolkit/themes/windows/global/global.css
@@ -89,12 +89,6 @@ xul|separator.groove[orient="vertical"] {
margin-inline: 0.4em;
}
-/* Input margins */
-
-html|input {
- margin: 2px 4px;
-}
-
/* Content select */
.contentSelectDropdown-ingroup > .menu-iconic-text {
diff --git a/toolkit/xre/dllservices/mozglue/WindowsDllBlocklist.cpp b/toolkit/xre/dllservices/mozglue/WindowsDllBlocklist.cpp
index 29745d5af6..4f97ccc39f 100644
--- a/toolkit/xre/dllservices/mozglue/WindowsDllBlocklist.cpp
+++ b/toolkit/xre/dllservices/mozglue/WindowsDllBlocklist.cpp
@@ -15,7 +15,6 @@
#include "BaseProfiler.h"
#include "nsWindowsDllInterceptor.h"
#include "mozilla/CmdLineAndEnvUtils.h"
-#include "mozilla/DebugOnly.h"
#include "mozilla/StackWalk_windows.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/UniquePtr.h"
@@ -44,9 +43,6 @@ glue::detail::DllServicesBase* gDllServices;
using namespace mozilla;
-using CrashReporter::Annotation;
-using CrashReporter::AnnotationWriter;
-
#define DLL_BLOCKLIST_ENTRY(name, ...) {name, __VA_ARGS__},
#define DLL_BLOCKLIST_STRING_TYPE const char*
#include "mozilla/WindowsDllBlocklistLegacyDefs.h"
@@ -54,10 +50,13 @@ using CrashReporter::AnnotationWriter;
// define this for very verbose dll load debug spew
#undef DEBUG_very_verbose
+using WritableBuffer = mozilla::glue::detail::WritableBuffer<1024>;
+
static uint32_t sInitFlags;
static bool sBlocklistInitAttempted;
static bool sBlocklistInitFailed;
static bool sUser32BeforeBlocklist;
+static WritableBuffer sBlocklistWriter;
typedef MOZ_NORETURN_PTR void(__fastcall* BaseThreadInitThunk_func)(
BOOL aIsInitialThread, void* aStartAddress, void* aThreadParam);
@@ -174,8 +173,6 @@ class ReentrancySentinel {
std::map<DWORD, const char*>* ReentrancySentinel::sThreadMap;
-using WritableBuffer = mozilla::glue::detail::WritableBuffer<1024>;
-
/**
* This is a linked list of DLLs that have been blocked. It doesn't use
* mozilla::LinkedList because this is an append-only list and doesn't need
@@ -673,22 +670,11 @@ MFBT_API void DllBlocklist_Initialize(uint32_t aInitFlags) {
MFBT_API void DllBlocklist_Shutdown() {}
#endif // DEBUG
-static void InternalWriteNotes(AnnotationWriter& aWriter) {
- WritableBuffer buffer;
- DllBlockSet::Write(buffer);
-
- aWriter.Write(Annotation::BlockedDllList, buffer.Data(), buffer.Length());
-
- if (sBlocklistInitFailed) {
- aWriter.Write(Annotation::BlocklistInitFailed, "1");
- }
-
- if (sUser32BeforeBlocklist) {
- aWriter.Write(Annotation::User32BeforeBlocklist, "1");
- }
+static void InternalWriteNotes(WritableBuffer& aBuffer) {
+ DllBlockSet::Write(aBuffer);
}
-using WriterFn = void (*)(AnnotationWriter&);
+using WriterFn = void (*)(WritableBuffer& aBuffer);
static WriterFn gWriterFn = &InternalWriteNotes;
static void GetNativeNtBlockSetWriter() {
@@ -699,16 +685,25 @@ static void GetNativeNtBlockSetWriter() {
}
}
-MFBT_API void DllBlocklist_WriteNotes(AnnotationWriter& aWriter) {
- MOZ_ASSERT(gWriterFn);
- gWriterFn(aWriter);
-}
+MFBT_API void DllBlocklist_WriteNotes() { gWriterFn(sBlocklistWriter); }
MFBT_API bool DllBlocklist_CheckStatus() {
if (sBlocklistInitFailed || sUser32BeforeBlocklist) return false;
return true;
}
+MFBT_API bool* DllBlocklist_GetBlocklistInitFailedPointer() {
+ return &sBlocklistInitFailed;
+}
+
+MFBT_API bool* DllBlocklist_GetUser32BeforeBlocklistPointer() {
+ return &sUser32BeforeBlocklist;
+}
+
+MFBT_API const char* DllBlocklist_GetBlocklistWriterData() {
+ return sBlocklistWriter.Data();
+}
+
// ============================================================================
// This section is for DLL Services
// ============================================================================
diff --git a/toolkit/xre/dllservices/mozglue/WindowsDllBlocklist.h b/toolkit/xre/dllservices/mozglue/WindowsDllBlocklist.h
index 1b43e6d1fc..35408b0269 100644
--- a/toolkit/xre/dllservices/mozglue/WindowsDllBlocklist.h
+++ b/toolkit/xre/dllservices/mozglue/WindowsDllBlocklist.h
@@ -10,10 +10,9 @@
(defined(_M_IX86) || defined(_M_X64) || defined(_M_ARM64))
# include <windows.h>
-# include "CrashAnnotations.h"
-# include "mozilla/Attributes.h"
# include "mozilla/ProcessType.h"
# include "mozilla/Types.h"
+# include <algorithm>
# define HAS_DLL_BLOCKLIST
@@ -47,9 +46,13 @@ extern uint32_t gBlocklistInitFlags;
MFBT_API void DllBlocklist_Initialize(
uint32_t aInitFlags = eDllBlocklistInitFlagDefault);
-MFBT_API void DllBlocklist_WriteNotes(CrashReporter::AnnotationWriter& aWriter);
+MFBT_API void DllBlocklist_WriteNotes();
MFBT_API bool DllBlocklist_CheckStatus();
+MFBT_API bool* DllBlocklist_GetBlocklistInitFailedPointer();
+MFBT_API bool* DllBlocklist_GetUser32BeforeBlocklistPointer();
+MFBT_API const char* DllBlocklist_GetBlocklistWriterData();
+
// This export intends to clean up after DllBlocklist_Initialize().
// It's disabled in release builds for performance and to limit callers' ability
// to interfere with dll blocking.
diff --git a/toolkit/xre/nsAppRunner.cpp b/toolkit/xre/nsAppRunner.cpp
index 230d0da3e7..c1b712984b 100644
--- a/toolkit/xre/nsAppRunner.cpp
+++ b/toolkit/xre/nsAppRunner.cpp
@@ -330,7 +330,7 @@ bool gIsGtest = false;
bool gKioskMode = false;
int gKioskMonitor = -1;
-bool gAllowContentAnalysis = false;
+bool gAllowContentAnalysisArgPresent = false;
nsString gAbsoluteArgv0Path;
@@ -1024,6 +1024,12 @@ bool SessionHistoryInParent() {
fission_disableSessionHistoryInParent_AtStartup_DoNotUseDirectly();
}
+bool SessionStorePlatformCollection() {
+ return SessionHistoryInParent() &&
+ !StaticPrefs::
+ browser_sessionstore_disable_platform_collection_AtStartup_DoNotUseDirectly();
+}
+
bool BFCacheInParent() {
return SessionHistoryInParent() &&
StaticPrefs::fission_bfcacheInParent_DoNotUseDirectly();
@@ -1429,6 +1435,12 @@ nsXULAppInfo::GetSessionHistoryInParent(bool* aResult) {
}
NS_IMETHODIMP
+nsXULAppInfo::GetSessionStorePlatformCollection(bool* aResult) {
+ *aResult = SessionStorePlatformCollection();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
nsXULAppInfo::GetBrowserTabsRemoteAutostart(bool* aResult) {
*aResult = BrowserTabsRemoteAutostart();
return NS_OK;
@@ -1797,7 +1809,8 @@ nsXULAppInfo::AnnotateCrashReport(const nsACString& key,
return NS_ERROR_INVALID_ARG;
}
- return CrashReporter::AnnotateCrashReport(annotation, data);
+ CrashReporter::RecordAnnotationNSCString(annotation, data);
+ return NS_OK;
}
NS_IMETHODIMP
@@ -1808,7 +1821,8 @@ nsXULAppInfo::RemoveCrashReportAnnotation(const nsACString& key) {
return NS_ERROR_INVALID_ARG;
}
- return CrashReporter::RemoveCrashReportAnnotation(annotation);
+ CrashReporter::UnrecordAnnotation(annotation);
+ return NS_OK;
}
NS_IMETHODIMP
@@ -2316,7 +2330,7 @@ static void SetupLauncherProcessPref() {
if (enabledState.isOk()) {
gLauncherProcessState = Some(enabledState.unwrap());
- CrashReporter::AnnotateCrashReport(
+ CrashReporter::RecordAnnotationU32(
CrashReporter::Annotation::LauncherProcessState,
static_cast<uint32_t>(enabledState.unwrap()));
@@ -3846,7 +3860,7 @@ static void MaybeAddCPUMicrocodeCrashAnnotation() {
}
if (cpuUpdateRevision > 0) {
- CrashReporter::AnnotateCrashReport(
+ CrashReporter::RecordAnnotationNSCString(
CrashReporter::Annotation::CPUMicrocodeVersion,
nsPrintfCString("0x%" PRIx32, cpuUpdateRevision));
}
@@ -3984,8 +3998,9 @@ int XREMain::XRE_mainInit(bool* aExitFlag) {
gKioskMonitor = atoi(kioskMonitorNumber);
}
- gAllowContentAnalysis = CheckArg("allow-content-analysis", nullptr,
- CheckArgFlag::RemoveArg) == ARG_FOUND;
+ gAllowContentAnalysisArgPresent =
+ CheckArg("allow-content-analysis", nullptr, CheckArgFlag::RemoveArg) ==
+ ARG_FOUND;
nsresult rv;
ArgResult ar;
@@ -4106,46 +4121,52 @@ int XREMain::XRE_mainInit(bool* aExitFlag) {
if (NS_SUCCEEDED(rv)) {
CrashReporter::SetUserAppDataDirectory(file);
}
- if (mAppData->crashReporterURL)
+ if (mAppData->crashReporterURL) {
CrashReporter::SetServerURL(
nsDependentCString(mAppData->crashReporterURL));
+ }
// We overwrite this once we finish starting up.
- CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::StartupCrash,
- true);
+ CrashReporter::RecordAnnotationBool(CrashReporter::Annotation::StartupCrash,
+ true);
// pass some basic info from the app data
- if (mAppData->vendor)
- CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::Vendor,
- nsDependentCString(mAppData->vendor));
- if (mAppData->name)
- CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::ProductName,
- nsDependentCString(mAppData->name));
- if (mAppData->ID)
- CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::ProductID,
- nsDependentCString(mAppData->ID));
- if (mAppData->version)
- CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::Version,
- nsDependentCString(mAppData->version));
- if (mAppData->buildID)
- CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::BuildID,
- nsDependentCString(mAppData->buildID));
+ if (mAppData->vendor) {
+ CrashReporter::RecordAnnotationCString(CrashReporter::Annotation::Vendor,
+ mAppData->vendor);
+ }
+ if (mAppData->name) {
+ CrashReporter::RecordAnnotationCString(
+ CrashReporter::Annotation::ProductName, mAppData->name);
+ }
+ if (mAppData->ID) {
+ CrashReporter::RecordAnnotationCString(
+ CrashReporter::Annotation::ProductID, mAppData->ID);
+ }
+ if (mAppData->version) {
+ CrashReporter::RecordAnnotationCString(CrashReporter::Annotation::Version,
+ mAppData->version);
+ }
+ if (mAppData->buildID) {
+ CrashReporter::RecordAnnotationCString(CrashReporter::Annotation::BuildID,
+ mAppData->buildID);
+ }
nsDependentCString releaseChannel(MOZ_STRINGIFY(MOZ_UPDATE_CHANNEL));
- CrashReporter::AnnotateCrashReport(
+ CrashReporter::RecordAnnotationNSCString(
CrashReporter::Annotation::ReleaseChannel, releaseChannel);
#ifdef XP_WIN
nsAutoString appInitDLLs;
if (widget::WinUtils::GetAppInitDLLs(appInitDLLs)) {
- CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::AppInitDLLs,
- NS_ConvertUTF16toUTF8(appInitDLLs));
+ CrashReporter::RecordAnnotationNSString(
+ CrashReporter::Annotation::AppInitDLLs, appInitDLLs);
}
nsString packageFamilyName = widget::WinUtils::GetPackageFamilyName();
if (StringBeginsWith(packageFamilyName, u"Mozilla."_ns) ||
StringBeginsWith(packageFamilyName, u"MozillaCorporation."_ns)) {
- CrashReporter::AnnotateCrashReport(
+ CrashReporter::RecordAnnotationNSCString(
CrashReporter::Annotation::WindowsPackageFamilyName,
NS_ConvertUTF16toUTF8(packageFamilyName));
}
@@ -4156,15 +4177,15 @@ int XREMain::XRE_mainInit(bool* aExitFlag) {
Maybe<nsCString> backgroundTasks = BackgroundTasks::GetBackgroundTasks();
if (backgroundTasks.isSome()) {
isBackgroundTaskMode = true;
- CrashReporter::AnnotateCrashReport(
+ CrashReporter::RecordAnnotationNSCString(
CrashReporter::Annotation::BackgroundTaskName, backgroundTasks.ref());
}
#endif
- CrashReporter::AnnotateCrashReport(
+ CrashReporter::RecordAnnotationBool(
CrashReporter::Annotation::BackgroundTaskMode, isBackgroundTaskMode);
- CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::HeadlessMode,
- gfxPlatform::IsHeadless());
+ CrashReporter::RecordAnnotationBool(CrashReporter::Annotation::HeadlessMode,
+ gfxPlatform::IsHeadless());
CrashReporter::SetRestartArgs(gArgc, gArgv);
@@ -4203,7 +4224,9 @@ int XREMain::XRE_mainInit(bool* aExitFlag) {
#if defined(MOZ_SANDBOX) && defined(XP_WIN)
if (mAppData->sandboxBrokerServices) {
- SandboxBroker::Initialize(mAppData->sandboxBrokerServices);
+ nsAutoString binDirPath;
+ MOZ_ALWAYS_SUCCEEDS(xreBinDirectory->GetPath(binDirPath));
+ SandboxBroker::Initialize(mAppData->sandboxBrokerServices, binDirPath);
} else {
# if defined(MOZ_SANDBOX)
// If we're sandboxing content and we fail to initialize, then crashing here
@@ -4300,8 +4323,8 @@ int XREMain::XRE_mainInit(bool* aExitFlag) {
gSafeMode = safeModeRequested.value();
MaybeAddCPUMicrocodeCrashAnnotation();
- CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::SafeMode,
- gSafeMode);
+ CrashReporter::RegisterAnnotationBool(CrashReporter::Annotation::SafeMode,
+ &gSafeMode);
#if defined(MOZ_HAS_REMOTE)
// Handle --no-remote and --new-instance command line arguments. Setup
@@ -4747,37 +4770,55 @@ int XREMain::XRE_mainStartup(bool* aExitFlag) {
if (saveDisplayArg) {
if (GdkIsX11Display(disp)) {
SaveWordToEnv("DISPLAY", nsDependentCString(display_name));
- }
-# ifdef MOZ_WAYLAND
- else if (GdkIsWaylandDisplay(disp)) {
+ } else if (GdkIsWaylandDisplay(disp)) {
SaveWordToEnv("WAYLAND_DISPLAY", nsDependentCString(display_name));
}
-# endif
}
- }
-# ifdef MOZ_WIDGET_GTK
- else {
+ } else {
gdk_display_manager_open_display(gdk_display_manager_get(), nullptr);
}
+# if defined(MOZ_WAYLAND)
+ // We want to use proxy for main connection only so
+ // restore original Wayland display for next potential Wayland connections
+ // from gfx probe code and so on.
+ if (gWaylandProxy) {
+ gWaylandProxy->RestoreWaylandDisplay();
+ }
+ if (waylandEnabled && PR_GetEnv("WAYLAND_DISPLAY") && GdkIsX11Display()) {
+ // Gtk somehow switched to X11 display but we want Wayland.
+ // It may happen if compositor response is missig or it's slow
+ // or WAYLAND_DISPLAY is wrong. In such case throw warning but
+ // run with X11.
+ Output(true,
+ "Error: Failed to open Wayland display, fallback to X11. "
+ "WAYLAND_DISPLAY='%s' DISPLAY='%s'\n",
+ PR_GetEnv("WAYLAND_DISPLAY"), PR_GetEnv("DISPLAY"));
+
+ // We need to unset WAYLAND_DISPLAY. Gfx probe code doesn't have fallback
+ // to X11 and we'll end with Browser running SW rendering only then.
+ g_unsetenv("WAYLAND_DISPLAY");
+ gWaylandProxy = nullptr;
+ }
# endif
+ if (!gdk_display_get_default()) {
+ Output(true,
+ "Error: we don't have any display, WAYLAND_DISPLAY='%s' "
+ "DISPLAY='%s'\n",
+ PR_GetEnv("WAYLAND_DISPLAY"), PR_GetEnv("DISPLAY"));
+ return 1;
+ }
// Check that Wayland only and X11 only builds
// use appropriate displays.
# if defined(MOZ_WAYLAND) && !defined(MOZ_X11)
if (!GdkIsWaylandDisplay()) {
Output(true, "Wayland only build is missig Wayland display!\n");
+ return 1;
}
# endif
# if !defined(MOZ_WAYLAND) && defined(MOZ_X11)
if (!GdkIsX11Display()) {
Output(true, "X11 only build is missig X11 display!\n");
- }
-# endif
-# if defined(MOZ_WAYLAND)
- // We want to use proxy for main connection only so
- // restore original Wayland display for next potential Wayland connections
- // from gfx probe code and so on.
- if (gWaylandProxy) {
- gWaylandProxy->RestoreWaylandDisplay();
+ return 1;
}
# endif
}
@@ -5145,7 +5186,7 @@ int XREMain::XRE_mainStartup(bool* aExitFlag) {
bool lastStartupWasCrash = CheckLastStartupWasCrash();
- CrashReporter::AnnotateCrashReport(
+ CrashReporter::RecordAnnotationBool(
CrashReporter::Annotation::LastStartupWasCrash, lastStartupWasCrash);
if (CheckArg("purgecaches") || PR_GetEnv("MOZ_PURGE_CACHES") ||
@@ -5153,7 +5194,7 @@ int XREMain::XRE_mainStartup(bool* aExitFlag) {
cachesOK = false;
}
- CrashReporter::AnnotateCrashReport(
+ CrashReporter::RecordAnnotationBool(
CrashReporter::Annotation::StartupCacheValid, cachesOK && versionOK);
// Every time a profile is loaded by a build with a different version,
@@ -5208,45 +5249,30 @@ int XREMain::XRE_mainStartup(bool* aExitFlag) {
#if defined(MOZ_SANDBOX)
void AddSandboxAnnotations() {
- {
- // Include the sandbox content level, regardless of platform
- int level = GetEffectiveContentSandboxLevel();
-
- nsAutoCString levelString;
- levelString.AppendInt(level);
-
- CrashReporter::AnnotateCrashReport(
- CrashReporter::Annotation::ContentSandboxLevel, levelString);
- }
-
- {
- int level = GetEffectiveGpuSandboxLevel();
-
- nsAutoCString levelString;
- levelString.AppendInt(level);
-
- CrashReporter::AnnotateCrashReport(
- CrashReporter::Annotation::GpuSandboxLevel, levelString);
- }
+ CrashReporter::RecordAnnotationU32(
+ CrashReporter::Annotation::ContentSandboxLevel,
+ GetEffectiveContentSandboxLevel());
+ CrashReporter::RecordAnnotationU32(CrashReporter::Annotation::GpuSandboxLevel,
+ GetEffectiveGpuSandboxLevel());
// Include whether or not this instance is capable of content sandboxing
- bool sandboxCapable = false;
+ bool sSandboxCapable = false;
# if defined(XP_WIN)
// All supported Windows versions support some level of content sandboxing
- sandboxCapable = true;
+ sSandboxCapable = true;
# elif defined(XP_MACOSX)
// All supported OS X versions are capable
- sandboxCapable = true;
+ sSandboxCapable = true;
# elif defined(XP_LINUX)
- sandboxCapable = SandboxInfo::Get().CanSandboxContent();
+ sSandboxCapable = SandboxInfo::Get().CanSandboxContent();
# elif defined(__OpenBSD__)
- sandboxCapable = true;
+ sSandboxCapable = true;
StartOpenBSDSandbox(GeckoProcessType_Default);
# endif
- CrashReporter::AnnotateCrashReport(
- CrashReporter::Annotation::ContentSandboxCapable, sandboxCapable);
+ CrashReporter::RecordAnnotationBool(
+ CrashReporter::Annotation::ContentSandboxCapable, sSandboxCapable);
}
#endif /* MOZ_SANDBOX */
@@ -5294,7 +5320,7 @@ nsresult XREMain::XRE_mainRun() {
nsAutoCString sval;
rv = defaultPrefBranch->GetCharPref("app.update.channel", sval);
if (NS_SUCCEEDED(rv)) {
- CrashReporter::AnnotateCrashReport(
+ CrashReporter::RecordAnnotationNSCString(
CrashReporter::Annotation::ReleaseChannel, sval);
}
}
@@ -5486,7 +5512,7 @@ nsresult XREMain::XRE_mainRun() {
nsCString userAgentLocale;
LocaleService::GetInstance()->GetAppLocaleAsBCP47(userAgentLocale);
- CrashReporter::AnnotateCrashReport(
+ CrashReporter::RecordAnnotationNSCString(
CrashReporter::Annotation::useragent_locale, userAgentLocale);
if (!AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
@@ -5624,7 +5650,7 @@ nsresult XREMain::XRE_mainRun() {
(void)appStartup->DoneStartingUp();
- CrashReporter::AnnotateCrashReport(
+ CrashReporter::RecordAnnotationBool(
CrashReporter::Annotation::StartupCrash, false);
AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed);
@@ -5677,11 +5703,10 @@ nsresult XREMain::XRE_mainRun() {
sandboxInfo.Test(SandboxInfo::kEnabledForContent));
Telemetry::Accumulate(Telemetry::SANDBOX_MEDIA_ENABLED,
sandboxInfo.Test(SandboxInfo::kEnabledForMedia));
- nsAutoCString flagsString;
- flagsString.AppendInt(sandboxInfo.AsInteger());
- CrashReporter::AnnotateCrashReport(
- CrashReporter::Annotation::ContentSandboxCapabilities, flagsString);
+ CrashReporter::RecordAnnotationU32(
+ CrashReporter::Annotation::ContentSandboxCapabilities,
+ sandboxInfo.AsInteger());
#endif /* MOZ_SANDBOX && XP_LINUX */
#if defined(XP_WIN)
diff --git a/toolkit/xre/nsAppRunner.h b/toolkit/xre/nsAppRunner.h
index 7c6f122dfa..b38a3dc349 100644
--- a/toolkit/xre/nsAppRunner.h
+++ b/toolkit/xre/nsAppRunner.h
@@ -56,7 +56,7 @@ extern bool gIsGtest;
extern bool gKioskMode;
extern int gKioskMonitor;
-extern bool gAllowContentAnalysis;
+extern bool gAllowContentAnalysisArgPresent;
namespace mozilla {
nsresult AppInfoConstructor(const nsID& aIID, void** aResult);
diff --git a/toolkit/xre/nsEmbedFunctions.cpp b/toolkit/xre/nsEmbedFunctions.cpp
index 72077d6ddf..3718d8db4c 100644
--- a/toolkit/xre/nsEmbedFunctions.cpp
+++ b/toolkit/xre/nsEmbedFunctions.cpp
@@ -207,13 +207,13 @@ void SetTaskbarGroupId(const nsString& aId) {
#if defined(MOZ_SANDBOX)
void AddContentSandboxLevelAnnotation() {
if (XRE_GetProcessType() == GeckoProcessType_Content) {
- int level = GetEffectiveContentSandboxLevel();
- CrashReporter::AnnotateCrashReport(
- CrashReporter::Annotation::ContentSandboxLevel, level);
+ uint32_t contentSandboxLevel = GetEffectiveContentSandboxLevel();
+ CrashReporter::RecordAnnotationU32(
+ CrashReporter::Annotation::ContentSandboxLevel, contentSandboxLevel);
} else if (XRE_GetProcessType() == GeckoProcessType_GPU) {
- int level = GetEffectiveGpuSandboxLevel();
- CrashReporter::AnnotateCrashReport(
- CrashReporter::Annotation::GpuSandboxLevel, level);
+ uint32_t gpuSandboxLevel = GetEffectiveGpuSandboxLevel();
+ CrashReporter::RecordAnnotationU32(
+ CrashReporter::Annotation::GpuSandboxLevel, gpuSandboxLevel);
}
}
#endif /* MOZ_SANDBOX */
@@ -525,7 +525,7 @@ nsresult XRE_InitChildProcess(int aArgc, char* aArgv[],
#if defined(XP_WIN)
# if defined(MOZ_SANDBOX)
if (aChildData->sandboxBrokerServices) {
- SandboxBroker::Initialize(aChildData->sandboxBrokerServices);
+ SandboxBroker::Initialize(aChildData->sandboxBrokerServices, u""_ns);
SandboxBroker::GeckoDependentInitialize();
}
# endif // defined(MOZ_SANDBOX)
diff --git a/toolkit/xre/nsWindowsWMain.cpp b/toolkit/xre/nsWindowsWMain.cpp
index 7eb9e11046..2a91deec5c 100644
--- a/toolkit/xre/nsWindowsWMain.cpp
+++ b/toolkit/xre/nsWindowsWMain.cpp
@@ -111,9 +111,32 @@ static void FreeAllocStrings(int argc, char** argv) {
delete[] argv;
}
+// Remove "/prefetch:##" argument from the command line, if present. (See
+// GeckoChildProcessHost.cpp for details.)
+//
+// Colons are not permitted in path-elements on Windows, so a string of this
+// form is extremely unlikely to appear with the intent of being a legitimate
+// path-argument.
+void RemovePrefetchArguments(int& argc, WCHAR** argv) {
+ size_t prefetchArgsCount [[maybe_unused]] = 0;
+ for (int i = 0; i < argc; ++i) {
+ constexpr const wchar_t prefix[] = L"/prefetch:";
+ auto const cmp = wcsncmp(argv[i], prefix, ARRAYSIZE(prefix) - 1);
+ if (cmp == 0) {
+ std::copy(argv + i + 1, argv + argc, argv + i);
+ --argc;
+ --i;
+ prefetchArgsCount++;
+ }
+ }
+ MOZ_ASSERT(prefetchArgsCount <= 1,
+ "at most one /prefetch:## argument should be present");
+}
+
int wmain(int argc, WCHAR** argv) {
SanitizeEnvironmentVariables();
SetDllDirectoryW(L"");
+ RemovePrefetchArguments(argc, argv);
// Only run this code if LauncherProcessWin.h was included beforehand, thus
// signalling that the hosting process should support launcher mode.