blob: d717926b05487be6498b221063d33376e6bbcf02 [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/common/page_state_serialization.h"
#include <algorithm>
#include <limits>
#include "base/pickle.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "content/common/page_state.mojom.h"
#include "content/common/unique_name_helper.h"
#include "ipc/ipc_message_utils.h"
#include "mojo/public/cpp/base/string16_mojom_traits.h"
#include "mojo/public/cpp/base/time_mojom_traits.h"
#include "services/network/public/cpp/resource_request_body.h"
#include "third_party/blink/public/platform/web_history_scroll_restoration_type.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/gfx/geometry/mojo/geometry_struct_traits.h"
#include "url/mojom/url_gurl_mojom_traits.h"
namespace content {
#define STATIC_ASSERT_ENUM(a, b) \
static_assert(static_cast<int>(a) == static_cast<int>(b), \
"mismatching enums: " #a)
STATIC_ASSERT_ENUM(history::mojom::ScrollRestorationType::kAuto,
blink::kWebHistoryScrollRestorationAuto);
STATIC_ASSERT_ENUM(history::mojom::ScrollRestorationType::kManual,
blink::kWebHistoryScrollRestorationManual);
namespace {
#if defined(OS_ANDROID)
float g_device_scale_factor_for_testing = 0.0;
#endif
//-----------------------------------------------------------------------------
void AppendDataToRequestBody(
const scoped_refptr<network::ResourceRequestBody>& request_body,
const char* data,
int data_length) {
request_body->AppendBytes(data, data_length);
}
void AppendFileRangeToRequestBody(
const scoped_refptr<network::ResourceRequestBody>& request_body,
const base::Optional<base::string16>& file_path,
int file_start,
int file_length,
base::Time file_modification_time) {
request_body->AppendFileRange(
file_path ? base::FilePath::FromUTF16Unsafe(*file_path)
: base::FilePath(),
static_cast<uint64_t>(file_start), static_cast<uint64_t>(file_length),
file_modification_time);
}
void AppendBlobToRequestBody(
const scoped_refptr<network::ResourceRequestBody>& request_body,
const std::string& uuid) {
request_body->AppendBlob(uuid);
}
//----------------------------------------------------------------------------
void AppendReferencedFilesFromHttpBody(
const std::vector<network::DataElement>& elements,
std::vector<base::Optional<base::string16>>* referenced_files) {
for (size_t i = 0; i < elements.size(); ++i) {
if (elements[i].type() == network::mojom::DataElementType::kFile)
referenced_files->emplace_back(elements[i].path().AsUTF16Unsafe());
}
}
bool AppendReferencedFilesFromDocumentState(
const std::vector<base::Optional<base::string16>>& document_state,
std::vector<base::Optional<base::string16>>* referenced_files) {
if (document_state.empty())
return true;
// This algorithm is adapted from Blink's FormController code.
// We only care about how that code worked when this code snapshot was taken
// as this code is only needed for backwards compat.
//
// For reference, see FormController::formStatesFromStateVector in
// third_party/WebKit/Source/core/html/forms/FormController.cpp.
size_t index = 0;
if (document_state.size() < 3)
return false;
index++; // Skip over magic signature.
index++; // Skip over form key.
size_t item_count;
if (!document_state[index] ||
!base::StringToSizeT(*document_state[index++], &item_count))
return false;
while (item_count--) {
if (index + 1 >= document_state.size())
return false;
index++; // Skip over name.
const base::Optional<base::string16>& type = document_state[index++];
if (index >= document_state.size())
return false;
size_t value_size;
if (!document_state[index] ||
!base::StringToSizeT(*document_state[index++], &value_size))
return false;
if (index + value_size > document_state.size() ||
index + value_size < index) // Check for overflow.
return false;
if (type && base::EqualsASCII(*type, "file")) {
if (value_size != 2)
return false;
referenced_files->emplace_back(document_state[index++]);
index++; // Skip over display name.
} else {
index += value_size;
}
}
return true;
}
bool RecursivelyAppendReferencedFiles(
const ExplodedFrameState& frame_state,
std::vector<base::Optional<base::string16>>* referenced_files) {
if (frame_state.http_body.request_body != nullptr) {
AppendReferencedFilesFromHttpBody(
*frame_state.http_body.request_body->elements(), referenced_files);
}
if (!AppendReferencedFilesFromDocumentState(frame_state.document_state,
referenced_files))
return false;
for (size_t i = 0; i < frame_state.children.size(); ++i) {
if (!RecursivelyAppendReferencedFiles(frame_state.children[i],
referenced_files))
return false;
}
return true;
}
//----------------------------------------------------------------------------
struct SerializeObject {
SerializeObject()
: version(0),
parse_error(false) {
}
SerializeObject(const char* data, int len)
: pickle(data, len),
version(0),
parse_error(false) {
iter = base::PickleIterator(pickle);
}
std::string GetAsString() {
return std::string(static_cast<const char*>(pickle.data()), pickle.size());
}
base::Pickle pickle;
base::PickleIterator iter;
int version;
bool parse_error;
};
// Version ID of serialized format.
// 11: Min version
// 12: Adds support for contains_passwords in HTTP body
// 13: Adds support for URL (FileSystem URL)
// 14: Adds list of referenced files, version written only for first item.
// 15: Removes a bunch of values we defined but never used.
// 16: Switched from blob urls to blob uuids.
// 17: Add a target frame id number.
// 18: Add referrer policy.
// 19: Remove target frame id, which was a bad idea, and original url string,
// which is no longer used.
// 20: Add visual viewport scroll offset, the offset of the pinched zoomed
// viewport within the unzoomed main frame.
// 21: Add frame sequence number.
// 22: Add scroll restoration type.
// 23: Remove frame sequence number, there are easier ways.
// 24: Add did save scroll or scale state.
// 25: Limit the length of unique names: https://crbug.com/626202
// 26: Switch to mojo-based serialization.
// 27: Add serialized scroll anchor to FrameState.
// NOTE: If the version is -1, then the pickle contains only a URL string.
// See ReadPageState.
//
const int kMinVersion = 11;
// NOTE: When changing the version, please add a backwards compatibility test.
// See PageStateSerializationTest.DumpExpectedPageStateForBackwardsCompat for
// instructions on how to generate the new test case.
const int kCurrentVersion = 27;
// A bunch of convenience functions to write to/read from SerializeObjects. The
// de-serializers assume the input data will be in the correct format and fall
// back to returning safe defaults when not. These are mostly used by
// legacy(pre-mojo) serialization methods. If you're making changes to the
// PageState serialization format you almost certainly want to add/remove fields
// in page_state.mojom rather than using these methods.
void WriteData(const void* data, int length, SerializeObject* obj) {
obj->pickle.WriteData(static_cast<const char*>(data), length);
}
void ReadData(SerializeObject* obj, const void** data, int* length) {
const char* tmp;
if (obj->iter.ReadData(&tmp, length)) {
*data = tmp;
} else {
obj->parse_error = true;
*data = nullptr;
*length = 0;
}
}
void WriteInteger(int data, SerializeObject* obj) {
obj->pickle.WriteInt(data);
}
int ReadInteger(SerializeObject* obj) {
int tmp;
if (obj->iter.ReadInt(&tmp))
return tmp;
obj->parse_error = true;
return 0;
}
void WriteInteger64(int64_t data, SerializeObject* obj) {
obj->pickle.WriteInt64(data);
}
int64_t ReadInteger64(SerializeObject* obj) {
int64_t tmp = 0;
if (obj->iter.ReadInt64(&tmp))
return tmp;
obj->parse_error = true;
return 0;
}
void WriteReal(double data, SerializeObject* obj) {
WriteData(&data, sizeof(double), obj);
}
double ReadReal(SerializeObject* obj) {
const void* tmp = nullptr;
int length = 0;
double value = 0.0;
ReadData(obj, &tmp, &length);
if (length == static_cast<int>(sizeof(double))) {
// Use memcpy, as tmp may not be correctly aligned.
memcpy(&value, tmp, sizeof(double));
} else {
obj->parse_error = true;
}
return value;
}
void WriteBoolean(bool data, SerializeObject* obj) {
obj->pickle.WriteInt(data ? 1 : 0);
}
bool ReadBoolean(SerializeObject* obj) {
bool tmp;
if (obj->iter.ReadBool(&tmp))
return tmp;
obj->parse_error = true;
return false;
}
GURL ReadGURL(SerializeObject* obj) {
std::string spec;
if (obj->iter.ReadString(&spec))
return GURL(spec);
obj->parse_error = true;
return GURL();
}
void WriteStdString(const std::string& s, SerializeObject* obj) {
obj->pickle.WriteString(s);
}
std::string ReadStdString(SerializeObject* obj) {
std::string s;
if (obj->iter.ReadString(&s))
return s;
obj->parse_error = true;
return std::string();
}
// Pickles a base::string16 as <int length>:<char*16 data> tuple>.
void WriteString(const base::string16& str, SerializeObject* obj) {
const base::char16* data = str.data();
size_t length_in_bytes = str.length() * sizeof(base::char16);
CHECK_LT(length_in_bytes,
static_cast<size_t>(std::numeric_limits<int>::max()));
obj->pickle.WriteInt(length_in_bytes);
obj->pickle.WriteBytes(data, length_in_bytes);
}
// If str is a null optional, this simply pickles a length of -1. Otherwise,
// delegates to the base::string16 overload.
void WriteString(const base::Optional<base::string16>& str,
SerializeObject* obj) {
if (!str) {
obj->pickle.WriteInt(-1);
} else {
WriteString(*str, obj);
}
}
// This reads a serialized base::Optional<base::string16> from obj. If a string
// can't be read, NULL is returned.
const base::char16* ReadStringNoCopy(SerializeObject* obj, int* num_chars) {
int length_in_bytes;
if (!obj->iter.ReadInt(&length_in_bytes)) {
obj->parse_error = true;
return nullptr;
}
if (length_in_bytes < 0)
return nullptr;
const char* data;
if (!obj->iter.ReadBytes(&data, length_in_bytes)) {
obj->parse_error = true;
return nullptr;
}
if (num_chars)
*num_chars = length_in_bytes / sizeof(base::char16);
return reinterpret_cast<const base::char16*>(data);
}
base::Optional<base::string16> ReadString(SerializeObject* obj) {
int num_chars;
const base::char16* chars = ReadStringNoCopy(obj, &num_chars);
base::Optional<base::string16> result;
if (chars)
result.emplace(chars, num_chars);
return result;
}
template <typename T>
void WriteAndValidateVectorSize(const std::vector<T>& v, SerializeObject* obj) {
CHECK_LT(v.size(), std::numeric_limits<int>::max() / sizeof(T));
WriteInteger(static_cast<int>(v.size()), obj);
}
size_t ReadAndValidateVectorSize(SerializeObject* obj, size_t element_size) {
size_t num_elements = static_cast<size_t>(ReadInteger(obj));
// Ensure that resizing a vector to size num_elements makes sense.
if (std::numeric_limits<int>::max() / element_size <= num_elements) {
obj->parse_error = true;
return 0;
}
// Ensure that it is plausible for the pickle to contain num_elements worth
// of data.
if (obj->pickle.payload_size() <= num_elements) {
obj->parse_error = true;
return 0;
}
return num_elements;
}
// Writes a Vector of strings into a SerializeObject for serialization.
void WriteStringVector(const std::vector<base::Optional<base::string16>>& data,
SerializeObject* obj) {
WriteAndValidateVectorSize(data, obj);
for (size_t i = 0; i < data.size(); ++i) {
WriteString(data[i], obj);
}
}
void ReadStringVector(SerializeObject* obj,
std::vector<base::Optional<base::string16>>* result) {
size_t num_elements =
ReadAndValidateVectorSize(obj, sizeof(base::Optional<base::string16>));
result->resize(num_elements);
for (size_t i = 0; i < num_elements; ++i)
(*result)[i] = ReadString(obj);
}
void WriteResourceRequestBody(const network::ResourceRequestBody& request_body,
SerializeObject* obj) {
WriteAndValidateVectorSize(*request_body.elements(), obj);
for (const auto& element : *request_body.elements()) {
switch (element.type()) {
case network::mojom::DataElementType::kBytes:
WriteInteger(blink::WebHTTPBody::Element::kTypeData, obj);
WriteData(element.bytes(), static_cast<int>(element.length()), obj);
break;
case network::mojom::DataElementType::kFile:
WriteInteger(blink::WebHTTPBody::Element::kTypeFile, obj);
WriteString(element.path().AsUTF16Unsafe(), obj);
WriteInteger64(static_cast<int64_t>(element.offset()), obj);
WriteInteger64(static_cast<int64_t>(element.length()), obj);
WriteReal(element.expected_modification_time().ToDoubleT(), obj);
break;
case network::mojom::DataElementType::kBlob:
WriteInteger(blink::WebHTTPBody::Element::kTypeBlob, obj);
WriteStdString(element.blob_uuid(), obj);
break;
case network::mojom::DataElementType::kRawFile:
default:
NOTREACHED();
continue;
}
}
WriteInteger64(request_body.identifier(), obj);
}
void ReadResourceRequestBody(
SerializeObject* obj,
const scoped_refptr<network::ResourceRequestBody>& request_body) {
int num_elements = ReadInteger(obj);
for (int i = 0; i < num_elements; ++i) {
int type = ReadInteger(obj);
if (type == blink::WebHTTPBody::Element::kTypeData) {
const void* data;
int length = -1;
ReadData(obj, &data, &length);
if (length >= 0) {
AppendDataToRequestBody(request_body, static_cast<const char*>(data),
length);
}
} else if (type == blink::WebHTTPBody::Element::kTypeFile) {
base::Optional<base::string16> file_path = ReadString(obj);
int64_t file_start = ReadInteger64(obj);
int64_t file_length = ReadInteger64(obj);
double file_modification_time = ReadReal(obj);
AppendFileRangeToRequestBody(
request_body, file_path, file_start, file_length,
base::Time::FromDoubleT(file_modification_time));
} else if (type == blink::WebHTTPBody::Element::kTypeBlob) {
if (obj->version >= 16) {
std::string blob_uuid = ReadStdString(obj);
AppendBlobToRequestBody(request_body, blob_uuid);
} else {
ReadGURL(obj); // Skip the obsolete blob url value.
}
}
}
request_body->set_identifier(ReadInteger64(obj));
}
void ReadHttpBody(SerializeObject* obj, ExplodedHttpBody* http_body) {
// An initial boolean indicates if we have an HTTP body.
if (!ReadBoolean(obj))
return;
http_body->request_body = new network::ResourceRequestBody();
ReadResourceRequestBody(obj, http_body->request_body);
if (obj->version >= 12)
http_body->contains_passwords = ReadBoolean(obj);
}
void WriteHttpBody(const ExplodedHttpBody& http_body, SerializeObject* obj) {
bool is_null = http_body.request_body == nullptr;
WriteBoolean(!is_null, obj);
if (is_null)
return;
WriteResourceRequestBody(*http_body.request_body, obj);
WriteBoolean(http_body.contains_passwords, obj);
}
void ReadFrameState(
SerializeObject* obj,
bool is_top,
std::vector<UniqueNameHelper::Replacement>* unique_name_replacements,
ExplodedFrameState* state) {
if (obj->version < 14 && !is_top)
ReadInteger(obj); // Skip over redundant version field.
state->url_string = ReadString(obj);
if (obj->version < 19)
ReadString(obj); // Skip obsolete original url string field.
state->target = ReadString(obj);
if (obj->version < 25 && state->target) {
state->target = base::UTF8ToUTF16(UniqueNameHelper::UpdateLegacyNameFromV24(
base::UTF16ToUTF8(*state->target), unique_name_replacements));
}
if (obj->version < 15) {
ReadString(obj); // Skip obsolete parent field.
ReadString(obj); // Skip obsolete title field.
ReadString(obj); // Skip obsolete alternate title field.
ReadReal(obj); // Skip obsolete visited time field.
}
if (obj->version >= 24) {
state->did_save_scroll_or_scale_state = ReadBoolean(obj);
} else {
state->did_save_scroll_or_scale_state = true;
}
if (state->did_save_scroll_or_scale_state) {
int x = ReadInteger(obj);
int y = ReadInteger(obj);
state->scroll_offset = gfx::Point(x, y);
}
if (obj->version < 15) {
ReadBoolean(obj); // Skip obsolete target item flag.
ReadInteger(obj); // Skip obsolete visit count field.
}
state->referrer = ReadString(obj);
ReadStringVector(obj, &state->document_state);
if (state->did_save_scroll_or_scale_state)
state->page_scale_factor = ReadReal(obj);
state->item_sequence_number = ReadInteger64(obj);
state->document_sequence_number = ReadInteger64(obj);
if (obj->version >= 21 && obj->version < 23)
ReadInteger64(obj); // Skip obsolete frame sequence number.
if (obj->version >= 17 && obj->version < 19)
ReadInteger64(obj); // Skip obsolete target frame id number.
if (obj->version >= 18) {
state->referrer_policy =
static_cast<network::mojom::ReferrerPolicy>(ReadInteger(obj));
}
if (obj->version >= 20 && state->did_save_scroll_or_scale_state) {
double x = ReadReal(obj);
double y = ReadReal(obj);
state->visual_viewport_scroll_offset = gfx::PointF(x, y);
} else {
state->visual_viewport_scroll_offset = gfx::PointF(-1, -1);
}
if (obj->version >= 22) {
state->scroll_restoration_type =
static_cast<blink::WebHistoryScrollRestorationType>(ReadInteger(obj));
}
bool has_state_object = ReadBoolean(obj);
if (has_state_object)
state->state_object = ReadString(obj);
ReadHttpBody(obj, &state->http_body);
// NOTE: It is a quirk of the format that we still have to read the
// http_content_type field when the HTTP body is null. That's why this code
// is here instead of inside ReadHttpBody.
state->http_body.http_content_type = ReadString(obj);
if (obj->version < 14)
ReadString(obj); // Skip unused referrer string.
#if defined(OS_ANDROID)
if (obj->version == 11) {
// Now-unused values that shipped in this version of Chrome for Android when
// it was on a private branch.
ReadReal(obj);
ReadBoolean(obj);
// In this version, page_scale_factor included device_scale_factor and
// scroll offsets were premultiplied by pageScaleFactor.
if (state->page_scale_factor) {
float device_scale_factor = g_device_scale_factor_for_testing;
if (!device_scale_factor) {
device_scale_factor = display::Screen::GetScreen()
->GetPrimaryDisplay()
.device_scale_factor();
}
state->scroll_offset =
gfx::Point(state->scroll_offset.x() / state->page_scale_factor,
state->scroll_offset.y() / state->page_scale_factor);
state->page_scale_factor /= device_scale_factor;
}
}
#endif
// Subitems
size_t num_children =
ReadAndValidateVectorSize(obj, sizeof(ExplodedFrameState));
state->children.resize(num_children);
for (size_t i = 0; i < num_children; ++i)
ReadFrameState(obj, false, unique_name_replacements, &state->children[i]);
}
// Writes the ExplodedFrameState data into the SerializeObject object for
// serialization. This uses the custom, legacy format, and its implementation
// should remain frozen in order to preserve this format.
// TODO(pnoland, dcheng) Move the legacy write methods into a test-only helper.
void WriteFrameState(const ExplodedFrameState& state,
SerializeObject* obj,
bool is_top) {
// WARNING: This data may be persisted for later use. As such, care must be
// taken when changing the serialized format. If a new field needs to be
// written, only adding at the end will make it easier to deal with loading
// older versions. Similarly, this should NOT save fields with sensitive
// data, such as password fields.
WriteString(state.url_string, obj);
WriteString(state.target, obj);
WriteBoolean(state.did_save_scroll_or_scale_state, obj);
if (state.did_save_scroll_or_scale_state) {
WriteInteger(state.scroll_offset.x(), obj);
WriteInteger(state.scroll_offset.y(), obj);
}
WriteString(state.referrer, obj);
WriteStringVector(state.document_state, obj);
if (state.did_save_scroll_or_scale_state)
WriteReal(state.page_scale_factor, obj);
WriteInteger64(state.item_sequence_number, obj);
WriteInteger64(state.document_sequence_number, obj);
WriteInteger(static_cast<int>(state.referrer_policy), obj);
if (state.did_save_scroll_or_scale_state) {
WriteReal(state.visual_viewport_scroll_offset.x(), obj);
WriteReal(state.visual_viewport_scroll_offset.y(), obj);
}
WriteInteger(state.scroll_restoration_type, obj);
bool has_state_object = state.state_object.has_value();
WriteBoolean(has_state_object, obj);
if (has_state_object)
WriteString(*state.state_object, obj);
WriteHttpBody(state.http_body, obj);
// NOTE: It is a quirk of the format that we still have to write the
// http_content_type field when the HTTP body is null. That's why this code
// is here instead of inside WriteHttpBody.
WriteString(state.http_body.http_content_type, obj);
// Subitems
const std::vector<ExplodedFrameState>& children = state.children;
WriteAndValidateVectorSize(children, obj);
for (size_t i = 0; i < children.size(); ++i)
WriteFrameState(children[i], obj, false);
}
void WritePageState(const ExplodedPageState& state, SerializeObject* obj) {
WriteInteger(obj->version, obj);
WriteStringVector(state.referenced_files, obj);
WriteFrameState(state.top, obj, true);
}
// Legacy read/write functions above this line. Don't change these.
//-----------------------------------------------------------------------------
// "Modern" read/write functions start here. These are probably what you want.
void WriteResourceRequestBody(const network::ResourceRequestBody& request_body,
history::mojom::RequestBody* mojo_body) {
for (const auto& element : *request_body.elements()) {
history::mojom::ElementPtr data_element = history::mojom::Element::New();
switch (element.type()) {
case network::mojom::DataElementType::kBytes: {
data_element->set_bytes(std::vector<unsigned char>(
reinterpret_cast<const char*>(element.bytes()),
element.bytes() + element.length()));
break;
}
case network::mojom::DataElementType::kFile: {
history::mojom::FilePtr file = history::mojom::File::New(
element.path().AsUTF16Unsafe(), element.offset(), element.length(),
element.expected_modification_time());
data_element->set_file(std::move(file));
break;
}
case network::mojom::DataElementType::kBlob:
data_element->set_blob_uuid(element.blob_uuid());
break;
case network::mojom::DataElementType::kDataPipe:
NOTIMPLEMENTED();
break;
case network::mojom::DataElementType::kRawFile:
case network::mojom::DataElementType::kChunkedDataPipe:
case network::mojom::DataElementType::kUnknown:
NOTREACHED();
continue;
}
mojo_body->elements.push_back(std::move(data_element));
}
mojo_body->identifier = request_body.identifier();
}
void ReadResourceRequestBody(
history::mojom::RequestBody* mojo_body,
const scoped_refptr<network::ResourceRequestBody>& request_body) {
for (const auto& element : mojo_body->elements) {
history::mojom::Element::Tag tag = element->which();
switch (tag) {
case history::mojom::Element::Tag::BYTES:
AppendDataToRequestBody(
request_body,
reinterpret_cast<const char*>(element->get_bytes().data()),
element->get_bytes().size());
break;
case history::mojom::Element::Tag::FILE: {
history::mojom::File* file = element->get_file().get();
AppendFileRangeToRequestBody(request_body, file->path, file->offset,
file->length, file->modification_time);
break;
}
case history::mojom::Element::Tag::BLOB_UUID:
AppendBlobToRequestBody(request_body, element->get_blob_uuid());
break;
case history::mojom::Element::Tag::DEPRECATED_FILE_SYSTEM_FILE:
// No longer supported.
break;
}
}
request_body->set_identifier(mojo_body->identifier);
}
void WriteHttpBody(const ExplodedHttpBody& http_body,
history::mojom::HttpBody* mojo_body) {
if (http_body.request_body != nullptr) {
mojo_body->request_body = history::mojom::RequestBody::New();
mojo_body->contains_passwords = http_body.contains_passwords;
mojo_body->http_content_type = http_body.http_content_type;
WriteResourceRequestBody(*http_body.request_body,
mojo_body->request_body.get());
}
}
void ReadHttpBody(history::mojom::HttpBody* mojo_body,
ExplodedHttpBody* http_body) {
http_body->contains_passwords = mojo_body->contains_passwords;
http_body->http_content_type = mojo_body->http_content_type;
if (mojo_body->request_body) {
http_body->request_body =
base::MakeRefCounted<network::ResourceRequestBody>();
ReadResourceRequestBody(mojo_body->request_body.get(),
http_body->request_body);
}
}
void WriteFrameState(const ExplodedFrameState& state,
history::mojom::FrameState* frame) {
frame->url_string = state.url_string;
frame->referrer = state.referrer;
frame->target = state.target;
frame->state_object = state.state_object;
for (const auto& s : state.document_state) {
frame->document_state.push_back(s);
}
frame->scroll_restoration_type =
static_cast<history::mojom::ScrollRestorationType>(
state.scroll_restoration_type);
if (state.did_save_scroll_or_scale_state) {
frame->view_state = history::mojom::ViewState::New();
frame->view_state->scroll_offset = state.scroll_offset;
frame->view_state->visual_viewport_scroll_offset =
state.visual_viewport_scroll_offset;
frame->view_state->page_scale_factor = state.page_scale_factor;
// We discard all scroll anchor data if the selector is over the length
// limit. We don't want to bloat the size of FrameState, and the other
// fields are useless without the selector.
if (state.scroll_anchor_selector && state.scroll_anchor_selector->length() <
kMaxScrollAnchorSelectorLength) {
frame->view_state->scroll_anchor_selector = state.scroll_anchor_selector;
frame->view_state->scroll_anchor_offset = state.scroll_anchor_offset;
frame->view_state->scroll_anchor_simhash = state.scroll_anchor_simhash;
}
}
frame->item_sequence_number = state.item_sequence_number;
frame->document_sequence_number = state.document_sequence_number;
frame->referrer_policy = state.referrer_policy;
frame->http_body = history::mojom::HttpBody::New();
WriteHttpBody(state.http_body, frame->http_body.get());
// Subitems
const std::vector<ExplodedFrameState>& children = state.children;
for (const auto& child : children) {
history::mojom::FrameStatePtr child_frame =
history::mojom::FrameState::New();
WriteFrameState(child, child_frame.get());
frame->children.push_back(std::move(child_frame));
}
}
void ReadFrameState(history::mojom::FrameState* frame,
ExplodedFrameState* state) {
state->url_string = frame->url_string;
state->referrer = frame->referrer;
state->target = frame->target;
state->state_object = frame->state_object;
for (const auto& s : frame->document_state) {
state->document_state.push_back(s);
}
state->scroll_restoration_type =
static_cast<blink::WebHistoryScrollRestorationType>(
frame->scroll_restoration_type);
if (frame->view_state) {
state->did_save_scroll_or_scale_state = true;
state->visual_viewport_scroll_offset =
frame->view_state->visual_viewport_scroll_offset;
state->scroll_offset = frame->view_state->scroll_offset;
state->page_scale_factor = frame->view_state->page_scale_factor;
}
if (frame->view_state) {
state->scroll_anchor_selector = frame->view_state->scroll_anchor_selector;
state->scroll_anchor_offset =
frame->view_state->scroll_anchor_offset.value_or(gfx::PointF());
state->scroll_anchor_simhash = frame->view_state->scroll_anchor_simhash;
}
state->item_sequence_number = frame->item_sequence_number;
state->document_sequence_number = frame->document_sequence_number;
state->referrer_policy = frame->referrer_policy;
if (frame->http_body) {
ReadHttpBody(frame->http_body.get(), &state->http_body);
} else {
state->http_body.request_body = nullptr;
}
state->children.resize(frame->children.size());
int i = 0;
for (const auto& child : frame->children)
ReadFrameState(child.get(), &state->children[i++]);
}
void ReadMojoPageState(SerializeObject* obj, ExplodedPageState* state) {
const void* tmp = nullptr;
int length = 0;
ReadData(obj, &tmp, &length);
DCHECK_GT(length, 0);
if (obj->parse_error)
return;
history::mojom::PageStatePtr page;
obj->parse_error =
!(history::mojom::PageState::Deserialize(tmp, length, &page));
if (obj->parse_error)
return;
for (const auto& referenced_file : page->referenced_files) {
state->referenced_files.push_back(referenced_file);
}
ReadFrameState(page->top.get(), &state->top);
state->referenced_files.erase(std::unique(state->referenced_files.begin(),
state->referenced_files.end()),
state->referenced_files.end());
}
void WriteMojoPageState(const ExplodedPageState& state, SerializeObject* obj) {
WriteInteger(obj->version, obj);
history::mojom::PageStatePtr page = history::mojom::PageState::New();
for (const auto& referenced_file : state.referenced_files) {
page->referenced_files.push_back(referenced_file.value());
}
page->top = history::mojom::FrameState::New();
WriteFrameState(state.top, page->top.get());
std::vector<uint8_t> page_bytes = history::mojom::PageState::Serialize(&page);
obj->pickle.WriteData(reinterpret_cast<char*>(page_bytes.data()),
page_bytes.size());
}
void ReadPageState(SerializeObject* obj, ExplodedPageState* state) {
obj->version = ReadInteger(obj);
if (obj->version == -1) {
GURL url = ReadGURL(obj);
// NOTE: GURL::possibly_invalid_spec() always returns valid UTF-8.
state->top.url_string = base::UTF8ToUTF16(url.possibly_invalid_spec());
return;
}
if (obj->version > kCurrentVersion || obj->version < kMinVersion) {
obj->parse_error = true;
return;
}
if (obj->version >= 26) {
ReadMojoPageState(obj, state);
return;
}
if (obj->version >= 14)
ReadStringVector(obj, &state->referenced_files);
std::vector<UniqueNameHelper::Replacement> unique_name_replacements;
ReadFrameState(obj, true, &unique_name_replacements, &state->top);
if (obj->version < 14)
RecursivelyAppendReferencedFiles(state->top, &state->referenced_files);
// De-dupe
state->referenced_files.erase(
std::unique(state->referenced_files.begin(),
state->referenced_files.end()),
state->referenced_files.end());
}
} // namespace
ExplodedHttpBody::ExplodedHttpBody() : contains_passwords(false) {}
ExplodedHttpBody::~ExplodedHttpBody() {
}
ExplodedFrameState::ExplodedFrameState()
: scroll_restoration_type(blink::kWebHistoryScrollRestorationAuto),
did_save_scroll_or_scale_state(true),
item_sequence_number(0),
document_sequence_number(0),
page_scale_factor(0.0),
referrer_policy(network::mojom::ReferrerPolicy::kDefault),
scroll_anchor_simhash(0) {}
ExplodedFrameState::ExplodedFrameState(const ExplodedFrameState& other) {
assign(other);
}
ExplodedFrameState::~ExplodedFrameState() {
}
void ExplodedFrameState::operator=(const ExplodedFrameState& other) {
if (&other != this)
assign(other);
}
void ExplodedFrameState::assign(const ExplodedFrameState& other) {
url_string = other.url_string;
referrer = other.referrer;
target = other.target;
state_object = other.state_object;
document_state = other.document_state;
scroll_restoration_type = other.scroll_restoration_type;
did_save_scroll_or_scale_state = other.did_save_scroll_or_scale_state;
visual_viewport_scroll_offset = other.visual_viewport_scroll_offset;
scroll_offset = other.scroll_offset;
item_sequence_number = other.item_sequence_number;
document_sequence_number = other.document_sequence_number;
page_scale_factor = other.page_scale_factor;
referrer_policy = other.referrer_policy;
http_body = other.http_body;
scroll_anchor_selector = other.scroll_anchor_selector;
scroll_anchor_offset = other.scroll_anchor_offset;
scroll_anchor_simhash = other.scroll_anchor_simhash;
children = other.children;
}
ExplodedPageState::ExplodedPageState() {
}
ExplodedPageState::~ExplodedPageState() {
}
int DecodePageStateInternal(const std::string& encoded,
ExplodedPageState* exploded) {
*exploded = ExplodedPageState();
if (encoded.empty())
return true;
SerializeObject obj(encoded.data(), static_cast<int>(encoded.size()));
ReadPageState(&obj, exploded);
return obj.parse_error ? -1 : obj.version;
}
bool DecodePageState(const std::string& encoded, ExplodedPageState* exploded) {
return DecodePageStateInternal(encoded, exploded) != -1;
}
int DecodePageStateForTesting(const std::string& encoded,
ExplodedPageState* exploded) {
return DecodePageStateInternal(encoded, exploded);
}
void EncodePageState(const ExplodedPageState& exploded, std::string* encoded) {
SerializeObject obj;
obj.version = kCurrentVersion;
WriteMojoPageState(exploded, &obj);
*encoded = obj.GetAsString();
}
void LegacyEncodePageStateForTesting(const ExplodedPageState& exploded,
int version,
std::string* encoded) {
SerializeObject obj;
obj.version = version;
WritePageState(exploded, &obj);
*encoded = obj.GetAsString();
}
#if defined(OS_ANDROID)
bool DecodePageStateWithDeviceScaleFactorForTesting(
const std::string& encoded,
float device_scale_factor,
ExplodedPageState* exploded) {
g_device_scale_factor_for_testing = device_scale_factor;
bool rv = DecodePageState(encoded, exploded);
g_device_scale_factor_for_testing = 0.0;
return rv;
}
scoped_refptr<network::ResourceRequestBody> DecodeResourceRequestBody(
const char* data,
size_t size) {
scoped_refptr<network::ResourceRequestBody> result =
new network::ResourceRequestBody();
SerializeObject obj(data, static_cast<int>(size));
ReadResourceRequestBody(&obj, result);
// Please see the EncodeResourceRequestBody() function below for information
// about why the contains_sensitive_info() field is being explicitly
// deserialized.
result->set_contains_sensitive_info(ReadBoolean(&obj));
return obj.parse_error ? nullptr : result;
}
std::string EncodeResourceRequestBody(
const network::ResourceRequestBody& resource_request_body) {
SerializeObject obj;
obj.version = 25;
WriteResourceRequestBody(resource_request_body, &obj);
// EncodeResourceRequestBody() is different from WriteResourceRequestBody()
// because it covers additional data (e.g.|contains_sensitive_info|) which
// is marshaled between native code and java. WriteResourceRequestBody()
// serializes data which needs to be saved out to disk.
WriteBoolean(resource_request_body.contains_sensitive_info(), &obj);
return obj.GetAsString();
}
#endif
} // namespace content