// 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/renderer/mhtml_handle_writer.h" #include "base/metrics/histogram_macros.h" #include "base/task/post_task.h" #include "base/task/task_traits.h" #include "base/task/thread_pool.h" #include "content/common/download/mhtml_file_writer.mojom.h" #include "content/public/renderer/render_thread.h" #include "third_party/blink/public/platform/web_thread_safe_data.h" namespace content { MHTMLHandleWriter::MHTMLHandleWriter( scoped_refptr main_thread_task_runner, MHTMLWriteCompleteCallback callback) : main_thread_task_runner_(std::move(main_thread_task_runner)), callback_(std::move(callback)) {} MHTMLHandleWriter::~MHTMLHandleWriter() {} void MHTMLHandleWriter::WriteContents( std::vector mhtml_contents) { TRACE_EVENT_ASYNC_BEGIN0("page-serialization", "Writing MHTML contents to handle", this); DCHECK(mhtml_write_start_time_.is_null()); mhtml_write_start_time_ = base::TimeTicks::Now(); WriteContentsImpl(std::move(mhtml_contents)); } void MHTMLHandleWriter::Finish(mojom::MhtmlSaveStatus save_status) { DCHECK(!RenderThread::IsMainThread()) << "Should not run in the main renderer thread"; // Only record UMA if WriteContents has been called. if (!mhtml_write_start_time_.is_null()) { TRACE_EVENT_ASYNC_END0("page-serialization", "WriteContentsImpl (MHTMLHandleWriter)", this); base::TimeDelta mhtml_write_time = base::TimeTicks::Now() - mhtml_write_start_time_; UMA_HISTOGRAM_TIMES( "PageSerialization.MhtmlGeneration.WriteToDiskTime.SingleFrame", mhtml_write_time); } Close(); main_thread_task_runner_->PostTask( FROM_HERE, base::BindOnce(std::move(callback_), save_status)); delete this; } MHTMLFileHandleWriter::MHTMLFileHandleWriter( scoped_refptr main_thread_task_runner, MHTMLWriteCompleteCallback callback, base::File file) : MHTMLHandleWriter(std::move(main_thread_task_runner), std::move(callback)), file_(std::move(file)) {} MHTMLFileHandleWriter::~MHTMLFileHandleWriter() {} void MHTMLFileHandleWriter::WriteContentsImpl( std::vector mhtml_contents) { mojom::MhtmlSaveStatus save_status = mojom::MhtmlSaveStatus::kSuccess; for (const blink::WebThreadSafeData& data : mhtml_contents) { if (!data.IsEmpty() && file_.WriteAtCurrentPos(data.Data(), data.size()) < 0) { save_status = mojom::MhtmlSaveStatus::kFileWritingError; break; } } Finish(save_status); } void MHTMLFileHandleWriter::Close() { file_.Close(); } MHTMLProducerHandleWriter::MHTMLProducerHandleWriter( scoped_refptr main_thread_task_runner, MHTMLWriteCompleteCallback callback, mojo::ScopedDataPipeProducerHandle producer) : MHTMLHandleWriter(std::move(main_thread_task_runner), std::move(callback)), producer_(std::move(producer)), current_block_(0), write_position_(0) {} void MHTMLProducerHandleWriter::WriteContentsImpl( std::vector mhtml_contents) { DCHECK(mhtml_contents_.empty()); mhtml_contents_ = std::move(mhtml_contents); scoped_refptr task_runner = base::ThreadPool::CreateSequencedTaskRunner( {base::MayBlock(), base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN}); task_runner->PostTask( FROM_HERE, base::BindOnce(&MHTMLProducerHandleWriter::BeginWatchingHandle, base::Unretained(this))); } MHTMLProducerHandleWriter::~MHTMLProducerHandleWriter() {} void MHTMLProducerHandleWriter::Close() { producer_.reset(); } void MHTMLProducerHandleWriter::BeginWatchingHandle() { // mojo::SimpleWatcher's constructor by default gets a reference ptr // to the current SequencedTaskRunner if one is not specified, keeping // the current SequencedTaskRunner's lifetime bound to |watcher_|. watcher_ = std::make_unique( FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::AUTOMATIC); // Using base::Unretained is safe, as |this| owns |watcher_|. watcher_->Watch( producer_.get(), MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, MOJO_WATCH_CONDITION_SATISFIED, base::BindRepeating(&MHTMLProducerHandleWriter::TryWritingContents, base::Unretained(this))); } // TODO(https://crbug.com/915966): This can be simplified with usage // of BlockingCopyToString once error signalling is implemented and // updated with usage of base::span instead of std::string. void MHTMLProducerHandleWriter::TryWritingContents( MojoResult result, const mojo::HandleSignalsState& state) { if (result != MOJO_RESULT_OK) { DLOG(ERROR) << "Error receiving notifications from producer handle watcher."; Finish(mojom::MhtmlSaveStatus::kStreamingError); return; } while (true) { const blink::WebThreadSafeData& data = mhtml_contents_.at(current_block_); // If there is no more data in this block, continue to next block or // finish. uint32_t num_bytes = data.size() - write_position_; if (num_bytes == 0) { write_position_ = 0; if (++current_block_ >= mhtml_contents_.size()) { Finish(mojom::MhtmlSaveStatus::kSuccess); return; } continue; } result = producer_->WriteData(data.Data() + write_position_, &num_bytes, MOJO_WRITE_DATA_FLAG_NONE); // Break out of loop early if write was not successful to avoid // incrementing the write position incorrectly. if (result != MOJO_RESULT_OK) break; // Reaching this indicates a successful write. write_position_ += num_bytes; DCHECK(write_position_ <= data.size()); } if (result != MOJO_RESULT_SHOULD_WAIT) { Finish(mojom::MhtmlSaveStatus::kStreamingError); } // Buffer is full, return to automatically re-arm the watcher. } } // namespace content