1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382
|
// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_CACHE_WRITER_H_
#define CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_CACHE_WRITER_H_
#include <stddef.h>
#include <map>
#include <memory>
#include <set>
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "components/services/storage/public/mojom/service_worker_storage_control.mojom.h"
#include "content/common/content_export.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
namespace crypto {
class SecureHash;
} // namespace crypto
namespace content {
// This class is responsible for possibly updating the ServiceWorker script
// cache for an installed ServiceWorker main script. If there is no existing
// cache entry, this class always writes supplied data back to the cache; if
// there is an existing cache entry, this class only writes supplied data back
// if there is a cache mismatch.
//
// Note that writes done by this class cannot be "short" - ie, if they succeed,
// they always write all the supplied data back. Therefore completions are
// signalled with net::Error without a count of bytes written.
//
// This class's behavior is modelled as a state machine; see the DoLoop function
// for comments about this.
class CONTENT_EXPORT ServiceWorkerCacheWriter {
public:
using OnWriteCompleteCallback = base::OnceCallback<void(net::Error)>;
// These values indicates the timing when the checksum update happens. As the
// sha256 checksum is a hash string calculated from script data, basically
// |kCacheMismatch| is preferable in terms of the efficiency.
//
// kCacheMismatch: Update the checksum when a cache mismatch and write data
// back to the cache.
// kAlways: Update the checksum regardless of whether there is a cache
// mismatch or not.
enum class ChecksumUpdateTiming { kCacheMismatch, kAlways };
// This class defines the interfaces of observer that observes write
// operations. The observer is notified when response head or data
// will be written to storage.
class WriteObserver {
public:
// Called before response headers are written to storage.
// Returns net::OK if success. Other values are treated as errors.
virtual int WillWriteResponseHead(
const network::mojom::URLResponseHead& response_head) = 0;
// Called before response data is written to storage.
// Return value is used by cache writer to decide what to do next. A net
// error code should be returned (e.g. net::OK, net::ERR_IO_PENDING). If it
// returns net::ERR_IO_PENDING, the cache writer waits until the callback
// is called asynchronously. Otherwise the callback should not be called.
// The parameter of the callback specifies result of the operation.
virtual int WillWriteData(
scoped_refptr<net::IOBuffer> data,
int length,
base::OnceCallback<void(net::Error)> callback) = 0;
};
// Create a cache writer instance that copies a script already in storage. The
// script is read by |copy_reader|.
static std::unique_ptr<ServiceWorkerCacheWriter> CreateForCopy(
mojo::Remote<storage::mojom::ServiceWorkerResourceReader> copy_reader,
mojo::Remote<storage::mojom::ServiceWorkerResourceWriter> writer,
int64_t writer_resource_id);
// Create a cache writer instance that unconditionally write back data
// supplied to |MaybeWriteHeaders| and |MaybeWriteData| to storage.
static std::unique_ptr<ServiceWorkerCacheWriter> CreateForWriteBack(
mojo::Remote<storage::mojom::ServiceWorkerResourceWriter> writer,
int64_t writer_resource_id);
// Create a cache writer that compares between a script in storage and data
// from network (supplied with |MaybeWriteHeaders| and |MaybeWriteData|).
// Nothing would be written to storage if it compares to be identical.
// When |pause_when_not_identical| is true and the cache writer detects a
// difference between bodies from the network and from the storage, the
// comparison stops immediately and the cache writer is paused and returns
// net::ERR_IO_PENDING, with nothing written to the storage. It can be
// resumed later. If |pause_when_not_identical| is false, and the data is
// different, it would be written to storage directly. |copy_reader| is used
// for copying identical data blocks during writing.
static std::unique_ptr<ServiceWorkerCacheWriter> CreateForComparison(
mojo::Remote<storage::mojom::ServiceWorkerResourceReader> compare_reader,
mojo::Remote<storage::mojom::ServiceWorkerResourceReader> copy_reader,
mojo::Remote<storage::mojom::ServiceWorkerResourceWriter> writer,
int64_t writer_resource_id,
bool pause_when_not_identical,
ChecksumUpdateTiming checksum_update_timing);
~ServiceWorkerCacheWriter();
// Writes the supplied |response_head| back to the cache. Returns
// ERR_IO_PENDING if the write will complete asynchronously, in which case
// |callback| will be called when it completes. Otherwise, returns a code
// other than ERR_IO_PENDING and does not invoke |callback|. Note that this
// method will not necessarily write data back to the cache if the incoming
// data is equivalent to the existing cached data. See the source of this
// function for details about how this function drives the state machine.
net::Error MaybeWriteHeaders(network::mojom::URLResponseHeadPtr response_head,
OnWriteCompleteCallback callback);
// Writes the supplied body data |data| back to the cache. Returns
// ERR_IO_PENDING if the write will complete asynchronously, in which case
// |callback| will be called when it completes. Otherwise, returns a code
// other than ERR_IO_PENDING and does not invoke |callback|. Note that this
// method will not necessarily write data back to the cache if the incoming
// data is equivalent to the existing cached data. See the source of this
// function for details about how this function drives the state machine.
net::Error MaybeWriteData(net::IOBuffer* buf,
size_t buf_size,
OnWriteCompleteCallback callback);
// Returns a count of bytes written back to the cache.
size_t bytes_written() const { return bytes_written_; }
bool did_replace() const { return did_replace_; }
bool is_pausing() const { return state_ == STATE_PAUSING; }
// Resumes a cache writer which were paused when a block of data from the
// network wasn't identical to the data in the storage. It is valid to call
// this method only when |pause_when_not_identical| is true in the constructor
// and |state_| is STATE_PAUSING.
net::Error Resume(OnWriteCompleteCallback callback);
// Start to copy a script in storage to a new position. |callback| is
// called when the work is done. This is used when an installed script
// is used by a new service worker with no content change, thus downloading
// could be avoided.
net::Error StartCopy(OnWriteCompleteCallback callback);
// Returns true when the cache writer is created by CreateForCopy().
bool IsCopying() const;
// Returns the resource ID being written to storage.
int64_t writer_resource_id() const;
void set_write_observer(WriteObserver* write_observer) {
write_observer_ = write_observer;
}
void FlushRemotesForTesting();
// Gets the hex-encoded checksum hash string calculated from the script body.
// This function should be called only once as it destroys the underlying data
// of the checksum. It resets |checksum_| not to be called multiple times.
std::string GetSha256Checksum();
ChecksumUpdateTiming checksum_update_timing() const {
return checksum_update_timing_;
}
private:
class ReadResponseHeadCallbackAdapter;
class DataPipeReader;
friend class ServiceWorkerUpdateCheckTestUtils;
// States for the state machine.
//
// The state machine flows roughly like this: if there is no existing cache
// entry, incoming headers and data are written directly back to the cache
// ("passthrough mode", the PASSTHROUGH states). If there is an existing cache
// entry, incoming headers and data are compared to the existing cache entry
// ("compare mode", the COMPARE states); if at any point the incoming
// headers/data are not equal to the cached headers/data, this class copies
// the cached data up to the point where the incoming data and the cached data
// diverged ("copy mode", the COPY states), then switches to "passthrough
// mode" to write the remainder of the incoming data. The overall effect is to
// avoid rewriting the cache entry if the incoming data is identical to the
// cached data.
//
// Note that after a call to MaybeWriteHeaders or MaybeWriteData completes,
// the machine is always in STATE_DONE, indicating that the call is finished;
// those methods are responsible for setting a new initial state.
enum State {
STATE_START,
// Control flows linearly through these four states, then loops from
// READ_DATA_FOR_COMPARE_DONE to READ_DATA_FOR_COMPARE, or exits to
// READ_HEADERS_FOR_COPY.
STATE_READ_HEADERS_FOR_COMPARE,
STATE_READ_HEADERS_FOR_COMPARE_DONE,
STATE_READ_DATA_FOR_COMPARE,
STATE_READ_DATA_FOR_COMPARE_DONE,
// The cache writer is paused because the network data wasn't identical with
// the stored data, and |pause_when_not_identical| is true.
STATE_PAUSING,
// Control flows linearly through these states, with each pass from
// READ_DATA_FOR_COPY to WRITE_DATA_FOR_COPY_DONE copying one block of data
// at a time. Control loops from WRITE_DATA_FOR_COPY_DONE back to
// READ_DATA_FOR_COPY if there is more data to copy. If there is no more
// data, it exits to WRITE_DATA_FOR_PASSTHROUGH in case IsCopying()
// returns false or exits to DONE in case IsCopying() returns true.
STATE_READ_HEADERS_FOR_COPY,
STATE_READ_HEADERS_FOR_COPY_DONE,
STATE_WRITE_HEADERS_FOR_COPY,
STATE_WRITE_HEADERS_FOR_COPY_DONE,
STATE_READ_DATA_FOR_COPY,
STATE_READ_DATA_FOR_COPY_DONE,
STATE_WRITE_DATA_FOR_COPY,
STATE_WRITE_DATA_FOR_COPY_DONE,
// Control flows linearly through these states, with a loop between
// WRITE_DATA_FOR_PASSTHROUGH and WRITE_DATA_FOR_PASSTHROUGH_DONE.
STATE_WRITE_HEADERS_FOR_PASSTHROUGH,
STATE_WRITE_HEADERS_FOR_PASSTHROUGH_DONE,
STATE_WRITE_DATA_FOR_PASSTHROUGH,
STATE_WRITE_DATA_FOR_PASSTHROUGH_DONE,
// This state means "done with the current call; ready for another one."
STATE_DONE,
};
ServiceWorkerCacheWriter(
mojo::Remote<storage::mojom::ServiceWorkerResourceReader> compare_reader,
mojo::Remote<storage::mojom::ServiceWorkerResourceReader> copy_reader,
mojo::Remote<storage::mojom::ServiceWorkerResourceWriter> writer,
int64_t writer_resource_id,
bool pause_when_not_identical,
ChecksumUpdateTiming checksum_update_timing);
// Drives this class's state machine. This function steps the state machine
// until one of:
// a) One of the state functions returns an error
// b) The state machine reaches STATE_DONE
// A successful value (net::OK or greater) indicates that the requested
// operation completed synchronously. A return value of ERR_IO_PENDING
// indicates that some step had to submit asynchronous IO for later
// completion, and the state machine will resume running (via AsyncDoLoop)
// when that asynchronous IO completes. Any other return value indicates that
// the requested operation failed synchronously.
int DoLoop(int result);
// State handlers. See function comments in the corresponding source file for
// details on these.
int DoStart(int result);
int DoReadHeadersForCompare(int result);
int DoReadHeadersForCompareDone(int result);
int DoReadDataForCompare(int result);
int DoReadDataForCompareDone(int result);
int DoReadHeadersForCopy(int result);
int DoReadHeadersForCopyDone(int result);
int DoWriteHeadersForCopy(int result);
int DoWriteHeadersForCopyDone(int result);
int DoReadDataForCopy(int result);
int DoReadDataForCopyDone(int result);
int DoWriteDataForCopy(int result);
int DoWriteDataForCopyDone(int result);
int DoWriteHeadersForPassthrough(int result);
int DoWriteHeadersForPassthroughDone(int result);
int DoWriteDataForPassthrough(int result);
int DoWriteDataForPassthroughDone(int result);
int DoDone(int result);
// Wrappers for asynchronous calls. These are responsible for scheduling a
// callback to drive the state machine if needed. These return ERR_IO_PENDING,
// and schedule a callback to run the state machine's Run() later.
int ReadResponseHead(storage::mojom::ServiceWorkerResourceReader* reader);
int ReadDataHelper(storage::mojom::ServiceWorkerResourceReader* reader,
std::unique_ptr<DataPipeReader>& data_pipe_reader,
scoped_refptr<net::IOBuffer> buf,
int buf_len);
// If no write observer is set through set_write_observer(),
// WriteResponseHead() operates the same as
// WriteResponseHeadToResponseWriter() and WriteData() operates the same as
// WriteDataToResponseWriter().
// If observer is set, the argument |response_head| or |data| is first sent
// to observer then WriteResponseHeadToResponseWriter() or
// WriteDataToResponseWriter() is called.
int WriteResponseHead(network::mojom::URLResponseHeadPtr response_head);
int WriteData(scoped_refptr<net::IOBuffer> data, size_t length);
int WriteResponseHeadToResponseWriter(
network::mojom::URLResponseHeadPtr response_head);
int WriteDataToResponseWriter(scoped_refptr<net::IOBuffer> data,
size_t length);
// Called when |write_observer_| finishes its WillWriteData() operation.
void OnWillWriteDataCompleted(scoped_refptr<net::IOBuffer> data,
size_t length,
net::Error error);
void OnRemoteDisconnected();
// Callback used by the above helpers for their IO operations. This is only
// run when those IO operations complete asynchronously, in which case it
// invokes the synchronous DoLoop function and runs the client callback (the
// one passed into MaybeWriteData/MaybeWriteHeaders) if that invocation
// of DoLoop completes synchronously.
void AsyncDoLoop(int result);
State state_;
// Note that this variable is only used for assertions; it reflects "state !=
// DONE && not in synchronous DoLoop".
bool io_pending_;
bool comparing_;
network::mojom::URLResponseHeadPtr response_head_to_read_;
network::mojom::URLResponseHeadPtr response_head_to_write_;
scoped_refptr<net::IOBuffer> data_to_read_;
int len_to_read_;
scoped_refptr<net::IOBuffer> data_to_copy_;
scoped_refptr<net::IOBuffer> data_to_write_;
int len_to_write_;
OnWriteCompleteCallback pending_callback_;
size_t cached_length_;
// The amount of data from the network (|data_to_write_|) which has already
// been compared with data from storage (|data_to_read_|). This is
// initialized to 0 for every new arrival of network data.
size_t compare_offset_;
// Count of bytes which has been read from the network for comparison, and
// known as identical with the stored scripts. It is incremented only when a
// full block of network data is compared, to avoid having to use only
// fragments of the buffered network data.
size_t bytes_compared_;
// The total size of the body for copying. Used only when IsCopying() returns
// true.
size_t bytes_to_copy_ = 0;
// Count of bytes copied from |copy_reader_| to |writer_|.
size_t bytes_copied_;
// Count of bytes written back to |writer_|.
size_t bytes_written_;
bool did_replace_ = false;
// When the cache writer finds any differences between bodies from the network
// and from the storage, and the |pause_when_not_identical_| is true, the
// cache writer pauses immediately.
const bool pause_when_not_identical_;
raw_ptr<WriteObserver, DanglingUntriaged> write_observer_ = nullptr;
mojo::Remote<storage::mojom::ServiceWorkerResourceReader> compare_reader_;
std::unique_ptr<DataPipeReader> compare_data_pipe_reader_;
mojo::Remote<storage::mojom::ServiceWorkerResourceReader> copy_reader_;
std::unique_ptr<DataPipeReader> copy_data_pipe_reader_;
mojo::Remote<storage::mojom::ServiceWorkerResourceWriter> writer_;
const int64_t writer_resource_id_ =
blink::mojom::kInvalidServiceWorkerResourceId;
// Normally, the sha256 hash string is calculated only when there is an update
// on the script. But if |checksum_update_timing_| is kAlways, the hash
// string is calculated even when there is no update in the script.
const ChecksumUpdateTiming checksum_update_timing_;
// Calculate the hash string for the written bytes. This will be used for the
// experiment which needs to identify some specific service worker scripts
// (crbug.com/1371756).
std::unique_ptr<crypto::SecureHash> checksum_;
base::WeakPtrFactory<ServiceWorkerCacheWriter> weak_factory_{this};
};
} // namespace content
#endif // CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_CACHE_WRITER_H_
|