diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:13:27 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:13:27 +0000 |
commit | 40a355a42d4a9444dc753c04c6608dade2f06a23 (patch) | |
tree | 871fc667d2de662f171103ce5ec067014ef85e61 /toolkit/components | |
parent | Adding upstream version 124.0.1. (diff) | |
download | firefox-adbda400be353e676059e335c3c0aaf99e719475.tar.xz firefox-adbda400be353e676059e335c3c0aaf99e719475.zip |
Adding upstream version 125.0.1.upstream/125.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components')
878 files changed, 15968 insertions, 5838 deletions
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, [¶mName, &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 Binary files differindex 2dd624b945..d77d8233b0 100644 --- a/toolkit/components/places/tests/migration/places_v75.sqlite +++ b/toolkit/components/places/tests/migration/places_v77.sqlite 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); -}; |