OLD | NEW |
---|---|
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 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_service.h" | 5 #include "chrome/browser/sessions/session_service.h" |
6 | 6 |
7 #include <algorithm> | 7 #include <algorithm> |
8 #include <limits> | 8 #include <limits> |
9 #include <set> | 9 #include <set> |
10 #include <vector> | 10 #include <vector> |
11 | 11 |
12 #include "base/command_line.h" | |
12 #include "base/file_util.h" | 13 #include "base/file_util.h" |
14 #include "base/memory/ref_counted.h" | |
13 #include "base/memory/scoped_vector.h" | 15 #include "base/memory/scoped_vector.h" |
14 #include "base/message_loop.h" | 16 #include "base/message_loop.h" |
15 #include "base/metrics/histogram.h" | 17 #include "base/metrics/histogram.h" |
16 #include "base/pickle.h" | 18 #include "base/pickle.h" |
17 #include "base/threading/thread.h" | 19 #include "base/threading/thread.h" |
18 #include "chrome/browser/extensions/extension_tab_helper.h" | 20 #include "chrome/browser/extensions/extension_tab_helper.h" |
21 #include "chrome/browser/net/chrome_cookie_notification_details.h" | |
19 #include "chrome/browser/prefs/session_startup_pref.h" | 22 #include "chrome/browser/prefs/session_startup_pref.h" |
20 #include "chrome/browser/profiles/profile.h" | 23 #include "chrome/browser/profiles/profile.h" |
21 #include "chrome/browser/sessions/restore_tab_helper.h" | 24 #include "chrome/browser/sessions/restore_tab_helper.h" |
22 #include "chrome/browser/sessions/session_backend.h" | 25 #include "chrome/browser/sessions/session_backend.h" |
23 #include "chrome/browser/sessions/session_command.h" | 26 #include "chrome/browser/sessions/session_command.h" |
24 #include "chrome/browser/sessions/session_restore.h" | 27 #include "chrome/browser/sessions/session_restore.h" |
25 #include "chrome/browser/sessions/session_types.h" | 28 #include "chrome/browser/sessions/session_types.h" |
26 #include "chrome/browser/tabs/tab_strip_model.h" | 29 #include "chrome/browser/tabs/tab_strip_model.h" |
27 #include "chrome/browser/ui/browser_init.h" | 30 #include "chrome/browser/ui/browser_init.h" |
28 #include "chrome/browser/ui/browser_list.h" | 31 #include "chrome/browser/ui/browser_list.h" |
29 #include "chrome/browser/ui/browser_window.h" | 32 #include "chrome/browser/ui/browser_window.h" |
30 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" | 33 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" |
31 #include "chrome/common/chrome_notification_types.h" | 34 #include "chrome/common/chrome_notification_types.h" |
35 #include "chrome/common/chrome_switches.h" | |
32 #include "chrome/common/extensions/extension.h" | 36 #include "chrome/common/extensions/extension.h" |
33 #include "content/browser/tab_contents/navigation_details.h" | 37 #include "content/browser/tab_contents/navigation_details.h" |
34 #include "content/browser/tab_contents/navigation_entry.h" | 38 #include "content/browser/tab_contents/navigation_entry.h" |
35 #include "content/browser/tab_contents/tab_contents.h" | 39 #include "content/browser/tab_contents/tab_contents.h" |
40 #include "content/public/browser/browser_thread.h" | |
36 #include "content/public/browser/notification_service.h" | 41 #include "content/public/browser/notification_service.h" |
37 #include "content/public/browser/notification_details.h" | 42 #include "content/public/browser/notification_details.h" |
43 #include "net/url_request/url_request_context.h" | |
44 #include "net/url_request/url_request_context_getter.h" | |
38 | 45 |
39 #if defined(OS_MACOSX) | 46 #if defined(OS_MACOSX) |
40 #include "chrome/browser/app_controller_cppsafe_mac.h" | 47 #include "chrome/browser/app_controller_cppsafe_mac.h" |
41 #endif | 48 #endif |
42 | 49 |
43 using base::Time; | 50 using base::Time; |
44 | 51 |
45 // Identifier for commands written to file. | 52 // Identifier for commands written to file. |
46 static const SessionCommand::id_type kCommandSetTabWindow = 0; | 53 static const SessionCommand::id_type kCommandSetTabWindow = 0; |
47 // OBSOLETE Superseded by kCommandSetWindowBounds3. | 54 // OBSOLETE Superseded by kCommandSetWindowBounds3. |
48 // static const SessionCommand::id_type kCommandSetWindowBounds = 1; | 55 // static const SessionCommand::id_type kCommandSetWindowBounds = 1; |
49 static const SessionCommand::id_type kCommandSetTabIndexInWindow = 2; | 56 static const SessionCommand::id_type kCommandSetTabIndexInWindow = 2; |
50 static const SessionCommand::id_type kCommandTabClosed = 3; | 57 static const SessionCommand::id_type kCommandTabClosed = 3; |
51 static const SessionCommand::id_type kCommandWindowClosed = 4; | 58 static const SessionCommand::id_type kCommandWindowClosed = 4; |
52 static const SessionCommand::id_type | 59 static const SessionCommand::id_type |
53 kCommandTabNavigationPathPrunedFromBack = 5; | 60 kCommandTabNavigationPathPrunedFromBack = 5; |
54 static const SessionCommand::id_type kCommandUpdateTabNavigation = 6; | 61 static const SessionCommand::id_type kCommandUpdateTabNavigation = 6; |
55 static const SessionCommand::id_type kCommandSetSelectedNavigationIndex = 7; | 62 static const SessionCommand::id_type kCommandSetSelectedNavigationIndex = 7; |
56 static const SessionCommand::id_type kCommandSetSelectedTabInIndex = 8; | 63 static const SessionCommand::id_type kCommandSetSelectedTabInIndex = 8; |
57 static const SessionCommand::id_type kCommandSetWindowType = 9; | 64 static const SessionCommand::id_type kCommandSetWindowType = 9; |
58 // OBSOLETE Superseded by kCommandSetWindowBounds3. Except for data migration. | 65 // OBSOLETE Superseded by kCommandSetWindowBounds3. Except for data migration. |
59 // static const SessionCommand::id_type kCommandSetWindowBounds2 = 10; | 66 // static const SessionCommand::id_type kCommandSetWindowBounds2 = 10; |
60 static const SessionCommand::id_type | 67 static const SessionCommand::id_type |
61 kCommandTabNavigationPathPrunedFromFront = 11; | 68 kCommandTabNavigationPathPrunedFromFront = 11; |
62 static const SessionCommand::id_type kCommandSetPinnedState = 12; | 69 static const SessionCommand::id_type kCommandSetPinnedState = 12; |
63 static const SessionCommand::id_type kCommandSetExtensionAppID = 13; | 70 static const SessionCommand::id_type kCommandSetExtensionAppID = 13; |
64 static const SessionCommand::id_type kCommandSetWindowBounds3 = 14; | 71 static const SessionCommand::id_type kCommandSetWindowBounds3 = 14; |
72 static const SessionCommand::id_type kCommandSessionCookieCreated = 15; | |
65 | 73 |
66 // Every kWritesPerReset commands triggers recreating the file. | 74 // Every kWritesPerReset commands triggers recreating the file. |
67 static const int kWritesPerReset = 250; | 75 static const int kWritesPerReset = 250; |
68 | 76 |
69 namespace { | 77 namespace { |
70 | 78 |
71 // The callback from GetLastSession is internally routed to SessionService | 79 // The callback from GetLastSession is internally routed to SessionService |
72 // first and then the caller. This is done so that the SessionWindows can be | 80 // first and then the caller. This is done so that the SessionWindows can be |
73 // recreated from the SessionCommands and the SessionWindows passed to the | 81 // recreated from the SessionCommands and the SessionWindows passed to the |
74 // caller. The following class is used for this. | 82 // caller. The following class is used for this. |
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
130 | 138 |
131 typedef IDAndIndexPayload WindowTypePayload; | 139 typedef IDAndIndexPayload WindowTypePayload; |
132 | 140 |
133 typedef IDAndIndexPayload TabNavigationPathPrunedFromFrontPayload; | 141 typedef IDAndIndexPayload TabNavigationPathPrunedFromFrontPayload; |
134 | 142 |
135 struct PinnedStatePayload { | 143 struct PinnedStatePayload { |
136 SessionID::id_type tab_id; | 144 SessionID::id_type tab_id; |
137 bool pinned_state; | 145 bool pinned_state; |
138 }; | 146 }; |
139 | 147 |
148 struct CookieListHolder : public base::RefCountedThreadSafe<CookieListHolder> { | |
149 net::CookieList cookie_list; | |
150 }; | |
151 | |
152 void RestoreCookies( | |
153 net::URLRequestContextGetter* url_request_context_getter, | |
154 CookieListHolder* cookie_list_holder) { | |
155 net::CookieMonster* cookie_monster = | |
156 url_request_context_getter->GetURLRequestContext()->cookie_store()-> | |
157 GetCookieMonster(); | |
158 cookie_monster->InitializeFrom(cookie_list_holder->cookie_list); | |
159 } | |
160 | |
140 } // namespace | 161 } // namespace |
141 | 162 |
142 // SessionService ------------------------------------------------------------- | 163 // SessionService ------------------------------------------------------------- |
143 | 164 |
144 SessionService::SessionService(Profile* profile) | 165 SessionService::SessionService(Profile* profile) |
145 : BaseSessionService(SESSION_RESTORE, profile, FilePath()), | 166 : BaseSessionService(SESSION_RESTORE, profile, FilePath()), |
146 has_open_trackable_browsers_(false), | 167 has_open_trackable_browsers_(false), |
147 move_on_new_browser_(false), | 168 move_on_new_browser_(false), |
148 save_delay_in_millis_(base::TimeDelta::FromMilliseconds(2500)), | 169 save_delay_in_millis_(base::TimeDelta::FromMilliseconds(2500)), |
149 save_delay_in_mins_(base::TimeDelta::FromMinutes(10)), | 170 save_delay_in_mins_(base::TimeDelta::FromMinutes(10)), |
(...skipping 308 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
458 RecordSessionUpdateHistogramData( | 479 RecordSessionUpdateHistogramData( |
459 chrome::NOTIFICATION_SESSION_SERVICE_SAVED, | 480 chrome::NOTIFICATION_SESSION_SERVICE_SAVED, |
460 &last_updated_save_time_); | 481 &last_updated_save_time_); |
461 content::NotificationService::current()->Notify( | 482 content::NotificationService::current()->Notify( |
462 chrome::NOTIFICATION_SESSION_SERVICE_SAVED, | 483 chrome::NOTIFICATION_SESSION_SERVICE_SAVED, |
463 content::Source<Profile>(profile()), | 484 content::Source<Profile>(profile()), |
464 content::NotificationService::NoDetails()); | 485 content::NotificationService::NoDetails()); |
465 } | 486 } |
466 } | 487 } |
467 | 488 |
489 void SessionService::StoreSessionCookie( | |
490 const net::CookieMonster::CanonicalCookie* cookie, | |
491 bool removed) { | |
492 if (!removed) { | |
493 Pickle saved_cookie; | |
494 saved_cookie.WriteString(cookie->Source()); | |
495 saved_cookie.WriteString(cookie->Name()); | |
496 saved_cookie.WriteString(cookie->Value()); | |
497 saved_cookie.WriteString(cookie->Domain()); | |
498 saved_cookie.WriteString(cookie->Path()); | |
499 saved_cookie.WriteString(cookie->MACKey()); | |
500 saved_cookie.WriteString(cookie->MACAlgorithm()); | |
501 saved_cookie.WriteInt64(cookie->CreationDate().ToInternalValue()); | |
502 saved_cookie.WriteInt64(cookie->ExpiryDate().ToInternalValue()); | |
503 saved_cookie.WriteBool(cookie->IsSecure()); | |
504 saved_cookie.WriteBool(cookie->IsHttpOnly()); | |
505 saved_cookie.WriteBool(cookie->IsPersistent()); | |
506 SessionCommand* command = new SessionCommand(kCommandSessionCookieCreated, | |
507 saved_cookie); | |
508 ScheduleCommand(command); | |
509 } | |
510 } | |
511 | |
468 void SessionService::Init() { | 512 void SessionService::Init() { |
469 // Register for the notifications we're interested in. | 513 // Register for the notifications we're interested in. |
470 registrar_.Add(this, content::NOTIFICATION_TAB_PARENTED, | 514 registrar_.Add(this, content::NOTIFICATION_TAB_PARENTED, |
471 content::NotificationService::AllSources()); | 515 content::NotificationService::AllSources()); |
472 registrar_.Add(this, content::NOTIFICATION_TAB_CLOSED, | 516 registrar_.Add(this, content::NOTIFICATION_TAB_CLOSED, |
473 content::NotificationService::AllSources()); | 517 content::NotificationService::AllSources()); |
474 registrar_.Add(this, content::NOTIFICATION_NAV_LIST_PRUNED, | 518 registrar_.Add(this, content::NOTIFICATION_NAV_LIST_PRUNED, |
475 content::NotificationService::AllSources()); | 519 content::NotificationService::AllSources()); |
476 registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_CHANGED, | 520 registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_CHANGED, |
477 content::NotificationService::AllSources()); | 521 content::NotificationService::AllSources()); |
478 registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED, | 522 registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED, |
479 content::NotificationService::AllSources()); | 523 content::NotificationService::AllSources()); |
480 registrar_.Add(this, chrome::NOTIFICATION_BROWSER_OPENED, | 524 registrar_.Add(this, chrome::NOTIFICATION_BROWSER_OPENED, |
481 content::NotificationService::AllBrowserContextsAndSources()); | 525 content::NotificationService::AllBrowserContextsAndSources()); |
482 registrar_.Add( | 526 registrar_.Add( |
483 this, chrome::NOTIFICATION_TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED, | 527 this, chrome::NOTIFICATION_TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED, |
484 content::NotificationService::AllSources()); | 528 content::NotificationService::AllSources()); |
529 registrar_.Add( | |
530 this, chrome::NOTIFICATION_COOKIE_CHANGED, | |
531 content::Source<Profile>(profile())); | |
485 } | 532 } |
486 | 533 |
487 bool SessionService::ShouldNewWindowStartSession() { | 534 bool SessionService::ShouldNewWindowStartSession() { |
488 if (!has_open_trackable_browsers_ && !BrowserInit::InProcessStartup() && | 535 if (!has_open_trackable_browsers_ && !BrowserInit::InProcessStartup() && |
489 !SessionRestore::IsRestoring() | 536 !SessionRestore::IsRestoring() |
490 #if defined(OS_MACOSX) | 537 #if defined(OS_MACOSX) |
491 // OSX has a fairly different idea of application lifetime than the | 538 // OSX has a fairly different idea of application lifetime than the |
492 // other platforms. We need to check that we aren't opening a window | 539 // other platforms. We need to check that we aren't opening a window |
493 // from the dock or the menubar. | 540 // from the dock or the menubar. |
494 && !app_controller_mac::IsOpeningNewWindow() | 541 && !app_controller_mac::IsOpeningNewWindow() |
(...skipping 148 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
643 if (extension_tab_helper->extension_app()) { | 690 if (extension_tab_helper->extension_app()) { |
644 RestoreTabHelper* helper = | 691 RestoreTabHelper* helper = |
645 extension_tab_helper->tab_contents_wrapper()->restore_tab_helper(); | 692 extension_tab_helper->tab_contents_wrapper()->restore_tab_helper(); |
646 SetTabExtensionAppID(helper->window_id(), | 693 SetTabExtensionAppID(helper->window_id(), |
647 helper->session_id(), | 694 helper->session_id(), |
648 extension_tab_helper->extension_app()->id()); | 695 extension_tab_helper->extension_app()->id()); |
649 } | 696 } |
650 break; | 697 break; |
651 } | 698 } |
652 | 699 |
700 case chrome::NOTIFICATION_COOKIE_CHANGED: { | |
701 if (CommandLine::ForCurrentProcess()->HasSwitch( | |
702 switches::kEnableRestoreSessionCookies)) { | |
703 ChromeCookieDetails* cookie_details = | |
704 content::Details<ChromeCookieDetails>(details).ptr(); | |
705 if (!cookie_details->cookie->IsPersistent()) { | |
706 StoreSessionCookie(cookie_details->cookie, cookie_details->removed); | |
707 } | |
708 } | |
709 break; | |
710 } | |
711 | |
653 default: | 712 default: |
654 NOTREACHED(); | 713 NOTREACHED(); |
655 } | 714 } |
656 } | 715 } |
657 | 716 |
658 void SessionService::SetTabExtensionAppID( | 717 void SessionService::SetTabExtensionAppID( |
659 const SessionID& window_id, | 718 const SessionID& window_id, |
660 const SessionID& tab_id, | 719 const SessionID& tab_id, |
661 const std::string& extension_app_id) { | 720 const std::string& extension_app_id) { |
662 if (!ShouldTrackChangesToWindow(window_id)) | 721 if (!ShouldTrackChangesToWindow(window_id)) |
(...skipping 279 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
942 } | 1001 } |
943 } | 1002 } |
944 | 1003 |
945 bool SessionService::CreateTabsAndWindows( | 1004 bool SessionService::CreateTabsAndWindows( |
946 const std::vector<SessionCommand*>& data, | 1005 const std::vector<SessionCommand*>& data, |
947 std::map<int, SessionTab*>* tabs, | 1006 std::map<int, SessionTab*>* tabs, |
948 std::map<int, SessionWindow*>* windows) { | 1007 std::map<int, SessionWindow*>* windows) { |
949 // If the file is corrupt (command with wrong size, or unknown command), we | 1008 // If the file is corrupt (command with wrong size, or unknown command), we |
950 // still return true and attempt to restore what we we can. | 1009 // still return true and attempt to restore what we we can. |
951 | 1010 |
1011 // Session cookies are collected here and restored all at once. | |
1012 scoped_refptr<CookieListHolder> cookie_list_holder = new CookieListHolder; | |
1013 bool restore_session_cookies = CommandLine::ForCurrentProcess()->HasSwitch( | |
1014 switches::kEnableRestoreSessionCookies); | |
1015 | |
952 for (std::vector<SessionCommand*>::const_iterator i = data.begin(); | 1016 for (std::vector<SessionCommand*>::const_iterator i = data.begin(); |
953 i != data.end(); ++i) { | 1017 i != data.end(); ++i) { |
954 const SessionCommand::id_type kCommandSetWindowBounds2 = 10; | 1018 const SessionCommand::id_type kCommandSetWindowBounds2 = 10; |
955 const SessionCommand* command = *i; | 1019 const SessionCommand* command = *i; |
956 | 1020 |
957 switch (command->id()) { | 1021 switch (command->id()) { |
958 case kCommandSetTabWindow: { | 1022 case kCommandSetTabWindow: { |
959 SessionID::id_type payload[2]; | 1023 SessionID::id_type payload[2]; |
960 if (!command->GetPayload(payload, sizeof(payload))) | 1024 if (!command->GetPayload(payload, sizeof(payload))) |
961 return true; | 1025 return true; |
(...skipping 153 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1115 std::string extension_app_id; | 1179 std::string extension_app_id; |
1116 if (!RestoreSetTabExtensionAppIDCommand( | 1180 if (!RestoreSetTabExtensionAppIDCommand( |
1117 *command, &tab_id, &extension_app_id)) { | 1181 *command, &tab_id, &extension_app_id)) { |
1118 return true; | 1182 return true; |
1119 } | 1183 } |
1120 | 1184 |
1121 GetTab(tab_id, tabs)->extension_app_id.swap(extension_app_id); | 1185 GetTab(tab_id, tabs)->extension_app_id.swap(extension_app_id); |
1122 break; | 1186 break; |
1123 } | 1187 } |
1124 | 1188 |
1189 case kCommandSessionCookieCreated: { | |
jochen (gone - plz use gerrit)
2011/11/15 10:42:14
does this restore the cookies even if you just re-
marja
2011/11/17 12:54:46
Done.
| |
1190 if (restore_session_cookies) { | |
1191 scoped_ptr<Pickle> pickle(command->PayloadAsPickle()); | |
1192 std::string source, name, value, domain, path, mac_key, mac_algorithm; | |
1193 int64 creation_date_internal, expiry_date_internal; | |
1194 bool secure, http_only, persistent; | |
1195 void* iter = NULL; | |
1196 pickle->ReadString(&iter, &source); | |
1197 pickle->ReadString(&iter, &name); | |
1198 pickle->ReadString(&iter, &value); | |
1199 pickle->ReadString(&iter, &domain); | |
1200 pickle->ReadString(&iter, &path); | |
1201 pickle->ReadString(&iter, &mac_key); | |
1202 pickle->ReadString(&iter, &mac_algorithm); | |
1203 pickle->ReadInt64(&iter, &creation_date_internal); | |
1204 pickle->ReadInt64(&iter, &expiry_date_internal); | |
1205 pickle->ReadBool(&iter, &secure); | |
1206 pickle->ReadBool(&iter, &http_only); | |
1207 pickle->ReadBool(&iter, &persistent); | |
1208 scoped_ptr<net::CookieMonster::CanonicalCookie> cc( | |
1209 net::CookieMonster::CanonicalCookie::Create( | |
1210 GURL(source), name, value, domain, path, mac_key, | |
1211 mac_algorithm, | |
1212 base::Time::FromInternalValue(creation_date_internal), | |
1213 base::Time::FromInternalValue(expiry_date_internal), | |
1214 secure, http_only, persistent)); | |
1215 cookie_list_holder->cookie_list.push_back(*cc); | |
1216 break; | |
1217 } | |
1218 } | |
1219 | |
1125 default: | 1220 default: |
1126 return true; | 1221 return true; |
1127 } | 1222 } |
1128 } | 1223 } |
1224 | |
1225 if (cookie_list_holder->cookie_list.size() > 0) { | |
1226 content::BrowserThread::PostTask( | |
1227 content::BrowserThread::IO, FROM_HERE, | |
1228 NewRunnableFunction(&RestoreCookies, | |
1229 make_scoped_refptr(profile()->GetRequestContext()), | |
1230 cookie_list_holder)); | |
1231 } | |
1232 | |
1129 return true; | 1233 return true; |
1130 } | 1234 } |
1131 | 1235 |
1132 void SessionService::BuildCommandsForTab( | 1236 void SessionService::BuildCommandsForTab( |
1133 const SessionID& window_id, | 1237 const SessionID& window_id, |
1134 TabContentsWrapper* tab, | 1238 TabContentsWrapper* tab, |
1135 int index_in_window, | 1239 int index_in_window, |
1136 bool is_pinned, | 1240 bool is_pinned, |
1137 std::vector<SessionCommand*>* commands, | 1241 std::vector<SessionCommand*>* commands, |
1138 IdToRange* tab_to_available_range) { | 1242 IdToRange* tab_to_available_range) { |
(...skipping 394 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1533 50); | 1637 50); |
1534 if (use_long_period) { | 1638 if (use_long_period) { |
1535 std::string long_name_("SessionRestore.SaveLongPeriod"); | 1639 std::string long_name_("SessionRestore.SaveLongPeriod"); |
1536 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_, | 1640 UMA_HISTOGRAM_CUSTOM_TIMES(long_name_, |
1537 delta, | 1641 delta, |
1538 save_delay_in_mins_, | 1642 save_delay_in_mins_, |
1539 save_delay_in_hrs_, | 1643 save_delay_in_hrs_, |
1540 50); | 1644 50); |
1541 } | 1645 } |
1542 } | 1646 } |
OLD | NEW |