Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(376)

Side by Side Diff: content/browser/download/mhtml_generation_manager.cc

Issue 2842653002: [Offline Pages] Generate MHTML header in the browser process. (Closed)
Patch Set: Update format string for gcc error. Created 3 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "content/browser/download/mhtml_generation_manager.h" 5 #include "content/browser/download/mhtml_generation_manager.h"
6 6
7 #include <map> 7 #include <map>
8 #include <queue> 8 #include <queue>
9 #include <utility> 9 #include <utility>
10 10
11 #include "base/bind.h" 11 #include "base/bind.h"
12 #include "base/files/file.h" 12 #include "base/files/file.h"
13 #include "base/guid.h" 13 #include "base/guid.h"
14 #include "base/macros.h" 14 #include "base/macros.h"
15 #include "base/memory/ptr_util.h" 15 #include "base/memory/ptr_util.h"
16 #include "base/metrics/histogram_macros.h" 16 #include "base/metrics/histogram_macros.h"
17 #include "base/scoped_observer.h" 17 #include "base/scoped_observer.h"
18 #include "base/stl_util.h" 18 #include "base/stl_util.h"
19 #include "base/strings/string_util.h" 19 #include "base/strings/string_util.h"
20 #include "base/strings/stringprintf.h" 20 #include "base/strings/stringprintf.h"
21 #include "base/strings/utf_string_conversion_utils.h"
21 #include "base/time/time.h" 22 #include "base/time/time.h"
22 #include "base/trace_event/trace_event.h" 23 #include "base/trace_event/trace_event.h"
23 #include "content/browser/bad_message.h" 24 #include "content/browser/bad_message.h"
24 #include "content/browser/download/mhtml_extra_parts_impl.h" 25 #include "content/browser/download/mhtml_extra_parts_impl.h"
25 #include "content/browser/frame_host/frame_tree_node.h" 26 #include "content/browser/frame_host/frame_tree_node.h"
26 #include "content/browser/frame_host/render_frame_host_impl.h" 27 #include "content/browser/frame_host/render_frame_host_impl.h"
27 #include "content/common/frame_messages.h" 28 #include "content/common/frame_messages.h"
28 #include "content/public/browser/browser_thread.h" 29 #include "content/public/browser/browser_thread.h"
29 #include "content/public/browser/mhtml_extra_parts.h" 30 #include "content/public/browser/mhtml_extra_parts.h"
30 #include "content/public/browser/render_frame_host.h" 31 #include "content/public/browser/render_frame_host.h"
31 #include "content/public/browser/render_process_host.h" 32 #include "content/public/browser/render_process_host.h"
32 #include "content/public/browser/render_process_host_observer.h" 33 #include "content/public/browser/render_process_host_observer.h"
33 #include "content/public/browser/web_contents.h" 34 #include "content/public/browser/web_contents.h"
34 #include "content/public/common/mhtml_generation_params.h" 35 #include "content/public/common/mhtml_generation_params.h"
35 #include "net/base/mime_util.h" 36 #include "net/base/mime_util.h"
36 37
37 namespace { 38 namespace {
38 const char kContentLocation[] = "Content-Location: "; 39 const char kContentLocation[] = "Content-Location: ";
39 const char kContentType[] = "Content-Type: "; 40 const char kContentType[] = "Content-Type: ";
40 int kInvalidFileSize = -1; 41 int kInvalidFileSize = -1;
42
43 // This is used for compatibility with IE.
44 bool IsASCIIPrintable(base::char16 c) {
45 return c >= ' ' && c <= '~';
46 }
47
48 constexpr int DAY_OF_WEEK_COUNT = 7;
49 const char weekdays[DAY_OF_WEEK_COUNT][4] = {"Sun", "Mon", "Tue", "Wed",
50 "Thu", "Fri", "Sat"};
51
52 constexpr int MONTH_COUNT = 12;
53 const char months[MONTH_COUNT][4] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
54 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
55
56 const char* GetDayOfWeek(int day_of_week) {
57 DCHECK(day_of_week >= 0);
58 DCHECK(day_of_week < DAY_OF_WEEK_COUNT);
59 return weekdays[day_of_week];
60 }
61
62 const char* GetMonthOfYear(int month_of_year) {
63 DCHECK(month_of_year >= 0);
64 DCHECK(month_of_year < MONTH_COUNT);
65 return months[month_of_year];
66 }
67
68 // See http://tools.ietf.org/html/rfc2822#section-3.3 for more information.
69 std::string MakeRFC2822DateString(base::Time time) {
70 base::Time::Exploded utc_exploded;
71 time.UTCExplode(&utc_exploded);
72 return base::StringPrintf(
73 "Date: %3.3s, %.2u %3.3s %u %.2u:%.2u:%.2u GMT",
74 GetDayOfWeek(utc_exploded.day_of_week), utc_exploded.day_of_month,
75 // Month of year is 1-based.
76 GetMonthOfYear(utc_exploded.month - 1), utc_exploded.year,
77 utc_exploded.hour, utc_exploded.minute, utc_exploded.second);
78 }
79
80 static std::string ReplaceNonPrintableCharacters(const base::string16& text) {
81 base::string16 quoted_text;
82 base::string16 question_mark = base::ASCIIToUTF16("?");
83
84 for (size_t i = 0; i < text.length(); ++i) {
85 base::char16 c = text[i];
86 if (IsASCIIPrintable(c))
87 quoted_text.append(1, c);
88 else
89 quoted_text.append(question_mark);
90 }
91 return base::UTF16ToUTF8(quoted_text);
92 }
93
41 } // namespace 94 } // namespace
42 95
43 namespace content { 96 namespace content {
44 97
45 // The class and all of its members live on the UI thread. Only static methods 98 // The class and all of its members live on the UI thread. Only static methods
46 // are executed on other threads. 99 // are executed on other threads.
47 class MHTMLGenerationManager::Job : public RenderProcessHostObserver { 100 class MHTMLGenerationManager::Job : public RenderProcessHostObserver {
48 public: 101 public:
49 Job(int job_id, 102 Job(int job_id,
50 WebContents* web_contents, 103 WebContents* web_contents,
51 const MHTMLGenerationParams& params, 104 const MHTMLGenerationParams& params,
52 const GenerateMHTMLCallback& callback); 105 const GenerateMHTMLCallback& callback);
53 ~Job() override; 106 ~Job() override;
54 107
55 int id() const { return job_id_; } 108 int id() const { return job_id_; }
109
56 void set_browser_file(base::File file) { browser_file_ = std::move(file); } 110 void set_browser_file(base::File file) { browser_file_ = std::move(file); }
57 base::TimeTicks creation_time() const { return creation_time_; } 111 base::TimeTicks creation_time() const { return creation_time_; }
58 112
59 const GenerateMHTMLCallback& callback() const { return callback_; } 113 const GenerateMHTMLCallback& callback() const { return callback_; }
60 114
61 // Indicates whether we expect a message from the |sender| at this time. 115 // Indicates whether we expect a message from the |sender| at this time.
62 // We expect only one message per frame - therefore calling this method 116 // We expect only one message per frame - therefore calling this method
63 // will always clear |frame_tree_node_id_of_busy_frame_|. 117 // will always clear |frame_tree_node_id_of_busy_frame_|.
64 bool IsMessageFromFrameExpected(RenderFrameHostImpl* sender); 118 bool IsMessageFromFrameExpected(RenderFrameHostImpl* sender);
65 119
66 // Handler for FrameHostMsg_SerializeAsMHTMLResponse (a notification from the 120 // Handler for FrameHostMsg_SerializeAsMHTMLResponse (a notification from the
67 // renderer that the MHTML generation for previous frame has finished). 121 // renderer that the MHTML generation for previous frame has finished).
68 // Returns MhtmlSaveStatus::SUCCESS or a specific error status. 122 // Returns MhtmlSaveStatus::SUCCESS or a specific error status.
69 MhtmlSaveStatus OnSerializeAsMHTMLResponse( 123 MhtmlSaveStatus OnSerializeAsMHTMLResponse(
70 const std::set<std::string>& digests_of_uris_of_serialized_resources); 124 const std::set<std::string>& digests_of_uris_of_serialized_resources);
71 125
72 // Sends IPC to the renderer, asking for MHTML generation of the next frame. 126 // Sends IPC to the renderer, asking for MHTML generation of the next frame.
73 // Returns MhtmlSaveStatus::SUCCESS or a specific error status. 127 // Returns MhtmlSaveStatus::SUCCESS or a specific error status.
74 MhtmlSaveStatus SendToNextRenderFrame(); 128 MhtmlSaveStatus SendToNextRenderFrame();
75 129
76 // Indicates if more calls to SendToNextRenderFrame are needed. 130 // Indicates if more calls to SendToNextRenderFrame are needed.
77 bool IsDone() const { 131 bool IsDone() const {
78 bool waiting_for_response_from_renderer = 132 bool waiting_for_response_from_renderer =
79 frame_tree_node_id_of_busy_frame_ != 133 frame_tree_node_id_of_busy_frame_ !=
80 FrameTreeNode::kFrameTreeNodeInvalidId; 134 FrameTreeNode::kFrameTreeNodeInvalidId;
81 bool no_more_requests_to_send = pending_frame_tree_node_ids_.empty(); 135 bool no_more_requests_to_send = pending_frame_tree_node_ids_.empty();
82 return !waiting_for_response_from_renderer && no_more_requests_to_send; 136 return !waiting_for_response_from_renderer && no_more_requests_to_send;
83 } 137 }
84 138
139 // Generates the MHTML header.
140 std::string CreateHeader();
141
85 // Write the MHTML footer and close the file on the file thread and respond 142 // Write the MHTML footer and close the file on the file thread and respond
86 // back on the UI thread with the updated status and file size (which will be 143 // back on the UI thread with the updated status and file size (which will be
87 // negative in case of errors). 144 // negative in case of errors).
88 void CloseFile( 145 void CloseFile(
89 base::Callback<void(const std::tuple<MhtmlSaveStatus, int64_t>&)> 146 base::Callback<void(const std::tuple<MhtmlSaveStatus, int64_t>&)>
90 callback, 147 callback,
91 MhtmlSaveStatus save_status); 148 MhtmlSaveStatus save_status);
92 149
93 // RenderProcessHostObserver: 150 // RenderProcessHostObserver:
94 void RenderProcessExited(RenderProcessHost* host, 151 void RenderProcessExited(RenderProcessHost* host,
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after
160 // The handle to the file the MHTML is saved to for the browser process. 217 // The handle to the file the MHTML is saved to for the browser process.
161 base::File browser_file_; 218 base::File browser_file_;
162 219
163 // Map from frames to content ids (see WebFrameSerializer::generateMHTMLParts 220 // Map from frames to content ids (see WebFrameSerializer::generateMHTMLParts
164 // for more details about what "content ids" are and how they are used). 221 // for more details about what "content ids" are and how they are used).
165 std::map<int, std::string> frame_tree_node_to_content_id_; 222 std::map<int, std::string> frame_tree_node_to_content_id_;
166 223
167 // MIME multipart boundary to use in the MHTML doc. 224 // MIME multipart boundary to use in the MHTML doc.
168 const std::string mhtml_boundary_marker_; 225 const std::string mhtml_boundary_marker_;
169 226
227 // Title of the main page (extracted from the WebContents) for use in the
228 // header.
229 const base::string16 title_;
230
231 // Mime type of the main page (extracted from the WebContents) for use in the
232 // header.
233 const std::string mime_type_;
234
170 // Digests of URIs of already generated MHTML parts. 235 // Digests of URIs of already generated MHTML parts.
171 std::set<std::string> digests_of_already_serialized_uris_; 236 std::set<std::string> digests_of_already_serialized_uris_;
172 std::string salt_; 237 std::string salt_;
173 238
174 // The callback to call once generation is complete. 239 // The callback to call once generation is complete.
175 const GenerateMHTMLCallback callback_; 240 const GenerateMHTMLCallback callback_;
176 241
177 // Whether the job is finished (set to true only for the short duration of 242 // Whether the job is finished (set to true only for the short duration of
178 // time between MHTMLGenerationManager::JobFinished is called and the job is 243 // time between MHTMLGenerationManager::JobFinished is called and the job is
179 // destroyed by MHTMLGenerationManager::OnFileClosed). 244 // destroyed by MHTMLGenerationManager::OnFileClosed).
(...skipping 11 matching lines...) Expand all
191 256
192 MHTMLGenerationManager::Job::Job(int job_id, 257 MHTMLGenerationManager::Job::Job(int job_id,
193 WebContents* web_contents, 258 WebContents* web_contents,
194 const MHTMLGenerationParams& params, 259 const MHTMLGenerationParams& params,
195 const GenerateMHTMLCallback& callback) 260 const GenerateMHTMLCallback& callback)
196 : job_id_(job_id), 261 : job_id_(job_id),
197 creation_time_(base::TimeTicks::Now()), 262 creation_time_(base::TimeTicks::Now()),
198 params_(params), 263 params_(params),
199 frame_tree_node_id_of_busy_frame_(FrameTreeNode::kFrameTreeNodeInvalidId), 264 frame_tree_node_id_of_busy_frame_(FrameTreeNode::kFrameTreeNodeInvalidId),
200 mhtml_boundary_marker_(net::GenerateMimeMultipartBoundary()), 265 mhtml_boundary_marker_(net::GenerateMimeMultipartBoundary()),
266 title_(web_contents->GetTitle()),
267 mime_type_(web_contents->GetContentsMimeType()),
201 salt_(base::GenerateGUID()), 268 salt_(base::GenerateGUID()),
202 callback_(callback), 269 callback_(callback),
203 is_finished_(false), 270 is_finished_(false),
204 observed_renderer_process_host_(this) { 271 observed_renderer_process_host_(this) {
205 DCHECK_CURRENTLY_ON(BrowserThread::UI); 272 DCHECK_CURRENTLY_ON(BrowserThread::UI);
206 web_contents->ForEachFrame(base::Bind( 273 web_contents->ForEachFrame(base::Bind(
207 &MHTMLGenerationManager::Job::AddFrame, 274 &MHTMLGenerationManager::Job::AddFrame,
208 base::Unretained(this))); // Safe because ForEachFrame is synchronous. 275 base::Unretained(this))); // Safe because ForEachFrame is synchronous.
209 276
210 // Main frame needs to be processed first. 277 // Main frame needs to be processed first.
(...skipping 287 matching lines...) Expand 10 before | Expand all | Expand 10 after
498 DCHECK(base::IsStringASCII(serialized_extra_data_part)); 565 DCHECK(base::IsStringASCII(serialized_extra_data_part));
499 566
500 serialized_extra_data_parts += serialized_extra_data_part; 567 serialized_extra_data_parts += serialized_extra_data_part;
501 } 568 }
502 569
503 // Write the string into the file. Returns false if we failed the write. 570 // Write the string into the file. Returns false if we failed the write.
504 return (file.WriteAtCurrentPos(serialized_extra_data_parts.data(), 571 return (file.WriteAtCurrentPos(serialized_extra_data_parts.data(),
505 serialized_extra_data_parts.size()) >= 0); 572 serialized_extra_data_parts.size()) >= 0);
506 } 573 }
507 574
575 std::string MHTMLGenerationManager::Job::CreateHeader() {
576 DCHECK(!mhtml_boundary_marker_.empty());
577 DCHECK(!mime_type_.empty());
578
579 std::vector<std::string> headers = {
580 "From: <Saved by Blink>",
581 base::StringPrintf("Subject: %s",
582 ReplaceNonPrintableCharacters(title_).c_str()),
583 MakeRFC2822DateString(base::Time::Now()),
584 "Mime-Version: 1.0",
585 "Content-Type: multipart/related;",
586 base::StringPrintf("\ttype=\"%s\";", mime_type_.c_str()),
587 base::StringPrintf("\tboundary=\"%s\";", mhtml_boundary_marker_.c_str()),
588 "\r\n"};
589
590 return base::JoinString(headers, "\r\n");
591 }
592
508 // static 593 // static
509 bool MHTMLGenerationManager::Job::WriteFooter(const std::string& boundary, 594 bool MHTMLGenerationManager::Job::WriteFooter(const std::string& boundary,
510 base::File& file) { 595 base::File& file) {
511 DCHECK_CURRENTLY_ON(BrowserThread::FILE); 596 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
512 std::string footer = base::StringPrintf("--%s--\r\n", boundary.c_str()); 597 std::string footer = base::StringPrintf("--%s--\r\n", boundary.c_str());
513 DCHECK(base::IsStringASCII(footer)); 598 DCHECK(base::IsStringASCII(footer));
514 return (file.WriteAtCurrentPos(footer.data(), footer.size()) >= 0); 599 return (file.WriteAtCurrentPos(footer.data(), footer.size()) >= 0);
515 } 600 }
516 601
517 // static 602 // static
(...skipping 25 matching lines...) Expand all
543 DCHECK_CURRENTLY_ON(BrowserThread::UI); 628 DCHECK_CURRENTLY_ON(BrowserThread::UI);
544 629
545 Job* job = NewJob(web_contents, params, callback); 630 Job* job = NewJob(web_contents, params, callback);
546 TRACE_EVENT_NESTABLE_ASYNC_BEGIN2( 631 TRACE_EVENT_NESTABLE_ASYNC_BEGIN2(
547 "page-serialization", "SavingMhtmlJob", job, "url", 632 "page-serialization", "SavingMhtmlJob", job, "url",
548 web_contents->GetLastCommittedURL().possibly_invalid_spec(), 633 web_contents->GetLastCommittedURL().possibly_invalid_spec(),
549 "file", params.file_path.AsUTF8Unsafe()); 634 "file", params.file_path.AsUTF8Unsafe());
550 635
551 BrowserThread::PostTaskAndReplyWithResult( 636 BrowserThread::PostTaskAndReplyWithResult(
552 BrowserThread::FILE, FROM_HERE, 637 BrowserThread::FILE, FROM_HERE,
553 base::Bind(&MHTMLGenerationManager::CreateFile, params.file_path), 638 base::Bind(&MHTMLGenerationManager::CreateFileAndWriteHeader,
639 params.file_path, job->CreateHeader()),
554 base::Bind(&MHTMLGenerationManager::OnFileAvailable, 640 base::Bind(&MHTMLGenerationManager::OnFileAvailable,
555 base::Unretained(this), // Safe b/c |this| is a singleton. 641 base::Unretained(this), // Safe b/c |this| is a singleton.
556 job->id())); 642 job->id()));
557 } 643 }
558 644
559 void MHTMLGenerationManager::OnSerializeAsMHTMLResponse( 645 void MHTMLGenerationManager::OnSerializeAsMHTMLResponse(
560 RenderFrameHostImpl* sender, 646 RenderFrameHostImpl* sender,
561 int job_id, 647 int job_id,
562 MhtmlSaveStatus save_status, 648 MhtmlSaveStatus save_status,
563 const std::set<std::string>& digests_of_uris_of_serialized_resources, 649 const std::set<std::string>& digests_of_uris_of_serialized_resources,
(...skipping 24 matching lines...) Expand all
588 JobFinished(job, save_status); 674 JobFinished(job, save_status);
589 return; 675 return;
590 } 676 }
591 677
592 // Otherwise report completion if the job is done. 678 // Otherwise report completion if the job is done.
593 if (job->IsDone()) 679 if (job->IsDone())
594 JobFinished(job, MhtmlSaveStatus::SUCCESS); 680 JobFinished(job, MhtmlSaveStatus::SUCCESS);
595 } 681 }
596 682
597 // static 683 // static
598 base::File MHTMLGenerationManager::CreateFile(const base::FilePath& file_path) { 684 MHTMLGenerationManager::FileCreationResult
685 MHTMLGenerationManager::CreateFileAndWriteHeader(
686 const base::FilePath& file_path,
687 const std::string& header) {
599 DCHECK_CURRENTLY_ON(BrowserThread::FILE); 688 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
600 689
601 // SECURITY NOTE: A file descriptor to the file created below will be passed 690 // SECURITY NOTE: A file descriptor to the file created below will be passed
602 // to multiple renderer processes which (in out-of-process iframes mode) can 691 // to multiple renderer processes which (in out-of-process iframes mode) can
603 // act on behalf of separate web principals. Therefore it is important to 692 // act on behalf of separate web principals. Therefore it is important to
604 // only allow writing to the file and forbid reading from the file (as this 693 // only allow writing to the file and forbid reading from the file (as this
605 // would allow reading content generated by other renderers / other web 694 // would allow reading content generated by other renderers / other web
606 // principals). 695 // principals).
607 uint32_t file_flags = base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE; 696 uint32_t file_flags = base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE;
697 FileCreationResult result;
698 result.file = base::File(file_path, file_flags);
699 result.header_size = -1;
carlosk 2017/04/25 01:28:08 Set default to -1 so not to have to re-set it here
700 if (!result.file.IsValid()) {
701 LOG(ERROR) << "Failed to create file to save MHTML at: "
702 << file_path.value();
703 return result;
704 }
608 705
609 base::File browser_file(file_path, file_flags); 706 result.header_size =
610 if (!browser_file.IsValid()) { 707 result.file.WriteAtCurrentPos(header.data(), header.size());
611 LOG(ERROR) << "Failed to create file to save MHTML at: " << 708 return result;
carlosk 2017/04/25 01:28:07 Could you differentiate here between an error crea
612 file_path.value();
613 }
614 return browser_file;
615 } 709 }
616 710
617 void MHTMLGenerationManager::OnFileAvailable(int job_id, 711 void MHTMLGenerationManager::OnFileAvailable(int job_id,
618 base::File browser_file) { 712 FileCreationResult result) {
619 DCHECK_CURRENTLY_ON(BrowserThread::UI); 713 DCHECK_CURRENTLY_ON(BrowserThread::UI);
620 714
621 Job* job = FindJob(job_id); 715 Job* job = FindJob(job_id);
622 DCHECK(job); 716 DCHECK(job);
623 717
624 if (!browser_file.IsValid()) { 718 if (!result.file.IsValid() || result.header_size < 0) {
625 LOG(ERROR) << "Failed to create file"; 719 LOG(ERROR) << "Failed to create file";
626 JobFinished(job, MhtmlSaveStatus::FILE_CREATION_ERROR); 720 JobFinished(job, MhtmlSaveStatus::FILE_CREATION_ERROR);
627 return; 721 return;
628 } 722 }
629 723
630 job->set_browser_file(std::move(browser_file)); 724 job->set_browser_file(std::move(result.file));
631 725
632 MhtmlSaveStatus save_status = job->SendToNextRenderFrame(); 726 MhtmlSaveStatus save_status = job->SendToNextRenderFrame();
633 if (save_status != MhtmlSaveStatus::SUCCESS) { 727 if (save_status != MhtmlSaveStatus::SUCCESS) {
634 JobFinished(job, save_status); 728 JobFinished(job, save_status);
635 } 729 }
636 } 730 }
637 731
638 void MHTMLGenerationManager::JobFinished(Job* job, 732 void MHTMLGenerationManager::JobFinished(Job* job,
639 MhtmlSaveStatus save_status) { 733 MhtmlSaveStatus save_status) {
640 DCHECK_CURRENTLY_ON(BrowserThread::UI); 734 DCHECK_CURRENTLY_ON(BrowserThread::UI);
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after
690 return iter->second.get(); 784 return iter->second.get();
691 } 785 }
692 786
693 void MHTMLGenerationManager::RenderProcessExited(Job* job) { 787 void MHTMLGenerationManager::RenderProcessExited(Job* job) {
694 DCHECK_CURRENTLY_ON(BrowserThread::UI); 788 DCHECK_CURRENTLY_ON(BrowserThread::UI);
695 DCHECK(job); 789 DCHECK(job);
696 JobFinished(job, MhtmlSaveStatus::RENDER_PROCESS_EXITED); 790 JobFinished(job, MhtmlSaveStatus::RENDER_PROCESS_EXITED);
697 } 791 }
698 792
699 } // namespace content 793 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698