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"
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"
19 #if defined(OS_ANDROID)
20 float g_device_scale_factor_for_testing
= 0.0;
23 //-----------------------------------------------------------------------------
25 void AppendDataToHttpBody(ExplodedHttpBody
* http_body
, const char* data
,
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
,
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
,
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())
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
95 if (document_state
.size() < 3)
98 index
++; // Skip over magic signature.
99 index
++; // Skip over form key.
102 if (!base::StringToSizeT(document_state
[index
++].string(), &item_count
))
105 while (item_count
--) {
106 if (index
+ 1 >= document_state
.size())
109 index
++; // Skip over name.
110 const base::NullableString16
& type
= document_state
[index
++];
112 if (index
>= document_state
.size())
116 if (!base::StringToSizeT(document_state
[index
++].string(), &value_size
))
119 if (index
+ value_size
> document_state
.size() ||
120 index
+ value_size
< index
) // Check for overflow.
123 if (EqualsASCII(type
.string(), "file")) {
127 referenced_files
->push_back(document_state
[index
++]);
128 index
++; // Skip over display name.
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
,
145 if (!AppendReferencedFilesFromDocumentState(frame_state
.document_state
,
149 for (size_t i
= 0; i
< frame_state
.children
.size(); ++i
) {
150 if (!RecursivelyAppendReferencedFiles(frame_state
.children
[i
],
158 //----------------------------------------------------------------------------
160 struct SerializeObject
{
166 SerializeObject(const char* data
, int len
)
170 iter
= PickleIterator(pickle
);
173 std::string
GetAsString() {
174 return std::string(static_cast<const char*>(pickle
.data()), pickle
.size());
183 // Version ID of serialized format.
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
198 // NOTE: If the version is -1, then the pickle contains only a URL string.
199 // See ReadPageState.
201 const int kMinVersion
= 11;
202 const int kCurrentVersion
= 21;
204 // A bunch of convenience functions to read/write to SerializeObjects. The
205 // de-serializers assume the input data will be in the correct format and fall
206 // back to returning safe defaults when not.
208 void WriteData(const void* data
, int length
, SerializeObject
* obj
) {
209 obj
->pickle
.WriteData(static_cast<const char*>(data
), length
);
212 void ReadData(SerializeObject
* obj
, const void** data
, int* length
) {
214 if (obj
->pickle
.ReadData(&obj
->iter
, &tmp
, length
)) {
217 obj
->parse_error
= true;
223 void WriteInteger(int data
, SerializeObject
* obj
) {
224 obj
->pickle
.WriteInt(data
);
227 int ReadInteger(SerializeObject
* obj
) {
229 if (obj
->pickle
.ReadInt(&obj
->iter
, &tmp
))
231 obj
->parse_error
= true;
235 void WriteInteger64(int64 data
, SerializeObject
* obj
) {
236 obj
->pickle
.WriteInt64(data
);
239 int64
ReadInteger64(SerializeObject
* obj
) {
241 if (obj
->pickle
.ReadInt64(&obj
->iter
, &tmp
))
243 obj
->parse_error
= true;
247 void WriteReal(double data
, SerializeObject
* obj
) {
248 WriteData(&data
, sizeof(double), obj
);
251 double ReadReal(SerializeObject
* obj
) {
252 const void* tmp
= NULL
;
255 ReadData(obj
, &tmp
, &length
);
256 if (length
== static_cast<int>(sizeof(double))) {
257 // Use memcpy, as tmp may not be correctly aligned.
258 memcpy(&value
, tmp
, sizeof(double));
260 obj
->parse_error
= true;
265 void WriteBoolean(bool data
, SerializeObject
* obj
) {
266 obj
->pickle
.WriteInt(data
? 1 : 0);
269 bool ReadBoolean(SerializeObject
* obj
) {
271 if (obj
->pickle
.ReadBool(&obj
->iter
, &tmp
))
273 obj
->parse_error
= true;
277 void WriteGURL(const GURL
& url
, SerializeObject
* obj
) {
278 obj
->pickle
.WriteString(url
.possibly_invalid_spec());
281 GURL
ReadGURL(SerializeObject
* obj
) {
283 if (obj
->pickle
.ReadString(&obj
->iter
, &spec
))
285 obj
->parse_error
= true;
289 void WriteStdString(const std::string
& s
, SerializeObject
* obj
) {
290 obj
->pickle
.WriteString(s
);
293 std::string
ReadStdString(SerializeObject
* obj
) {
295 if (obj
->pickle
.ReadString(&obj
->iter
, &s
))
297 obj
->parse_error
= true;
298 return std::string();
301 // WriteString pickles the NullableString16 as <int length><char16* data>.
302 // If length == -1, then the NullableString16 itself is null. Otherwise the
303 // length is the number of char16 (not bytes) in the NullableString16.
304 void WriteString(const base::NullableString16
& str
, SerializeObject
* obj
) {
306 obj
->pickle
.WriteInt(-1);
308 const base::char16
* data
= str
.string().data();
309 size_t length_in_bytes
= str
.string().length() * sizeof(base::char16
);
311 CHECK_LT(length_in_bytes
,
312 static_cast<size_t>(std::numeric_limits
<int>::max()));
313 obj
->pickle
.WriteInt(length_in_bytes
);
314 obj
->pickle
.WriteBytes(data
, length_in_bytes
);
318 // This reads a serialized NullableString16 from obj. If a string can't be
319 // read, NULL is returned.
320 const base::char16
* ReadStringNoCopy(SerializeObject
* obj
, int* num_chars
) {
322 if (!obj
->pickle
.ReadInt(&obj
->iter
, &length_in_bytes
)) {
323 obj
->parse_error
= true;
327 if (length_in_bytes
< 0)
331 if (!obj
->pickle
.ReadBytes(&obj
->iter
, &data
, length_in_bytes
)) {
332 obj
->parse_error
= true;
337 *num_chars
= length_in_bytes
/ sizeof(base::char16
);
338 return reinterpret_cast<const base::char16
*>(data
);
341 base::NullableString16
ReadString(SerializeObject
* obj
) {
343 const base::char16
* chars
= ReadStringNoCopy(obj
, &num_chars
);
345 base::NullableString16(base::string16(chars
, num_chars
), false) :
346 base::NullableString16();
349 template <typename T
>
350 void WriteAndValidateVectorSize(const std::vector
<T
>& v
, SerializeObject
* obj
) {
351 CHECK_LT(v
.size(), std::numeric_limits
<int>::max() / sizeof(T
));
352 WriteInteger(static_cast<int>(v
.size()), obj
);
355 size_t ReadAndValidateVectorSize(SerializeObject
* obj
, size_t element_size
) {
356 size_t num_elements
= static_cast<size_t>(ReadInteger(obj
));
358 // Ensure that resizing a vector to size num_elements makes sense.
359 if (std::numeric_limits
<int>::max() / element_size
<= num_elements
) {
360 obj
->parse_error
= true;
364 // Ensure that it is plausible for the pickle to contain num_elements worth
366 if (obj
->pickle
.payload_size() <= num_elements
) {
367 obj
->parse_error
= true;
374 // Writes a Vector of strings into a SerializeObject for serialization.
375 void WriteStringVector(
376 const std::vector
<base::NullableString16
>& data
, SerializeObject
* obj
) {
377 WriteAndValidateVectorSize(data
, obj
);
378 for (size_t i
= 0; i
< data
.size(); ++i
) {
379 WriteString(data
[i
], obj
);
383 void ReadStringVector(SerializeObject
* obj
,
384 std::vector
<base::NullableString16
>* result
) {
385 size_t num_elements
=
386 ReadAndValidateVectorSize(obj
, sizeof(base::NullableString16
));
388 result
->resize(num_elements
);
389 for (size_t i
= 0; i
< num_elements
; ++i
)
390 (*result
)[i
] = ReadString(obj
);
393 // Writes an ExplodedHttpBody object into a SerializeObject for serialization.
394 void WriteHttpBody(const ExplodedHttpBody
& http_body
, SerializeObject
* obj
) {
395 WriteBoolean(!http_body
.is_null
, obj
);
397 if (http_body
.is_null
)
400 WriteAndValidateVectorSize(http_body
.elements
, obj
);
401 for (size_t i
= 0; i
< http_body
.elements
.size(); ++i
) {
402 const ExplodedHttpBodyElement
& element
= http_body
.elements
[i
];
403 WriteInteger(element
.type
, obj
);
404 if (element
.type
== blink::WebHTTPBody::Element::TypeData
) {
405 WriteData(element
.data
.data(), static_cast<int>(element
.data
.size()),
407 } else if (element
.type
== blink::WebHTTPBody::Element::TypeFile
) {
408 WriteString(element
.file_path
, obj
);
409 WriteInteger64(element
.file_start
, obj
);
410 WriteInteger64(element
.file_length
, obj
);
411 WriteReal(element
.file_modification_time
, obj
);
412 } else if (element
.type
==
413 blink::WebHTTPBody::Element::TypeFileSystemURL
) {
414 WriteGURL(element
.filesystem_url
, obj
);
415 WriteInteger64(element
.file_start
, obj
);
416 WriteInteger64(element
.file_length
, obj
);
417 WriteReal(element
.file_modification_time
, obj
);
419 DCHECK(element
.type
== blink::WebHTTPBody::Element::TypeBlob
);
420 WriteStdString(element
.blob_uuid
, obj
);
423 WriteInteger64(http_body
.identifier
, obj
);
424 WriteBoolean(http_body
.contains_passwords
, obj
);
427 void ReadHttpBody(SerializeObject
* obj
, ExplodedHttpBody
* http_body
) {
428 // An initial boolean indicates if we have an HTTP body.
429 if (!ReadBoolean(obj
))
431 http_body
->is_null
= false;
433 int num_elements
= ReadInteger(obj
);
435 for (int i
= 0; i
< num_elements
; ++i
) {
436 int type
= ReadInteger(obj
);
437 if (type
== blink::WebHTTPBody::Element::TypeData
) {
440 ReadData(obj
, &data
, &length
);
442 AppendDataToHttpBody(http_body
, static_cast<const char*>(data
),
445 } else if (type
== blink::WebHTTPBody::Element::TypeFile
) {
446 base::NullableString16 file_path
= ReadString(obj
);
447 int64 file_start
= ReadInteger64(obj
);
448 int64 file_length
= ReadInteger64(obj
);
449 double file_modification_time
= ReadReal(obj
);
450 AppendFileRangeToHttpBody(http_body
, file_path
, file_start
, file_length
,
451 file_modification_time
);
452 } else if (type
== blink::WebHTTPBody::Element::TypeFileSystemURL
) {
453 GURL url
= ReadGURL(obj
);
454 int64 file_start
= ReadInteger64(obj
);
455 int64 file_length
= ReadInteger64(obj
);
456 double file_modification_time
= ReadReal(obj
);
457 AppendURLRangeToHttpBody(http_body
, url
, file_start
, file_length
,
458 file_modification_time
);
459 } else if (type
== blink::WebHTTPBody::Element::TypeBlob
) {
460 if (obj
->version
>= 16) {
461 std::string blob_uuid
= ReadStdString(obj
);
462 AppendBlobToHttpBody(http_body
, blob_uuid
);
464 ReadGURL(obj
); // Skip the obsolete blob url value.
468 http_body
->identifier
= ReadInteger64(obj
);
470 if (obj
->version
>= 12)
471 http_body
->contains_passwords
= ReadBoolean(obj
);
474 // Writes the ExplodedFrameState data into the SerializeObject object for
476 void WriteFrameState(
477 const ExplodedFrameState
& state
, SerializeObject
* obj
, bool is_top
) {
478 // WARNING: This data may be persisted for later use. As such, care must be
479 // taken when changing the serialized format. If a new field needs to be
480 // written, only adding at the end will make it easier to deal with loading
481 // older versions. Similarly, this should NOT save fields with sensitive
482 // data, such as password fields.
484 WriteString(state
.url_string
, obj
);
485 WriteString(state
.target
, obj
);
486 WriteInteger(state
.scroll_offset
.x(), obj
);
487 WriteInteger(state
.scroll_offset
.y(), obj
);
488 WriteString(state
.referrer
, obj
);
490 WriteStringVector(state
.document_state
, obj
);
492 WriteReal(state
.page_scale_factor
, obj
);
493 WriteInteger64(state
.item_sequence_number
, obj
);
494 WriteInteger64(state
.document_sequence_number
, obj
);
495 WriteInteger64(state
.frame_sequence_number
, obj
);
496 WriteInteger(state
.referrer_policy
, obj
);
497 WriteReal(state
.pinch_viewport_scroll_offset
.x(), obj
);
498 WriteReal(state
.pinch_viewport_scroll_offset
.y(), obj
);
500 bool has_state_object
= !state
.state_object
.is_null();
501 WriteBoolean(has_state_object
, obj
);
502 if (has_state_object
)
503 WriteString(state
.state_object
, obj
);
505 WriteHttpBody(state
.http_body
, obj
);
507 // NOTE: It is a quirk of the format that we still have to write the
508 // http_content_type field when the HTTP body is null. That's why this code
509 // is here instead of inside WriteHttpBody.
510 WriteString(state
.http_body
.http_content_type
, obj
);
513 const std::vector
<ExplodedFrameState
>& children
= state
.children
;
514 WriteAndValidateVectorSize(children
, obj
);
515 for (size_t i
= 0; i
< children
.size(); ++i
)
516 WriteFrameState(children
[i
], obj
, false);
519 void ReadFrameState(SerializeObject
* obj
, bool is_top
,
520 ExplodedFrameState
* state
) {
521 if (obj
->version
< 14 && !is_top
)
522 ReadInteger(obj
); // Skip over redundant version field.
524 state
->url_string
= ReadString(obj
);
526 if (obj
->version
< 19)
527 ReadString(obj
); // Skip obsolete original url string field.
529 state
->target
= ReadString(obj
);
530 if (obj
->version
< 15) {
531 ReadString(obj
); // Skip obsolete parent field.
532 ReadString(obj
); // Skip obsolete title field.
533 ReadString(obj
); // Skip obsolete alternate title field.
534 ReadReal(obj
); // Skip obsolete visited time field.
537 int x
= ReadInteger(obj
);
538 int y
= ReadInteger(obj
);
539 state
->scroll_offset
= gfx::Point(x
, y
);
541 if (obj
->version
< 15) {
542 ReadBoolean(obj
); // Skip obsolete target item flag.
543 ReadInteger(obj
); // Skip obsolete visit count field.
545 state
->referrer
= ReadString(obj
);
547 ReadStringVector(obj
, &state
->document_state
);
549 state
->page_scale_factor
= ReadReal(obj
);
550 state
->item_sequence_number
= ReadInteger64(obj
);
551 state
->document_sequence_number
= ReadInteger64(obj
);
552 if (obj
->version
>= 21)
553 state
->frame_sequence_number
= ReadInteger64(obj
);
555 if (obj
->version
>= 17 && obj
->version
< 19)
556 ReadInteger64(obj
); // Skip obsolete target frame id number.
558 if (obj
->version
>= 18) {
559 state
->referrer_policy
=
560 static_cast<blink::WebReferrerPolicy
>(ReadInteger(obj
));
563 if (obj
->version
>= 20) {
564 double x
= ReadReal(obj
);
565 double y
= ReadReal(obj
);
566 state
->pinch_viewport_scroll_offset
= gfx::PointF(x
, y
);
568 state
->pinch_viewport_scroll_offset
= gfx::PointF(-1, -1);
571 bool has_state_object
= ReadBoolean(obj
);
572 if (has_state_object
)
573 state
->state_object
= ReadString(obj
);
575 ReadHttpBody(obj
, &state
->http_body
);
577 // NOTE: It is a quirk of the format that we still have to read the
578 // http_content_type field when the HTTP body is null. That's why this code
579 // is here instead of inside ReadHttpBody.
580 state
->http_body
.http_content_type
= ReadString(obj
);
582 if (obj
->version
< 14)
583 ReadString(obj
); // Skip unused referrer string.
585 #if defined(OS_ANDROID)
586 if (obj
->version
== 11) {
587 // Now-unused values that shipped in this version of Chrome for Android when
588 // it was on a private branch.
592 // In this version, page_scale_factor included device_scale_factor and
593 // scroll offsets were premultiplied by pageScaleFactor.
594 if (state
->page_scale_factor
) {
595 float device_scale_factor
= g_device_scale_factor_for_testing
;
596 if (!device_scale_factor
) {
597 device_scale_factor
=
598 gfx::Screen::GetNativeScreen()->GetPrimaryDisplay().
599 device_scale_factor();
601 state
->scroll_offset
=
602 gfx::Point(state
->scroll_offset
.x() / state
->page_scale_factor
,
603 state
->scroll_offset
.y() / state
->page_scale_factor
);
604 state
->page_scale_factor
/= device_scale_factor
;
610 size_t num_children
=
611 ReadAndValidateVectorSize(obj
, sizeof(ExplodedFrameState
));
612 state
->children
.resize(num_children
);
613 for (size_t i
= 0; i
< num_children
; ++i
)
614 ReadFrameState(obj
, false, &state
->children
[i
]);
617 void WritePageState(const ExplodedPageState
& state
, SerializeObject
* obj
) {
618 WriteInteger(obj
->version
, obj
);
619 WriteStringVector(state
.referenced_files
, obj
);
620 WriteFrameState(state
.top
, obj
, true);
623 void ReadPageState(SerializeObject
* obj
, ExplodedPageState
* state
) {
624 obj
->version
= ReadInteger(obj
);
626 if (obj
->version
== -1) {
627 GURL url
= ReadGURL(obj
);
628 // NOTE: GURL::possibly_invalid_spec() always returns valid UTF-8.
629 state
->top
.url_string
=
630 base::NullableString16(
631 base::UTF8ToUTF16(url
.possibly_invalid_spec()), false);
635 if (obj
->version
> kCurrentVersion
|| obj
->version
< kMinVersion
) {
636 obj
->parse_error
= true;
640 if (obj
->version
>= 14)
641 ReadStringVector(obj
, &state
->referenced_files
);
643 ReadFrameState(obj
, true, &state
->top
);
645 if (obj
->version
< 14)
646 RecursivelyAppendReferencedFiles(state
->top
, &state
->referenced_files
);
649 state
->referenced_files
.erase(
650 std::unique(state
->referenced_files
.begin(),
651 state
->referenced_files
.end()),
652 state
->referenced_files
.end());
657 ExplodedHttpBodyElement::ExplodedHttpBodyElement()
658 : type(blink::WebHTTPBody::Element::TypeData
),
661 file_modification_time(std::numeric_limits
<double>::quiet_NaN()) {
664 ExplodedHttpBodyElement::~ExplodedHttpBodyElement() {
667 ExplodedHttpBody::ExplodedHttpBody()
669 contains_passwords(false),
673 ExplodedHttpBody::~ExplodedHttpBody() {
676 ExplodedFrameState::ExplodedFrameState()
677 : item_sequence_number(0),
678 document_sequence_number(0),
679 frame_sequence_number(0),
680 page_scale_factor(0.0),
681 referrer_policy(blink::WebReferrerPolicyDefault
) {
684 ExplodedFrameState::ExplodedFrameState(const ExplodedFrameState
& other
) {
688 ExplodedFrameState::~ExplodedFrameState() {
691 void ExplodedFrameState::operator=(const ExplodedFrameState
& other
) {
696 void ExplodedFrameState::assign(const ExplodedFrameState
& other
) {
697 url_string
= other
.url_string
;
698 referrer
= other
.referrer
;
699 target
= other
.target
;
700 state_object
= other
.state_object
;
701 document_state
= other
.document_state
;
702 pinch_viewport_scroll_offset
= other
.pinch_viewport_scroll_offset
;
703 scroll_offset
= other
.scroll_offset
;
704 item_sequence_number
= other
.item_sequence_number
;
705 document_sequence_number
= other
.document_sequence_number
;
706 frame_sequence_number
= other
.frame_sequence_number
;
707 page_scale_factor
= other
.page_scale_factor
;
708 referrer_policy
= other
.referrer_policy
;
709 http_body
= other
.http_body
;
710 children
= other
.children
;
713 ExplodedPageState::ExplodedPageState() {
716 ExplodedPageState::~ExplodedPageState() {
719 bool DecodePageState(const std::string
& encoded
, ExplodedPageState
* exploded
) {
720 *exploded
= ExplodedPageState();
725 SerializeObject
obj(encoded
.data(), static_cast<int>(encoded
.size()));
726 ReadPageState(&obj
, exploded
);
727 return !obj
.parse_error
;
730 bool EncodePageState(const ExplodedPageState
& exploded
, std::string
* encoded
) {
732 obj
.version
= kCurrentVersion
;
733 WritePageState(exploded
, &obj
);
734 *encoded
= obj
.GetAsString();
738 #if defined(OS_ANDROID)
739 bool DecodePageStateWithDeviceScaleFactorForTesting(
740 const std::string
& encoded
,
741 float device_scale_factor
,
742 ExplodedPageState
* exploded
) {
743 g_device_scale_factor_for_testing
= device_scale_factor
;
744 bool rv
= DecodePageState(encoded
, exploded
);
745 g_device_scale_factor_for_testing
= 0.0;
750 } // namespace content