page_state_serialization.cc revision 68043e1e95eeb07d5cae7aca370b26518b0867d6
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. 4 5#include "content/common/page_state_serialization.h" 6 7#include <algorithm> 8#include <limits> 9 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" 15 16namespace content { 17namespace { 18 19#if defined(OS_ANDROID) 20float g_device_scale_factor_for_testing = 0.0; 21#endif 22 23//----------------------------------------------------------------------------- 24 25void AppendDataToHttpBody(ExplodedHttpBody* http_body, const char* data, 26 int data_length) { 27 ExplodedHttpBodyElement element; 28 element.type = WebKit::WebHTTPBody::Element::TypeData; 29 element.data.assign(data, data_length); 30 http_body->elements.push_back(element); 31} 32 33void 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 = WebKit::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); 45} 46 47void 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 = WebKit::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); 59} 60 61#ifdef USE_BLOB_UUIDS 62void AppendBlobToHttpBody(ExplodedHttpBody* http_body, 63 const std::string& uuid) { 64 ExplodedHttpBodyElement element; 65 element.type = WebKit::WebHTTPBody::Element::TypeBlob; 66 element.blob_uuid = uuid; 67 http_body->elements.push_back(element); 68} 69#else 70void DeprecatedAppendBlobToHttpBody(ExplodedHttpBody* http_body, 71 const GURL& url) { 72 ExplodedHttpBodyElement element; 73 element.type = WebKit::WebHTTPBody::Element::TypeBlob; 74 element.deprecated_blob_url = url; 75 http_body->elements.push_back(element); 76} 77#endif 78 79//---------------------------------------------------------------------------- 80 81void AppendReferencedFilesFromHttpBody( 82 const std::vector<ExplodedHttpBodyElement>& elements, 83 std::vector<base::NullableString16>* referenced_files) { 84 for (size_t i = 0; i < elements.size(); ++i) { 85 if (elements[i].type == WebKit::WebHTTPBody::Element::TypeFile) 86 referenced_files->push_back(elements[i].file_path); 87 } 88} 89 90bool AppendReferencedFilesFromDocumentState( 91 const std::vector<base::NullableString16>& document_state, 92 std::vector<base::NullableString16>* referenced_files) { 93 if (document_state.empty()) 94 return true; 95 96 // This algorithm is adapted from Blink's core/html/FormController.cpp code. 97 // We only care about how that code worked when this code snapshot was taken 98 // as this code is only needed for backwards compat. 99 // 100 // For reference, see FormController::formStatesFromStateVector at: 101 // http://src.chromium.org/viewvc/blink/trunk/Source/core/html/FormController.cpp?pathrev=152274 102 103 size_t index = 0; 104 105 if (document_state.size() < 3) 106 return false; 107 108 index++; // Skip over magic signature. 109 index++; // Skip over form key. 110 111 size_t item_count; 112 if (!base::StringToSizeT(document_state[index++].string(), &item_count)) 113 return false; 114 115 while (item_count--) { 116 if (index + 1 >= document_state.size()) 117 return false; 118 119 index++; // Skip over name. 120 const base::NullableString16& type = document_state[index++]; 121 122 if (index >= document_state.size()) 123 return false; 124 125 size_t value_size; 126 if (!base::StringToSizeT(document_state[index++].string(), &value_size)) 127 return false; 128 129 if (index + value_size > document_state.size() || 130 index + value_size < index) // Check for overflow. 131 return false; 132 133 if (EqualsASCII(type.string(), "file")) { 134 if (value_size != 2) 135 return false; 136 137 referenced_files->push_back(document_state[index++]); 138 index++; // Skip over display name. 139 } else { 140 index += value_size; 141 } 142 } 143 144 return true; 145} 146 147bool RecursivelyAppendReferencedFiles( 148 const ExplodedFrameState& frame_state, 149 std::vector<base::NullableString16>* referenced_files) { 150 if (!frame_state.http_body.is_null) { 151 AppendReferencedFilesFromHttpBody(frame_state.http_body.elements, 152 referenced_files); 153 } 154 155 if (!AppendReferencedFilesFromDocumentState(frame_state.document_state, 156 referenced_files)) 157 return false; 158 159 for (size_t i = 0; i < frame_state.children.size(); ++i) { 160 if (!RecursivelyAppendReferencedFiles(frame_state.children[i], 161 referenced_files)) 162 return false; 163 } 164 165 return true; 166} 167 168//---------------------------------------------------------------------------- 169 170struct SerializeObject { 171 SerializeObject() 172 : version(0), 173 parse_error(false) { 174 } 175 176 SerializeObject(const char* data, int len) 177 : pickle(data, len), 178 version(0), 179 parse_error(false) { 180 iter = PickleIterator(pickle); 181 } 182 183 std::string GetAsString() { 184 return std::string(static_cast<const char*>(pickle.data()), pickle.size()); 185 } 186 187 Pickle pickle; 188 PickleIterator iter; 189 int version; 190 bool parse_error; 191}; 192 193// Version ID of serialized format. 194// 11: Min version 195// 12: Adds support for contains_passwords in HTTP body 196// 13: Adds support for URL (FileSystem URL) 197// 14: Adds list of referenced files, version written only for first item. 198// 15: Switched from blob urls to blob uuids. 199// 200// NOTE: If the version is -1, then the pickle contains only a URL string. 201// See ReadPageState. 202// 203const int kMinVersion = 11; 204#ifdef USE_BLOB_UUIDS 205// This is not used yet, if a version bump is needed in advance of 206// this becoming used, bump both values by one and fixup the comment 207// and change the test for '15' in ReadHttpBody(). 208// -- michaeln 209const int kCurrentVersion = 15; 210#else 211const int kCurrentVersion = 14; 212#endif 213 214// A bunch of convenience functions to read/write to SerializeObjects. The 215// de-serializers assume the input data will be in the correct format and fall 216// back to returning safe defaults when not. 217 218void WriteData(const void* data, int length, SerializeObject* obj) { 219 obj->pickle.WriteData(static_cast<const char*>(data), length); 220} 221 222void ReadData(SerializeObject* obj, const void** data, int* length) { 223 const char* tmp; 224 if (obj->pickle.ReadData(&obj->iter, &tmp, length)) { 225 *data = tmp; 226 } else { 227 obj->parse_error = true; 228 *data = NULL; 229 *length = 0; 230 } 231} 232 233void WriteInteger(int data, SerializeObject* obj) { 234 obj->pickle.WriteInt(data); 235} 236 237int ReadInteger(SerializeObject* obj) { 238 int tmp; 239 if (obj->pickle.ReadInt(&obj->iter, &tmp)) 240 return tmp; 241 obj->parse_error = true; 242 return 0; 243} 244 245void ConsumeInteger(SerializeObject* obj) { 246 int unused ALLOW_UNUSED = ReadInteger(obj); 247} 248 249void WriteInteger64(int64 data, SerializeObject* obj) { 250 obj->pickle.WriteInt64(data); 251} 252 253int64 ReadInteger64(SerializeObject* obj) { 254 int64 tmp = 0; 255 if (obj->pickle.ReadInt64(&obj->iter, &tmp)) 256 return tmp; 257 obj->parse_error = true; 258 return 0; 259} 260 261void WriteReal(double data, SerializeObject* obj) { 262 WriteData(&data, sizeof(double), obj); 263} 264 265double ReadReal(SerializeObject* obj) { 266 const void* tmp = NULL; 267 int length = 0; 268 double value = 0.0; 269 ReadData(obj, &tmp, &length); 270 if (length == static_cast<int>(sizeof(double))) { 271 // Use memcpy, as tmp may not be correctly aligned. 272 memcpy(&value, tmp, sizeof(double)); 273 } else { 274 obj->parse_error = true; 275 } 276 return value; 277} 278 279void WriteBoolean(bool data, SerializeObject* obj) { 280 obj->pickle.WriteInt(data ? 1 : 0); 281} 282 283bool ReadBoolean(SerializeObject* obj) { 284 bool tmp; 285 if (obj->pickle.ReadBool(&obj->iter, &tmp)) 286 return tmp; 287 obj->parse_error = true; 288 return false; 289} 290 291void WriteGURL(const GURL& url, SerializeObject* obj) { 292 obj->pickle.WriteString(url.possibly_invalid_spec()); 293} 294 295GURL ReadGURL(SerializeObject* obj) { 296 std::string spec; 297 if (obj->pickle.ReadString(&obj->iter, &spec)) 298 return GURL(spec); 299 obj->parse_error = true; 300 return GURL(); 301} 302 303void WriteStdString(const std::string& s, SerializeObject* obj) { 304 obj->pickle.WriteString(s); 305} 306 307std::string ReadStdString(SerializeObject* obj) { 308 std::string s; 309 if (obj->pickle.ReadString(&obj->iter, &s)) 310 return s; 311 obj->parse_error = true; 312 return std::string(); 313} 314 315// WriteString pickles the NullableString16 as <int length><char16* data>. 316// If length == -1, then the NullableString16 itself is null. Otherwise the 317// length is the number of char16 (not bytes) in the NullableString16. 318void WriteString(const base::NullableString16& str, SerializeObject* obj) { 319 if (str.is_null()) { 320 obj->pickle.WriteInt(-1); 321 } else { 322 const char16* data = str.string().data(); 323 size_t length_in_bytes = str.string().length() * sizeof(char16); 324 325 CHECK_LT(length_in_bytes, 326 static_cast<size_t>(std::numeric_limits<int>::max())); 327 obj->pickle.WriteInt(length_in_bytes); 328 obj->pickle.WriteBytes(data, length_in_bytes); 329 } 330} 331 332// This reads a serialized NullableString16 from obj. If a string can't be 333// read, NULL is returned. 334const char16* ReadStringNoCopy(SerializeObject* obj, int* num_chars) { 335 int length_in_bytes; 336 if (!obj->pickle.ReadInt(&obj->iter, &length_in_bytes)) { 337 obj->parse_error = true; 338 return NULL; 339 } 340 341 if (length_in_bytes < 0) 342 return NULL; 343 344 const char* data; 345 if (!obj->pickle.ReadBytes(&obj->iter, &data, length_in_bytes)) { 346 obj->parse_error = true; 347 return NULL; 348 } 349 350 if (num_chars) 351 *num_chars = length_in_bytes / sizeof(char16); 352 return reinterpret_cast<const char16*>(data); 353} 354 355base::NullableString16 ReadString(SerializeObject* obj) { 356 int num_chars; 357 const char16* chars = ReadStringNoCopy(obj, &num_chars); 358 return chars ? 359 base::NullableString16(base::string16(chars, num_chars), false) : 360 base::NullableString16(); 361} 362 363void ConsumeString(SerializeObject* obj) { 364 const char16* unused ALLOW_UNUSED = ReadStringNoCopy(obj, NULL); 365} 366 367template <typename T> 368void WriteAndValidateVectorSize(const std::vector<T>& v, SerializeObject* obj) { 369 CHECK_LT(v.size(), std::numeric_limits<int>::max() / sizeof(T)); 370 WriteInteger(static_cast<int>(v.size()), obj); 371} 372 373size_t ReadAndValidateVectorSize(SerializeObject* obj, size_t element_size) { 374 size_t num_elements = static_cast<size_t>(ReadInteger(obj)); 375 376 // Ensure that resizing a vector to size num_elements makes sense. 377 if (std::numeric_limits<int>::max() / element_size <= num_elements) { 378 obj->parse_error = true; 379 return 0; 380 } 381 382 // Ensure that it is plausible for the pickle to contain num_elements worth 383 // of data. 384 if (obj->pickle.payload_size() <= num_elements) { 385 obj->parse_error = true; 386 return 0; 387 } 388 389 return num_elements; 390} 391 392// Writes a Vector of strings into a SerializeObject for serialization. 393void WriteStringVector( 394 const std::vector<base::NullableString16>& data, SerializeObject* obj) { 395 WriteAndValidateVectorSize(data, obj); 396 for (size_t i = 0; i < data.size(); ++i) { 397 WriteString(data[i], obj); 398 } 399} 400 401void ReadStringVector(SerializeObject* obj, 402 std::vector<base::NullableString16>* result) { 403 size_t num_elements = 404 ReadAndValidateVectorSize(obj, sizeof(base::NullableString16)); 405 406 result->resize(num_elements); 407 for (size_t i = 0; i < num_elements; ++i) 408 (*result)[i] = ReadString(obj); 409} 410 411// Writes an ExplodedHttpBody object into a SerializeObject for serialization. 412void WriteHttpBody(const ExplodedHttpBody& http_body, SerializeObject* obj) { 413 WriteBoolean(!http_body.is_null, obj); 414 415 if (http_body.is_null) 416 return; 417 418 WriteAndValidateVectorSize(http_body.elements, obj); 419 for (size_t i = 0; i < http_body.elements.size(); ++i) { 420 const ExplodedHttpBodyElement& element = http_body.elements[i]; 421 WriteInteger(element.type, obj); 422 if (element.type == WebKit::WebHTTPBody::Element::TypeData) { 423 WriteData(element.data.data(), static_cast<int>(element.data.size()), 424 obj); 425 } else if (element.type == WebKit::WebHTTPBody::Element::TypeFile) { 426 WriteString(element.file_path, obj); 427 WriteInteger64(element.file_start, obj); 428 WriteInteger64(element.file_length, obj); 429 WriteReal(element.file_modification_time, obj); 430 } else if (element.type == 431 WebKit::WebHTTPBody::Element::TypeFileSystemURL) { 432 WriteGURL(element.filesystem_url, obj); 433 WriteInteger64(element.file_start, obj); 434 WriteInteger64(element.file_length, obj); 435 WriteReal(element.file_modification_time, obj); 436 } else { 437 DCHECK(element.type == WebKit::WebHTTPBody::Element::TypeBlob); 438#ifdef USE_BLOB_UUIDS 439 WriteStdString(element.blob_uuid, obj); 440#else 441 WriteGURL(element.deprecated_blob_url, obj); 442#endif 443 } 444 } 445 WriteInteger64(http_body.identifier, obj); 446 WriteBoolean(http_body.contains_passwords, obj); 447} 448 449void ReadHttpBody(SerializeObject* obj, ExplodedHttpBody* http_body) { 450 // An initial boolean indicates if we have an HTTP body. 451 if (!ReadBoolean(obj)) 452 return; 453 http_body->is_null = false; 454 455 int num_elements = ReadInteger(obj); 456 457 for (int i = 0; i < num_elements; ++i) { 458 int type = ReadInteger(obj); 459 if (type == WebKit::WebHTTPBody::Element::TypeData) { 460 const void* data; 461 int length = -1; 462 ReadData(obj, &data, &length); 463 if (length >= 0) { 464 AppendDataToHttpBody(http_body, static_cast<const char*>(data), 465 length); 466 } 467 } else if (type == WebKit::WebHTTPBody::Element::TypeFile) { 468 base::NullableString16 file_path = ReadString(obj); 469 int64 file_start = ReadInteger64(obj); 470 int64 file_length = ReadInteger64(obj); 471 double file_modification_time = ReadReal(obj); 472 AppendFileRangeToHttpBody(http_body, file_path, file_start, file_length, 473 file_modification_time); 474 } else if (type == WebKit::WebHTTPBody::Element::TypeFileSystemURL) { 475 GURL url = ReadGURL(obj); 476 int64 file_start = ReadInteger64(obj); 477 int64 file_length = ReadInteger64(obj); 478 double file_modification_time = ReadReal(obj); 479 AppendURLRangeToHttpBody(http_body, url, file_start, file_length, 480 file_modification_time); 481 } else if (type == WebKit::WebHTTPBody::Element::TypeBlob) { 482#ifdef USE_BLOB_UUIDS 483 if (obj->version >= 15) { 484 std::string blob_uuid = ReadStdString(obj); 485 AppendBlobToHttpBody(http_body, blob_uuid); 486 } else { 487 ReadGURL(obj); // Skip the obsolete blob url value. 488 } 489#else 490 GURL blob_url = ReadGURL(obj); 491 DeprecatedAppendBlobToHttpBody(http_body, blob_url); 492#endif 493 } 494 } 495 http_body->identifier = ReadInteger64(obj); 496 497 if (obj->version >= 12) 498 http_body->contains_passwords = ReadBoolean(obj); 499} 500 501// Writes the ExplodedFrameState data into the SerializeObject object for 502// serialization. 503void WriteFrameState( 504 const ExplodedFrameState& state, SerializeObject* obj, bool is_top) { 505 // WARNING: This data may be persisted for later use. As such, care must be 506 // taken when changing the serialized format. If a new field needs to be 507 // written, only adding at the end will make it easier to deal with loading 508 // older versions. Similarly, this should NOT save fields with sensitive 509 // data, such as password fields. 510 511 WriteString(state.url_string, obj); 512 WriteString(state.original_url_string, obj); 513 WriteString(state.target, obj); 514 WriteString(state.parent, obj); 515 WriteString(state.title, obj); 516 WriteString(state.alternate_title, obj); 517 WriteReal(state.visited_time, obj); 518 WriteInteger(state.scroll_offset.x(), obj); 519 WriteInteger(state.scroll_offset.y(), obj); 520 WriteBoolean(state.is_target_item, obj); 521 WriteInteger(state.visit_count, obj); 522 WriteString(state.referrer, obj); 523 524 WriteStringVector(state.document_state, obj); 525 526 WriteReal(state.page_scale_factor, obj); 527 WriteInteger64(state.item_sequence_number, obj); 528 WriteInteger64(state.document_sequence_number, obj); 529 530 bool has_state_object = !state.state_object.is_null(); 531 WriteBoolean(has_state_object, obj); 532 if (has_state_object) 533 WriteString(state.state_object, obj); 534 535 WriteHttpBody(state.http_body, obj); 536 537 // NOTE: It is a quirk of the format that we still have to write the 538 // http_content_type field when the HTTP body is null. That's why this code 539 // is here instead of inside WriteHttpBody. 540 WriteString(state.http_body.http_content_type, obj); 541 542 // Subitems 543 const std::vector<ExplodedFrameState>& children = state.children; 544 WriteAndValidateVectorSize(children, obj); 545 for (size_t i = 0; i < children.size(); ++i) 546 WriteFrameState(children[i], obj, false); 547} 548 549void ReadFrameState(SerializeObject* obj, bool is_top, 550 ExplodedFrameState* state) { 551 if (obj->version < 14 && !is_top) 552 ConsumeInteger(obj); // Skip over redundant version field. 553 554 state->url_string = ReadString(obj); 555 state->original_url_string = ReadString(obj); 556 state->target = ReadString(obj); 557 state->parent = ReadString(obj); 558 state->title = ReadString(obj); 559 state->alternate_title = ReadString(obj); 560 state->visited_time = ReadReal(obj); 561 562 int x = ReadInteger(obj); 563 int y = ReadInteger(obj); 564 state->scroll_offset = gfx::Point(x, y); 565 566 state->is_target_item = ReadBoolean(obj); 567 state->visit_count = ReadInteger(obj); 568 state->referrer = ReadString(obj); 569 570 ReadStringVector(obj, &state->document_state); 571 572 state->page_scale_factor = ReadReal(obj); 573 state->item_sequence_number = ReadInteger64(obj); 574 state->document_sequence_number = ReadInteger64(obj); 575 576 bool has_state_object = ReadBoolean(obj); 577 if (has_state_object) 578 state->state_object = ReadString(obj); 579 580 ReadHttpBody(obj, &state->http_body); 581 582 // NOTE: It is a quirk of the format that we still have to read the 583 // http_content_type field when the HTTP body is null. That's why this code 584 // is here instead of inside ReadHttpBody. 585 state->http_body.http_content_type = ReadString(obj); 586 587 if (obj->version < 14) 588 ConsumeString(obj); // Skip unused referrer string. 589 590#if defined(OS_ANDROID) 591 if (obj->version == 11) { 592 // Now-unused values that shipped in this version of Chrome for Android when 593 // it was on a private branch. 594 ReadReal(obj); 595 ReadBoolean(obj); 596 597 // In this version, page_scale_factor included device_scale_factor and 598 // scroll offsets were premultiplied by pageScaleFactor. 599 if (state->page_scale_factor) { 600 float device_scale_factor = g_device_scale_factor_for_testing; 601 if (!device_scale_factor) { 602 device_scale_factor = 603 gfx::Screen::GetNativeScreen()->GetPrimaryDisplay(). 604 device_scale_factor(); 605 } 606 state->scroll_offset = 607 gfx::Point(state->scroll_offset.x() / state->page_scale_factor, 608 state->scroll_offset.y() / state->page_scale_factor); 609 state->page_scale_factor /= device_scale_factor; 610 } 611 } 612#endif 613 614 // Subitems 615 size_t num_children = 616 ReadAndValidateVectorSize(obj, sizeof(ExplodedFrameState)); 617 state->children.resize(num_children); 618 for (size_t i = 0; i < num_children; ++i) 619 ReadFrameState(obj, false, &state->children[i]); 620} 621 622void WritePageState(const ExplodedPageState& state, SerializeObject* obj) { 623 WriteInteger(obj->version, obj); 624 WriteStringVector(state.referenced_files, obj); 625 WriteFrameState(state.top, obj, true); 626} 627 628void ReadPageState(SerializeObject* obj, ExplodedPageState* state) { 629 obj->version = ReadInteger(obj); 630 631 if (obj->version == -1) { 632 GURL url = ReadGURL(obj); 633 // NOTE: GURL::possibly_invalid_spec() always returns valid UTF-8. 634 state->top.url_string = state->top.original_url_string = 635 base::NullableString16(UTF8ToUTF16(url.possibly_invalid_spec()), false); 636 return; 637 } 638 639 if (obj->version > kCurrentVersion || obj->version < kMinVersion) { 640 obj->parse_error = true; 641 return; 642 } 643 644 if (obj->version >= 14) 645 ReadStringVector(obj, &state->referenced_files); 646 647 ReadFrameState(obj, true, &state->top); 648 649 if (obj->version < 14) 650 RecursivelyAppendReferencedFiles(state->top, &state->referenced_files); 651 652 // De-dupe 653 state->referenced_files.erase( 654 std::unique(state->referenced_files.begin(), 655 state->referenced_files.end()), 656 state->referenced_files.end()); 657} 658 659} // namespace 660 661ExplodedHttpBodyElement::ExplodedHttpBodyElement() 662 : type(WebKit::WebHTTPBody::Element::TypeData), 663 file_start(0), 664 file_length(-1), 665 file_modification_time(std::numeric_limits<double>::quiet_NaN()) { 666} 667 668ExplodedHttpBodyElement::~ExplodedHttpBodyElement() { 669} 670 671ExplodedHttpBody::ExplodedHttpBody() 672 : identifier(0), 673 contains_passwords(false), 674 is_null(true) { 675} 676 677ExplodedHttpBody::~ExplodedHttpBody() { 678} 679 680ExplodedFrameState::ExplodedFrameState() 681 : item_sequence_number(0), 682 document_sequence_number(0), 683 visit_count(0), 684 visited_time(0.0), 685 page_scale_factor(0.0), 686 is_target_item(false) { 687} 688 689ExplodedFrameState::~ExplodedFrameState() { 690} 691 692ExplodedPageState::ExplodedPageState() { 693} 694 695ExplodedPageState::~ExplodedPageState() { 696} 697 698bool DecodePageState(const std::string& encoded, ExplodedPageState* exploded) { 699 *exploded = ExplodedPageState(); 700 701 if (encoded.empty()) 702 return true; 703 704 SerializeObject obj(encoded.data(), static_cast<int>(encoded.size())); 705 ReadPageState(&obj, exploded); 706 return !obj.parse_error; 707} 708 709bool EncodePageState(const ExplodedPageState& exploded, std::string* encoded) { 710 SerializeObject obj; 711 obj.version = kCurrentVersion; 712 WritePageState(exploded, &obj); 713 *encoded = obj.GetAsString(); 714 return true; 715} 716 717#if defined(OS_ANDROID) 718bool DecodePageStateWithDeviceScaleFactorForTesting( 719 const std::string& encoded, 720 float device_scale_factor, 721 ExplodedPageState* exploded) { 722 g_device_scale_factor_for_testing = device_scale_factor; 723 bool rv = DecodePageState(encoded, exploded); 724 g_device_scale_factor_for_testing = 0.0; 725 return rv; 726} 727#endif 728 729} // namespace content 730