Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(143)

Side by Side Diff: chrome/browser/sessions/session_types.cc

Issue 14497003: Moves TabNavigation into components/sessions and renames (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Really remove webkit_support Created 7 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "chrome/browser/sessions/session_types.h" 5 #include "chrome/browser/sessions/session_types.h"
6 6
7 #include "base/basictypes.h" 7 #include "base/basictypes.h"
8 #include "base/pickle.h"
9 #include "base/stl_util.h" 8 #include "base/stl_util.h"
10 #include "base/string_util.h"
11 #include "base/utf_string_conversions.h"
12 #include "chrome/browser/search/search.h"
13 #include "chrome/browser/sessions/session_command.h" 9 #include "chrome/browser/sessions/session_command.h"
14 #include "chrome/browser/ui/browser.h" 10 #include "chrome/browser/ui/browser.h"
15 #include "content/public/browser/favicon_status.h"
16 #include "content/public/browser/navigation_controller.h"
17 #include "content/public/browser/navigation_entry.h"
18 #include "sync/util/time.h"
19 #include "third_party/WebKit/Source/Platform/chromium/public/WebReferrerPolicy.h "
20 #include "webkit/glue/glue_serialize.h"
21 11
22 using content::NavigationEntry; 12 using sessions::SerializedNavigationEntry;
23
24 // TabNavigation --------------------------------------------------------------
25
26 TabNavigation::TabNavigation()
27 : index_(-1),
28 unique_id_(0),
29 transition_type_(content::PAGE_TRANSITION_TYPED),
30 has_post_data_(false),
31 post_id_(-1),
32 is_overriding_user_agent_(false) {}
33
34 TabNavigation::~TabNavigation() {}
35
36 // static
37 TabNavigation TabNavigation::FromNavigationEntry(
38 int index,
39 const NavigationEntry& entry) {
40 TabNavigation navigation;
41 navigation.index_ = index;
42 navigation.unique_id_ = entry.GetUniqueID();
43 navigation.referrer_ = entry.GetReferrer();
44 navigation.virtual_url_ = entry.GetVirtualURL();
45 navigation.title_ = entry.GetTitle();
46 navigation.content_state_ = entry.GetContentState();
47 navigation.transition_type_ = entry.GetTransitionType();
48 navigation.has_post_data_ = entry.GetHasPostData();
49 navigation.post_id_ = entry.GetPostID();
50 navigation.original_request_url_ = entry.GetOriginalRequestURL();
51 navigation.is_overriding_user_agent_ = entry.GetIsOverridingUserAgent();
52 navigation.timestamp_ = entry.GetTimestamp();
53 // If you want to navigate a named frame in Chrome, you will first need to
54 // add support for persisting it. It is currently only used for layout tests.
55 CHECK(entry.GetFrameToNavigate().empty());
56 navigation.search_terms_ =
57 chrome::GetSearchTermsFromNavigationEntry(&entry);
58 if (entry.GetFavicon().valid)
59 navigation.favicon_url_ = entry.GetFavicon().url;
60
61 return navigation;
62 }
63
64 TabNavigation TabNavigation::FromSyncData(
65 int index,
66 const sync_pb::TabNavigation& sync_data) {
67 TabNavigation navigation;
68 navigation.index_ = index;
69 navigation.unique_id_ = sync_data.unique_id();
70 navigation.referrer_ =
71 content::Referrer(GURL(sync_data.referrer()),
72 WebKit::WebReferrerPolicyDefault);
73 navigation.virtual_url_ = GURL(sync_data.virtual_url());
74 navigation.title_ = UTF8ToUTF16(sync_data.title());
75 navigation.content_state_ = sync_data.state();
76
77 uint32 transition = 0;
78 if (sync_data.has_page_transition()) {
79 switch (sync_data.page_transition()) {
80 case sync_pb::SyncEnums_PageTransition_LINK:
81 transition = content::PAGE_TRANSITION_LINK;
82 break;
83 case sync_pb::SyncEnums_PageTransition_TYPED:
84 transition = content::PAGE_TRANSITION_TYPED;
85 break;
86 case sync_pb::SyncEnums_PageTransition_AUTO_BOOKMARK:
87 transition = content::PAGE_TRANSITION_AUTO_BOOKMARK;
88 break;
89 case sync_pb::SyncEnums_PageTransition_AUTO_SUBFRAME:
90 transition = content::PAGE_TRANSITION_AUTO_SUBFRAME;
91 break;
92 case sync_pb::SyncEnums_PageTransition_MANUAL_SUBFRAME:
93 transition = content::PAGE_TRANSITION_MANUAL_SUBFRAME;
94 break;
95 case sync_pb::SyncEnums_PageTransition_GENERATED:
96 transition = content::PAGE_TRANSITION_GENERATED;
97 break;
98 case sync_pb::SyncEnums_PageTransition_AUTO_TOPLEVEL:
99 transition = content::PAGE_TRANSITION_AUTO_TOPLEVEL;
100 break;
101 case sync_pb::SyncEnums_PageTransition_FORM_SUBMIT:
102 transition = content::PAGE_TRANSITION_FORM_SUBMIT;
103 break;
104 case sync_pb::SyncEnums_PageTransition_RELOAD:
105 transition = content::PAGE_TRANSITION_RELOAD;
106 break;
107 case sync_pb::SyncEnums_PageTransition_KEYWORD:
108 transition = content::PAGE_TRANSITION_KEYWORD;
109 break;
110 case sync_pb::SyncEnums_PageTransition_KEYWORD_GENERATED:
111 transition =
112 content::PAGE_TRANSITION_KEYWORD_GENERATED;
113 break;
114 default:
115 transition = content::PAGE_TRANSITION_LINK;
116 break;
117 }
118 }
119
120 if (sync_data.has_redirect_type()) {
121 switch (sync_data.redirect_type()) {
122 case sync_pb::SyncEnums_PageTransitionRedirectType_CLIENT_REDIRECT:
123 transition |= content::PAGE_TRANSITION_CLIENT_REDIRECT;
124 break;
125 case sync_pb::SyncEnums_PageTransitionRedirectType_SERVER_REDIRECT:
126 transition |= content::PAGE_TRANSITION_SERVER_REDIRECT;
127 break;
128 }
129 }
130 if (sync_data.navigation_forward_back())
131 transition |= content::PAGE_TRANSITION_FORWARD_BACK;
132 if (sync_data.navigation_from_address_bar())
133 transition |= content::PAGE_TRANSITION_FROM_ADDRESS_BAR;
134 if (sync_data.navigation_home_page())
135 transition |= content::PAGE_TRANSITION_HOME_PAGE;
136 if (sync_data.navigation_chain_start())
137 transition |= content::PAGE_TRANSITION_CHAIN_START;
138 if (sync_data.navigation_chain_end())
139 transition |= content::PAGE_TRANSITION_CHAIN_END;
140
141 navigation.transition_type_ =
142 static_cast<content::PageTransition>(transition);
143
144 navigation.timestamp_ = base::Time();
145 navigation.search_terms_ = UTF8ToUTF16(sync_data.search_terms());
146 if (sync_data.has_favicon_url())
147 navigation.favicon_url_ = GURL(sync_data.favicon_url());
148
149 return navigation;
150 }
151
152 namespace {
153
154 // Helper used by TabNavigation::WriteToPickle(). It writes |str| to
155 // |pickle|, if and only if |str| fits within (|max_bytes| -
156 // |*bytes_written|). |bytes_written| is incremented to reflect the
157 // data written.
158 //
159 // TODO(akalin): Unify this with the same function in
160 // base_session_service.cc.
161 void WriteStringToPickle(Pickle* pickle,
162 int* bytes_written,
163 int max_bytes,
164 const std::string& str) {
165 int num_bytes = str.size() * sizeof(char);
166 if (*bytes_written + num_bytes < max_bytes) {
167 *bytes_written += num_bytes;
168 pickle->WriteString(str);
169 } else {
170 pickle->WriteString(std::string());
171 }
172 }
173
174 // string16 version of WriteStringToPickle.
175 //
176 // TODO(akalin): Unify this, too.
177 void WriteString16ToPickle(Pickle* pickle,
178 int* bytes_written,
179 int max_bytes,
180 const string16& str) {
181 int num_bytes = str.size() * sizeof(char16);
182 if (*bytes_written + num_bytes < max_bytes) {
183 *bytes_written += num_bytes;
184 pickle->WriteString16(str);
185 } else {
186 pickle->WriteString16(string16());
187 }
188 }
189
190 // A mask used for arbitrary boolean values needed to represent a
191 // NavigationEntry. Currently only contains HAS_POST_DATA.
192 //
193 // NOTE(akalin): We may want to just serialize |has_post_data_|
194 // directly. Other bools (|is_overriding_user_agent_|) haven't been
195 // added to this mask.
196 enum TypeMask {
197 HAS_POST_DATA = 1
198 };
199
200 } // namespace
201
202 // Pickle order:
203 //
204 // index_
205 // virtual_url_
206 // title_
207 // content_state_
208 // transition_type_
209 //
210 // Added on later:
211 //
212 // type_mask (has_post_data_)
213 // referrer_
214 // original_request_url_
215 // is_overriding_user_agent_
216 // timestamp_
217 // search_terms_
218
219 void TabNavigation::WriteToPickle(Pickle* pickle) const {
220 pickle->WriteInt(index_);
221
222 // We only allow navigations up to 63k (which should be completely
223 // reasonable). On the off chance we get one that is too big, try to
224 // keep the url.
225
226 // Bound the string data (which is variable length) to
227 // |max_state_size bytes| bytes.
228 static const size_t max_state_size =
229 std::numeric_limits<SessionCommand::size_type>::max() - 1024;
230 int bytes_written = 0;
231
232 WriteStringToPickle(pickle, &bytes_written, max_state_size,
233 virtual_url_.spec());
234
235 WriteString16ToPickle(pickle, &bytes_written, max_state_size, title_);
236
237 std::string content_state = content_state_;
238 if (has_post_data_) {
239 content_state =
240 webkit_glue::RemovePasswordDataFromHistoryState(content_state);
241 }
242 WriteStringToPickle(pickle, &bytes_written, max_state_size, content_state);
243
244 pickle->WriteInt(transition_type_);
245
246 const int type_mask = has_post_data_ ? HAS_POST_DATA : 0;
247 pickle->WriteInt(type_mask);
248
249 WriteStringToPickle(
250 pickle, &bytes_written, max_state_size,
251 referrer_.url.is_valid() ? referrer_.url.spec() : std::string());
252
253 pickle->WriteInt(referrer_.policy);
254
255 // Save info required to override the user agent.
256 WriteStringToPickle(
257 pickle, &bytes_written, max_state_size,
258 original_request_url_.is_valid() ?
259 original_request_url_.spec() : std::string());
260 pickle->WriteBool(is_overriding_user_agent_);
261 pickle->WriteInt64(timestamp_.ToInternalValue());
262
263 WriteString16ToPickle(pickle, &bytes_written, max_state_size, search_terms_);
264 }
265
266 bool TabNavigation::ReadFromPickle(PickleIterator* iterator) {
267 *this = TabNavigation();
268 std::string virtual_url_spec;
269 int transition_type_int = 0;
270 if (!iterator->ReadInt(&index_) ||
271 !iterator->ReadString(&virtual_url_spec) ||
272 !iterator->ReadString16(&title_) ||
273 !iterator->ReadString(&content_state_) ||
274 !iterator->ReadInt(&transition_type_int))
275 return false;
276 virtual_url_ = GURL(virtual_url_spec);
277 transition_type_ = static_cast<content::PageTransition>(transition_type_int);
278
279 // type_mask did not always exist in the written stream. As such, we
280 // don't fail if it can't be read.
281 int type_mask = 0;
282 bool has_type_mask = iterator->ReadInt(&type_mask);
283
284 if (has_type_mask) {
285 has_post_data_ = type_mask & HAS_POST_DATA;
286 // the "referrer" property was added after type_mask to the written
287 // stream. As such, we don't fail if it can't be read.
288 std::string referrer_spec;
289 if (!iterator->ReadString(&referrer_spec))
290 referrer_spec = std::string();
291 // The "referrer policy" property was added even later, so we fall back to
292 // the default policy if the property is not present.
293 int policy_int;
294 WebKit::WebReferrerPolicy policy;
295 if (iterator->ReadInt(&policy_int))
296 policy = static_cast<WebKit::WebReferrerPolicy>(policy_int);
297 else
298 policy = WebKit::WebReferrerPolicyDefault;
299 referrer_ = content::Referrer(GURL(referrer_spec), policy);
300
301 // If the original URL can't be found, leave it empty.
302 std::string original_request_url_spec;
303 if (!iterator->ReadString(&original_request_url_spec))
304 original_request_url_spec = std::string();
305 original_request_url_ = GURL(original_request_url_spec);
306
307 // Default to not overriding the user agent if we don't have info.
308 if (!iterator->ReadBool(&is_overriding_user_agent_))
309 is_overriding_user_agent_ = false;
310
311 int64 timestamp_internal_value = 0;
312 if (iterator->ReadInt64(&timestamp_internal_value)) {
313 timestamp_ = base::Time::FromInternalValue(timestamp_internal_value);
314 } else {
315 timestamp_ = base::Time();
316 }
317
318 // If the search terms field can't be found, leave it empty.
319 if (!iterator->ReadString16(&search_terms_))
320 search_terms_.clear();
321 }
322
323 return true;
324 }
325
326 scoped_ptr<NavigationEntry> TabNavigation::ToNavigationEntry(
327 int page_id,
328 content::BrowserContext* browser_context) const {
329 scoped_ptr<NavigationEntry> entry(
330 content::NavigationController::CreateNavigationEntry(
331 virtual_url_,
332 referrer_,
333 // Use a transition type of reload so that we don't incorrectly
334 // increase the typed count.
335 content::PAGE_TRANSITION_RELOAD,
336 false,
337 // The extra headers are not sync'ed across sessions.
338 std::string(),
339 browser_context));
340
341 entry->SetTitle(title_);
342 entry->SetContentState(content_state_);
343 entry->SetPageID(page_id);
344 entry->SetHasPostData(has_post_data_);
345 entry->SetPostID(post_id_);
346 entry->SetOriginalRequestURL(original_request_url_);
347 entry->SetIsOverridingUserAgent(is_overriding_user_agent_);
348 entry->SetTimestamp(timestamp_);
349 entry->SetExtraData(chrome::kInstantExtendedSearchTermsKey, search_terms_);
350
351 return entry.Pass();
352 }
353
354 // TODO(zea): perhaps sync state (scroll position, form entries, etc.) as well?
355 // See http://crbug.com/67068.
356 sync_pb::TabNavigation TabNavigation::ToSyncData() const {
357 sync_pb::TabNavigation sync_data;
358 sync_data.set_virtual_url(virtual_url_.spec());
359 // FIXME(zea): Support referrer policy?
360 sync_data.set_referrer(referrer_.url.spec());
361 sync_data.set_title(UTF16ToUTF8(title_));
362
363 // Page transition core.
364 COMPILE_ASSERT(content::PAGE_TRANSITION_LAST_CORE ==
365 content::PAGE_TRANSITION_KEYWORD_GENERATED,
366 PageTransitionCoreBounds);
367 switch (PageTransitionStripQualifier(transition_type_)) {
368 case content::PAGE_TRANSITION_LINK:
369 sync_data.set_page_transition(
370 sync_pb::SyncEnums_PageTransition_LINK);
371 break;
372 case content::PAGE_TRANSITION_TYPED:
373 sync_data.set_page_transition(
374 sync_pb::SyncEnums_PageTransition_TYPED);
375 break;
376 case content::PAGE_TRANSITION_AUTO_BOOKMARK:
377 sync_data.set_page_transition(
378 sync_pb::SyncEnums_PageTransition_AUTO_BOOKMARK);
379 break;
380 case content::PAGE_TRANSITION_AUTO_SUBFRAME:
381 sync_data.set_page_transition(
382 sync_pb::SyncEnums_PageTransition_AUTO_SUBFRAME);
383 break;
384 case content::PAGE_TRANSITION_MANUAL_SUBFRAME:
385 sync_data.set_page_transition(
386 sync_pb::SyncEnums_PageTransition_MANUAL_SUBFRAME);
387 break;
388 case content::PAGE_TRANSITION_GENERATED:
389 sync_data.set_page_transition(
390 sync_pb::SyncEnums_PageTransition_GENERATED);
391 break;
392 case content::PAGE_TRANSITION_AUTO_TOPLEVEL:
393 sync_data.set_page_transition(
394 sync_pb::SyncEnums_PageTransition_AUTO_TOPLEVEL);
395 break;
396 case content::PAGE_TRANSITION_FORM_SUBMIT:
397 sync_data.set_page_transition(
398 sync_pb::SyncEnums_PageTransition_FORM_SUBMIT);
399 break;
400 case content::PAGE_TRANSITION_RELOAD:
401 sync_data.set_page_transition(
402 sync_pb::SyncEnums_PageTransition_RELOAD);
403 break;
404 case content::PAGE_TRANSITION_KEYWORD:
405 sync_data.set_page_transition(
406 sync_pb::SyncEnums_PageTransition_KEYWORD);
407 break;
408 case content::PAGE_TRANSITION_KEYWORD_GENERATED:
409 sync_data.set_page_transition(
410 sync_pb::SyncEnums_PageTransition_KEYWORD_GENERATED);
411 break;
412 default:
413 NOTREACHED();
414 }
415
416 // Page transition qualifiers.
417 if (PageTransitionIsRedirect(transition_type_)) {
418 if (transition_type_ & content::PAGE_TRANSITION_CLIENT_REDIRECT) {
419 sync_data.set_redirect_type(
420 sync_pb::SyncEnums_PageTransitionRedirectType_CLIENT_REDIRECT);
421 } else if (transition_type_ & content::PAGE_TRANSITION_SERVER_REDIRECT) {
422 sync_data.set_redirect_type(
423 sync_pb::SyncEnums_PageTransitionRedirectType_SERVER_REDIRECT);
424 }
425 }
426 sync_data.set_navigation_forward_back(
427 (transition_type_ & content::PAGE_TRANSITION_FORWARD_BACK) != 0);
428 sync_data.set_navigation_from_address_bar(
429 (transition_type_ & content::PAGE_TRANSITION_FROM_ADDRESS_BAR) != 0);
430 sync_data.set_navigation_home_page(
431 (transition_type_ & content::PAGE_TRANSITION_HOME_PAGE) != 0);
432 sync_data.set_navigation_chain_start(
433 (transition_type_ & content::PAGE_TRANSITION_CHAIN_START) != 0);
434 sync_data.set_navigation_chain_end(
435 (transition_type_ & content::PAGE_TRANSITION_CHAIN_END) != 0);
436
437 sync_data.set_unique_id(unique_id_);
438 sync_data.set_timestamp_msec(syncer::TimeToProtoTime(timestamp_));
439 // The full-resolution timestamp works as a global ID.
440 sync_data.set_global_id(timestamp_.ToInternalValue());
441
442 sync_data.set_search_terms(UTF16ToUTF8(search_terms_));
443
444 if (favicon_url_.is_valid())
445 sync_data.set_favicon_url(favicon_url_.spec());
446
447 return sync_data;
448 }
449
450 // static
451 std::vector<NavigationEntry*>
452 TabNavigation::CreateNavigationEntriesFromTabNavigations(
453 const std::vector<TabNavigation>& navigations,
454 content::BrowserContext* browser_context) {
455 int page_id = 0;
456 std::vector<NavigationEntry*> entries;
457 for (std::vector<TabNavigation>::const_iterator it = navigations.begin();
458 it != navigations.end(); ++it) {
459 entries.push_back(
460 it->ToNavigationEntry(page_id, browser_context).release());
461 ++page_id;
462 }
463 return entries;
464 }
465 13
466 // SessionTab ----------------------------------------------------------------- 14 // SessionTab -----------------------------------------------------------------
467 15
468 SessionTab::SessionTab() 16 SessionTab::SessionTab()
469 : tab_visual_index(-1), 17 : tab_visual_index(-1),
470 current_navigation_index(-1), 18 current_navigation_index(-1),
471 pinned(false) { 19 pinned(false) {
472 } 20 }
473 21
474 SessionTab::~SessionTab() { 22 SessionTab::~SessionTab() {
475 } 23 }
476 24
477 void SessionTab::SetFromSyncData(const sync_pb::SessionTab& sync_data, 25 void SessionTab::SetFromSyncData(const sync_pb::SessionTab& sync_data,
478 base::Time timestamp) { 26 base::Time timestamp) {
479 window_id.set_id(sync_data.window_id()); 27 window_id.set_id(sync_data.window_id());
480 tab_id.set_id(sync_data.tab_id()); 28 tab_id.set_id(sync_data.tab_id());
481 tab_visual_index = sync_data.tab_visual_index(); 29 tab_visual_index = sync_data.tab_visual_index();
482 current_navigation_index = sync_data.current_navigation_index(); 30 current_navigation_index = sync_data.current_navigation_index();
483 pinned = sync_data.pinned(); 31 pinned = sync_data.pinned();
484 extension_app_id = sync_data.extension_app_id(); 32 extension_app_id = sync_data.extension_app_id();
485 user_agent_override.clear(); 33 user_agent_override.clear();
486 this->timestamp = timestamp; 34 this->timestamp = timestamp;
487 navigations.clear(); 35 navigations.clear();
488 for (int i = 0; i < sync_data.navigation_size(); ++i) { 36 for (int i = 0; i < sync_data.navigation_size(); ++i) {
489 navigations.push_back( 37 navigations.push_back(
490 TabNavigation::FromSyncData(i, sync_data.navigation(i))); 38 SerializedNavigationEntry::FromSyncData(i, sync_data.navigation(i)));
491 } 39 }
492 session_storage_persistent_id.clear(); 40 session_storage_persistent_id.clear();
493 } 41 }
494 42
495 sync_pb::SessionTab SessionTab::ToSyncData() const { 43 sync_pb::SessionTab SessionTab::ToSyncData() const {
496 sync_pb::SessionTab sync_data; 44 sync_pb::SessionTab sync_data;
497 sync_data.set_tab_id(tab_id.id()); 45 sync_data.set_tab_id(tab_id.id());
498 sync_data.set_window_id(window_id.id()); 46 sync_data.set_window_id(window_id.id());
499 sync_data.set_tab_visual_index(tab_visual_index); 47 sync_data.set_tab_visual_index(tab_visual_index);
500 sync_data.set_current_navigation_index(current_navigation_index); 48 sync_data.set_current_navigation_index(current_navigation_index);
501 sync_data.set_pinned(pinned); 49 sync_data.set_pinned(pinned);
502 sync_data.set_extension_app_id(extension_app_id); 50 sync_data.set_extension_app_id(extension_app_id);
503 for (std::vector<TabNavigation>::const_iterator it = navigations.begin(); 51 for (std::vector<SerializedNavigationEntry>::const_iterator
504 it != navigations.end(); ++it) { 52 it = navigations.begin(); it != navigations.end(); ++it) {
505 *sync_data.add_navigation() = it->ToSyncData(); 53 *sync_data.add_navigation() = it->ToSyncData();
506 } 54 }
507 return sync_data; 55 return sync_data;
508 } 56 }
509 57
510 // SessionWindow --------------------------------------------------------------- 58 // SessionWindow ---------------------------------------------------------------
511 59
512 SessionWindow::SessionWindow() 60 SessionWindow::SessionWindow()
513 : selected_tab_index(-1), 61 : selected_tab_index(-1),
514 type(Browser::TYPE_TABBED), 62 type(Browser::TYPE_TABBED),
515 is_constrained(true), 63 is_constrained(true),
516 show_state(ui::SHOW_STATE_DEFAULT) { 64 show_state(ui::SHOW_STATE_DEFAULT) {
517 } 65 }
518 66
519 SessionWindow::~SessionWindow() { 67 SessionWindow::~SessionWindow() {
520 STLDeleteElements(&tabs); 68 STLDeleteElements(&tabs);
521 } 69 }
OLDNEW
« no previous file with comments | « chrome/browser/sessions/session_types.h ('k') | chrome/browser/sessions/session_types_test_helper.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698