Add ICU message format support
[chromium-blink-merge.git] / content / common / page_state_serialization.cc
blobc067725fad02454011379c41ffb0340e230624b1
1 // Copyright 2013 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.
5 #include "content/common/page_state_serialization.h"
7 #include <algorithm>
8 #include <limits>
10 #include "base/pickle.h"
11 #include "base/strings/string_number_conversions.h"
12 #include "base/strings/string_util.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "ui/gfx/screen.h"
16 namespace content {
17 namespace {
19 #if defined(OS_ANDROID)
20 float g_device_scale_factor_for_testing = 0.0;
21 #endif
23 //-----------------------------------------------------------------------------
25 void AppendDataToHttpBody(ExplodedHttpBody* http_body, const char* data,
26 int data_length) {
27 ExplodedHttpBodyElement element;
28 element.type = blink::WebHTTPBody::Element::TypeData;
29 element.data.assign(data, data_length);
30 http_body->elements.push_back(element);
33 void AppendFileRangeToHttpBody(ExplodedHttpBody* http_body,
34 const base::NullableString16& file_path,
35 int file_start,
36 int file_length,
37 double file_modification_time) {
38 ExplodedHttpBodyElement element;
39 element.type = blink::WebHTTPBody::Element::TypeFile;
40 element.file_path = file_path;
41 element.file_start = file_start;
42 element.file_length = file_length;
43 element.file_modification_time = file_modification_time;
44 http_body->elements.push_back(element);
47 void AppendURLRangeToHttpBody(ExplodedHttpBody* http_body,
48 const GURL& url,
49 int file_start,
50 int file_length,
51 double file_modification_time) {
52 ExplodedHttpBodyElement element;
53 element.type = blink::WebHTTPBody::Element::TypeFileSystemURL;
54 element.filesystem_url = url;
55 element.file_start = file_start;
56 element.file_length = file_length;
57 element.file_modification_time = file_modification_time;
58 http_body->elements.push_back(element);
61 void AppendBlobToHttpBody(ExplodedHttpBody* http_body,
62 const std::string& uuid) {
63 ExplodedHttpBodyElement element;
64 element.type = blink::WebHTTPBody::Element::TypeBlob;
65 element.blob_uuid = uuid;
66 http_body->elements.push_back(element);
69 //----------------------------------------------------------------------------
71 void AppendReferencedFilesFromHttpBody(
72 const std::vector<ExplodedHttpBodyElement>& elements,
73 std::vector<base::NullableString16>* referenced_files) {
74 for (size_t i = 0; i < elements.size(); ++i) {
75 if (elements[i].type == blink::WebHTTPBody::Element::TypeFile)
76 referenced_files->push_back(elements[i].file_path);
80 bool AppendReferencedFilesFromDocumentState(
81 const std::vector<base::NullableString16>& document_state,
82 std::vector<base::NullableString16>* referenced_files) {
83 if (document_state.empty())
84 return true;
86 // This algorithm is adapted from Blink's core/html/FormController.cpp code.
87 // We only care about how that code worked when this code snapshot was taken
88 // as this code is only needed for backwards compat.
90 // For reference, see FormController::formStatesFromStateVector at:
91 // http://src.chromium.org/viewvc/blink/trunk/Source/core/html/FormController.cpp?pathrev=152274
93 size_t index = 0;
95 if (document_state.size() < 3)
96 return false;
98 index++; // Skip over magic signature.
99 index++; // Skip over form key.
101 size_t item_count;
102 if (!base::StringToSizeT(document_state[index++].string(), &item_count))
103 return false;
105 while (item_count--) {
106 if (index + 1 >= document_state.size())
107 return false;
109 index++; // Skip over name.
110 const base::NullableString16& type = document_state[index++];
112 if (index >= document_state.size())
113 return false;
115 size_t value_size;
116 if (!base::StringToSizeT(document_state[index++].string(), &value_size))
117 return false;
119 if (index + value_size > document_state.size() ||
120 index + value_size < index) // Check for overflow.
121 return false;
123 if (base::EqualsASCII(type.string(), "file")) {
124 if (value_size != 2)
125 return false;
127 referenced_files->push_back(document_state[index++]);
128 index++; // Skip over display name.
129 } else {
130 index += value_size;
134 return true;
137 bool RecursivelyAppendReferencedFiles(
138 const ExplodedFrameState& frame_state,
139 std::vector<base::NullableString16>* referenced_files) {
140 if (!frame_state.http_body.is_null) {
141 AppendReferencedFilesFromHttpBody(frame_state.http_body.elements,
142 referenced_files);
145 if (!AppendReferencedFilesFromDocumentState(frame_state.document_state,
146 referenced_files))
147 return false;
149 for (size_t i = 0; i < frame_state.children.size(); ++i) {
150 if (!RecursivelyAppendReferencedFiles(frame_state.children[i],
151 referenced_files))
152 return false;
155 return true;
158 //----------------------------------------------------------------------------
160 struct SerializeObject {
161 SerializeObject()
162 : version(0),
163 parse_error(false) {
166 SerializeObject(const char* data, int len)
167 : pickle(data, len),
168 version(0),
169 parse_error(false) {
170 iter = base::PickleIterator(pickle);
173 std::string GetAsString() {
174 return std::string(static_cast<const char*>(pickle.data()), pickle.size());
177 base::Pickle pickle;
178 base::PickleIterator iter;
179 int version;
180 bool parse_error;
183 // Version ID of serialized format.
184 // 11: Min version
185 // 12: Adds support for contains_passwords in HTTP body
186 // 13: Adds support for URL (FileSystem URL)
187 // 14: Adds list of referenced files, version written only for first item.
188 // 15: Removes a bunch of values we defined but never used.
189 // 16: Switched from blob urls to blob uuids.
190 // 17: Add a target frame id number.
191 // 18: Add referrer policy.
192 // 19: Remove target frame id, which was a bad idea, and original url string,
193 // which is no longer used.
194 // 20: Add pinch viewport scroll offset, the offset of the pinched zoomed
195 // viewport within the unzoomed main frame.
196 // 21: Add frame sequence number.
197 // 22: Add scroll restoration type.
198 // 23: Remove frame sequence number, there are easier ways.
200 // NOTE: If the version is -1, then the pickle contains only a URL string.
201 // See ReadPageState.
203 const int kMinVersion = 11;
204 const int kCurrentVersion = 23;
206 // A bunch of convenience functions to read/write to SerializeObjects. The
207 // de-serializers assume the input data will be in the correct format and fall
208 // back to returning safe defaults when not.
210 void WriteData(const void* data, int length, SerializeObject* obj) {
211 obj->pickle.WriteData(static_cast<const char*>(data), length);
214 void ReadData(SerializeObject* obj, const void** data, int* length) {
215 const char* tmp;
216 if (obj->iter.ReadData(&tmp, length)) {
217 *data = tmp;
218 } else {
219 obj->parse_error = true;
220 *data = NULL;
221 *length = 0;
225 void WriteInteger(int data, SerializeObject* obj) {
226 obj->pickle.WriteInt(data);
229 int ReadInteger(SerializeObject* obj) {
230 int tmp;
231 if (obj->iter.ReadInt(&tmp))
232 return tmp;
233 obj->parse_error = true;
234 return 0;
237 void WriteInteger64(int64 data, SerializeObject* obj) {
238 obj->pickle.WriteInt64(data);
241 int64 ReadInteger64(SerializeObject* obj) {
242 int64 tmp = 0;
243 if (obj->iter.ReadInt64(&tmp))
244 return tmp;
245 obj->parse_error = true;
246 return 0;
249 void WriteReal(double data, SerializeObject* obj) {
250 WriteData(&data, sizeof(double), obj);
253 double ReadReal(SerializeObject* obj) {
254 const void* tmp = NULL;
255 int length = 0;
256 double value = 0.0;
257 ReadData(obj, &tmp, &length);
258 if (length == static_cast<int>(sizeof(double))) {
259 // Use memcpy, as tmp may not be correctly aligned.
260 memcpy(&value, tmp, sizeof(double));
261 } else {
262 obj->parse_error = true;
264 return value;
267 void WriteBoolean(bool data, SerializeObject* obj) {
268 obj->pickle.WriteInt(data ? 1 : 0);
271 bool ReadBoolean(SerializeObject* obj) {
272 bool tmp;
273 if (obj->iter.ReadBool(&tmp))
274 return tmp;
275 obj->parse_error = true;
276 return false;
279 void WriteGURL(const GURL& url, SerializeObject* obj) {
280 obj->pickle.WriteString(url.possibly_invalid_spec());
283 GURL ReadGURL(SerializeObject* obj) {
284 std::string spec;
285 if (obj->iter.ReadString(&spec))
286 return GURL(spec);
287 obj->parse_error = true;
288 return GURL();
291 void WriteStdString(const std::string& s, SerializeObject* obj) {
292 obj->pickle.WriteString(s);
295 std::string ReadStdString(SerializeObject* obj) {
296 std::string s;
297 if (obj->iter.ReadString(&s))
298 return s;
299 obj->parse_error = true;
300 return std::string();
303 // WriteString pickles the NullableString16 as <int length><char16* data>.
304 // If length == -1, then the NullableString16 itself is null. Otherwise the
305 // length is the number of char16 (not bytes) in the NullableString16.
306 void WriteString(const base::NullableString16& str, SerializeObject* obj) {
307 if (str.is_null()) {
308 obj->pickle.WriteInt(-1);
309 } else {
310 const base::char16* data = str.string().data();
311 size_t length_in_bytes = str.string().length() * sizeof(base::char16);
313 CHECK_LT(length_in_bytes,
314 static_cast<size_t>(std::numeric_limits<int>::max()));
315 obj->pickle.WriteInt(length_in_bytes);
316 obj->pickle.WriteBytes(data, length_in_bytes);
320 // This reads a serialized NullableString16 from obj. If a string can't be
321 // read, NULL is returned.
322 const base::char16* ReadStringNoCopy(SerializeObject* obj, int* num_chars) {
323 int length_in_bytes;
324 if (!obj->iter.ReadInt(&length_in_bytes)) {
325 obj->parse_error = true;
326 return NULL;
329 if (length_in_bytes < 0)
330 return NULL;
332 const char* data;
333 if (!obj->iter.ReadBytes(&data, length_in_bytes)) {
334 obj->parse_error = true;
335 return NULL;
338 if (num_chars)
339 *num_chars = length_in_bytes / sizeof(base::char16);
340 return reinterpret_cast<const base::char16*>(data);
343 base::NullableString16 ReadString(SerializeObject* obj) {
344 int num_chars;
345 const base::char16* chars = ReadStringNoCopy(obj, &num_chars);
346 return chars ?
347 base::NullableString16(base::string16(chars, num_chars), false) :
348 base::NullableString16();
351 template <typename T>
352 void WriteAndValidateVectorSize(const std::vector<T>& v, SerializeObject* obj) {
353 CHECK_LT(v.size(), std::numeric_limits<int>::max() / sizeof(T));
354 WriteInteger(static_cast<int>(v.size()), obj);
357 size_t ReadAndValidateVectorSize(SerializeObject* obj, size_t element_size) {
358 size_t num_elements = static_cast<size_t>(ReadInteger(obj));
360 // Ensure that resizing a vector to size num_elements makes sense.
361 if (std::numeric_limits<int>::max() / element_size <= num_elements) {
362 obj->parse_error = true;
363 return 0;
366 // Ensure that it is plausible for the pickle to contain num_elements worth
367 // of data.
368 if (obj->pickle.payload_size() <= num_elements) {
369 obj->parse_error = true;
370 return 0;
373 return num_elements;
376 // Writes a Vector of strings into a SerializeObject for serialization.
377 void WriteStringVector(
378 const std::vector<base::NullableString16>& data, SerializeObject* obj) {
379 WriteAndValidateVectorSize(data, obj);
380 for (size_t i = 0; i < data.size(); ++i) {
381 WriteString(data[i], obj);
385 void ReadStringVector(SerializeObject* obj,
386 std::vector<base::NullableString16>* result) {
387 size_t num_elements =
388 ReadAndValidateVectorSize(obj, sizeof(base::NullableString16));
390 result->resize(num_elements);
391 for (size_t i = 0; i < num_elements; ++i)
392 (*result)[i] = ReadString(obj);
395 // Writes an ExplodedHttpBody object into a SerializeObject for serialization.
396 void WriteHttpBody(const ExplodedHttpBody& http_body, SerializeObject* obj) {
397 WriteBoolean(!http_body.is_null, obj);
399 if (http_body.is_null)
400 return;
402 WriteAndValidateVectorSize(http_body.elements, obj);
403 for (size_t i = 0; i < http_body.elements.size(); ++i) {
404 const ExplodedHttpBodyElement& element = http_body.elements[i];
405 WriteInteger(element.type, obj);
406 if (element.type == blink::WebHTTPBody::Element::TypeData) {
407 WriteData(element.data.data(), static_cast<int>(element.data.size()),
408 obj);
409 } else if (element.type == blink::WebHTTPBody::Element::TypeFile) {
410 WriteString(element.file_path, obj);
411 WriteInteger64(element.file_start, obj);
412 WriteInteger64(element.file_length, obj);
413 WriteReal(element.file_modification_time, obj);
414 } else if (element.type ==
415 blink::WebHTTPBody::Element::TypeFileSystemURL) {
416 WriteGURL(element.filesystem_url, obj);
417 WriteInteger64(element.file_start, obj);
418 WriteInteger64(element.file_length, obj);
419 WriteReal(element.file_modification_time, obj);
420 } else {
421 DCHECK(element.type == blink::WebHTTPBody::Element::TypeBlob);
422 WriteStdString(element.blob_uuid, obj);
425 WriteInteger64(http_body.identifier, obj);
426 WriteBoolean(http_body.contains_passwords, obj);
429 void ReadHttpBody(SerializeObject* obj, ExplodedHttpBody* http_body) {
430 // An initial boolean indicates if we have an HTTP body.
431 if (!ReadBoolean(obj))
432 return;
433 http_body->is_null = false;
435 int num_elements = ReadInteger(obj);
437 for (int i = 0; i < num_elements; ++i) {
438 int type = ReadInteger(obj);
439 if (type == blink::WebHTTPBody::Element::TypeData) {
440 const void* data;
441 int length = -1;
442 ReadData(obj, &data, &length);
443 if (length >= 0) {
444 AppendDataToHttpBody(http_body, static_cast<const char*>(data),
445 length);
447 } else if (type == blink::WebHTTPBody::Element::TypeFile) {
448 base::NullableString16 file_path = ReadString(obj);
449 int64 file_start = ReadInteger64(obj);
450 int64 file_length = ReadInteger64(obj);
451 double file_modification_time = ReadReal(obj);
452 AppendFileRangeToHttpBody(http_body, file_path, file_start, file_length,
453 file_modification_time);
454 } else if (type == blink::WebHTTPBody::Element::TypeFileSystemURL) {
455 GURL url = ReadGURL(obj);
456 int64 file_start = ReadInteger64(obj);
457 int64 file_length = ReadInteger64(obj);
458 double file_modification_time = ReadReal(obj);
459 AppendURLRangeToHttpBody(http_body, url, file_start, file_length,
460 file_modification_time);
461 } else if (type == blink::WebHTTPBody::Element::TypeBlob) {
462 if (obj->version >= 16) {
463 std::string blob_uuid = ReadStdString(obj);
464 AppendBlobToHttpBody(http_body, blob_uuid);
465 } else {
466 ReadGURL(obj); // Skip the obsolete blob url value.
470 http_body->identifier = ReadInteger64(obj);
472 if (obj->version >= 12)
473 http_body->contains_passwords = ReadBoolean(obj);
476 // Writes the ExplodedFrameState data into the SerializeObject object for
477 // serialization.
478 void WriteFrameState(
479 const ExplodedFrameState& state, SerializeObject* obj, bool is_top) {
480 // WARNING: This data may be persisted for later use. As such, care must be
481 // taken when changing the serialized format. If a new field needs to be
482 // written, only adding at the end will make it easier to deal with loading
483 // older versions. Similarly, this should NOT save fields with sensitive
484 // data, such as password fields.
486 WriteString(state.url_string, obj);
487 WriteString(state.target, obj);
488 WriteInteger(state.scroll_offset.x(), obj);
489 WriteInteger(state.scroll_offset.y(), obj);
490 WriteString(state.referrer, obj);
492 WriteStringVector(state.document_state, obj);
494 WriteReal(state.page_scale_factor, obj);
495 WriteInteger64(state.item_sequence_number, obj);
496 WriteInteger64(state.document_sequence_number, obj);
497 WriteInteger(state.referrer_policy, obj);
498 WriteReal(state.pinch_viewport_scroll_offset.x(), obj);
499 WriteReal(state.pinch_viewport_scroll_offset.y(), obj);
501 WriteInteger(state.scroll_restoration_type, obj);
503 bool has_state_object = !state.state_object.is_null();
504 WriteBoolean(has_state_object, obj);
505 if (has_state_object)
506 WriteString(state.state_object, obj);
508 WriteHttpBody(state.http_body, obj);
510 // NOTE: It is a quirk of the format that we still have to write the
511 // http_content_type field when the HTTP body is null. That's why this code
512 // is here instead of inside WriteHttpBody.
513 WriteString(state.http_body.http_content_type, obj);
515 // Subitems
516 const std::vector<ExplodedFrameState>& children = state.children;
517 WriteAndValidateVectorSize(children, obj);
518 for (size_t i = 0; i < children.size(); ++i)
519 WriteFrameState(children[i], obj, false);
522 void ReadFrameState(SerializeObject* obj, bool is_top,
523 ExplodedFrameState* state) {
524 if (obj->version < 14 && !is_top)
525 ReadInteger(obj); // Skip over redundant version field.
527 state->url_string = ReadString(obj);
529 if (obj->version < 19)
530 ReadString(obj); // Skip obsolete original url string field.
532 state->target = ReadString(obj);
533 if (obj->version < 15) {
534 ReadString(obj); // Skip obsolete parent field.
535 ReadString(obj); // Skip obsolete title field.
536 ReadString(obj); // Skip obsolete alternate title field.
537 ReadReal(obj); // Skip obsolete visited time field.
540 int x = ReadInteger(obj);
541 int y = ReadInteger(obj);
542 state->scroll_offset = gfx::Point(x, y);
544 if (obj->version < 15) {
545 ReadBoolean(obj); // Skip obsolete target item flag.
546 ReadInteger(obj); // Skip obsolete visit count field.
548 state->referrer = ReadString(obj);
550 ReadStringVector(obj, &state->document_state);
552 state->page_scale_factor = ReadReal(obj);
553 state->item_sequence_number = ReadInteger64(obj);
554 state->document_sequence_number = ReadInteger64(obj);
555 if (obj->version >= 21 && obj->version < 23)
556 ReadInteger64(obj); // Skip obsolete frame sequence number.
558 if (obj->version >= 17 && obj->version < 19)
559 ReadInteger64(obj); // Skip obsolete target frame id number.
561 if (obj->version >= 18) {
562 state->referrer_policy =
563 static_cast<blink::WebReferrerPolicy>(ReadInteger(obj));
566 if (obj->version >= 20) {
567 double x = ReadReal(obj);
568 double y = ReadReal(obj);
569 state->pinch_viewport_scroll_offset = gfx::PointF(x, y);
570 } else {
571 state->pinch_viewport_scroll_offset = gfx::PointF(-1, -1);
574 if (obj->version >= 22) {
575 state->scroll_restoration_type =
576 static_cast<blink::WebHistoryScrollRestorationType>(ReadInteger(obj));
579 bool has_state_object = ReadBoolean(obj);
580 if (has_state_object)
581 state->state_object = ReadString(obj);
583 ReadHttpBody(obj, &state->http_body);
585 // NOTE: It is a quirk of the format that we still have to read the
586 // http_content_type field when the HTTP body is null. That's why this code
587 // is here instead of inside ReadHttpBody.
588 state->http_body.http_content_type = ReadString(obj);
590 if (obj->version < 14)
591 ReadString(obj); // Skip unused referrer string.
593 #if defined(OS_ANDROID)
594 if (obj->version == 11) {
595 // Now-unused values that shipped in this version of Chrome for Android when
596 // it was on a private branch.
597 ReadReal(obj);
598 ReadBoolean(obj);
600 // In this version, page_scale_factor included device_scale_factor and
601 // scroll offsets were premultiplied by pageScaleFactor.
602 if (state->page_scale_factor) {
603 float device_scale_factor = g_device_scale_factor_for_testing;
604 if (!device_scale_factor) {
605 device_scale_factor =
606 gfx::Screen::GetNativeScreen()->GetPrimaryDisplay().
607 device_scale_factor();
609 state->scroll_offset =
610 gfx::Point(state->scroll_offset.x() / state->page_scale_factor,
611 state->scroll_offset.y() / state->page_scale_factor);
612 state->page_scale_factor /= device_scale_factor;
615 #endif
617 // Subitems
618 size_t num_children =
619 ReadAndValidateVectorSize(obj, sizeof(ExplodedFrameState));
620 state->children.resize(num_children);
621 for (size_t i = 0; i < num_children; ++i)
622 ReadFrameState(obj, false, &state->children[i]);
625 void WritePageState(const ExplodedPageState& state, SerializeObject* obj) {
626 WriteInteger(obj->version, obj);
627 WriteStringVector(state.referenced_files, obj);
628 WriteFrameState(state.top, obj, true);
631 void ReadPageState(SerializeObject* obj, ExplodedPageState* state) {
632 obj->version = ReadInteger(obj);
634 if (obj->version == -1) {
635 GURL url = ReadGURL(obj);
636 // NOTE: GURL::possibly_invalid_spec() always returns valid UTF-8.
637 state->top.url_string =
638 base::NullableString16(
639 base::UTF8ToUTF16(url.possibly_invalid_spec()), false);
640 return;
643 if (obj->version > kCurrentVersion || obj->version < kMinVersion) {
644 obj->parse_error = true;
645 return;
648 if (obj->version >= 14)
649 ReadStringVector(obj, &state->referenced_files);
651 ReadFrameState(obj, true, &state->top);
653 if (obj->version < 14)
654 RecursivelyAppendReferencedFiles(state->top, &state->referenced_files);
656 // De-dupe
657 state->referenced_files.erase(
658 std::unique(state->referenced_files.begin(),
659 state->referenced_files.end()),
660 state->referenced_files.end());
663 } // namespace
665 ExplodedHttpBodyElement::ExplodedHttpBodyElement()
666 : type(blink::WebHTTPBody::Element::TypeData),
667 file_start(0),
668 file_length(-1),
669 file_modification_time(std::numeric_limits<double>::quiet_NaN()) {
672 ExplodedHttpBodyElement::~ExplodedHttpBodyElement() {
675 ExplodedHttpBody::ExplodedHttpBody()
676 : identifier(0),
677 contains_passwords(false),
678 is_null(true) {
681 ExplodedHttpBody::~ExplodedHttpBody() {
684 ExplodedFrameState::ExplodedFrameState()
685 : scroll_restoration_type(blink::WebHistoryScrollRestorationAuto),
686 item_sequence_number(0),
687 document_sequence_number(0),
688 page_scale_factor(0.0),
689 referrer_policy(blink::WebReferrerPolicyDefault) {
692 ExplodedFrameState::ExplodedFrameState(const ExplodedFrameState& other) {
693 assign(other);
696 ExplodedFrameState::~ExplodedFrameState() {
699 void ExplodedFrameState::operator=(const ExplodedFrameState& other) {
700 if (&other != this)
701 assign(other);
704 void ExplodedFrameState::assign(const ExplodedFrameState& other) {
705 url_string = other.url_string;
706 referrer = other.referrer;
707 target = other.target;
708 state_object = other.state_object;
709 document_state = other.document_state;
710 scroll_restoration_type = other.scroll_restoration_type;
711 pinch_viewport_scroll_offset = other.pinch_viewport_scroll_offset;
712 scroll_offset = other.scroll_offset;
713 item_sequence_number = other.item_sequence_number;
714 document_sequence_number = other.document_sequence_number;
715 page_scale_factor = other.page_scale_factor;
716 referrer_policy = other.referrer_policy;
717 http_body = other.http_body;
718 children = other.children;
721 ExplodedPageState::ExplodedPageState() {
724 ExplodedPageState::~ExplodedPageState() {
727 bool DecodePageState(const std::string& encoded, ExplodedPageState* exploded) {
728 *exploded = ExplodedPageState();
730 if (encoded.empty())
731 return true;
733 SerializeObject obj(encoded.data(), static_cast<int>(encoded.size()));
734 ReadPageState(&obj, exploded);
735 return !obj.parse_error;
738 bool EncodePageState(const ExplodedPageState& exploded, std::string* encoded) {
739 SerializeObject obj;
740 obj.version = kCurrentVersion;
741 WritePageState(exploded, &obj);
742 *encoded = obj.GetAsString();
743 return true;
746 #if defined(OS_ANDROID)
747 bool DecodePageStateWithDeviceScaleFactorForTesting(
748 const std::string& encoded,
749 float device_scale_factor,
750 ExplodedPageState* exploded) {
751 g_device_scale_factor_for_testing = device_scale_factor;
752 bool rv = DecodePageState(encoded, exploded);
753 g_device_scale_factor_for_testing = 0.0;
754 return rv;
756 #endif
758 } // namespace content