| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "third_party/blink/renderer/modules/cookie_store/cookie_store.h" |
| |
| #include <utility> |
| |
| #include "base/optional.h" |
| #include "services/network/public/mojom/restricted_cookie_manager.mojom-blink.h" |
| #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h" |
| #include "third_party/blink/renderer/core/dom/document.h" |
| #include "third_party/blink/renderer/core/dom/dom_exception.h" |
| #include "third_party/blink/renderer/modules/cookie_store/cookie_change_event.h" |
| #include "third_party/blink/renderer/modules/cookie_store/cookie_list_item.h" |
| #include "third_party/blink/renderer/modules/cookie_store/cookie_store_get_options.h" |
| #include "third_party/blink/renderer/modules/cookie_store/cookie_store_set_options.h" |
| #include "third_party/blink/renderer/modules/event_modules.h" |
| #include "third_party/blink/renderer/modules/event_target_modules.h" |
| #include "third_party/blink/renderer/modules/serviceworkers/service_worker_global_scope.h" |
| #include "third_party/blink/renderer/modules/serviceworkers/service_worker_registration.h" |
| #include "third_party/blink/renderer/platform/bindings/script_state.h" |
| #include "third_party/blink/renderer/platform/heap/handle.h" |
| #include "third_party/blink/renderer/platform/weborigin/kurl.h" |
| #include "third_party/blink/renderer/platform/weborigin/security_origin.h" |
| #include "third_party/blink/renderer/platform/wtf/functional.h" |
| #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" |
| #include "third_party/blink/renderer/platform/wtf/time.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| // Returns null if and only if an exception is thrown. |
| network::mojom::blink::CookieManagerGetOptionsPtr ToBackendOptions( |
| const String& name, // Value of the "name" positional argument. |
| const CookieStoreGetOptions& options, |
| ExceptionState& exception_state) { |
| auto backend_options = network::mojom::blink::CookieManagerGetOptions::New(); |
| |
| // TODO(crbug.com/729800): Handle the url option. |
| |
| if (options.matchType() == "startsWith") { |
| backend_options->match_type = |
| network::mojom::blink::CookieMatchType::STARTS_WITH; |
| } else { |
| DCHECK_EQ(options.matchType(), WTF::String("equals")); |
| backend_options->match_type = |
| network::mojom::blink::CookieMatchType::EQUALS; |
| } |
| |
| if (name.IsNull()) { |
| if (options.hasName()) { |
| backend_options->name = options.name(); |
| } else { |
| // No name provided. Use a filter that matches all cookies. This overrides |
| // a user-provided matchType. |
| backend_options->match_type = |
| network::mojom::blink::CookieMatchType::STARTS_WITH; |
| backend_options->name = g_empty_string; |
| } |
| } else { |
| if (options.hasName()) { |
| exception_state.ThrowTypeError( |
| "Cookie name specified both as an argument and as an option"); |
| return nullptr; |
| } |
| backend_options->name = name; |
| } |
| |
| return backend_options; |
| } |
| |
| // Returns no value if and only if an exception is thrown. |
| base::Optional<WebCanonicalCookie> ToWebCanonicalCookie( |
| const KURL& cookie_url, |
| const String& name_arg, // Value of the "name" positional argument. |
| const String& value_arg, // Value of the "value" positional argument. |
| bool for_deletion, // True for CookieStore.delete, false for set. |
| const CookieStoreSetOptions& options, |
| ExceptionState& exception_state) { |
| String name; |
| if (name_arg.IsNull()) { |
| if (!options.hasName()) { |
| exception_state.ThrowTypeError("Unspecified cookie name"); |
| return base::nullopt; |
| } |
| name = options.name(); |
| } else { |
| if (options.hasName()) { |
| exception_state.ThrowTypeError( |
| "Cookie name specified both as an argument and as an option"); |
| return base::nullopt; |
| } |
| name = name_arg; |
| } |
| |
| String value; |
| base::Time expiry; |
| if (for_deletion) { |
| DCHECK(value_arg.IsNull()); |
| if (options.hasValue()) { |
| exception_state.ThrowTypeError( |
| "Cookie value is meaningless when deleting"); |
| return base::nullopt; |
| } |
| value = g_empty_string; |
| |
| if (options.hasExpires()) { |
| exception_state.ThrowTypeError( |
| "Cookie expiration time is meaningless when deleting"); |
| return base::nullopt; |
| } |
| expiry = WTF::Time::Min(); |
| } else { |
| if (value_arg.IsNull()) { |
| if (!options.hasValue()) { |
| exception_state.ThrowTypeError("Unspecified cookie value"); |
| return base::nullopt; |
| } |
| value = options.value(); |
| } else { |
| if (options.hasValue()) { |
| exception_state.ThrowTypeError( |
| "Cookie value specified both as an argument and as an option"); |
| return base::nullopt; |
| } |
| value = value_arg; |
| } |
| |
| if (name.IsEmpty() && value.Contains('=')) { |
| exception_state.ThrowTypeError( |
| "Cookie value cannot contain '=' if the name is empty"); |
| return base::nullopt; |
| } |
| |
| if (options.hasExpires()) { |
| expiry = WTF::Time::FromJavaTime(options.expires()); |
| } else { |
| expiry = WTF::Time(); |
| } |
| } |
| |
| String cookie_url_host = cookie_url.Host(); |
| String domain; |
| if (options.hasDomain()) { |
| // The leading dot (".") from the domain attribute is stripped in the |
| // Set-Cookie header, for compatibility. This API doesn't have compatibility |
| // constraints, so reject the edge case outright. |
| if (options.domain().StartsWith(".")) { |
| exception_state.ThrowTypeError("Cookie domain cannot start with \".\""); |
| return base::nullopt; |
| } |
| |
| domain = String(".") + options.domain(); |
| if (!cookie_url_host.EndsWith(domain) && |
| cookie_url_host != options.domain()) { |
| exception_state.ThrowTypeError( |
| "Cookie domain must domain-match current host"); |
| return base::nullopt; |
| } |
| } else { |
| // The absence of "domain" implies a host-only cookie. |
| domain = cookie_url_host; |
| } |
| |
| const String path = options.hasPath() ? options.path() : String("/"); |
| |
| const bool is_secure_origin = SecurityOrigin::IsSecure(cookie_url); |
| const bool secure = options.hasSecure() ? options.secure() : is_secure_origin; |
| |
| if (name.StartsWith("__Secure-") || name.StartsWith("__Host-")) { |
| if (!secure) { |
| exception_state.ThrowTypeError( |
| "__Secure- and __Host- cookies must be secure"); |
| return base::nullopt; |
| } |
| if (!is_secure_origin) { |
| exception_state.ThrowTypeError( |
| "__Secure- and __Host- cookies must be written from secure origin"); |
| return base::nullopt; |
| } |
| } |
| |
| return WebCanonicalCookie::Create( |
| name, value, domain, path, WTF::Time() /*creation*/, expiry, |
| WTF::Time() /*last_access*/, secure, options.httpOnly(), |
| WebCanonicalCookie::kDefaultSameSiteMode, |
| WebCanonicalCookie::kDefaultPriority); |
| } |
| |
| // Returns null if and only if an exception is thrown. |
| blink::mojom::blink::CookieChangeSubscriptionPtr ToBackendSubscription( |
| const KURL& default_cookie_url, |
| const CookieStoreGetOptions& subscription, |
| ExceptionState& exception_state) { |
| auto backend_subscription = |
| blink::mojom::blink::CookieChangeSubscription::New(); |
| |
| if (subscription.hasURL()) { |
| KURL subscription_url(default_cookie_url, subscription.url()); |
| // TODO(crbug.com/729800): Check that the URL is under default_cookie_url. |
| backend_subscription->url = subscription_url; |
| } else { |
| backend_subscription->url = default_cookie_url; |
| } |
| |
| if (subscription.matchType() == "startsWith") { |
| backend_subscription->match_type = |
| network::mojom::blink::CookieMatchType::STARTS_WITH; |
| } else { |
| DCHECK_EQ(subscription.matchType(), WTF::String("equals")); |
| backend_subscription->match_type = |
| network::mojom::blink::CookieMatchType::EQUALS; |
| } |
| |
| if (subscription.hasName()) { |
| backend_subscription->name = subscription.name(); |
| } else { |
| // No name provided. Use a filter that matches all cookies. This overrides |
| // a user-provided matchType. |
| backend_subscription->match_type = |
| network::mojom::blink::CookieMatchType::STARTS_WITH; |
| backend_subscription->name = g_empty_string; |
| } |
| |
| return backend_subscription; |
| } |
| |
| void ToCookieChangeSubscription( |
| const blink::mojom::blink::CookieChangeSubscription& backend_subscription, |
| CookieStoreGetOptions& subscription) { |
| subscription.setURL(backend_subscription.url); |
| |
| if (backend_subscription.match_type != |
| network::mojom::blink::CookieMatchType::STARTS_WITH || |
| !backend_subscription.name.IsEmpty()) { |
| subscription.setName(backend_subscription.name); |
| } |
| |
| switch (backend_subscription.match_type) { |
| case network::mojom::blink::CookieMatchType::STARTS_WITH: |
| subscription.setMatchType(WTF::String("startsWith")); |
| break; |
| case network::mojom::blink::CookieMatchType::EQUALS: |
| subscription.setMatchType(WTF::String("equals")); |
| break; |
| } |
| |
| subscription.setURL(backend_subscription.url); |
| } |
| |
| const KURL& DefaultCookieURL(ExecutionContext* execution_context) { |
| DCHECK(execution_context); |
| |
| if (execution_context->IsDocument()) { |
| Document* document = ToDocument(execution_context); |
| return document->CookieURL(); |
| } |
| |
| DCHECK(execution_context->IsServiceWorkerGlobalScope()); |
| ServiceWorkerGlobalScope* scope = |
| ToServiceWorkerGlobalScope(execution_context); |
| return scope->Url(); |
| } |
| |
| KURL DefaultSiteForCookies(ExecutionContext* execution_context) { |
| DCHECK(execution_context); |
| |
| if (execution_context->IsDocument()) { |
| Document* document = ToDocument(execution_context); |
| return document->SiteForCookies(); |
| } |
| |
| DCHECK(execution_context->IsServiceWorkerGlobalScope()); |
| ServiceWorkerGlobalScope* scope = |
| ToServiceWorkerGlobalScope(execution_context); |
| return scope->Url(); |
| } |
| |
| } // namespace |
| |
| CookieStore::~CookieStore() = default; |
| |
| ScriptPromise CookieStore::getAll(ScriptState* script_state, |
| const CookieStoreGetOptions& options, |
| ExceptionState& exception_state) { |
| return getAll(script_state, WTF::String(), options, exception_state); |
| } |
| |
| ScriptPromise CookieStore::getAll(ScriptState* script_state, |
| const String& name, |
| const CookieStoreGetOptions& options, |
| ExceptionState& exception_state) { |
| return DoRead(script_state, name, options, |
| &CookieStore::GetAllForUrlToGetAllResult, exception_state); |
| } |
| |
| ScriptPromise CookieStore::get(ScriptState* script_state, |
| const CookieStoreGetOptions& options, |
| ExceptionState& exception_state) { |
| return get(script_state, WTF::String(), options, exception_state); |
| } |
| |
| ScriptPromise CookieStore::get(ScriptState* script_state, |
| const String& name, |
| const CookieStoreGetOptions& options, |
| ExceptionState& exception_state) { |
| return DoRead(script_state, name, options, |
| &CookieStore::GetAllForUrlToGetResult, exception_state); |
| } |
| |
| ScriptPromise CookieStore::has(ScriptState* script_state, |
| const CookieStoreGetOptions& options, |
| ExceptionState& exception_state) { |
| return has(script_state, WTF::String(), options, exception_state); |
| } |
| |
| ScriptPromise CookieStore::has(ScriptState* script_state, |
| const String& name, |
| const CookieStoreGetOptions& options, |
| ExceptionState& exception_state) { |
| return DoRead(script_state, name, options, |
| &CookieStore::GetAllForUrlToHasResult, exception_state); |
| } |
| |
| ScriptPromise CookieStore::set(ScriptState* script_state, |
| const CookieStoreSetOptions& options, |
| ExceptionState& exception_state) { |
| return set(script_state, WTF::String(), WTF::String(), options, |
| exception_state); |
| } |
| |
| ScriptPromise CookieStore::set(ScriptState* script_state, |
| const String& name, |
| const String& value, |
| const CookieStoreSetOptions& options, |
| ExceptionState& exception_state) { |
| return DoWrite(script_state, name, value, options, false /* is_deletion */, |
| exception_state); |
| } |
| |
| ScriptPromise CookieStore::Delete(ScriptState* script_state, |
| const CookieStoreSetOptions& options, |
| ExceptionState& exception_state) { |
| return Delete(script_state, WTF::String(), options, exception_state); |
| } |
| |
| ScriptPromise CookieStore::Delete(ScriptState* script_state, |
| const String& name, |
| const CookieStoreSetOptions& options, |
| ExceptionState& exception_state) { |
| return DoWrite(script_state, name, WTF::String(), options, |
| true /* is_deletion */, exception_state); |
| } |
| |
| ScriptPromise CookieStore::subscribeToChanges( |
| ScriptState* script_state, |
| const HeapVector<CookieStoreGetOptions>& subscriptions, |
| ExceptionState& exception_state) { |
| DCHECK(GetExecutionContext()->IsServiceWorkerGlobalScope()); |
| |
| Vector<blink::mojom::blink::CookieChangeSubscriptionPtr> |
| backend_subscriptions; |
| backend_subscriptions.ReserveInitialCapacity(subscriptions.size()); |
| for (const CookieStoreGetOptions& subscription : subscriptions) { |
| blink::mojom::blink::CookieChangeSubscriptionPtr backend_subscription = |
| ToBackendSubscription(default_cookie_url_, subscription, |
| exception_state); |
| if (backend_subscription.is_null()) { |
| DCHECK(exception_state.HadException()); |
| return ScriptPromise(); |
| } |
| backend_subscriptions.emplace_back(std::move(backend_subscription)); |
| } |
| |
| if (!subscription_backend_) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError, |
| "CookieStore backend went away"); |
| return ScriptPromise(); |
| } |
| |
| ServiceWorkerGlobalScope* scope = |
| ToServiceWorkerGlobalScope(GetExecutionContext()); |
| |
| if (!scope->IsInstalling()) { |
| exception_state.ThrowTypeError("Outside the installation phase"); |
| return ScriptPromise(); |
| } |
| |
| ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state); |
| int64_t service_worker_registration_id = |
| scope->registration()->WebRegistration()->RegistrationId(); |
| subscription_backend_->AppendSubscriptions( |
| service_worker_registration_id, std::move(backend_subscriptions), |
| WTF::Bind(&CookieStore::OnSubscribeToCookieChangesResult, |
| WrapPersistent(resolver))); |
| return resolver->Promise(); |
| } |
| |
| ScriptPromise CookieStore::getChangeSubscriptions( |
| ScriptState* script_state, |
| ExceptionState& exception_state) { |
| DCHECK(GetExecutionContext()->IsServiceWorkerGlobalScope()); |
| |
| if (!subscription_backend_) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError, |
| "CookieStore backend went away"); |
| return ScriptPromise(); |
| } |
| |
| ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state); |
| ServiceWorkerGlobalScope* scope = |
| ToServiceWorkerGlobalScope(GetExecutionContext()); |
| int64_t service_worker_registration_id = |
| scope->registration()->WebRegistration()->RegistrationId(); |
| subscription_backend_->GetSubscriptions( |
| service_worker_registration_id, |
| WTF::Bind(&CookieStore::OnGetCookieChangeSubscriptionResult, |
| WrapPersistent(resolver))); |
| return resolver->Promise(); |
| } |
| |
| void CookieStore::ContextDestroyed(ExecutionContext* execution_context) { |
| StopObserving(); |
| backend_.reset(); |
| } |
| |
| const AtomicString& CookieStore::InterfaceName() const { |
| return EventTargetNames::CookieStore; |
| } |
| |
| ExecutionContext* CookieStore::GetExecutionContext() const { |
| return ContextLifecycleObserver::GetExecutionContext(); |
| } |
| |
| void CookieStore::RemoveAllEventListeners() { |
| EventTargetWithInlineData::RemoveAllEventListeners(); |
| DCHECK(!HasEventListeners()); |
| StopObserving(); |
| } |
| |
| void CookieStore::OnCookieChange( |
| const WebCanonicalCookie& backend_cookie, |
| network::mojom::blink::CookieChangeCause change_cause) { |
| HeapVector<CookieListItem> changed, deleted; |
| CookieChangeEvent::ToEventInfo(backend_cookie, change_cause, changed, |
| deleted); |
| if (changed.IsEmpty() && deleted.IsEmpty()) { |
| // The backend only reported OVERWRITE events, which are dropped. |
| return; |
| } |
| DispatchEvent(CookieChangeEvent::Create( |
| EventTypeNames::change, std::move(changed), std::move(deleted))); |
| } |
| |
| void CookieStore::AddedEventListener( |
| const AtomicString& event_type, |
| RegisteredEventListener& registered_listener) { |
| EventTargetWithInlineData::AddedEventListener(event_type, |
| registered_listener); |
| StartObserving(); |
| } |
| |
| void CookieStore::RemovedEventListener( |
| const AtomicString& event_type, |
| const RegisteredEventListener& registered_listener) { |
| EventTargetWithInlineData::RemovedEventListener(event_type, |
| registered_listener); |
| if (!HasEventListeners()) |
| StopObserving(); |
| } |
| |
| CookieStore::CookieStore( |
| ExecutionContext* execution_context, |
| network::mojom::blink::RestrictedCookieManagerPtr backend, |
| blink::mojom::blink::CookieStorePtr subscription_backend) |
| : ContextLifecycleObserver(execution_context), |
| backend_(std::move(backend)), |
| subscription_backend_(std::move(subscription_backend)), |
| change_listener_binding_(this), |
| default_cookie_url_(DefaultCookieURL(execution_context)), |
| default_site_for_cookies_(DefaultSiteForCookies(execution_context)) { |
| DCHECK(backend_); |
| } |
| |
| ScriptPromise CookieStore::DoRead( |
| ScriptState* script_state, |
| const String& name, |
| const CookieStoreGetOptions& options, |
| DoReadBackendResultConverter backend_result_converter, |
| ExceptionState& exception_state) { |
| network::mojom::blink::CookieManagerGetOptionsPtr backend_options = |
| ToBackendOptions(name, options, exception_state); |
| if (backend_options.is_null()) { |
| DCHECK(exception_state.HadException()); |
| return ScriptPromise(); |
| } |
| |
| if (!backend_) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError, |
| "CookieStore backend went away"); |
| return ScriptPromise(); |
| } |
| |
| ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state); |
| backend_->GetAllForUrl( |
| default_cookie_url_, default_site_for_cookies_, |
| std::move(backend_options), |
| WTF::Bind(backend_result_converter, WrapPersistent(resolver))); |
| return resolver->Promise(); |
| } |
| |
| // static |
| void CookieStore::GetAllForUrlToGetAllResult( |
| ScriptPromiseResolver* resolver, |
| const Vector<WebCanonicalCookie>& backend_cookies) { |
| ScriptState* script_state = resolver->GetScriptState(); |
| if (!script_state->ContextIsValid()) |
| return; |
| |
| HeapVector<CookieListItem> cookies; |
| cookies.ReserveInitialCapacity(backend_cookies.size()); |
| for (const auto& backend_cookie : backend_cookies) { |
| CookieListItem& cookie = cookies.emplace_back(); |
| CookieChangeEvent::ToCookieListItem(backend_cookie, false /* is_deleted */, |
| cookie); |
| } |
| |
| resolver->Resolve(std::move(cookies)); |
| } |
| |
| // static |
| void CookieStore::GetAllForUrlToGetResult( |
| ScriptPromiseResolver* resolver, |
| const Vector<WebCanonicalCookie>& backend_cookies) { |
| ScriptState* script_state = resolver->GetScriptState(); |
| if (!script_state->ContextIsValid()) |
| return; |
| |
| if (backend_cookies.IsEmpty()) { |
| resolver->Resolve(v8::Null(script_state->GetIsolate())); |
| return; |
| } |
| |
| const auto& backend_cookie = backend_cookies.front(); |
| CookieListItem cookie; |
| CookieChangeEvent::ToCookieListItem(backend_cookie, false /* is_deleted */, |
| cookie); |
| resolver->Resolve(cookie); |
| } |
| |
| // static |
| void CookieStore::GetAllForUrlToHasResult( |
| ScriptPromiseResolver* resolver, |
| const Vector<WebCanonicalCookie>& backend_cookies) { |
| ScriptState* script_state = resolver->GetScriptState(); |
| if (!script_state->ContextIsValid()) |
| return; |
| |
| resolver->Resolve(!backend_cookies.IsEmpty()); |
| } |
| |
| ScriptPromise CookieStore::DoWrite(ScriptState* script_state, |
| const String& name, |
| const String& value, |
| const CookieStoreSetOptions& options, |
| bool is_deletion, |
| ExceptionState& exception_state) { |
| base::Optional<WebCanonicalCookie> canonical_cookie = ToWebCanonicalCookie( |
| default_cookie_url_, name, value, is_deletion, options, exception_state); |
| if (!canonical_cookie) { |
| DCHECK(exception_state.HadException()); |
| return ScriptPromise(); |
| } |
| |
| if (!backend_) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError, |
| "CookieStore backend went away"); |
| return ScriptPromise(); |
| } |
| |
| ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state); |
| backend_->SetCanonicalCookie( |
| std::move(canonical_cookie.value()), default_cookie_url_, |
| default_site_for_cookies_, |
| WTF::Bind(&CookieStore::OnSetCanonicalCookieResult, |
| WrapPersistent(resolver))); |
| return resolver->Promise(); |
| } |
| |
| // static |
| void CookieStore::OnSetCanonicalCookieResult(ScriptPromiseResolver* resolver, |
| bool backend_success) { |
| ScriptState* script_state = resolver->GetScriptState(); |
| if (!script_state->ContextIsValid()) |
| return; |
| |
| if (!backend_success) { |
| resolver->Reject(DOMException::Create( |
| DOMExceptionCode::kUnknownError, |
| "An unknown error occured while writing the cookie.")); |
| return; |
| } |
| resolver->Resolve(); |
| } |
| |
| // static |
| void CookieStore::OnSubscribeToCookieChangesResult( |
| ScriptPromiseResolver* resolver, |
| bool backend_success) { |
| ScriptState* script_state = resolver->GetScriptState(); |
| if (!script_state->ContextIsValid()) |
| return; |
| |
| if (!backend_success) { |
| resolver->Reject(DOMException::Create( |
| DOMExceptionCode::kUnknownError, |
| "An unknown error occured while subscribing to cookie changes.")); |
| return; |
| } |
| resolver->Resolve(); |
| } |
| |
| // static |
| void CookieStore::OnGetCookieChangeSubscriptionResult( |
| ScriptPromiseResolver* resolver, |
| Vector<blink::mojom::blink::CookieChangeSubscriptionPtr> backend_result, |
| bool backend_success) { |
| ScriptState* script_state = resolver->GetScriptState(); |
| if (!script_state->ContextIsValid()) |
| return; |
| |
| if (!backend_success) { |
| resolver->Reject(DOMException::Create( |
| DOMExceptionCode::kUnknownError, |
| "An unknown error occured while reading cookie change subscriptions.")); |
| return; |
| } |
| |
| HeapVector<CookieStoreGetOptions> subscriptions; |
| subscriptions.ReserveInitialCapacity(backend_result.size()); |
| for (const auto& backend_subscription : backend_result) { |
| CookieStoreGetOptions& subscription = subscriptions.emplace_back(); |
| ToCookieChangeSubscription(*backend_subscription, subscription); |
| } |
| |
| resolver->Resolve(std::move(subscriptions)); |
| } |
| |
| void CookieStore::StartObserving() { |
| if (change_listener_binding_ || !backend_) |
| return; |
| |
| network::mojom::blink::CookieChangeListenerPtr change_listener; |
| change_listener_binding_.Bind(mojo::MakeRequest(&change_listener)); |
| backend_->AddChangeListener(default_cookie_url_, default_site_for_cookies_, |
| std::move(change_listener), {}); |
| } |
| |
| void CookieStore::StopObserving() { |
| if (!change_listener_binding_.is_bound()) |
| return; |
| change_listener_binding_.Close(); |
| } |
| |
| } // namespace blink |