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