diff options
Diffstat (limited to '')
-rw-r--r-- | linden/indra/newview/llfloatertos.cpp | 594 |
1 files changed, 310 insertions, 284 deletions
diff --git a/linden/indra/newview/llfloatertos.cpp b/linden/indra/newview/llfloatertos.cpp index 52d7b1f..2684e10 100644 --- a/linden/indra/newview/llfloatertos.cpp +++ b/linden/indra/newview/llfloatertos.cpp | |||
@@ -1,284 +1,310 @@ | |||
1 | /** | 1 | /** |
2 | * @file llfloatertos.cpp | 2 | * @file llfloatertos.cpp |
3 | * @brief Terms of Service Agreement dialog | 3 | * @brief Terms of Service Agreement dialog |
4 | * | 4 | * |
5 | * $LicenseInfo:firstyear=2003&license=viewergpl$ | 5 | * $LicenseInfo:firstyear=2003&license=viewergpl$ |
6 | * | 6 | * |
7 | * Copyright (c) 2003-2009, Linden Research, Inc. | 7 | * Copyright (c) 2003-2009, Linden Research, Inc. |
8 | * | 8 | * |
9 | * Second Life Viewer Source Code | 9 | * Second Life Viewer Source Code |
10 | * The source code in this file ("Source Code") is provided by Linden Lab | 10 | * The source code in this file ("Source Code") is provided by Linden Lab |
11 | * to you under the terms of the GNU General Public License, version 2.0 | 11 | * to you under the terms of the GNU General Public License, version 2.0 |
12 | * ("GPL"), unless you have obtained a separate licensing agreement | 12 | * ("GPL"), unless you have obtained a separate licensing agreement |
13 | * ("Other License"), formally executed by you and Linden Lab. Terms of | 13 | * ("Other License"), formally executed by you and Linden Lab. Terms of |
14 | * the GPL can be found in doc/GPL-license.txt in this distribution, or | 14 | * the GPL can be found in doc/GPL-license.txt in this distribution, or |
15 | * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 | 15 | * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 |
16 | * | 16 | * |
17 | * There are special exceptions to the terms and conditions of the GPL as | 17 | * There are special exceptions to the terms and conditions of the GPL as |
18 | * it is applied to this Source Code. View the full text of the exception | 18 | * it is applied to this Source Code. View the full text of the exception |
19 | * in the file doc/FLOSS-exception.txt in this software distribution, or | 19 | * in the file doc/FLOSS-exception.txt in this software distribution, or |
20 | * online at | 20 | * online at |
21 | * http://secondlifegrid.net/programs/open_source/licensing/flossexception | 21 | * http://secondlifegrid.net/programs/open_source/licensing/flossexception |
22 | * | 22 | * |
23 | * By copying, modifying or distributing this software, you acknowledge | 23 | * By copying, modifying or distributing this software, you acknowledge |
24 | * that you have read and understood your obligations described above, | 24 | * that you have read and understood your obligations described above, |
25 | * and agree to abide by those obligations. | 25 | * and agree to abide by those obligations. |
26 | * | 26 | * |
27 | * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO | 27 | * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO |
28 | * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, | 28 | * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, |
29 | * COMPLETENESS OR PERFORMANCE. | 29 | * COMPLETENESS OR PERFORMANCE. |
30 | * $/LicenseInfo$ | 30 | * $/LicenseInfo$ |
31 | */ | 31 | */ |
32 | 32 | ||
33 | #include "llviewerprecompiledheaders.h" | 33 | #include "llviewerprecompiledheaders.h" |
34 | 34 | ||
35 | #include "llfloatertos.h" | 35 | #include "llfloatertos.h" |
36 | 36 | ||
37 | // viewer includes | 37 | // viewer includes |
38 | #include "llagent.h" | 38 | #include "llagent.h" |
39 | #include "llappviewer.h" | 39 | #include "llappviewer.h" |
40 | #include "llstartup.h" | 40 | #include "llstartup.h" |
41 | #include "llviewerstats.h" | 41 | #include "llviewerstats.h" |
42 | #include "llviewertexteditor.h" | 42 | #include "llviewertexteditor.h" |
43 | #include "llviewerwindow.h" | 43 | #include "llviewerwindow.h" |
44 | 44 | ||
45 | // linden library includes | 45 | // linden library includes |
46 | #include "llbutton.h" | 46 | #include "llbutton.h" |
47 | #include "llhttpclient.h" | 47 | #include "llhttpclient.h" |
48 | #include "llhttpstatuscodes.h" // for HTTP_FOUND | 48 | #include "llhttpstatuscodes.h" // for HTTP_FOUND |
49 | #include "llradiogroup.h" | 49 | #include "llradiogroup.h" |
50 | #include "lltextbox.h" | 50 | #include "lltextbox.h" |
51 | #include "llui.h" | 51 | #include "llui.h" |
52 | #include "lluictrlfactory.h" | 52 | #include "lluictrlfactory.h" |
53 | #include "llvfile.h" | 53 | #include "llvfile.h" |
54 | #include "message.h" | 54 | #include "message.h" |
55 | 55 | #include "hippoGridManager.h" | |
56 | 56 | ||
57 | // static | 57 | |
58 | LLFloaterTOS* LLFloaterTOS::sInstance = NULL; | 58 | // static |
59 | 59 | LLFloaterTOS* LLFloaterTOS::sInstance = NULL; | |
60 | // static | 60 | |
61 | LLFloaterTOS* LLFloaterTOS::show(ETOSType type, const std::string & message) | 61 | // static |
62 | { | 62 | LLFloaterTOS* LLFloaterTOS::show(ETOSType type, const std::string & message) |
63 | if( !LLFloaterTOS::sInstance ) | 63 | { |
64 | { | 64 | if( !LLFloaterTOS::sInstance ) |
65 | LLFloaterTOS::sInstance = new LLFloaterTOS(type, message); | 65 | { |
66 | } | 66 | LLFloaterTOS::sInstance = new LLFloaterTOS(type, message); |
67 | 67 | } | |
68 | if (type == TOS_TOS) | 68 | |
69 | { | 69 | if (type == TOS_TOS) |
70 | LLUICtrlFactory::getInstance()->buildFloater(LLFloaterTOS::sInstance, "floater_tos.xml"); | 70 | { |
71 | } | 71 | LLUICtrlFactory::getInstance()->buildFloater(LLFloaterTOS::sInstance, "floater_tos.xml"); |
72 | else | 72 | } |
73 | { | 73 | else |
74 | LLUICtrlFactory::getInstance()->buildFloater(LLFloaterTOS::sInstance, "floater_critical.xml"); | 74 | { |
75 | } | 75 | LLUICtrlFactory::getInstance()->buildFloater(LLFloaterTOS::sInstance, "floater_critical.xml"); |
76 | 76 | } | |
77 | return LLFloaterTOS::sInstance; | 77 | |
78 | } | 78 | return LLFloaterTOS::sInstance; |
79 | 79 | } | |
80 | 80 | ||
81 | LLFloaterTOS::LLFloaterTOS(ETOSType type, const std::string & message) | 81 | |
82 | : LLModalDialog( std::string(" "), 100, 100 ), | 82 | LLFloaterTOS::LLFloaterTOS(ETOSType type, const std::string & message) |
83 | mType(type), | 83 | : LLModalDialog( std::string(" "), 100, 100 ), |
84 | mMessage(message), | 84 | mType(type), |
85 | mWebBrowserWindowId( 0 ), | 85 | mMessage(message), |
86 | mLoadCompleteCount( 0 ) | 86 | mWebBrowserWindowId( 0 ), |
87 | { | 87 | mLoadCompleteCount( 0 ) |
88 | } | 88 | { |
89 | 89 | } | |
90 | // helper class that trys to download a URL from a web site and calls a method | 90 | |
91 | // on parent class indicating if the web server is working or not | 91 | // helper class that trys to download a URL from a web site and calls a method |
92 | class LLIamHereTOS : public LLHTTPClient::Responder | 92 | // on parent class indicating if the web server is working or not |
93 | { | 93 | class LLIamHereTOS : public LLHTTPClient::Responder |
94 | private: | 94 | { |
95 | LLIamHereTOS( LLFloaterTOS* parent ) : | 95 | private: |
96 | mParent( parent ) | 96 | LLIamHereTOS( LLFloaterTOS* parent ) : |
97 | {} | 97 | mParent( parent ) |
98 | 98 | {} | |
99 | LLFloaterTOS* mParent; | 99 | |
100 | 100 | LLFloaterTOS* mParent; | |
101 | public: | 101 | |
102 | 102 | public: | |
103 | static boost::intrusive_ptr< LLIamHereTOS > build( LLFloaterTOS* parent ) | 103 | |
104 | { | 104 | static boost::intrusive_ptr< LLIamHereTOS > build( LLFloaterTOS* parent ) |
105 | return boost::intrusive_ptr< LLIamHereTOS >( new LLIamHereTOS( parent ) ); | 105 | { |
106 | }; | 106 | return boost::intrusive_ptr< LLIamHereTOS >( new LLIamHereTOS( parent ) ); |
107 | 107 | }; | |
108 | virtual void setParent( LLFloaterTOS* parentIn ) | 108 | |
109 | { | 109 | virtual void setParent( LLFloaterTOS* parentIn ) |
110 | mParent = parentIn; | 110 | { |
111 | }; | 111 | mParent = parentIn; |
112 | 112 | }; | |
113 | virtual void result( const LLSD& content ) | 113 | |
114 | { | 114 | virtual void result( const LLSD& content ) |
115 | if ( mParent ) | 115 | { |
116 | mParent->setSiteIsAlive( true ); | 116 | if ( mParent ) |
117 | }; | 117 | mParent->setSiteIsAlive( true ); |
118 | 118 | }; | |
119 | virtual void error( U32 status, const std::string& reason ) | 119 | |
120 | { | 120 | virtual void error( U32 status, const std::string& reason ) |
121 | if ( mParent ) | 121 | { |
122 | { | 122 | if ( mParent ) |
123 | // *HACK: For purposes of this alive check, 302 Found | 123 | { |
124 | // (aka Moved Temporarily) is considered alive. The web site | 124 | // *HACK: For purposes of this alive check, 302 Found |
125 | // redirects this link to a "cache busting" temporary URL. JC | 125 | // (aka Moved Temporarily) is considered alive. The web site |
126 | bool alive = (status == HTTP_FOUND); | 126 | // redirects this link to a "cache busting" temporary URL. JC |
127 | mParent->setSiteIsAlive( alive ); | 127 | bool alive = (status == HTTP_FOUND); |
128 | } | 128 | mParent->setSiteIsAlive( alive ); |
129 | }; | 129 | } |
130 | }; | 130 | }; |
131 | 131 | }; | |
132 | // this is global and not a class member to keep crud out of the header file | 132 | |
133 | namespace { | 133 | // this is global and not a class member to keep crud out of the header file |
134 | boost::intrusive_ptr< LLIamHereTOS > gResponsePtr = 0; | 134 | namespace { |
135 | }; | 135 | boost::intrusive_ptr< LLIamHereTOS > gResponsePtr = 0; |
136 | 136 | }; | |
137 | BOOL LLFloaterTOS::postBuild() | 137 | |
138 | { | 138 | BOOL LLFloaterTOS::postBuild() |
139 | childSetAction("Continue", onContinue, this); | 139 | { |
140 | childSetAction("Cancel", onCancel, this); | 140 | childSetAction("Continue", onContinue, this); |
141 | childSetCommitCallback("agree_chk", updateAgree, this); | 141 | childSetAction("Cancel", onCancel, this); |
142 | 142 | childSetCommitCallback("agree_chk", updateAgree, this); | |
143 | if ( mType != TOS_TOS ) | 143 | |
144 | { | 144 | LLCheckBoxCtrl* tos_agreement = getChild<LLCheckBoxCtrl>("agree_chk"); |
145 | llinfos << "tos_type != TOS_TOS" << llendl; | 145 | tos_agreement->setEnabled( true ); |
146 | // this displays the critical message | 146 | |
147 | LLTextEditor *editor = getChild<LLTextEditor>("tos_text"); | 147 | //Always set this so that the TOS is displayed whether the web browser pops up or not. |
148 | editor->setHandleEditKeysDirectly( TRUE ); | 148 | LLTextEditor *editor = getChild<LLTextEditor>("tos_text"); |
149 | editor->setEnabled( FALSE ); | 149 | editor->setHandleEditKeysDirectly( TRUE ); |
150 | editor->setWordWrap(TRUE); | 150 | editor->setEnabled( FALSE ); |
151 | editor->setFocus(TRUE); | 151 | editor->setWordWrap(TRUE); |
152 | // editor->setValue(LLSD(mMessage)); | 152 | editor->setFocus(TRUE); |
153 | editor->setValue(mMessage); | 153 | editor->setValue(LLSD(mMessage)); |
154 | 154 | LLWebBrowserCtrl* web_browser = getChild<LLWebBrowserCtrl>("tos_html"); | |
155 | return TRUE; | 155 | if (web_browser) |
156 | } | 156 | { |
157 | 157 | //Disable for critical messages and text messages, it is reenabled later | |
158 | // disable Agree to TOS radio button until the page has fully loaded | 158 | web_browser->setVisible( FALSE ); |
159 | LLCheckBoxCtrl* tos_agreement = getChild<LLCheckBoxCtrl>("agree_chk"); | 159 | } |
160 | tos_agreement->setEnabled( false ); | 160 | |
161 | 161 | if ( mType != TOS_TOS ) | |
162 | // hide the SL text widget if we're displaying TOS with using a browser widget. | 162 | { |
163 | LLTextEditor *editor = getChild<LLTextEditor>("tos_text"); | 163 | // this displays the critical message only |
164 | editor->setVisible(FALSE); | 164 | return TRUE; |
165 | 165 | } | |
166 | LLWebBrowserCtrl* web_browser = getChild<LLWebBrowserCtrl>("tos_html"); | 166 | bool use_web_browser = false; |
167 | if ( web_browser ) | 167 | |
168 | { | 168 | //Check to see if the message is a link to display |
169 | // start to observe it so we see navigate complete events | 169 | std::string token = "http://"; |
170 | web_browser->addObserver( this ); | 170 | std::string::size_type iIndex = mMessage.rfind(token); |
171 | 171 | //IF it has http:// in it, we use the web browser | |
172 | gResponsePtr = LLIamHereTOS::build( this ); | 172 | if(iIndex != std::string::npos && mMessage.length() >= 2) |
173 | LLHTTPClient::head( getString( "real_url" ), gResponsePtr ); | 173 | { |
174 | } | 174 | // it exists |
175 | 175 | use_web_browser = true; | |
176 | return TRUE; | 176 | } |
177 | } | 177 | else if (gHippoGridManager->getConnectedGrid()->isSecondLife()) |
178 | 178 | { | |
179 | void LLFloaterTOS::setSiteIsAlive( bool alive ) | 179 | //Its SL, use the browser for it as thats what it should do |
180 | { | 180 | use_web_browser = true; |
181 | // only do this for TOS pages | 181 | } |
182 | if ( mType == TOS_TOS ) | 182 | |
183 | { | 183 | if ( web_browser && use_web_browser) |
184 | LLWebBrowserCtrl* web_browser = getChild<LLWebBrowserCtrl>("tos_html"); | 184 | { |
185 | // if the contents of the site was retrieved | 185 | // hide the SL text widget if we're displaying TOS with using a browser widget. |
186 | if ( alive ) | 186 | LLTextEditor *editor = getChild<LLTextEditor>("tos_text"); |
187 | { | 187 | editor->setVisible( FALSE ); |
188 | if ( web_browser ) | 188 | |
189 | { | 189 | // disable Agree to TOS radio button until the page has fully loaded |
190 | // navigate to the "real" page | 190 | tos_agreement->setEnabled( false ); |
191 | web_browser->navigateTo( getString( "real_url" ) ); | 191 | |
192 | }; | 192 | // Reenable the web browser |
193 | } | 193 | web_browser->setVisible( TRUE ); |
194 | else | 194 | |
195 | { | 195 | // start to observe it so we see navigate complete events |
196 | // normally this is set when navigation to TOS page navigation completes (so you can't accept before TOS loads) | 196 | web_browser->addObserver( this ); |
197 | // but if the page is unavailable, we need to do this now | 197 | |
198 | LLCheckBoxCtrl* tos_agreement = getChild<LLCheckBoxCtrl>("agree_chk"); | 198 | gResponsePtr = LLIamHereTOS::build( this ); |
199 | tos_agreement->setEnabled( true ); | 199 | LLHTTPClient::head( getString( "real_url" ), gResponsePtr ); |
200 | }; | 200 | } |
201 | }; | 201 | |
202 | } | 202 | return TRUE; |
203 | 203 | } | |
204 | LLFloaterTOS::~LLFloaterTOS() | 204 | |
205 | { | 205 | void LLFloaterTOS::setSiteIsAlive( bool alive ) |
206 | // stop obsaerving events | 206 | { |
207 | LLWebBrowserCtrl* web_browser = getChild<LLWebBrowserCtrl>("tos_html"); | 207 | // only do this for TOS pages |
208 | if ( web_browser ) | 208 | if ( mType == TOS_TOS ) |
209 | { | 209 | { |
210 | web_browser->remObserver( this ); | 210 | LLWebBrowserCtrl* web_browser = getChild<LLWebBrowserCtrl>("tos_html"); |
211 | }; | 211 | // if the contents of the site was retrieved |
212 | 212 | if ( alive ) | |
213 | // tell the responder we're not here anymore | 213 | { |
214 | if ( gResponsePtr ) | 214 | if ( web_browser ) |
215 | gResponsePtr->setParent( 0 ); | 215 | { |
216 | 216 | // navigate to the "real" page | |
217 | LLFloaterTOS::sInstance = NULL; | 217 | web_browser->navigateTo( getString( "real_url" ) ); |
218 | } | 218 | }; |
219 | 219 | } | |
220 | // virtual | 220 | else |
221 | void LLFloaterTOS::draw() | 221 | { |
222 | { | 222 | // normally this is set when navigation to TOS page navigation completes (so you can't accept before TOS loads) |
223 | // draw children | 223 | // but if the page is unavailable, we need to do this now |
224 | LLModalDialog::draw(); | 224 | LLCheckBoxCtrl* tos_agreement = getChild<LLCheckBoxCtrl>("agree_chk"); |
225 | } | 225 | tos_agreement->setEnabled( true ); |
226 | 226 | }; | |
227 | // static | 227 | }; |
228 | void LLFloaterTOS::updateAgree(LLUICtrl*, void* userdata ) | 228 | } |
229 | { | 229 | |
230 | LLFloaterTOS* self = (LLFloaterTOS*) userdata; | 230 | LLFloaterTOS::~LLFloaterTOS() |
231 | bool agree = self->childGetValue("agree_chk").asBoolean(); | 231 | { |
232 | self->childSetEnabled("Continue", agree); | 232 | // stop obsaerving events |
233 | } | 233 | LLWebBrowserCtrl* web_browser = getChild<LLWebBrowserCtrl>("tos_html"); |
234 | 234 | if ( web_browser ) | |
235 | // static | 235 | { |
236 | void LLFloaterTOS::onContinue( void* userdata ) | 236 | web_browser->remObserver( this ); |
237 | { | 237 | }; |
238 | LLFloaterTOS* self = (LLFloaterTOS*) userdata; | 238 | |
239 | llinfos << "User agrees with TOS." << llendl; | 239 | // tell the responder we're not here anymore |
240 | if (self->mType == TOS_TOS) | 240 | if ( gResponsePtr ) |
241 | { | 241 | gResponsePtr->setParent( 0 ); |
242 | gAcceptTOS = TRUE; | 242 | |
243 | } | 243 | LLFloaterTOS::sInstance = NULL; |
244 | else | 244 | } |
245 | { | 245 | |
246 | gAcceptCriticalMessage = TRUE; | 246 | // virtual |
247 | } | 247 | void LLFloaterTOS::draw() |
248 | 248 | { | |
249 | // Testing TOS dialog | 249 | // draw children |
250 | #if ! LL_RELEASE_FOR_DOWNLOAD | 250 | LLModalDialog::draw(); |
251 | if ( LLStartUp::getStartupState() == STATE_LOGIN_WAIT ) | 251 | } |
252 | { | 252 | |
253 | LLStartUp::setStartupState( STATE_LOGIN_SHOW ); | 253 | // static |
254 | } | 254 | void LLFloaterTOS::updateAgree(LLUICtrl*, void* userdata ) |
255 | else | 255 | { |
256 | #endif | 256 | LLFloaterTOS* self = (LLFloaterTOS*) userdata; |
257 | 257 | bool agree = self->childGetValue("agree_chk").asBoolean(); | |
258 | LLStartUp::setStartupState( STATE_LOGIN_AUTH_INIT ); // Go back and finish authentication | 258 | self->childSetEnabled("Continue", agree); |
259 | self->close(); // destroys this object | 259 | } |
260 | } | 260 | |
261 | 261 | // static | |
262 | // static | 262 | void LLFloaterTOS::onContinue( void* userdata ) |
263 | void LLFloaterTOS::onCancel( void* userdata ) | 263 | { |
264 | { | 264 | LLFloaterTOS* self = (LLFloaterTOS*) userdata; |
265 | LLFloaterTOS* self = (LLFloaterTOS*) userdata; | 265 | llinfos << "User agrees with TOS." << llendl; |
266 | llinfos << "User disagrees with TOS." << llendl; | 266 | if (self->mType == TOS_TOS) |
267 | LLNotifications::instance().add("MustAgreeToLogIn", LLSD(), LLSD(), login_alert_done); | 267 | { |
268 | LLStartUp::setStartupState( STATE_LOGIN_SHOW ); | 268 | gAcceptTOS = TRUE; |
269 | self->mLoadCompleteCount = 0; // reset counter for next time we come to TOS | 269 | } |
270 | self->close(); // destroys this object | 270 | else |
271 | } | 271 | { |
272 | 272 | gAcceptCriticalMessage = TRUE; | |
273 | //virtual | 273 | } |
274 | void LLFloaterTOS::onNavigateComplete( const EventType& eventIn ) | 274 | |
275 | { | 275 | // Testing TOS dialog |
276 | // skip past the loading screen navigate complete | 276 | #if ! LL_RELEASE_FOR_DOWNLOAD |
277 | if ( ++mLoadCompleteCount == 2 ) | 277 | if ( LLStartUp::getStartupState() == STATE_LOGIN_WAIT ) |
278 | { | 278 | { |
279 | llinfos << "NAVIGATE COMPLETE" << llendl; | 279 | LLStartUp::setStartupState( STATE_LOGIN_SHOW ); |
280 | // enable Agree to TOS radio button now that page has loaded | 280 | } |
281 | LLCheckBoxCtrl * tos_agreement = getChild<LLCheckBoxCtrl>("agree_chk"); | 281 | else |
282 | tos_agreement->setEnabled( true ); | 282 | #endif |
283 | }; | 283 | |
284 | } | 284 | LLStartUp::setStartupState( STATE_LOGIN_AUTH_INIT ); // Go back and finish authentication |
285 | self->close(); // destroys this object | ||
286 | } | ||
287 | |||
288 | // static | ||
289 | void LLFloaterTOS::onCancel( void* userdata ) | ||
290 | { | ||
291 | LLFloaterTOS* self = (LLFloaterTOS*) userdata; | ||
292 | llinfos << "User disagrees with TOS." << llendl; | ||
293 | LLNotifications::instance().add("MustAgreeToLogIn", LLSD(), LLSD(), login_alert_done); | ||
294 | LLStartUp::setStartupState( STATE_LOGIN_SHOW ); | ||
295 | self->mLoadCompleteCount = 0; // reset counter for next time we come to TOS | ||
296 | self->close(); // destroys this object | ||
297 | } | ||
298 | |||
299 | //virtual | ||
300 | void LLFloaterTOS::onNavigateComplete( const EventType& eventIn ) | ||
301 | { | ||
302 | // skip past the loading screen navigate complete | ||
303 | if ( ++mLoadCompleteCount == 2 ) | ||
304 | { | ||
305 | llinfos << "NAVIGATE COMPLETE" << llendl; | ||
306 | // enable Agree to TOS radio button now that page has loaded | ||
307 | LLCheckBoxCtrl * tos_agreement = getChild<LLCheckBoxCtrl>("agree_chk"); | ||
308 | tos_agreement->setEnabled( true ); | ||
309 | }; | ||
310 | } | ||