blob: 8e68957d0131f093e1edaaad4011ff0d79fa37b2 [file] [log] [blame]
// Copyright 2019 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 "content/browser/native_file_system/native_file_system_handle_base.h"
#include "base/task/post_task.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/web_contents_observer.h"
namespace content {
class NativeFileSystemHandleBase::UsageIndicatorTracker
: public WebContentsObserver {
public:
UsageIndicatorTracker(int process_id,
int frame_id,
bool is_directory,
const base::FilePath& directory_path)
: WebContentsObserver(
WebContentsImpl::FromRenderFrameHostID(process_id, frame_id)),
is_directory_(is_directory),
directory_path_(directory_path) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (web_contents() && is_directory_)
web_contents()->AddNativeFileSystemDirectoryHandle(directory_path_);
}
~UsageIndicatorTracker() override {
if (web_contents()) {
if (is_directory_ && is_readable_)
web_contents()->RemoveNativeFileSystemDirectoryHandle(directory_path_);
if (is_writable_)
web_contents()->DecrementWritableNativeFileSystemHandleCount();
}
}
void UpdateStatus(bool readable, bool writable) {
SetReadable(readable);
SetWritable(writable);
}
void SetReadable(bool readable) {
if (readable == is_readable_ || !web_contents())
return;
is_readable_ = readable;
if (is_directory_) {
if (is_readable_)
web_contents()->AddNativeFileSystemDirectoryHandle(directory_path_);
else
web_contents()->RemoveNativeFileSystemDirectoryHandle(directory_path_);
}
}
void SetWritable(bool writable) {
if (writable == is_writable_ || !web_contents())
return;
is_writable_ = writable;
if (is_writable_)
web_contents()->IncrementWritableNativeFileSystemHandleCount();
else
web_contents()->DecrementWritableNativeFileSystemHandleCount();
}
WebContentsImpl* web_contents() const {
return static_cast<WebContentsImpl*>(WebContentsObserver::web_contents());
}
private:
const bool is_directory_;
const base::FilePath directory_path_;
bool is_readable_ = true;
bool is_writable_ = false;
};
NativeFileSystemHandleBase::NativeFileSystemHandleBase(
NativeFileSystemManagerImpl* manager,
const BindingContext& context,
const storage::FileSystemURL& url,
const SharedHandleState& handle_state,
bool is_directory)
: manager_(manager),
context_(context),
url_(url),
handle_state_(handle_state) {
DCHECK(manager_);
DCHECK_EQ(url_.mount_type() == storage::kFileSystemTypeIsolated,
handle_state_.file_system.is_valid())
<< url_.mount_type();
// For now only support sandboxed file system and native file system.
DCHECK(url_.type() == storage::kFileSystemTypeNativeLocal ||
url_.type() == storage::kFileSystemTypePersistent ||
url_.type() == storage::kFileSystemTypeTemporary ||
url_.type() == storage::kFileSystemTypeTest)
<< url_.type();
if (url_.type() == storage::kFileSystemTypeNativeLocal) {
DCHECK_EQ(url_.mount_type(), storage::kFileSystemTypeIsolated);
handle_state_.read_grant->AddObserver(this);
// In some cases we use the same grant for read and write access. In that
// case only add an observer once.
if (handle_state_.read_grant != handle_state_.write_grant)
handle_state_.write_grant->AddObserver(this);
base::FilePath directory_path;
if (is_directory) {
// For usage reporting purposes try to get the root path of the isolated
// file system, i.e. the path the user picked in a directory picker.
auto* isolated_context = storage::IsolatedContext::GetInstance();
if (!isolated_context->GetRegisteredPath(handle_state_.file_system.id(),
&directory_path)) {
// If for some reason the isolated file system no longer exists, fall
// back to the path of the handle itself, which could be a child of the
// originally picked path.
directory_path = url.path();
}
}
usage_indicator_tracker_ = base::SequenceBound<UsageIndicatorTracker>(
base::CreateSingleThreadTaskRunnerWithTraits({BrowserThread::UI}),
context_.process_id, context_.frame_id, bool{is_directory},
base::FilePath(directory_path));
UpdateUsage();
}
}
NativeFileSystemHandleBase::~NativeFileSystemHandleBase() {
// It is fine to remove an observer that never was added, so no need to check
// for URL type and/or the same grant being used for read and write access.
handle_state_.read_grant->RemoveObserver(this);
handle_state_.write_grant->RemoveObserver(this);
}
NativeFileSystemHandleBase::PermissionStatus
NativeFileSystemHandleBase::GetReadPermissionStatus() {
return handle_state_.read_grant->GetStatus();
}
NativeFileSystemHandleBase::PermissionStatus
NativeFileSystemHandleBase::GetWritePermissionStatus() {
UpdateUsage();
// It is not currently possible to have write only handles, so first check the
// read permission status. See also:
// http://wicg.github.io/native-file-system/#api-filesystemhandle-querypermission
PermissionStatus read_status = GetReadPermissionStatus();
if (read_status != PermissionStatus::GRANTED)
return read_status;
return handle_state_.write_grant->GetStatus();
}
void NativeFileSystemHandleBase::DoGetPermissionStatus(
bool writable,
base::OnceCallback<void(PermissionStatus)> callback) {
std::move(callback).Run(writable ? GetWritePermissionStatus()
: GetReadPermissionStatus());
}
void NativeFileSystemHandleBase::DoRequestPermission(
bool writable,
base::OnceCallback<void(PermissionStatus)> callback) {
PermissionStatus current_status =
writable ? GetWritePermissionStatus() : GetReadPermissionStatus();
// If we already have a valid permission status, just return that. Also just
// return the current permission status if this is called from a worker, as we
// don't support prompting for increased permissions from workers.
//
// Currently the worker check here is redundant because there is no way for
// workers to get native file system handles. While workers will never be able
// to call chooseEntries(), they will be able to receive existing handles from
// windows via postMessage() and IndexedDB.
if (current_status != PermissionStatus::ASK || context_.is_worker()) {
std::move(callback).Run(current_status);
return;
}
if (!writable) {
handle_state_.read_grant->RequestPermission(
context().process_id, context().frame_id,
base::BindOnce(&NativeFileSystemHandleBase::DoGetPermissionStatus,
AsWeakPtr(), writable, std::move(callback)));
return;
}
// TODO(https://crbug.com/971401): Today read permission isn't revokable, so
// current status should always be GRANTED.
DCHECK_EQ(GetReadPermissionStatus(), PermissionStatus::GRANTED);
handle_state_.write_grant->RequestPermission(
context().process_id, context().frame_id,
base::BindOnce(&NativeFileSystemHandleBase::DoGetPermissionStatus,
AsWeakPtr(), writable, std::move(callback)));
}
void NativeFileSystemHandleBase::UpdateUsage() {
if (!usage_indicator_tracker_)
return;
bool is_readable =
handle_state_.read_grant->GetStatus() == PermissionStatus::GRANTED;
bool is_writable = is_readable && handle_state_.write_grant->GetStatus() ==
PermissionStatus::GRANTED;
if (is_writable != was_writable_at_last_check_ ||
is_readable != was_readable_at_last_check_) {
was_readable_at_last_check_ = is_readable;
was_writable_at_last_check_ = is_writable;
usage_indicator_tracker_.Post(FROM_HERE,
&UsageIndicatorTracker::UpdateStatus,
is_readable, is_writable);
}
}
void NativeFileSystemHandleBase::OnPermissionStatusChanged() {
UpdateUsage();
}
} // namespace content