1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "content/browser/native_file_system/native_file_system_handle_base.h"
6
7 #include "base/task/post_task.h"
8 #include "content/browser/native_file_system/native_file_system_error.h"
9 #include "content/browser/web_contents/web_contents_impl.h"
10 #include "content/public/browser/back_forward_cache.h"
11 #include "content/public/browser/browser_task_traits.h"
12
13 namespace content {
14
NativeFileSystemHandleBase(NativeFileSystemManagerImpl * manager,const BindingContext & context,const storage::FileSystemURL & url,const SharedHandleState & handle_state,bool is_directory)15 NativeFileSystemHandleBase::NativeFileSystemHandleBase(
16 NativeFileSystemManagerImpl* manager,
17 const BindingContext& context,
18 const storage::FileSystemURL& url,
19 const SharedHandleState& handle_state,
20 bool is_directory)
21 : manager_(manager),
22 context_(context),
23 url_(url),
24 handle_state_(handle_state) {
25 DCHECK(manager_);
26 DCHECK_EQ(url_.mount_type() == storage::kFileSystemTypeIsolated,
27 handle_state_.file_system.is_valid())
28 << url_.mount_type();
29 // For now only support sandboxed file system and native file system.
30 DCHECK(url_.type() == storage::kFileSystemTypeNativeLocal ||
31 url_.type() == storage::kFileSystemTypePersistent ||
32 url_.type() == storage::kFileSystemTypeTemporary ||
33 url_.type() == storage::kFileSystemTypeTest)
34 << url_.type();
35
36 if (ShouldTrackUsage()) {
37 DCHECK_EQ(url_.type(), storage::kFileSystemTypeNativeLocal);
38 DCHECK_EQ(url_.mount_type(), storage::kFileSystemTypeIsolated);
39
40 handle_state_.read_grant->AddObserver(this);
41 // In some cases we use the same grant for read and write access. In that
42 // case only add an observer once.
43 if (handle_state_.read_grant != handle_state_.write_grant)
44 handle_state_.write_grant->AddObserver(this);
45
46 Observe(WebContentsImpl::FromRenderFrameHostID(context_.process_id,
47 context_.frame_id));
48
49 // Disable back-forward cache as native file system's usage of
50 // RenderFrameHost::IsCurrent at the moment is not compatible with bfcache.
51 BackForwardCache::DisableForRenderFrameHost(
52 GlobalFrameRoutingId(context_.process_id, context_.frame_id),
53 "NativeFileSystem");
54
55 if (is_directory) {
56 // For usage reporting purposes try to get the root path of the isolated
57 // file system, i.e. the path the user picked in a directory picker.
58 auto* isolated_context = storage::IsolatedContext::GetInstance();
59 if (!isolated_context->GetRegisteredPath(
60 handle_state_.file_system.id(), &directory_for_usage_tracking_)) {
61 // If for some reason the isolated file system no longer exists, fall
62 // back to the path of the handle itself, which could be a child of
63 // the originally picked path.
64 directory_for_usage_tracking_ = url.path();
65 }
66 }
67
68 if (web_contents())
69 web_contents()->IncrementNativeFileSystemHandleCount();
70 UpdateUsage();
71 }
72 }
73
~NativeFileSystemHandleBase()74 NativeFileSystemHandleBase::~NativeFileSystemHandleBase() {
75 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
76 // It is fine to remove an observer that never was added, so no need to check
77 // for URL type and/or the same grant being used for read and write access.
78 handle_state_.read_grant->RemoveObserver(this);
79 handle_state_.write_grant->RemoveObserver(this);
80
81 if (ShouldTrackUsage() && web_contents()) {
82 web_contents()->DecrementNativeFileSystemHandleCount();
83 if (!directory_for_usage_tracking_.empty() && was_readable_at_last_check_) {
84 web_contents()->RemoveNativeFileSystemDirectoryHandle(
85 directory_for_usage_tracking_);
86 }
87 if (was_writable_at_last_check_)
88 web_contents()->DecrementWritableNativeFileSystemHandleCount();
89 }
90 }
91
92 NativeFileSystemHandleBase::PermissionStatus
GetReadPermissionStatus()93 NativeFileSystemHandleBase::GetReadPermissionStatus() {
94 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
95 return handle_state_.read_grant->GetStatus();
96 }
97
98 NativeFileSystemHandleBase::PermissionStatus
GetWritePermissionStatus()99 NativeFileSystemHandleBase::GetWritePermissionStatus() {
100 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
101 UpdateUsage();
102 // It is not currently possible to have write only handles, so first check the
103 // read permission status. See also:
104 // http://wicg.github.io/native-file-system/#api-filesystemhandle-querypermission
105 PermissionStatus read_status = GetReadPermissionStatus();
106 if (read_status != PermissionStatus::GRANTED)
107 return read_status;
108
109 return handle_state_.write_grant->GetStatus();
110 }
111
DoGetPermissionStatus(bool writable,base::OnceCallback<void (PermissionStatus)> callback)112 void NativeFileSystemHandleBase::DoGetPermissionStatus(
113 bool writable,
114 base::OnceCallback<void(PermissionStatus)> callback) {
115 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
116 std::move(callback).Run(writable ? GetWritePermissionStatus()
117 : GetReadPermissionStatus());
118 }
119
DoRequestPermission(bool writable,base::OnceCallback<void (blink::mojom::NativeFileSystemErrorPtr,PermissionStatus)> callback)120 void NativeFileSystemHandleBase::DoRequestPermission(
121 bool writable,
122 base::OnceCallback<void(blink::mojom::NativeFileSystemErrorPtr,
123 PermissionStatus)> callback) {
124 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
125 PermissionStatus current_status =
126 writable ? GetWritePermissionStatus() : GetReadPermissionStatus();
127 // If we already have a valid permission status, just return that. Also just
128 // return the current permission status if this is called from a worker, as we
129 // don't support prompting for increased permissions from workers.
130 //
131 // Currently the worker check here is redundant because there is no way for
132 // workers to get native file system handles. While workers will never be able
133 // to call chooseEntries(), they will be able to receive existing handles from
134 // windows via postMessage() and IndexedDB.
135 if (current_status != PermissionStatus::ASK || context_.is_worker()) {
136 std::move(callback).Run(native_file_system_error::Ok(), current_status);
137 return;
138 }
139 if (!writable) {
140 handle_state_.read_grant->RequestPermission(
141 context().process_id, context().frame_id,
142 base::BindOnce(&NativeFileSystemHandleBase::DidRequestPermission,
143 AsWeakPtr(), writable, std::move(callback)));
144 return;
145 }
146
147 // Ask for both read and write permission at the same time, the permission
148 // context should coalesce these into one prompt.
149 if (GetReadPermissionStatus() == PermissionStatus::ASK) {
150 // Ignore callback for the read permission request; if the request fails,
151 // the write permission request probably fails the same way. And we check
152 // the final permission status after the permission request completes
153 // anyway.
154 handle_state_.read_grant->RequestPermission(
155 context().process_id, context().frame_id, base::DoNothing());
156 }
157
158 handle_state_.write_grant->RequestPermission(
159 context().process_id, context().frame_id,
160 base::BindOnce(&NativeFileSystemHandleBase::DidRequestPermission,
161 AsWeakPtr(), writable, std::move(callback)));
162 }
163
DidRequestPermission(bool writable,base::OnceCallback<void (blink::mojom::NativeFileSystemErrorPtr,PermissionStatus)> callback,NativeFileSystemPermissionGrant::PermissionRequestOutcome outcome)164 void NativeFileSystemHandleBase::DidRequestPermission(
165 bool writable,
166 base::OnceCallback<void(blink::mojom::NativeFileSystemErrorPtr,
167 PermissionStatus)> callback,
168 NativeFileSystemPermissionGrant::PermissionRequestOutcome outcome) {
169 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
170 using Outcome = NativeFileSystemPermissionGrant::PermissionRequestOutcome;
171 switch (outcome) {
172 case Outcome::kInvalidFrame:
173 case Outcome::kThirdPartyContext:
174 std::move(callback).Run(
175 native_file_system_error::FromStatus(
176 blink::mojom::NativeFileSystemStatus::kPermissionDenied,
177 "Not allowed to request permissions in this context."),
178 writable ? GetWritePermissionStatus() : GetReadPermissionStatus());
179 return;
180 case Outcome::kNoUserActivation:
181 std::move(callback).Run(
182 native_file_system_error::FromStatus(
183 blink::mojom::NativeFileSystemStatus::kPermissionDenied,
184 "User activation is required to request permissions."),
185 writable ? GetWritePermissionStatus() : GetReadPermissionStatus());
186 return;
187 case Outcome::kBlockedByContentSetting:
188 case Outcome::kUserGranted:
189 case Outcome::kUserDenied:
190 case Outcome::kUserDismissed:
191 case Outcome::kRequestAborted:
192 case Outcome::kGrantedByContentSetting:
193 std::move(callback).Run(
194 native_file_system_error::Ok(),
195 writable ? GetWritePermissionStatus() : GetReadPermissionStatus());
196 return;
197 }
198 NOTREACHED();
199 }
200
UpdateUsage()201 void NativeFileSystemHandleBase::UpdateUsage() {
202 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
203 if (!ShouldTrackUsage() || !web_contents())
204 return;
205
206 bool is_readable =
207 handle_state_.read_grant->GetStatus() == PermissionStatus::GRANTED;
208 if (is_readable != was_readable_at_last_check_) {
209 was_readable_at_last_check_ = is_readable;
210 if (!directory_for_usage_tracking_.empty()) {
211 if (is_readable) {
212 web_contents()->AddNativeFileSystemDirectoryHandle(
213 directory_for_usage_tracking_);
214 } else {
215 web_contents()->RemoveNativeFileSystemDirectoryHandle(
216 directory_for_usage_tracking_);
217 }
218 }
219 }
220
221 bool is_writable = is_readable && handle_state_.write_grant->GetStatus() ==
222 PermissionStatus::GRANTED;
223 if (is_writable != was_writable_at_last_check_) {
224 was_writable_at_last_check_ = is_writable;
225 if (is_writable)
226 web_contents()->IncrementWritableNativeFileSystemHandleCount();
227 else
228 web_contents()->DecrementWritableNativeFileSystemHandleCount();
229 }
230 }
231
OnPermissionStatusChanged()232 void NativeFileSystemHandleBase::OnPermissionStatusChanged() {
233 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
234 UpdateUsage();
235 }
236
237 } // namespace content
238