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 /netwerk/cookie | |
parent | Adding upstream version 124.0.1. (diff) | |
download | firefox-upstream/125.0.1.tar.xz firefox-upstream/125.0.1.zip |
Adding upstream version 125.0.1.upstream/125.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'netwerk/cookie')
-rw-r--r-- | netwerk/cookie/CookieCommons.cpp | 3 | ||||
-rw-r--r-- | netwerk/cookie/CookiePersistentStorage.cpp | 11 | ||||
-rw-r--r-- | netwerk/cookie/CookieService.cpp | 431 | ||||
-rw-r--r-- | netwerk/cookie/CookieService.h | 2 | ||||
-rw-r--r-- | netwerk/cookie/CookieServiceChild.cpp | 169 | ||||
-rw-r--r-- | netwerk/cookie/CookieServiceChild.h | 2 | ||||
-rw-r--r-- | netwerk/cookie/CookieServiceParent.cpp | 53 | ||||
-rw-r--r-- | netwerk/cookie/CookieServiceParent.h | 9 | ||||
-rw-r--r-- | netwerk/cookie/CookieStorage.cpp | 76 | ||||
-rw-r--r-- | netwerk/cookie/CookieStorage.h | 8 | ||||
-rw-r--r-- | netwerk/cookie/PCookieService.ipdl | 7 | ||||
-rw-r--r-- | netwerk/cookie/test/browser/browser_cookie_purge_sync.js | 4 | ||||
-rw-r--r-- | netwerk/cookie/test/browser/browser_indexedDB.js | 4 | ||||
-rw-r--r-- | netwerk/cookie/test/unit/test_rawSameSite.js | 2 |
14 files changed, 464 insertions, 317 deletions
diff --git a/netwerk/cookie/CookieCommons.cpp b/netwerk/cookie/CookieCommons.cpp index 3708f23daa..9b26fc4a6e 100644 --- a/netwerk/cookie/CookieCommons.cpp +++ b/netwerk/cookie/CookieCommons.cpp @@ -487,7 +487,8 @@ bool CookieCommons::ShouldIncludeCrossSiteCookieForDocument( aCookie->GetSameSite(&sameSiteAttr); if (aDocument->CookieJarSettings()->GetPartitionForeign() && - StaticPrefs::network_cookie_cookieBehavior_optInPartitioning()) { + StaticPrefs::network_cookie_cookieBehavior_optInPartitioning() && + !aCookie->IsPartitioned()) { return false; } diff --git a/netwerk/cookie/CookiePersistentStorage.cpp b/netwerk/cookie/CookiePersistentStorage.cpp index bb4e64f0f1..6358b39e3a 100644 --- a/netwerk/cookie/CookiePersistentStorage.cpp +++ b/netwerk/cookie/CookiePersistentStorage.cpp @@ -132,14 +132,7 @@ NS_IMETHODIMP ConvertAppIdToOriginAttrsSQLFunction::OnFunctionCall( mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult) { nsresult rv; - int32_t inIsolatedMozBrowser; - - rv = aFunctionArguments->GetInt32(1, &inIsolatedMozBrowser); - NS_ENSURE_SUCCESS(rv, rv); - - // Create an originAttributes object by inIsolatedMozBrowser. - // Then create the originSuffix string from this object. - OriginAttributes attrs(inIsolatedMozBrowser != 0); + OriginAttributes attrs; nsAutoCString suffix; attrs.CreateSuffix(suffix); @@ -205,7 +198,7 @@ SetInBrowserFromOriginAttributesSQLFunction::OnFunctionCall( NS_ENSURE_TRUE(success, NS_ERROR_FAILURE); RefPtr<nsVariant> outVar(new nsVariant()); - rv = outVar->SetAsInt32(attrs.mInIsolatedMozBrowser); + rv = outVar->SetAsInt32(false); NS_ENSURE_SUCCESS(rv, rv); outVar.forget(aResult); diff --git a/netwerk/cookie/CookieService.cpp b/netwerk/cookie/CookieService.cpp index d306203c2f..0e90b8cbae 100644 --- a/netwerk/cookie/CookieService.cpp +++ b/netwerk/cookie/CookieService.cpp @@ -13,6 +13,7 @@ #include "mozilla/dom/Document.h" #include "mozilla/dom/nsMixedContentBlocker.h" #include "mozilla/dom/Promise.h" +#include "mozilla/glean/GleanMetrics.h" #include "mozilla/net/CookieJarSettings.h" #include "mozilla/net/CookiePersistentStorage.h" #include "mozilla/net/CookiePrivateStorage.h" @@ -372,44 +373,14 @@ CookieService::GetCookieStringFromDocument(Document* aDocument, return NS_OK; } - nsCOMPtr<nsIPrincipal> principal = aDocument->EffectiveCookiePrincipal(); + nsCOMPtr<nsIPrincipal> cookiePrincipal = + aDocument->EffectiveCookiePrincipal(); - if (!CookieCommons::IsSchemeSupported(principal)) { - return NS_OK; - } - - CookieStorage* storage = PickStorage(principal->OriginAttributesRef()); - - nsAutoCString baseDomain; - rv = CookieCommons::GetBaseDomain(principal, baseDomain); - if (NS_WARN_IF(NS_FAILED(rv))) { - return NS_OK; - } - - nsAutoCString hostFromURI; - rv = nsContentUtils::GetHostOrIPv6WithBrackets(principal, hostFromURI); - if (NS_WARN_IF(NS_FAILED(rv))) { - return NS_OK; - } - - nsAutoCString pathFromURI; - rv = principal->GetFilePath(pathFromURI); - if (NS_WARN_IF(NS_FAILED(rv))) { - return NS_OK; - } - - int64_t currentTimeInUsec = PR_Now(); - int64_t currentTime = currentTimeInUsec / PR_USEC_PER_SEC; - - const nsTArray<RefPtr<Cookie>>* cookies = - storage->GetCookiesFromHost(baseDomain, principal->OriginAttributesRef()); - if (!cookies) { - return NS_OK; - } - - // check if the nsIPrincipal is using an https secure protocol. - // if it isn't, then we can't send a secure cookie over the connection. - bool potentiallyTrustworthy = principal->GetIsOriginPotentiallyTrustworthy(); + // TODO (Bug 1874174): A document could access both unpartitioned and + // partitioned cookie jars. We will need to prepare partitioned and + // unpartitioned principals for access both cookie jars. + nsTArray<nsCOMPtr<nsIPrincipal>> principals; + principals.AppendElement(cookiePrincipal); bool thirdParty = true; nsPIDOMWindowInner* innerWindow = aDocument->GetInnerWindow(); @@ -424,47 +395,98 @@ CookieService::GetCookieStringFromDocument(Document* aDocument, } } - bool stale = false; nsTArray<Cookie*> cookieList; - // iterate the cookies! - for (Cookie* cookie : *cookies) { - // check the host, since the base domain lookup is conservative. - if (!CookieCommons::DomainMatches(cookie, hostFromURI)) { - continue; + for (auto& principal : principals) { + if (!CookieCommons::IsSchemeSupported(principal)) { + return NS_OK; } - // if the cookie is httpOnly and it's not going directly to the HTTP - // connection, don't send it - if (cookie->IsHttpOnly()) { - continue; + CookieStorage* storage = PickStorage(principal->OriginAttributesRef()); + + nsAutoCString baseDomain; + rv = CookieCommons::GetBaseDomain(principal, baseDomain); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NS_OK; } - if (thirdParty && !CookieCommons::ShouldIncludeCrossSiteCookieForDocument( - cookie, aDocument)) { - continue; + nsAutoCString hostFromURI; + rv = nsContentUtils::GetHostOrIPv6WithBrackets(principal, hostFromURI); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NS_OK; } - // if the cookie is secure and the host scheme isn't, we can't send it - if (cookie->IsSecure() && !potentiallyTrustworthy) { - continue; + nsAutoCString pathFromURI; + rv = principal->GetFilePath(pathFromURI); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NS_OK; } - // if the nsIURI path doesn't match the cookie path, don't send it back - if (!CookieCommons::PathMatches(cookie, pathFromURI)) { + int64_t currentTimeInUsec = PR_Now(); + int64_t currentTime = currentTimeInUsec / PR_USEC_PER_SEC; + + const nsTArray<RefPtr<Cookie>>* cookies = storage->GetCookiesFromHost( + baseDomain, principal->OriginAttributesRef()); + if (!cookies) { continue; } - // check if the cookie has expired - if (cookie->Expiry() <= currentTime) { + // check if the nsIPrincipal is using an https secure protocol. + // if it isn't, then we can't send a secure cookie over the connection. + bool potentiallyTrustworthy = + principal->GetIsOriginPotentiallyTrustworthy(); + + bool stale = false; + + // iterate the cookies! + for (Cookie* cookie : *cookies) { + // check the host, since the base domain lookup is conservative. + if (!CookieCommons::DomainMatches(cookie, hostFromURI)) { + continue; + } + + // if the cookie is httpOnly and it's not going directly to the HTTP + // connection, don't send it + if (cookie->IsHttpOnly()) { + continue; + } + + if (thirdParty && !CookieCommons::ShouldIncludeCrossSiteCookieForDocument( + cookie, aDocument)) { + continue; + } + + // if the cookie is secure and the host scheme isn't, we can't send it + if (cookie->IsSecure() && !potentiallyTrustworthy) { + continue; + } + + // if the nsIURI path doesn't match the cookie path, don't send it back + if (!CookieCommons::PathMatches(cookie, pathFromURI)) { + continue; + } + + // check if the cookie has expired + if (cookie->Expiry() <= currentTime) { + continue; + } + + // all checks passed - add to list and check if lastAccessed stamp needs + // updating + cookieList.AppendElement(cookie); + if (cookie->IsStale()) { + stale = true; + } + } + + if (cookieList.IsEmpty()) { continue; } - // all checks passed - add to list and check if lastAccessed stamp needs - // updating - cookieList.AppendElement(cookie); - if (cookie->IsStale()) { - stale = true; + // update lastAccessed timestamps. we only do this if the timestamp is stale + // by a certain amount, to avoid thrashing the db during pageload. + if (stale) { + storage->StaleCookies(cookieList, currentTimeInUsec); } } @@ -472,12 +494,6 @@ CookieService::GetCookieStringFromDocument(Document* aDocument, return NS_OK; } - // update lastAccessed timestamps. we only do this if the timestamp is stale - // by a certain amount, to avoid thrashing the db during pageload. - if (stale) { - storage->StaleCookies(cookieList, currentTimeInUsec); - } - // return cookies in order of path length; longest to shortest. // this is required per RFC2109. if cookies match in length, // then sort by creation time (see bug 236772). @@ -512,6 +528,12 @@ CookieService::GetCookieStringFromHttp(nsIURI* aHostURI, nsIChannel* aChannel, bool isSameSiteForeign = CookieCommons::IsSameSiteForeign( aChannel, aHostURI, &hadCrossSiteRedirects); + // TODO (Bug 1874174): A channel could load both unpartitioned and partitioned + // cookie jars together. We will need to get cookies from both unpartitioned + // and partitioned cookie jars according to storage access. + nsTArray<OriginAttributes> originAttributesList; + originAttributesList.AppendElement(attrs); + AutoTArray<Cookie*, 8> foundCookieList; GetCookiesForURI( aHostURI, aChannel, result.contains(ThirdPartyAnalysis::IsForeign), @@ -519,7 +541,8 @@ CookieService::GetCookieStringFromHttp(nsIURI* aHostURI, nsIChannel* aChannel, result.contains(ThirdPartyAnalysis::IsThirdPartySocialTrackingResource), result.contains(ThirdPartyAnalysis::IsStorageAccessPermissionGranted), rejectedReason, isSafeTopLevelNav, isSameSiteForeign, - hadCrossSiteRedirects, true, false, attrs, foundCookieList); + hadCrossSiteRedirects, true, false, originAttributesList, + foundCookieList); ComposeCookieString(foundCookieList, aCookieString); @@ -936,7 +959,8 @@ void CookieService::GetCookiesForURI( bool aIsSafeTopLevelNav, bool aIsSameSiteForeign, bool aHadCrossSiteRedirects, bool aHttpBound, bool aAllowSecureCookiesToInsecureOrigin, - const OriginAttributes& aOriginAttrs, nsTArray<Cookie*>& aCookieList) { + const nsTArray<OriginAttributes>& aOriginAttrsList, + nsTArray<Cookie*>& aCookieList) { NS_ASSERTION(aHostURI, "null host!"); if (!CookieCommons::IsSchemeSupported(aHostURI)) { @@ -947,152 +971,165 @@ void CookieService::GetCookiesForURI( return; } - CookieStorage* storage = PickStorage(aOriginAttrs); - - // get the base domain, host, and path from the URI. - // e.g. for "www.bbc.co.uk", the base domain would be "bbc.co.uk". - // file:// URI's (i.e. with an empty host) are allowed, but any other - // scheme must have a non-empty host. A trailing dot in the host - // is acceptable. - bool requireHostMatch; - nsAutoCString baseDomain; - nsAutoCString hostFromURI; - nsAutoCString pathFromURI; - nsresult rv = CookieCommons::GetBaseDomain(mTLDService, aHostURI, baseDomain, - requireHostMatch); - if (NS_SUCCEEDED(rv)) { - rv = nsContentUtils::GetHostOrIPv6WithBrackets(aHostURI, hostFromURI); - } - if (NS_SUCCEEDED(rv)) { - rv = aHostURI->GetFilePath(pathFromURI); - } - if (NS_FAILED(rv)) { - COOKIE_LOGFAILURE(GET_COOKIE, aHostURI, VoidCString(), - "invalid host/path from URI"); - return; - } - nsCOMPtr<nsICookieJarSettings> cookieJarSettings = CookieCommons::GetCookieJarSettings(aChannel); - nsAutoCString normalizedHostFromURI(hostFromURI); - rv = NormalizeHost(normalizedHostFromURI); - NS_ENSURE_SUCCESS_VOID(rv); - - nsAutoCString baseDomainFromURI; - rv = CookieCommons::GetBaseDomainFromHost(mTLDService, normalizedHostFromURI, - baseDomainFromURI); - NS_ENSURE_SUCCESS_VOID(rv); - - // check default prefs - uint32_t rejectedReason = aRejectedReason; - uint32_t priorCookieCount = storage->CountCookiesFromHost( - baseDomainFromURI, aOriginAttrs.mPrivateBrowsingId); - nsCOMPtr<nsIConsoleReportCollector> crc = do_QueryInterface(aChannel); - CookieStatus cookieStatus = CheckPrefs( - crc, cookieJarSettings, aHostURI, aIsForeign, - aIsThirdPartyTrackingResource, aIsThirdPartySocialTrackingResource, - aStorageAccessPermissionGranted, VoidCString(), priorCookieCount, - aOriginAttrs, &rejectedReason); - - MOZ_ASSERT_IF(rejectedReason, cookieStatus == STATUS_REJECTED); - // for GetCookie(), we only fire acceptance/rejection notifications - // (but not if there was an error) - switch (cookieStatus) { - case STATUS_REJECTED: - // If we don't have any cookies from this host, fail silently. - if (priorCookieCount) { - CookieCommons::NotifyRejected(aHostURI, aChannel, rejectedReason, - OPERATION_READ); - } + for (const auto& attrs : aOriginAttrsList) { + CookieStorage* storage = PickStorage(attrs); + + // get the base domain, host, and path from the URI. + // e.g. for "www.bbc.co.uk", the base domain would be "bbc.co.uk". + // file:// URI's (i.e. with an empty host) are allowed, but any other + // scheme must have a non-empty host. A trailing dot in the host + // is acceptable. + bool requireHostMatch; + nsAutoCString baseDomain; + nsAutoCString hostFromURI; + nsAutoCString pathFromURI; + nsresult rv = CookieCommons::GetBaseDomain(mTLDService, aHostURI, + baseDomain, requireHostMatch); + if (NS_SUCCEEDED(rv)) { + rv = nsContentUtils::GetHostOrIPv6WithBrackets(aHostURI, hostFromURI); + } + if (NS_SUCCEEDED(rv)) { + rv = aHostURI->GetFilePath(pathFromURI); + } + if (NS_FAILED(rv)) { + COOKIE_LOGFAILURE(GET_COOKIE, aHostURI, VoidCString(), + "invalid host/path from URI"); return; - default: - break; - } - - // Note: The following permissions logic is mirrored in - // extensions::MatchPattern::MatchesCookie. - // If it changes, please update that function, or file a bug for someone - // else to do so. + } - // check if aHostURI is using an https secure protocol. - // if it isn't, then we can't send a secure cookie over the connection. - // if SchemeIs fails, assume an insecure connection, to be on the safe side - bool potentiallyTrustworthy = - nsMixedContentBlocker::IsPotentiallyTrustworthyOrigin(aHostURI); + nsAutoCString normalizedHostFromURI(hostFromURI); + rv = NormalizeHost(normalizedHostFromURI); + NS_ENSURE_SUCCESS_VOID(rv); + + nsAutoCString baseDomainFromURI; + rv = CookieCommons::GetBaseDomainFromHost( + mTLDService, normalizedHostFromURI, baseDomainFromURI); + NS_ENSURE_SUCCESS_VOID(rv); + + // check default prefs + uint32_t rejectedReason = aRejectedReason; + uint32_t priorCookieCount = storage->CountCookiesFromHost( + baseDomainFromURI, attrs.mPrivateBrowsingId); + + CookieStatus cookieStatus = CheckPrefs( + crc, cookieJarSettings, aHostURI, aIsForeign, + aIsThirdPartyTrackingResource, aIsThirdPartySocialTrackingResource, + aStorageAccessPermissionGranted, VoidCString(), priorCookieCount, attrs, + &rejectedReason); + + MOZ_ASSERT_IF(rejectedReason, cookieStatus == STATUS_REJECTED); + + // for GetCookie(), we only fire acceptance/rejection notifications + // (but not if there was an error) + switch (cookieStatus) { + case STATUS_REJECTED: + // If we don't have any cookies from this host, fail silently. + if (priorCookieCount) { + CookieCommons::NotifyRejected(aHostURI, aChannel, rejectedReason, + OPERATION_READ); + } + return; + default: + break; + } - int64_t currentTimeInUsec = PR_Now(); - int64_t currentTime = currentTimeInUsec / PR_USEC_PER_SEC; - bool stale = false; + // Note: The following permissions logic is mirrored in + // extensions::MatchPattern::MatchesCookie. + // If it changes, please update that function, or file a bug for someone + // else to do so. - const nsTArray<RefPtr<Cookie>>* cookies = - storage->GetCookiesFromHost(baseDomain, aOriginAttrs); - if (!cookies) { - return; - } + // check if aHostURI is using an https secure protocol. + // if it isn't, then we can't send a secure cookie over the connection. + // if SchemeIs fails, assume an insecure connection, to be on the safe side + bool potentiallyTrustworthy = + nsMixedContentBlocker::IsPotentiallyTrustworthyOrigin(aHostURI); - bool laxByDefault = - StaticPrefs::network_cookie_sameSite_laxByDefault() && - !nsContentUtils::IsURIInPrefList( - aHostURI, "network.cookie.sameSite.laxByDefault.disabledHosts"); + int64_t currentTimeInUsec = PR_Now(); + int64_t currentTime = currentTimeInUsec / PR_USEC_PER_SEC; + bool stale = false; - // iterate the cookies! - for (Cookie* cookie : *cookies) { - // check the host, since the base domain lookup is conservative. - if (!CookieCommons::DomainMatches(cookie, hostFromURI)) { + const nsTArray<RefPtr<Cookie>>* cookies = + storage->GetCookiesFromHost(baseDomain, attrs); + if (!cookies) { continue; } - // if the cookie is secure and the host scheme isn't, we avoid sending - // cookie if possible. But for process synchronization purposes, we may want - // the content process to know about the cookie (without it's value). In - // which case we will wipe the value before sending - if (cookie->IsSecure() && !potentiallyTrustworthy && - !aAllowSecureCookiesToInsecureOrigin) { - continue; - } + bool laxByDefault = + StaticPrefs::network_cookie_sameSite_laxByDefault() && + !nsContentUtils::IsURIInPrefList( + aHostURI, "network.cookie.sameSite.laxByDefault.disabledHosts"); - // if the cookie is httpOnly and it's not going directly to the HTTP - // connection, don't send it - if (cookie->IsHttpOnly() && !aHttpBound) { - continue; - } + // iterate the cookies! + for (Cookie* cookie : *cookies) { + // check the host, since the base domain lookup is conservative. + if (!CookieCommons::DomainMatches(cookie, hostFromURI)) { + continue; + } - // if the nsIURI path doesn't match the cookie path, don't send it back - if (!CookieCommons::PathMatches(cookie, pathFromURI)) { - continue; - } + // if the cookie is secure and the host scheme isn't, we avoid sending + // cookie if possible. But for process synchronization purposes, we may + // want the content process to know about the cookie (without it's value). + // In which case we will wipe the value before sending + if (cookie->IsSecure() && !potentiallyTrustworthy && + !aAllowSecureCookiesToInsecureOrigin) { + continue; + } - // check if the cookie has expired - if (cookie->Expiry() <= currentTime) { - continue; - } + // if the cookie is httpOnly and it's not going directly to the HTTP + // connection, don't send it + if (cookie->IsHttpOnly() && !aHttpBound) { + continue; + } - if (aHttpBound && aIsSameSiteForeign) { - bool blockCookie = !ProcessSameSiteCookieForForeignRequest( - aChannel, cookie, aIsSafeTopLevelNav, aHadCrossSiteRedirects, - laxByDefault); - - if (blockCookie) { - if (aHadCrossSiteRedirects) { - CookieLogging::LogMessageToConsole( - crc, aHostURI, nsIScriptError::warningFlag, - CONSOLE_REJECTION_CATEGORY, "CookieBlockedCrossSiteRedirect"_ns, - AutoTArray<nsString, 1>{ - NS_ConvertUTF8toUTF16(cookie->Name()), - }); - } + // if the nsIURI path doesn't match the cookie path, don't send it back + if (!CookieCommons::PathMatches(cookie, pathFromURI)) { continue; } + + // check if the cookie has expired + if (cookie->Expiry() <= currentTime) { + continue; + } + + if (aHttpBound && aIsSameSiteForeign) { + bool blockCookie = !ProcessSameSiteCookieForForeignRequest( + aChannel, cookie, aIsSafeTopLevelNav, aHadCrossSiteRedirects, + laxByDefault); + + if (blockCookie) { + if (aHadCrossSiteRedirects) { + CookieLogging::LogMessageToConsole( + crc, aHostURI, nsIScriptError::warningFlag, + CONSOLE_REJECTION_CATEGORY, "CookieBlockedCrossSiteRedirect"_ns, + AutoTArray<nsString, 1>{ + NS_ConvertUTF8toUTF16(cookie->Name()), + }); + } + continue; + } + } + + // all checks passed - add to list and check if lastAccessed stamp needs + // updating + aCookieList.AppendElement(cookie); + if (cookie->IsStale()) { + stale = true; + } } - // all checks passed - add to list and check if lastAccessed stamp needs - // updating - aCookieList.AppendElement(cookie); - if (cookie->IsStale()) { - stale = true; + if (aCookieList.IsEmpty()) { + continue; + } + + // update lastAccessed timestamps. we only do this if the timestamp is stale + // by a certain amount, to avoid thrashing the db during pageload. + if (stale) { + storage->StaleCookies(aCookieList, currentTimeInUsec); } } @@ -1104,12 +1141,6 @@ void CookieService::GetCookiesForURI( // some. NotifyAccepted(aChannel); - // update lastAccessed timestamps. we only do this if the timestamp is stale - // by a certain amount, to avoid thrashing the db during pageload. - if (stale) { - storage->StaleCookies(aCookieList, currentTimeInUsec); - } - // return cookies in order of path length; longest to shortest. // this is required per RFC2109. if cookies match in length, // then sort by creation time (see bug 236772). diff --git a/netwerk/cookie/CookieService.h b/netwerk/cookie/CookieService.h index 09eb4c1289..51344d6909 100644 --- a/netwerk/cookie/CookieService.h +++ b/netwerk/cookie/CookieService.h @@ -88,7 +88,7 @@ class CookieService final : public nsICookieService, bool aIsSameSiteForeign, bool aHadCrossSiteRedirects, bool aHttpBound, bool aAllowSecureCookiesToInsecureOrigin, - const OriginAttributes& aOriginAttrs, + const nsTArray<OriginAttributes>& aOriginAttrsList, nsTArray<Cookie*>& aCookieList); /** diff --git a/netwerk/cookie/CookieServiceChild.cpp b/netwerk/cookie/CookieServiceChild.cpp index a005b5dbe7..84f34a9a37 100644 --- a/netwerk/cookie/CookieServiceChild.cpp +++ b/netwerk/cookie/CookieServiceChild.cpp @@ -107,6 +107,12 @@ RefPtr<GenericPromise> CookieServiceChild::TrackCookieLoad( RefPtr<CookieServiceChild> self(this); + // TODO (Bug 1874174): A channel could access both unpartitioned and + // partitioned cookie jars. We will need to pass partitioned and unpartitioned + // originAttributes according the storage access. + nsTArray<OriginAttributes> attrsList; + attrsList.AppendElement(attrs); + return SendGetCookieList( uri, result.contains(ThirdPartyAnalysis::IsForeign), result.contains(ThirdPartyAnalysis::IsThirdPartyTrackingResource), @@ -115,14 +121,18 @@ RefPtr<GenericPromise> CookieServiceChild::TrackCookieLoad( result.contains( ThirdPartyAnalysis::IsStorageAccessPermissionGranted), rejectedReason, isSafeTopLevelNav, isSameSiteForeign, - hadCrossSiteRedirects, attrs) + hadCrossSiteRedirects, attrsList) ->Then( GetCurrentSerialEventTarget(), __func__, - [self, uri, attrs](const nsTArray<CookieStruct>& aCookiesList) { - for (uint32_t i = 0; i < aCookiesList.Length(); i++) { - RefPtr<Cookie> cookie = Cookie::Create(aCookiesList[i], attrs); - cookie->SetIsHttpOnly(false); - self->RecordDocumentCookie(cookie, attrs); + [self, uri](const nsTArray<CookieStructTable>& aCookiesListTable) { + for (auto& entry : aCookiesListTable) { + auto& cookies = entry.cookies(); + for (auto& cookieEntry : cookies) { + RefPtr<Cookie> cookie = + Cookie::Create(cookieEntry, entry.attrs()); + cookie->SetIsHttpOnly(false); + self->RecordDocumentCookie(cookie, entry.attrs()); + } } return GenericPromise::CreateAndResolve(true, __func__); }, @@ -215,11 +225,13 @@ IPCResult CookieServiceChild::RecvRemoveBatchDeletedCookies( } IPCResult CookieServiceChild::RecvTrackCookiesLoad( - nsTArray<CookieStruct>&& aCookiesList, const OriginAttributes& aAttrs) { - for (uint32_t i = 0; i < aCookiesList.Length(); i++) { - RefPtr<Cookie> cookie = Cookie::Create(aCookiesList[i], aAttrs); - cookie->SetIsHttpOnly(false); - RecordDocumentCookie(cookie, aAttrs); + nsTArray<CookieStructTable>&& aCookiesListTable) { + for (auto& entry : aCookiesListTable) { + for (auto& cookieEntry : entry.cookies()) { + RefPtr<Cookie> cookie = Cookie::Create(cookieEntry, entry.attrs()); + cookie->SetIsHttpOnly(false); + RecordDocumentCookie(cookie, entry.attrs()); + } } nsCOMPtr<nsIObserverService> obsService = services::GetObserverService(); @@ -313,34 +325,14 @@ CookieServiceChild::GetCookieStringFromDocument(dom::Document* aDocument, aCookieString.Truncate(); - nsCOMPtr<nsIPrincipal> principal = aDocument->EffectiveCookiePrincipal(); - - if (!CookieCommons::IsSchemeSupported(principal)) { - return NS_OK; - } - - nsAutoCString baseDomain; - nsresult rv = CookieCommons::GetBaseDomain(principal, baseDomain); - if (NS_WARN_IF(NS_FAILED(rv))) { - return NS_OK; - } - - CookieKey key(baseDomain, principal->OriginAttributesRef()); - CookiesList* cookiesList = nullptr; - mCookiesMap.Get(key, &cookiesList); - - if (!cookiesList) { - return NS_OK; - } + nsCOMPtr<nsIPrincipal> cookiePrincipal = + aDocument->EffectiveCookiePrincipal(); - nsAutoCString hostFromURI; - rv = nsContentUtils::GetHostOrIPv6WithBrackets(principal, hostFromURI); - if (NS_WARN_IF(NS_FAILED(rv))) { - return NS_OK; - } - - nsAutoCString pathFromURI; - principal->GetFilePath(pathFromURI); + // TODO (Bug 1874174): A document could access both unpartitioned and + // partitioned cookie jars. We will need to prepare partitioned and + // unpartitioned principals for access both cookie jars. + nsTArray<nsCOMPtr<nsIPrincipal>> principals; + principals.AppendElement(cookiePrincipal); bool thirdParty = true; nsPIDOMWindowInner* innerWindow = aDocument->GetInnerWindow(); @@ -355,54 +347,83 @@ CookieServiceChild::GetCookieStringFromDocument(dom::Document* aDocument, } } - bool isPotentiallyTrustworthy = - principal->GetIsOriginPotentiallyTrustworthy(); - int64_t currentTimeInUsec = PR_Now(); - int64_t currentTime = currentTimeInUsec / PR_USEC_PER_SEC; - - cookiesList->Sort(CompareCookiesForSending()); - for (uint32_t i = 0; i < cookiesList->Length(); i++) { - Cookie* cookie = cookiesList->ElementAt(i); - // check the host, since the base domain lookup is conservative. - if (!CookieCommons::DomainMatches(cookie, hostFromURI)) { - continue; + for (auto& principal : principals) { + if (!CookieCommons::IsSchemeSupported(principal)) { + return NS_OK; } - // We don't show HttpOnly cookies in content processes. - if (cookie->IsHttpOnly()) { - continue; + nsAutoCString baseDomain; + nsresult rv = CookieCommons::GetBaseDomain(principal, baseDomain); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NS_OK; } - if (thirdParty && !CookieCommons::ShouldIncludeCrossSiteCookieForDocument( - cookie, aDocument)) { - continue; - } + CookieKey key(baseDomain, principal->OriginAttributesRef()); + CookiesList* cookiesList = nullptr; + mCookiesMap.Get(key, &cookiesList); - // do not display the cookie if it is secure and the host scheme isn't - if (cookie->IsSecure() && !isPotentiallyTrustworthy) { + if (!cookiesList) { continue; } - // if the nsIURI path doesn't match the cookie path, don't send it back - if (!CookieCommons::PathMatches(cookie, pathFromURI)) { - continue; + nsAutoCString hostFromURI; + rv = nsContentUtils::GetHostOrIPv6WithBrackets(principal, hostFromURI); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NS_OK; } - // check if the cookie has expired - if (cookie->Expiry() <= currentTime) { - continue; - } + nsAutoCString pathFromURI; + principal->GetFilePath(pathFromURI); - if (!cookie->Name().IsEmpty() || !cookie->Value().IsEmpty()) { - if (!aCookieString.IsEmpty()) { - aCookieString.AppendLiteral("; "); + bool isPotentiallyTrustworthy = + principal->GetIsOriginPotentiallyTrustworthy(); + int64_t currentTimeInUsec = PR_Now(); + int64_t currentTime = currentTimeInUsec / PR_USEC_PER_SEC; + + cookiesList->Sort(CompareCookiesForSending()); + for (uint32_t i = 0; i < cookiesList->Length(); i++) { + Cookie* cookie = cookiesList->ElementAt(i); + // check the host, since the base domain lookup is conservative. + if (!CookieCommons::DomainMatches(cookie, hostFromURI)) { + continue; } - if (!cookie->Name().IsEmpty()) { - aCookieString.Append(cookie->Name().get()); - aCookieString.AppendLiteral("="); - aCookieString.Append(cookie->Value().get()); - } else { - aCookieString.Append(cookie->Value().get()); + + // We don't show HttpOnly cookies in content processes. + if (cookie->IsHttpOnly()) { + continue; + } + + if (thirdParty && !CookieCommons::ShouldIncludeCrossSiteCookieForDocument( + cookie, aDocument)) { + continue; + } + + // do not display the cookie if it is secure and the host scheme isn't + if (cookie->IsSecure() && !isPotentiallyTrustworthy) { + continue; + } + + // if the nsIURI path doesn't match the cookie path, don't send it back + if (!CookieCommons::PathMatches(cookie, pathFromURI)) { + continue; + } + + // check if the cookie has expired + if (cookie->Expiry() <= currentTime) { + continue; + } + + if (!cookie->Name().IsEmpty() || !cookie->Value().IsEmpty()) { + if (!aCookieString.IsEmpty()) { + aCookieString.AppendLiteral("; "); + } + if (!cookie->Name().IsEmpty()) { + aCookieString.Append(cookie->Name().get()); + aCookieString.AppendLiteral("="); + aCookieString.Append(cookie->Value().get()); + } else { + aCookieString.Append(cookie->Value().get()); + } } } } diff --git a/netwerk/cookie/CookieServiceChild.h b/netwerk/cookie/CookieServiceChild.h index b9caa2aaa7..562ee7f1ae 100644 --- a/netwerk/cookie/CookieServiceChild.h +++ b/netwerk/cookie/CookieServiceChild.h @@ -54,7 +54,7 @@ class CookieServiceChild final : public PCookieServiceChild, static bool RequireThirdPartyCheck(nsILoadInfo* aLoadInfo); mozilla::ipc::IPCResult RecvTrackCookiesLoad( - nsTArray<CookieStruct>&& aCookiesList, const OriginAttributes& aAttrs); + nsTArray<CookieStructTable>&& aCookiesListTable); mozilla::ipc::IPCResult RecvRemoveAll(); diff --git a/netwerk/cookie/CookieServiceParent.cpp b/netwerk/cookie/CookieServiceParent.cpp index c78a67513f..75c024cec6 100644 --- a/netwerk/cookie/CookieServiceParent.cpp +++ b/netwerk/cookie/CookieServiceParent.cpp @@ -5,6 +5,7 @@ #include "CookieCommons.h" #include "CookieLogging.h" +#include "CookieServiceParent.h" #include "mozilla/net/CookieService.h" #include "mozilla/net/CookieServiceParent.h" #include "mozilla/net/NeckoParent.h" @@ -124,6 +125,8 @@ void CookieServiceParent::TrackCookieLoad(nsIChannel* aChannel) { bool isSameSiteForeign = CookieCommons::IsSameSiteForeign(aChannel, uri, &hadCrossSiteRedirects); + // TODO (Bug 1874174): A channel could load both unpartitioned and partitioned + // cookie jars together. We will need to track both originAttributes for them. StoragePrincipalHelper::PrepareEffectiveStoragePrincipalOriginAttributes( aChannel, attrs); @@ -134,7 +137,12 @@ void CookieServiceParent::TrackCookieLoad(nsIChannel* aChannel) { ThirdPartyAnalysisResult result = thirdPartyUtil->AnalyzeChannel( aChannel, false, nullptr, nullptr, &rejectedReason); - UpdateCookieInContentList(uri, attrs); + nsTArray<OriginAttributes> originAttributesList; + originAttributesList.AppendElement(attrs); + + for (auto& originAttributes : originAttributesList) { + UpdateCookieInContentList(uri, originAttributes); + } // Send matching cookies to Child. nsTArray<Cookie*> foundCookieList; @@ -144,10 +152,11 @@ void CookieServiceParent::TrackCookieLoad(nsIChannel* aChannel) { result.contains(ThirdPartyAnalysis::IsThirdPartySocialTrackingResource), result.contains(ThirdPartyAnalysis::IsStorageAccessPermissionGranted), rejectedReason, isSafeTopLevelNav, isSameSiteForeign, - hadCrossSiteRedirects, false, true, attrs, foundCookieList); - nsTArray<CookieStruct> matchingCookiesList; - SerializeCookieList(foundCookieList, matchingCookiesList, uri); - Unused << SendTrackCookiesLoad(matchingCookiesList, attrs); + hadCrossSiteRedirects, false, true, originAttributesList, + foundCookieList); + nsTArray<CookieStructTable> matchingCookiesListTable; + SerializeCookieListTable(foundCookieList, matchingCookiesListTable, uri); + Unused << SendTrackCookiesLoad(matchingCookiesListTable); } // we append outgoing cookie info into a list here so the ContentParent can @@ -170,12 +179,22 @@ void CookieServiceParent::UpdateCookieInContentList( } // static -void CookieServiceParent::SerializeCookieList( +void CookieServiceParent::SerializeCookieListTable( const nsTArray<Cookie*>& aFoundCookieList, - nsTArray<CookieStruct>& aCookiesList, nsIURI* aHostURI) { - for (uint32_t i = 0; i < aFoundCookieList.Length(); i++) { - Cookie* cookie = aFoundCookieList.ElementAt(i); - CookieStruct* cookieStruct = aCookiesList.AppendElement(); + nsTArray<CookieStructTable>& aCookiesListTable, nsIURI* aHostURI) { + nsTHashMap<nsCStringHashKey, CookieStructTable*> cookieListTable; + + for (Cookie* cookie : aFoundCookieList) { + nsAutoCString attrsSuffix; + cookie->OriginAttributesRef().CreateSuffix(attrsSuffix); + CookieStructTable* table = + cookieListTable.LookupOrInsertWith(attrsSuffix, [&] { + CookieStructTable* newTable = aCookiesListTable.AppendElement(); + newTable->attrs() = cookie->OriginAttributesRef(); + return newTable; + }); + + CookieStruct* cookieStruct = table->cookies().AppendElement(); *cookieStruct = cookie->ToIPC(); // clear http-only cookie values @@ -200,7 +219,7 @@ IPCResult CookieServiceParent::RecvGetCookieList( const bool& aStorageAccessPermissionGranted, const uint32_t& aRejectedReason, const bool& aIsSafeTopLevelNav, const bool& aIsSameSiteForeign, const bool& aHadCrossSiteRedirects, - const OriginAttributes& aAttrs, GetCookieListResolver&& aResolve) { + nsTArray<OriginAttributes>&& aAttrsList, GetCookieListResolver&& aResolve) { // Send matching cookies to Child. if (!aHost) { return IPC_FAIL(this, "aHost must not be null"); @@ -208,7 +227,9 @@ IPCResult CookieServiceParent::RecvGetCookieList( // we append outgoing cookie info into a list here so the ContentParent can // filter cookies that do not need to go to certain ContentProcesses - UpdateCookieInContentList(aHost, aAttrs); + for (const auto& attrs : aAttrsList) { + UpdateCookieInContentList(aHost, attrs); + } nsTArray<Cookie*> foundCookieList; // Note: passing nullptr as aChannel to GetCookiesForURI() here is fine since @@ -218,12 +239,12 @@ IPCResult CookieServiceParent::RecvGetCookieList( aHost, nullptr, aIsForeign, aIsThirdPartyTrackingResource, aIsThirdPartySocialTrackingResource, aStorageAccessPermissionGranted, aRejectedReason, aIsSafeTopLevelNav, aIsSameSiteForeign, - aHadCrossSiteRedirects, false, true, aAttrs, foundCookieList); + aHadCrossSiteRedirects, false, true, aAttrsList, foundCookieList); - nsTArray<CookieStruct> matchingCookiesList; - SerializeCookieList(foundCookieList, matchingCookiesList, aHost); + nsTArray<CookieStructTable> matchingCookiesListTable; + SerializeCookieListTable(foundCookieList, matchingCookiesListTable, aHost); - aResolve(matchingCookiesList); + aResolve(matchingCookiesListTable); return IPC_OK(); } diff --git a/netwerk/cookie/CookieServiceParent.h b/netwerk/cookie/CookieServiceParent.h index 45b46883fb..9859879b05 100644 --- a/netwerk/cookie/CookieServiceParent.h +++ b/netwerk/cookie/CookieServiceParent.h @@ -70,11 +70,12 @@ class CookieServiceParent : public PCookieServiceParent { const bool& aStorageAccessPermissionGranted, const uint32_t& aRejectedReason, const bool& aIsSafeTopLevelNav, const bool& aIsSameSiteForeign, const bool& aHadCrossSiteRedirects, - const OriginAttributes& aAttrs, GetCookieListResolver&& aResolve); + nsTArray<OriginAttributes>&& aAttrsList, + GetCookieListResolver&& aResolve); - static void SerializeCookieList(const nsTArray<Cookie*>& aFoundCookieList, - nsTArray<CookieStruct>& aCookiesList, - nsIURI* aHostURI); + static void SerializeCookieListTable( + const nsTArray<Cookie*>& aFoundCookieList, + nsTArray<CookieStructTable>& aCookiesListTable, nsIURI* aHostURI); nsCOMPtr<nsIEffectiveTLDService> mTLDService; RefPtr<CookieService> mCookieService; diff --git a/netwerk/cookie/CookieStorage.cpp b/netwerk/cookie/CookieStorage.cpp index cc827a2372..fd0af6129b 100644 --- a/netwerk/cookie/CookieStorage.cpp +++ b/netwerk/cookie/CookieStorage.cpp @@ -7,6 +7,7 @@ #include "CookieCommons.h" #include "CookieLogging.h" #include "CookieNotification.h" +#include "mozilla/net/MozURL_ffi.h" #include "nsCOMPtr.h" #include "nsICookieNotification.h" #include "CookieStorage.h" @@ -334,14 +335,83 @@ void CookieStorage::RemoveCookiesWithOriginAttributes( } } +/* static */ bool CookieStorage::isIPv6BaseDomain( + const nsACString& aBaseDomain) { + return aBaseDomain.Contains(':'); +} + +/* static */ bool CookieStorage::SerializeIPv6BaseDomain( + nsACString& aBaseDomain) { + bool hasStartBracket = aBaseDomain.First() == '['; + bool hasEndBracket = aBaseDomain.Last() == ']'; + + // If only start or end bracket exists host is malformed. + if (hasStartBracket != hasEndBracket) { + return false; + } + + // If the base domain is not in URL format (e.g. [::1]) add brackets so we + // can use rusturl_parse_ipv6addr(). + if (!hasStartBracket) { + aBaseDomain.Insert('[', 0); + aBaseDomain.Append(']'); + } + + // Serialize base domain to "zero abbreviation" and lower-case hex + // representation. + nsAutoCString baseDomain; + nsresult rv = (nsresult)rusturl_parse_ipv6addr(&aBaseDomain, &baseDomain); + NS_ENSURE_SUCCESS(rv, false); + + // Strip brackets to match principal representation. + aBaseDomain = Substring(baseDomain, 1, baseDomain.Length() - 2); + + return true; +} + void CookieStorage::RemoveCookiesFromExactHost( const nsACString& aHost, const nsACString& aBaseDomain, const OriginAttributesPattern& aPattern) { + // Intermediate fix until Bug 1882259 is resolved. + // Bug 1860033 - Cookies do not serialize IPv6 host / base domain in contrast + // to principals. To allow deletion by principal serialize before comparison. + // We check the base domain since it is used as the CookieList key and equals + // the normalized (ASCII) host for IP addresses + // (it is equal to the CookieService::NormalizeHost() output). + nsAutoCString removeBaseDomain; + bool isIPv6 = isIPv6BaseDomain(aBaseDomain); + if (isIPv6) { + MOZ_ASSERT(!aBaseDomain.IsEmpty()); + // Copy base domain since argument is immutable. + removeBaseDomain = aBaseDomain; + if (NS_WARN_IF(!SerializeIPv6BaseDomain(removeBaseDomain))) { + // Return on malformed base domains. + return; + } + } + // Iterate the hash table of CookieEntry. for (auto iter = mHostTable.Iter(); !iter.Done(); iter.Next()) { CookieEntry* entry = iter.Get(); - if (!aBaseDomain.Equals(entry->mBaseDomain)) { + // IPv6 host / base domain cookies + if (isIPv6) { + // If we look for a IPv6 cookie skip non-IPv6 cookie entries. + if (!isIPv6BaseDomain(entry->mBaseDomain)) { + continue; + } + // Serialize IPv6 base domains before comparison. + // Copy base domain since argument is immutable. + nsAutoCString entryBaseDomain; + entryBaseDomain = entry->mBaseDomain; + if (NS_WARN_IF(!SerializeIPv6BaseDomain(entryBaseDomain))) { + continue; + } + if (!removeBaseDomain.Equals(entryBaseDomain)) { + continue; + } + // Non-IPv6 cookies + } else if (!aBaseDomain.Equals(entry->mBaseDomain)) { continue; } @@ -354,7 +424,9 @@ void CookieStorage::RemoveCookiesFromExactHost( CookieListIter iter(entry, i - 1); RefPtr<Cookie> cookie = iter.Cookie(); - if (!aHost.Equals(cookie->RawHost())) { + // For IP addresses (ASCII normalized) host == baseDomain, we checked + // equality already. + if (!isIPv6 && !aHost.Equals(cookie->RawHost())) { continue; } diff --git a/netwerk/cookie/CookieStorage.h b/netwerk/cookie/CookieStorage.h index 3836edbb9c..a85a16cd5e 100644 --- a/netwerk/cookie/CookieStorage.h +++ b/netwerk/cookie/CookieStorage.h @@ -200,6 +200,14 @@ class CookieStorage : public nsIObserver, public nsSupportsWeakReference { uint16_t aMaxNumberOfCookies, int64_t aCookiePurgeAge) = 0; + // This method returns true if aBaseDomain contains any colons since only + // IPv6 baseDomains may contain colons. + static bool isIPv6BaseDomain(const nsACString& aBaseDomain); + + // Serialize aBaseDomain e.g. apply "zero abbreveation" (::), use single + // zeros and remove brackets to match principal base domain representation. + static bool SerializeIPv6BaseDomain(nsACString& aBaseDomain); + virtual void CollectCookieJarSizeData() = 0; int64_t mCookieOldestTime{INT64_MAX}; diff --git a/netwerk/cookie/PCookieService.ipdl b/netwerk/cookie/PCookieService.ipdl index f8ec4c8d0f..053b8f35cc 100644 --- a/netwerk/cookie/PCookieService.ipdl +++ b/netwerk/cookie/PCookieService.ipdl @@ -47,14 +47,13 @@ parent: bool isSafeTopLevelNav, bool isSameSiteForeign, bool hadCrossSiteRedirects, - OriginAttributes attrs) - returns (CookieStruct[] cookies); + OriginAttributes[] attrsList) + returns (CookieStructTable[] cookies); async __delete__(); child: - async TrackCookiesLoad(CookieStruct[] cookiesList, - OriginAttributes attrs); + async TrackCookiesLoad(CookieStructTable[] cookiesListTable); async RemoveCookie(CookieStruct cookie, OriginAttributes attrs); diff --git a/netwerk/cookie/test/browser/browser_cookie_purge_sync.js b/netwerk/cookie/test/browser/browser_cookie_purge_sync.js index 186797d058..42614f0ca6 100644 --- a/netwerk/cookie/test/browser/browser_cookie_purge_sync.js +++ b/netwerk/cookie/test/browser/browser_cookie_purge_sync.js @@ -29,7 +29,7 @@ add_setup(async function () { function waitForNotificationPromise(notification, expected) { return new Promise(resolve => { - function observer(subject, topic, data) { + function observer() { is(content.document.cookie, expected); Services.obs.removeObserver(observer, notification); resolve(); @@ -70,7 +70,7 @@ add_task(async function test_purge_sync_batch_and_deleted() { () => content.document.cookie == "", "cookie did not expire in time", 200 - ).catch(msg => { + ).catch(() => { is(false, "Cookie did not expire in time"); }); }); diff --git a/netwerk/cookie/test/browser/browser_indexedDB.js b/netwerk/cookie/test/browser/browser_indexedDB.js index 7f417077eb..d5d47c1883 100644 --- a/netwerk/cookie/test/browser/browser_indexedDB.js +++ b/netwerk/cookie/test/browser/browser_indexedDB.js @@ -42,7 +42,7 @@ CookiePolicyHelper.runTest("IndexedDB in workers", { } }; - worker.onerror = function (e) { + worker.onerror = function () { reject(); }; }); @@ -76,7 +76,7 @@ CookiePolicyHelper.runTest("IndexedDB in workers", { } }; - worker.onerror = function (e) { + worker.onerror = function () { reject(); }; }); diff --git a/netwerk/cookie/test/unit/test_rawSameSite.js b/netwerk/cookie/test/unit/test_rawSameSite.js index dc739ef852..9a933ce5bf 100644 --- a/netwerk/cookie/test/unit/test_rawSameSite.js +++ b/netwerk/cookie/test/unit/test_rawSameSite.js @@ -88,7 +88,7 @@ add_task(async _ => { let test = tests[i]; let promise = new Promise(resolve => { - function observer(subject, topic, data) { + function observer() { Services.obs.removeObserver(observer, "cookie-saved-on-disk"); resolve(); } |