Index: content/browser/download/mhtml_generation_manager.cc |
diff --git a/content/browser/download/mhtml_generation_manager.cc b/content/browser/download/mhtml_generation_manager.cc |
index 9937db7b2062c785acc8da25c75d45085c7a757e..c1cad619876f93af377af034be4168d0af745131 100644 |
--- a/content/browser/download/mhtml_generation_manager.cc |
+++ b/content/browser/download/mhtml_generation_manager.cc |
@@ -18,12 +18,14 @@ |
#include "base/strings/string_number_conversions.h" |
#include "base/strings/stringprintf.h" |
#include "content/browser/frame_host/frame_tree_node.h" |
+#include "content/browser/frame_host/render_frame_host_impl.h" |
#include "content/common/frame_messages.h" |
#include "content/public/browser/browser_thread.h" |
#include "content/public/browser/render_frame_host.h" |
#include "content/public/browser/render_process_host.h" |
#include "content/public/browser/render_process_host_observer.h" |
#include "content/public/browser/web_contents.h" |
+#include "url/gurl.h" |
namespace content { |
@@ -38,14 +40,25 @@ class MHTMLGenerationManager::Job : public RenderProcessHostObserver { |
GenerateMHTMLCallback callback() const { return callback_; } |
+ // Handler for FrameHostMsg_SerializeAsMHTMLResponse (a notification from the |
+ // renderer that the MHTML generation for previous frame has finished). |
+ // Returns |true| upon success; |false| otherwise. |
+ bool OnSerializeAsMHTMLResponse( |
+ RenderFrameHostImpl* sender, |
+ const std::set<std::string>& digests_of_uris_of_serialized_resources); |
+ |
// Sends IPC to the renderer, asking for MHTML generation of the next frame. |
// |
// Returns true if the message was sent successfully; false otherwise. |
bool SendToNextRenderFrame(); |
// Indicates if more calls to SendToNextRenderFrame are needed. |
- bool HasMoreFramesToProcess() const { |
- return !pending_frame_tree_node_ids_.empty(); |
+ bool IsDone() const { |
+ bool waiting_for_response_from_renderer = |
+ frame_tree_node_id_of_busy_frame_ != |
+ FrameTreeNode::kFrameTreeNodeInvalidId; |
+ bool no_more_requests_to_send = pending_frame_tree_node_ids_.empty(); |
+ return !waiting_for_response_from_renderer && no_more_requests_to_send; |
} |
// Close the file on the file thread and respond back on the UI thread with |
@@ -73,12 +86,17 @@ class MHTMLGenerationManager::Job : public RenderProcessHostObserver { |
// See also MHTMLGenerationManager::id_to_job_ map. |
int job_id_; |
+ // The IDs of frames that still need to be processed. |
+ std::queue<int> pending_frame_tree_node_ids_; |
+ |
+ // Identifies a frame to which we've sent FrameMsg_SerializeAsMHTML but for |
+ // which we didn't yet process FrameHostMsg_SerializeAsMHTMLResponse via |
+ // OnSerializeAsMHTMLResponse. |
+ int frame_tree_node_id_of_busy_frame_; |
+ |
// The handle to the file the MHTML is saved to for the browser process. |
base::File browser_file_; |
- // The IDs of frames we still need to process. |
- std::queue<int> pending_frame_tree_node_ids_; |
- |
// Map from frames into content ids (see WebPageSerializer::generateMHTMLParts |
// for more details about what "content ids" are and how they are used). |
std::map<int, std::string> frame_tree_node_to_content_id_; |
@@ -86,6 +104,10 @@ class MHTMLGenerationManager::Job : public RenderProcessHostObserver { |
// MIME multipart boundary to use in the MHTML doc. |
std::string mhtml_boundary_marker_; |
+ // Digests of URIs of already generated MHTML parts. |
+ std::set<std::string> digests_of_already_serialized_uris_; |
+ std::string salt_; |
+ |
// The callback to call once generation is complete. |
GenerateMHTMLCallback callback_; |
@@ -100,7 +122,9 @@ MHTMLGenerationManager::Job::Job(int job_id, |
WebContents* web_contents, |
GenerateMHTMLCallback callback) |
: job_id_(job_id), |
+ frame_tree_node_id_of_busy_frame_(FrameTreeNode::kFrameTreeNodeInvalidId), |
mhtml_boundary_marker_(GenerateMHTMLBoundaryMarker()), |
+ salt_(base::GenerateGUID()), |
callback_(callback), |
observed_renderer_process_host_(this) { |
DCHECK_CURRENTLY_ON(BrowserThread::UI); |
@@ -144,9 +168,13 @@ bool MHTMLGenerationManager::Job::SendToNextRenderFrame() { |
DCHECK(browser_file_.IsValid()); |
DCHECK_LT(0u, pending_frame_tree_node_ids_.size()); |
+ FrameMsg_SerializeAsMHTML_Params ipc_params; |
+ ipc_params.job_id = job_id_; |
+ ipc_params.mhtml_boundary_marker = mhtml_boundary_marker_; |
+ |
int frame_tree_node_id = pending_frame_tree_node_ids_.front(); |
pending_frame_tree_node_ids_.pop(); |
- bool is_last_frame = pending_frame_tree_node_ids_.empty(); |
+ ipc_params.is_last_frame = pending_frame_tree_node_ids_.empty(); |
FrameTreeNode* ftn = FrameTreeNode::GloballyFindByID(frame_tree_node_id); |
if (!ftn) // The contents went away. |
@@ -157,12 +185,21 @@ bool MHTMLGenerationManager::Job::SendToNextRenderFrame() { |
observed_renderer_process_host_.RemoveAll(); |
observed_renderer_process_host_.Add(rfh->GetProcess()); |
- IPC::PlatformFileForTransit renderer_file = IPC::GetFileHandleForProcess( |
+ // Tell the renderer to skip (= deduplicate) already covered MHTML parts. |
+ ipc_params.salt = salt_; |
+ ipc_params.digests_of_uris_to_skip = digests_of_already_serialized_uris_; |
+ |
+ ipc_params.destination_file = IPC::GetFileHandleForProcess( |
browser_file_.GetPlatformFile(), rfh->GetProcess()->GetHandle(), |
false); // |close_source_handle|. |
- rfh->Send(new FrameMsg_SerializeAsMHTML( |
- rfh->GetRoutingID(), job_id_, renderer_file, mhtml_boundary_marker_, |
- CreateFrameRoutingIdToContentId(rfh->GetSiteInstance()), is_last_frame)); |
+ ipc_params.frame_routing_id_to_content_id = |
+ CreateFrameRoutingIdToContentId(rfh->GetSiteInstance()); |
+ |
+ // Send the IPC asking the renderer to serialize the frame. |
+ DCHECK_EQ(FrameTreeNode::kFrameTreeNodeInvalidId, |
+ frame_tree_node_id_of_busy_frame_); |
+ frame_tree_node_id_of_busy_frame_ = frame_tree_node_id; |
+ rfh->Send(new FrameMsg_SerializeAsMHTML(rfh->GetRoutingID(), ipc_params)); |
return true; |
} |
@@ -207,6 +244,31 @@ void MHTMLGenerationManager::Job::CloseFile( |
callback); |
} |
+bool MHTMLGenerationManager::Job::OnSerializeAsMHTMLResponse( |
+ RenderFrameHostImpl* sender, |
+ const std::set<std::string>& digests_of_uris_of_serialized_resources) { |
+ // Sanitize renderer input / reject unexpected messages. |
+ int sender_id = sender->frame_tree_node()->frame_tree_node_id(); |
+ if (sender_id != frame_tree_node_id_of_busy_frame_) { |
+ NOTREACHED(); |
+ return false; // Report failure. |
+ } |
+ frame_tree_node_id_of_busy_frame_ = FrameTreeNode::kFrameTreeNodeInvalidId; |
+ |
+ // Renderer should be deduping resources with the same uris. |
+ DCHECK_EQ(0u, base::STLSetIntersection<std::set<std::string>>( |
+ digests_of_already_serialized_uris_, |
+ digests_of_uris_of_serialized_resources).size()); |
+ digests_of_already_serialized_uris_.insert( |
+ digests_of_uris_of_serialized_resources.begin(), |
+ digests_of_uris_of_serialized_resources.end()); |
+ |
+ if (pending_frame_tree_node_ids_.empty()) |
+ return true; // Report success. |
+ |
+ return SendToNextRenderFrame(); |
+} |
+ |
// static |
int64_t MHTMLGenerationManager::Job::CloseFileOnFileThread(base::File file) { |
DCHECK_CURRENTLY_ON(BrowserThread::FILE); |
@@ -260,9 +322,11 @@ void MHTMLGenerationManager::SaveMHTML(WebContents* web_contents, |
job_id)); |
} |
-void MHTMLGenerationManager::OnSavedFrameAsMHTML( |
+void MHTMLGenerationManager::OnSerializeAsMHTMLResponse( |
+ RenderFrameHostImpl* sender, |
int job_id, |
- bool mhtml_generation_in_renderer_succeeded) { |
+ bool mhtml_generation_in_renderer_succeeded, |
+ const std::set<std::string>& digests_of_uris_of_serialized_resources) { |
DCHECK_CURRENTLY_ON(BrowserThread::UI); |
if (!mhtml_generation_in_renderer_succeeded) { |
@@ -274,14 +338,14 @@ void MHTMLGenerationManager::OnSavedFrameAsMHTML( |
if (!job) |
return; |
- if (job->HasMoreFramesToProcess()) { |
- if (!job->SendToNextRenderFrame()) { |
- JobFinished(job_id, JobStatus::FAILURE); |
- } |
+ if (!job->OnSerializeAsMHTMLResponse( |
+ sender, digests_of_uris_of_serialized_resources)) { |
+ JobFinished(job_id, JobStatus::FAILURE); |
return; |
} |
- JobFinished(job_id, JobStatus::SUCCESS); |
+ if (job->IsDone()) |
+ JobFinished(job_id, JobStatus::SUCCESS); |
} |
// static |