blob: 050ac94822fc5cc501f91632c8c38078ec940b8a [file] [log] [blame]
// Copyright 2015 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 "components/web_restrictions/browser/web_restrictions_client.h"
#include "base/android/jni_string.h"
#include "base/bind.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/task_scheduler/post_task.h"
#include "base/threading/thread_restrictions.h"
#include "content/public/browser/browser_thread.h"
#include "jni/WebRestrictionsClient_jni.h"
using base::android::ScopedJavaGlobalRef;
namespace web_restrictions {
namespace {
const size_t kMaxCacheSize = 100;
bool RequestPermissionTask(
const std::string& url,
const base::android::JavaRef<jobject>& java_provider) {
JNIEnv* env = base::android::AttachCurrentThread();
return Java_WebRestrictionsClient_requestPermission(
env, java_provider,
base::android::ConvertUTF8ToJavaString(env, url));
}
bool CheckSupportsRequestTask(
const base::android::JavaRef<jobject>& java_provider) {
base::AssertBlockingAllowed();
JNIEnv* env = base::android::AttachCurrentThread();
return Java_WebRestrictionsClient_supportsRequest(env, java_provider);
}
} // namespace
WebRestrictionsClient::WebRestrictionsClient()
: initialized_(false), supports_request_(false) {
background_task_runner_ = base::CreateSequencedTaskRunnerWithTraits(
{base::MayBlock(), base::TaskPriority::BACKGROUND,
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN});
}
WebRestrictionsClient::~WebRestrictionsClient() {
if (java_provider_.is_null())
return;
JNIEnv* env = base::android::AttachCurrentThread();
Java_WebRestrictionsClient_onDestroy(env, java_provider_);
java_provider_.Reset();
}
void WebRestrictionsClient::SetAuthority(
const std::string& content_provider_authority) {
// This is called from the UI thread, but class members should only be
// accessed from the IO thread.
content::BrowserThread::PostTask(
content::BrowserThread::IO, FROM_HERE,
base::Bind(&WebRestrictionsClient::SetAuthorityTask,
base::Unretained(this), content_provider_authority));
}
void WebRestrictionsClient::SetAuthorityTask(
const std::string& content_provider_authority) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
// Destroy any existing content resolver.
JNIEnv* env = base::android::AttachCurrentThread();
if (!java_provider_.is_null()) {
Java_WebRestrictionsClient_onDestroy(env, java_provider_);
java_provider_.Reset();
}
ClearCache();
provider_authority_ = content_provider_authority;
// Initialize the content resolver.
initialized_ = !content_provider_authority.empty();
if (!initialized_)
return;
java_provider_.Reset(Java_WebRestrictionsClient_create(
env,
base::android::ConvertUTF8ToJavaString(env, content_provider_authority),
reinterpret_cast<jlong>(this)));
supports_request_ = false;
base::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE, {base::MayBlock(), base::TaskPriority::BACKGROUND},
base::Bind(&CheckSupportsRequestTask, java_provider_),
base::Bind(&WebRestrictionsClient::RequestSupportKnown,
base::Unretained(this), provider_authority_));
}
UrlAccess WebRestrictionsClient::ShouldProceed(
bool is_main_frame,
const std::string& url,
const base::Callback<void(bool)>& callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
if (!initialized_)
return ALLOW;
std::unique_ptr<const WebRestrictionsClientResult> result =
cache_.GetCacheEntry(url);
if (result) {
RecordURLAccess(url);
return result->ShouldProceed() ? ALLOW : DISALLOW;
}
base::PostTaskAndReplyWithResult(
background_task_runner_.get(), FROM_HERE,
base::Bind(&WebRestrictionsClient::ShouldProceedTask, url,
java_provider_),
base::Bind(&WebRestrictionsClient::OnShouldProceedComplete,
base::Unretained(this), provider_authority_, url, callback));
return PENDING;
}
bool WebRestrictionsClient::SupportsRequest() const {
return initialized_ && supports_request_;
}
void WebRestrictionsClient::RequestPermission(
const std::string& url,
const base::Callback<void(bool)>& request_success) {
if (!initialized_) {
request_success.Run(false);
return;
}
base::PostTaskAndReplyWithResult(
background_task_runner_.get(), FROM_HERE,
base::Bind(&RequestPermissionTask, url, java_provider_), request_success);
}
void WebRestrictionsClient::OnWebRestrictionsChanged(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj) {
content::BrowserThread::PostTask(
content::BrowserThread::IO, FROM_HERE,
base::Bind(&WebRestrictionsClient::ClearCache, base::Unretained(this)));
}
void WebRestrictionsClient::RecordURLAccess(const std::string& url) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
// Move the URL to the front of the cache.
recent_urls_.remove(url);
recent_urls_.push_front(url);
}
void WebRestrictionsClient::UpdateCache(const std::string& provider_authority,
const std::string& url,
ScopedJavaGlobalRef<jobject> result) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
// If the webrestrictions provider changed when the old one was being queried,
// do not update the cache for the new provider.
if (provider_authority != provider_authority_)
return;
RecordURLAccess(url);
if (recent_urls_.size() >= kMaxCacheSize) {
cache_.RemoveCacheEntry(recent_urls_.back());
recent_urls_.pop_back();
}
cache_.SetCacheEntry(url, WebRestrictionsClientResult(result));
}
void WebRestrictionsClient::RequestSupportKnown(
const std::string& provider_authority,
bool supports_request) {
// |supports_request_| is initialized to false.
DCHECK(!supports_request_);
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
// If the webrestrictions provider changed when the old one was being queried,
// ignore the result.
if (provider_authority != provider_authority_)
return;
supports_request_ = supports_request;
}
void WebRestrictionsClient::OnShouldProceedComplete(
std::string provider_authority,
const std::string& url,
const base::Callback<void(bool)>& callback,
const ScopedJavaGlobalRef<jobject>& result) {
UpdateCache(provider_authority, url, result);
callback.Run(cache_.GetCacheEntry(url)->ShouldProceed());
}
void WebRestrictionsClient::ClearCache() {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
cache_.Clear();
recent_urls_.clear();
}
std::unique_ptr<WebRestrictionsClientResult>
WebRestrictionsClient::GetCachedWebRestrictionsResult(const std::string& url) {
return cache_.GetCacheEntry(url);
}
// static
ScopedJavaGlobalRef<jobject> WebRestrictionsClient::ShouldProceedTask(
const std::string& url,
const base::android::JavaRef<jobject>& java_provider) {
JNIEnv* env = base::android::AttachCurrentThread();
base::android::ScopedJavaGlobalRef<jobject> result(
Java_WebRestrictionsClient_shouldProceed(
env, java_provider,
base::android::ConvertUTF8ToJavaString(env, url)));
return result;
}
WebRestrictionsClient::Cache::Cache() = default;
WebRestrictionsClient::Cache::~Cache() = default;
std::unique_ptr<WebRestrictionsClientResult>
WebRestrictionsClient::Cache::GetCacheEntry(const std::string& url) {
base::AutoLock lock(lock_);
auto iter = cache_data_.find(url);
if (iter == cache_data_.end())
return nullptr;
// This has to be thread-safe, so copy the data.
return std::unique_ptr<WebRestrictionsClientResult>(
new WebRestrictionsClientResult(iter->second));
}
void WebRestrictionsClient::Cache::SetCacheEntry(
const std::string& url,
const WebRestrictionsClientResult& entry) {
base::AutoLock lock(lock_);
cache_data_.emplace(url, entry);
}
void WebRestrictionsClient::Cache::RemoveCacheEntry(const std::string& url) {
base::AutoLock lock(lock_);
cache_data_.erase(url);
}
void WebRestrictionsClient::Cache::Clear() {
base::AutoLock lock(lock_);
cache_data_.clear();
}
} // namespace web_restrictions