| // Copyright (c) 2019 The Chromium Embedded Framework Authors. Portions |
| // Copyright (c) 2018 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 "libcef/browser/net_service/browser_urlrequest_impl.h" |
| |
| #include <string> |
| #include <utility> |
| |
| #include "libcef/browser/browser_context.h" |
| #include "libcef/browser/frame_host_impl.h" |
| #include "libcef/browser/net_service/url_loader_factory_getter.h" |
| #include "libcef/browser/request_context_impl.h" |
| #include "libcef/browser/thread_util.h" |
| #include "libcef/common/net_service/net_service_util.h" |
| #include "libcef/common/request_impl.h" |
| #include "libcef/common/response_impl.h" |
| #include "libcef/common/task_runner_impl.h" |
| |
| #include "base/lazy_instance.h" |
| #include "base/logging.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/strings/string_util.h" |
| #include "content/public/browser/global_request_id.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "net/base/mime_util.h" |
| #include "net/base/net_errors.h" |
| #include "net/http/http_response_headers.h" |
| #include "services/network/public/cpp/shared_url_loader_factory.h" |
| #include "services/network/public/cpp/simple_url_loader.h" |
| #include "services/network/public/cpp/simple_url_loader_stream_consumer.h" |
| #include "third_party/blink/public/mojom/loader/resource_load_info.mojom.h" |
| |
| namespace { |
| |
| const int32_t kInitialRequestID = -2; |
| |
| // Request ID for requests initiated by CefBrowserURLRequest. request_ids |
| // generated by child processes are counted up from 0, while browser |
| // created requests start at -2 and go down from there. (We need to start at -2 |
| // because -1 is used as a special value all over the resource_dispatcher_host |
| // for uninitialized variables.) The resource_dispatcher_host code path is not |
| // used when NetworkService is enabled so it's safe to repurpose the -2 and |
| // below range here. |
| // This method is only called on the UI thread. |
| int32_t MakeRequestID() { |
| static int32_t request_id = kInitialRequestID; |
| return --request_id; |
| } |
| |
| // Manages the mapping of request IDs to request objects. |
| class RequestManager { |
| public: |
| RequestManager() {} |
| ~RequestManager() { DCHECK(map_.empty()); } |
| |
| void Add(int32_t request_id, |
| CefRefPtr<CefBrowserURLRequest> request, |
| CefRefPtr<CefURLRequestClient> client) { |
| DCHECK_LE(request_id, kInitialRequestID); |
| |
| base::AutoLock lock_scope(lock_); |
| DCHECK(map_.find(request_id) == map_.end()); |
| map_.insert(std::make_pair(request_id, std::make_pair(request, client))); |
| } |
| |
| void Remove(int32_t request_id) { |
| if (request_id > kInitialRequestID) |
| return; |
| |
| base::AutoLock lock_scope(lock_); |
| RequestMap::iterator it = map_.find(request_id); |
| DCHECK(it != map_.end()); |
| map_.erase(it); |
| } |
| |
| base::Optional<CefBrowserURLRequest::RequestInfo> Get(int32_t request_id) { |
| if (request_id > kInitialRequestID) |
| return base::nullopt; |
| |
| base::AutoLock lock_scope(lock_); |
| RequestMap::const_iterator it = map_.find(request_id); |
| if (it != map_.end()) { |
| return it->second; |
| } |
| return base::nullopt; |
| } |
| |
| private: |
| base::Lock lock_; |
| |
| using RequestMap = std::map<int32_t, CefBrowserURLRequest::RequestInfo>; |
| RequestMap map_; |
| |
| DISALLOW_COPY_AND_ASSIGN(RequestManager); |
| }; |
| |
| #if DCHECK_IS_ON() |
| // Because of DCHECK()s in the object destructor. |
| base::LazyInstance<RequestManager>::DestructorAtExit g_manager = |
| LAZY_INSTANCE_INITIALIZER; |
| #else |
| base::LazyInstance<RequestManager>::Leaky g_manager = LAZY_INSTANCE_INITIALIZER; |
| #endif |
| |
| } // namespace |
| |
| // CefBrowserURLRequest::Context ---------------------------------------------- |
| |
| class CefBrowserURLRequest::Context |
| : public network::SimpleURLLoaderStreamConsumer { |
| public: |
| Context(CefRefPtr<CefBrowserURLRequest> url_request, |
| CefRefPtr<CefFrame> frame, |
| CefRefPtr<CefRequest> request, |
| CefRefPtr<CefURLRequestClient> client, |
| CefRefPtr<CefRequestContext> request_context) |
| : url_request_(url_request), |
| frame_(frame), |
| request_(static_cast<CefRequestImpl*>(request.get())), |
| client_(client), |
| request_context_(request_context), |
| task_runner_(CefTaskRunnerImpl::GetCurrentTaskRunner()), |
| response_(new CefResponseImpl()), |
| weak_ptr_factory_(this) { |
| // Mark the request/response objects as read-only. |
| request_->SetReadOnly(true); |
| response_->SetReadOnly(true); |
| } |
| ~Context() override = default; |
| |
| bool Start() { |
| DCHECK(CalledOnValidThread()); |
| |
| const GURL& url = GURL(request_->GetURL().ToString()); |
| if (!url.is_valid()) |
| return false; |
| |
| CEF_POST_TASK( |
| CEF_UIT, |
| base::BindOnce( |
| &CefBrowserURLRequest::Context::GetURLLoaderFactoryGetterOnUIThread, |
| frame_, request_context_, weak_ptr_factory_.GetWeakPtr(), |
| task_runner_)); |
| |
| return true; |
| } |
| |
| void Cancel() { |
| DCHECK(CalledOnValidThread()); |
| |
| // The request may already be complete or canceled. |
| if (!url_request_) |
| return; |
| |
| DCHECK_EQ(status_, UR_IO_PENDING); |
| status_ = UR_CANCELED; |
| |
| response_->SetReadOnly(false); |
| response_->SetError(ERR_ABORTED); |
| response_->SetReadOnly(true); |
| |
| cleanup_immediately_ = true; |
| OnComplete(false); |
| } |
| |
| CefRefPtr<CefRequest> request() const { return request_.get(); } |
| CefRefPtr<CefURLRequestClient> client() const { return client_; } |
| CefURLRequest::Status status() const { return status_; } |
| CefRefPtr<CefResponse> response() const { return response_.get(); } |
| bool response_was_cached() const { return response_was_cached_; } |
| |
| inline bool CalledOnValidThread() { |
| return task_runner_->RunsTasksInCurrentSequence(); |
| } |
| |
| private: |
| static void GetURLLoaderFactoryGetterOnUIThread( |
| CefRefPtr<CefFrame> frame, |
| CefRefPtr<CefRequestContext> request_context, |
| base::WeakPtr<CefBrowserURLRequest::Context> self, |
| scoped_refptr<base::SequencedTaskRunner> task_runner) { |
| CEF_REQUIRE_UIT(); |
| |
| // Get or create the request context and browser context. |
| CefRefPtr<CefRequestContextImpl> request_context_impl = |
| CefRequestContextImpl::GetOrCreateForRequestContext(request_context); |
| DCHECK(request_context_impl); |
| CefBrowserContext* browser_context = |
| request_context_impl->GetBrowserContext(); |
| DCHECK(browser_context); |
| |
| int render_frame_id = MSG_ROUTING_NONE; |
| scoped_refptr<net_service::URLLoaderFactoryGetter> loader_factory_getter; |
| if (frame) { |
| // The request will be associated with this frame/browser if it's valid, |
| // otherwise the request will be canceled. |
| content::RenderFrameHost* rfh = |
| static_cast<CefFrameHostImpl*>(frame.get())->GetRenderFrameHost(); |
| if (rfh) { |
| // In cases where authentication is required this value will be passed |
| // as the |routing_id| parameter to |
| // NetworkServiceClient::OnAuthRequired. Despite the naming the |
| // GetWebContents method in network_service_client.cc expects it to be a |
| // FrameTreeNodeId. The |process_id| parameter will always be |
| // network::mojom::kBrowserProcessId (value 0) for these requests. |
| render_frame_id = rfh->GetFrameTreeNodeId(); |
| |
| loader_factory_getter = |
| net_service::URLLoaderFactoryGetter::Create(rfh, browser_context); |
| } |
| } else { |
| loader_factory_getter = |
| net_service::URLLoaderFactoryGetter::Create(nullptr, browser_context); |
| } |
| |
| task_runner->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| &CefBrowserURLRequest::Context::ContinueOnOriginatingThread, self, |
| render_frame_id, MakeRequestID(), loader_factory_getter)); |
| } |
| |
| void ContinueOnOriginatingThread( |
| int render_frame_id, |
| int32_t request_id, |
| scoped_refptr<net_service::URLLoaderFactoryGetter> |
| loader_factory_getter) { |
| DCHECK(CalledOnValidThread()); |
| |
| // The request may have been canceled. |
| if (!url_request_) |
| return; |
| |
| if (!loader_factory_getter) { |
| // Cancel the request immediately. |
| Cancel(); |
| return; |
| } |
| |
| DCHECK_EQ(status_, UR_IO_PENDING); |
| |
| loader_factory_getter_ = loader_factory_getter; |
| |
| const int request_flags = request_->GetFlags(); |
| |
| // Create the URLLoaderFactory and bind to this thread. |
| auto loader_factory = loader_factory_getter_->GetURLLoaderFactory(); |
| |
| auto resource_request = std::make_unique<network::ResourceRequest>(); |
| static_cast<CefRequestImpl*>(request_.get()) |
| ->Get(resource_request.get(), false); |
| |
| resource_request->render_frame_id = render_frame_id; |
| |
| // Behave the same as a subresource load. |
| resource_request->fetch_request_context_type = |
| static_cast<int>(blink::mojom::RequestContextType::SUBRESOURCE); |
| resource_request->resource_type = |
| static_cast<int>(blink::mojom::ResourceType::kSubResource); |
| |
| // Set the origin to match the request. |
| const GURL& url = GURL(request_->GetURL().ToString()); |
| resource_request->request_initiator = url::Origin::Create(url); |
| |
| if (request_flags & UR_FLAG_ALLOW_STORED_CREDENTIALS) { |
| // Include SameSite cookies. |
| resource_request->attach_same_site_cookies = true; |
| resource_request->site_for_cookies = |
| net::SiteForCookies::FromOrigin(*resource_request->request_initiator); |
| } |
| |
| // SimpleURLLoader is picky about the body contents. Try to populate them |
| // correctly below. |
| auto request_body = resource_request->request_body; |
| resource_request->request_body = nullptr; |
| |
| std::string content_type; |
| std::string method = resource_request->method; |
| if (request_body) { |
| if (method == "GET" || method == "HEAD") { |
| // Fix the method value to allow a request body. |
| method = "POST"; |
| resource_request->method = method; |
| |
| request_->SetReadOnly(false); |
| request_->SetMethod(method); |
| request_->SetReadOnly(true); |
| } |
| resource_request->headers.GetHeader(net::HttpRequestHeaders::kContentType, |
| &content_type); |
| } |
| |
| loader_ = network::SimpleURLLoader::Create(std::move(resource_request), |
| MISSING_TRAFFIC_ANNOTATION); |
| |
| // Associate the request with |request_id|. |
| request_id_ = request_id; |
| loader_->SetRequestID(request_id); |
| g_manager.Get().Add(request_id, url_request_, client_); |
| |
| if (request_body) { |
| if (request_body->elements()->size() == 1) { |
| const auto& element = (*request_body->elements())[0]; |
| if (element.type() == network::mojom::DataElementType::kFile) { |
| if (content_type.empty()) { |
| const auto& extension = element.path().Extension(); |
| if (!extension.empty()) { |
| // Requests should not block on the disk! On POSIX this goes to |
| // disk. http://code.google.com/p/chromium/issues/detail?id=59849 |
| base::ThreadRestrictions::ScopedAllowIO allow_io; |
| // Also remove the leading period. |
| net::GetMimeTypeFromExtension(extension.substr(1), &content_type); |
| } |
| } |
| loader_->AttachFileForUpload(element.path(), content_type); |
| } else if (element.type() == network::mojom::DataElementType::kBytes) { |
| if (content_type.empty()) { |
| content_type = net_service::kContentTypeApplicationFormURLEncoded; |
| } |
| loader_->AttachStringForUpload( |
| std::string(element.bytes() + element.offset(), |
| element.length() - element.offset()), |
| content_type); |
| |
| if (request_flags & UR_FLAG_REPORT_UPLOAD_PROGRESS) { |
| // Report the expected upload data size. |
| upload_data_size_ = element.length() - element.offset(); |
| } |
| } else { |
| NOTIMPLEMENTED() << "Unsupported element type: " << element.type(); |
| } |
| } else if (request_body->elements()->size() > 1) { |
| NOTIMPLEMENTED() << "Multi-part form data is not supported"; |
| } |
| } |
| |
| // Allow delivery of non-2xx response bodies. |
| loader_->SetAllowHttpErrorResults(true); |
| |
| if (!(request_flags & UR_FLAG_NO_RETRY_ON_5XX)) { |
| // Allow 2 retries on 5xx response or network change. |
| // TODO(network): Consider exposing configuration of max retries and/or |
| // RETRY_ON_NETWORK_CHANGE as a separate flag. |
| loader_->SetRetryOptions( |
| 2, network::SimpleURLLoader::RETRY_ON_5XX | |
| network::SimpleURLLoader::RETRY_ON_NETWORK_CHANGE); |
| } |
| |
| if (request_flags & UR_FLAG_STOP_ON_REDIRECT) { |
| // The request will be canceled in OnRedirect. |
| loader_->SetOnRedirectCallback( |
| base::Bind(&CefBrowserURLRequest::Context::OnRedirect, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| if (request_flags & UR_FLAG_REPORT_UPLOAD_PROGRESS) { |
| loader_->SetOnUploadProgressCallback( |
| base::Bind(&CefBrowserURLRequest::Context::OnUploadProgress, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| if ((request_flags & UR_FLAG_NO_DOWNLOAD_DATA) || method == "HEAD") { |
| loader_->DownloadHeadersOnly( |
| loader_factory.get(), |
| base::BindOnce(&CefBrowserURLRequest::Context::OnHeadersOnly, |
| weak_ptr_factory_.GetWeakPtr())); |
| } else { |
| loader_->SetOnResponseStartedCallback( |
| base::BindOnce(&CefBrowserURLRequest::Context::OnResponseStarted, |
| weak_ptr_factory_.GetWeakPtr())); |
| loader_->SetOnDownloadProgressCallback( |
| base::Bind(&CefBrowserURLRequest::Context::OnDownloadProgress, |
| weak_ptr_factory_.GetWeakPtr())); |
| |
| loader_->DownloadAsStream(loader_factory.get(), this); |
| } |
| } |
| |
| void OnHeadersOnly(scoped_refptr<net::HttpResponseHeaders> headers) { |
| DCHECK(CalledOnValidThread()); |
| DCHECK_EQ(status_, UR_IO_PENDING); |
| |
| response_->SetReadOnly(false); |
| response_->SetResponseHeaders(*headers); |
| response_->SetReadOnly(true); |
| |
| // Match the previous behavior of sending download progress notifications |
| // for UR_FLAG_NO_DOWNLOAD_DATA requests but not HEAD requests. |
| if (request_->GetMethod().ToString() != "HEAD") { |
| download_data_size_ = headers->GetContentLength(); |
| OnDownloadProgress(0); |
| } |
| |
| cleanup_immediately_ = true; |
| OnComplete(true); |
| } |
| |
| void OnRedirect(const net::RedirectInfo& redirect_info, |
| const network::mojom::URLResponseHead& response_head, |
| std::vector<std::string>* removed_headers) { |
| DCHECK(CalledOnValidThread()); |
| DCHECK_EQ(status_, UR_IO_PENDING); |
| |
| // This method is only called if we intend to stop on redirects. |
| DCHECK(request_->GetFlags() | UR_FLAG_STOP_ON_REDIRECT); |
| |
| response_->SetReadOnly(false); |
| response_->SetURL(redirect_info.new_url.spec()); |
| response_->SetResponseHeaders(*response_head.headers); |
| response_->SetReadOnly(true); |
| |
| Cancel(); |
| } |
| |
| void OnResponseStarted(const GURL& final_url, |
| const network::mojom::URLResponseHead& response_head) { |
| DCHECK(CalledOnValidThread()); |
| DCHECK_EQ(status_, UR_IO_PENDING); |
| |
| response_->SetReadOnly(false); |
| response_->SetURL(final_url.spec()); |
| response_->SetResponseHeaders(*response_head.headers); |
| response_->SetReadOnly(true); |
| |
| download_data_size_ = response_head.content_length; |
| } |
| |
| void OnUploadProgress(uint64_t position, uint64_t total) { |
| DCHECK(CalledOnValidThread()); |
| DCHECK_EQ(status_, UR_IO_PENDING); |
| |
| upload_data_size_ = total; |
| if (position == total) |
| got_upload_progress_complete_ = true; |
| |
| client_->OnUploadProgress(url_request_.get(), position, total); |
| } |
| |
| void OnDownloadProgress(uint64_t current) { |
| DCHECK(CalledOnValidThread()); |
| DCHECK_EQ(status_, UR_IO_PENDING); |
| |
| if (response_->GetStatus() == 0) { |
| // With failed requests this callback may arrive without a proceeding |
| // OnHeadersOnly or OnResponseStarted. |
| return; |
| } |
| |
| NotifyUploadProgressIfNecessary(); |
| |
| client_->OnDownloadProgress(url_request_.get(), current, |
| download_data_size_); |
| } |
| |
| void NotifyUploadProgressIfNecessary() { |
| if (!got_upload_progress_complete_ && upload_data_size_ > 0) { |
| // URLLoader sends upload notifications using a timer and will not send |
| // a notification if the request completes too quickly. We therefore |
| // send the notification here if necessary. |
| client_->OnUploadProgress(url_request_.get(), upload_data_size_, |
| upload_data_size_); |
| got_upload_progress_complete_ = true; |
| } |
| } |
| |
| // SimpleURLLoaderStreamConsumer methods: |
| void OnDataReceived(base::StringPiece string_piece, |
| base::OnceClosure resume) override { |
| DCHECK(CalledOnValidThread()); |
| DCHECK_EQ(status_, UR_IO_PENDING); |
| |
| client_->OnDownloadData(url_request_.get(), string_piece.data(), |
| string_piece.length()); |
| std::move(resume).Run(); |
| } |
| |
| void OnComplete(bool success) override { |
| DCHECK(CalledOnValidThread()); |
| |
| // The request may already be complete or canceled. |
| if (!url_request_) |
| return; |
| |
| // Status will be UR_IO_PENDING if we're called when the request is complete |
| // (via SimpleURLLoaderStreamConsumer or OnHeadersOnly). We can only call |
| // these SimpleURLLoader methods if the request is complete. |
| if (status_ == UR_IO_PENDING) { |
| status_ = success ? UR_SUCCESS : UR_FAILED; |
| |
| response_->SetReadOnly(false); |
| response_->SetURL(loader_->GetFinalURL().spec()); |
| response_->SetError(static_cast<cef_errorcode_t>(loader_->NetError())); |
| response_->SetReadOnly(true); |
| |
| response_was_cached_ = loader_->LoadedFromCache(); |
| } |
| |
| if (success) |
| NotifyUploadProgressIfNecessary(); |
| |
| client_->OnRequestComplete(url_request_.get()); |
| |
| // When called via SimpleURLLoaderStreamConsumer we need to cleanup |
| // asynchronously. If the load is still pending this will also cancel it. |
| Cleanup(); |
| } |
| |
| void OnRetry(base::OnceClosure start_retry) override { |
| DCHECK(CalledOnValidThread()); |
| DCHECK_EQ(status_, UR_IO_PENDING); |
| std::move(start_retry).Run(); |
| } |
| |
| void Cleanup() { |
| DCHECK(CalledOnValidThread()); |
| DCHECK(url_request_); |
| |
| g_manager.Get().Remove(request_id_); |
| |
| client_ = nullptr; |
| request_context_ = nullptr; |
| |
| // We may be canceled before the loader is created. |
| if (loader_) { |
| // Must delete the loader before the factory. |
| if (cleanup_immediately_) { |
| // Most SimpleURLLoader callbacks let us delete the URLLoader objects |
| // immediately. |
| loader_.reset(); |
| loader_factory_getter_ = nullptr; |
| } else { |
| // Delete the URLLoader objects asynchronously on the correct thread. |
| task_runner_->DeleteSoon(FROM_HERE, std::move(loader_)); |
| task_runner_->ReleaseSoon(FROM_HERE, std::move(loader_factory_getter_)); |
| } |
| } |
| |
| // We may be holding the last reference to |url_request_|, destruction of |
| // which will delete |this|. Use a local variable to keep |url_request_| |
| // alive until this method returns. |
| auto url_request = url_request_; |
| url_request_ = nullptr; |
| } |
| |
| // Members only accessed on the initialization thread. |
| CefRefPtr<CefBrowserURLRequest> url_request_; |
| CefRefPtr<CefFrame> frame_; |
| CefRefPtr<CefRequestImpl> request_; |
| CefRefPtr<CefURLRequestClient> client_; |
| CefRefPtr<CefRequestContext> request_context_; |
| scoped_refptr<base::SequencedTaskRunner> task_runner_; |
| |
| scoped_refptr<net_service::URLLoaderFactoryGetter> loader_factory_getter_; |
| std::unique_ptr<network::SimpleURLLoader> loader_; |
| |
| int32_t request_id_ = 0; |
| |
| CefURLRequest::Status status_ = UR_IO_PENDING; |
| CefRefPtr<CefResponseImpl> response_; |
| bool response_was_cached_ = false; |
| int64 upload_data_size_ = 0; |
| int64 download_data_size_ = -1; |
| bool got_upload_progress_complete_ = false; |
| bool cleanup_immediately_ = false; |
| |
| // Must be the last member. |
| base::WeakPtrFactory<CefBrowserURLRequest::Context> weak_ptr_factory_; |
| }; |
| |
| // CefBrowserURLRequest ------------------------------------------------------- |
| |
| // static |
| base::Optional<CefBrowserURLRequest::RequestInfo> |
| CefBrowserURLRequest::FromRequestID(int32_t request_id) { |
| return g_manager.Get().Get(request_id); |
| } |
| |
| // static |
| base::Optional<CefBrowserURLRequest::RequestInfo> |
| CefBrowserURLRequest::FromRequestID( |
| const content::GlobalRequestID& request_id) { |
| if (request_id.child_id == network::mojom::kBrowserProcessId) { |
| return FromRequestID(request_id.request_id); |
| } |
| return base::nullopt; |
| } |
| |
| CefBrowserURLRequest::CefBrowserURLRequest( |
| CefRefPtr<CefFrame> frame, |
| CefRefPtr<CefRequest> request, |
| CefRefPtr<CefURLRequestClient> client, |
| CefRefPtr<CefRequestContext> request_context) { |
| context_.reset(new Context(this, frame, request, client, request_context)); |
| } |
| |
| CefBrowserURLRequest::~CefBrowserURLRequest() {} |
| |
| bool CefBrowserURLRequest::Start() { |
| if (!VerifyContext()) |
| return false; |
| return context_->Start(); |
| } |
| |
| CefRefPtr<CefRequest> CefBrowserURLRequest::GetRequest() { |
| if (!VerifyContext()) |
| return nullptr; |
| return context_->request(); |
| } |
| |
| CefRefPtr<CefURLRequestClient> CefBrowserURLRequest::GetClient() { |
| if (!VerifyContext()) |
| return nullptr; |
| return context_->client(); |
| } |
| |
| CefURLRequest::Status CefBrowserURLRequest::GetRequestStatus() { |
| if (!VerifyContext()) |
| return UR_UNKNOWN; |
| return context_->status(); |
| } |
| |
| CefURLRequest::ErrorCode CefBrowserURLRequest::GetRequestError() { |
| if (!VerifyContext()) |
| return ERR_NONE; |
| return context_->response()->GetError(); |
| } |
| |
| CefRefPtr<CefResponse> CefBrowserURLRequest::GetResponse() { |
| if (!VerifyContext()) |
| return nullptr; |
| return context_->response(); |
| } |
| |
| bool CefBrowserURLRequest::ResponseWasCached() { |
| if (!VerifyContext()) |
| return false; |
| return context_->response_was_cached(); |
| } |
| |
| void CefBrowserURLRequest::Cancel() { |
| if (!VerifyContext()) |
| return; |
| return context_->Cancel(); |
| } |
| |
| bool CefBrowserURLRequest::VerifyContext() { |
| if (!context_->CalledOnValidThread()) { |
| NOTREACHED() << "called on invalid thread"; |
| return false; |
| } |
| |
| return true; |
| } |