• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
4 
5 #include "chrome/browser/devtools/devtools_targets_ui.h"
6 
7 #include "base/memory/weak_ptr.h"
8 #include "base/stl_util.h"
9 #include "base/strings/stringprintf.h"
10 #include "base/values.h"
11 #include "chrome/browser/devtools/devtools_adb_bridge.h"
12 #include "chrome/browser/devtools/devtools_target_impl.h"
13 #include "chrome/browser/devtools/port_forwarding_controller.h"
14 #include "content/public/browser/browser_child_process_observer.h"
15 #include "content/public/browser/browser_thread.h"
16 #include "content/public/browser/child_process_data.h"
17 #include "content/public/browser/notification_observer.h"
18 #include "content/public/browser/notification_registrar.h"
19 #include "content/public/browser/notification_service.h"
20 #include "content/public/browser/notification_source.h"
21 #include "content/public/browser/notification_types.h"
22 #include "content/public/browser/render_process_host.h"
23 #include "content/public/browser/render_view_host.h"
24 #include "content/public/browser/web_contents.h"
25 #include "content/public/browser/worker_service.h"
26 #include "content/public/browser/worker_service_observer.h"
27 #include "content/public/common/process_type.h"
28 #include "net/base/escape.h"
29 
30 using content::BrowserThread;
31 using content::WebContents;
32 
33 namespace {
34 
35 const char kTargetSourceField[]  = "source";
36 const char kTargetSourceRenderer[]  = "renderers";
37 const char kTargetSourceWorker[]  = "workers";
38 const char kTargetSourceAdb[]  = "adb";
39 
40 const char kTargetIdField[]  = "id";
41 const char kTargetTypeField[]  = "type";
42 const char kAttachedField[]  = "attached";
43 const char kUrlField[]  = "url";
44 const char kNameField[]  = "name";
45 const char kFaviconUrlField[] = "faviconUrl";
46 const char kDescriptionField[] = "description";
47 
48 const char kGuestList[] = "guests";
49 
50 const char kAdbModelField[] = "adbModel";
51 const char kAdbConnectedField[] = "adbConnected";
52 const char kAdbSerialField[] = "adbSerial";
53 const char kAdbPortStatus[] = "adbPortStatus";
54 const char kAdbBrowsersList[] = "browsers";
55 
56 const char kAdbBrowserNameField[] = "adbBrowserName";
57 const char kAdbBrowserVersionField[] = "adbBrowserVersion";
58 const char kAdbBrowserChromeVersionField[] = "adbBrowserChromeVersion";
59 const char kAdbPagesList[] = "pages";
60 
61 const char kAdbScreenWidthField[] = "adbScreenWidth";
62 const char kAdbScreenHeightField[] = "adbScreenHeight";
63 const char kAdbAttachedForeignField[]  = "adbAttachedForeign";
64 
65 // CancelableTimer ------------------------------------------------------------
66 
67 class CancelableTimer {
68  public:
CancelableTimer(base::Closure callback,base::TimeDelta delay)69   CancelableTimer(base::Closure callback, base::TimeDelta delay)
70       : callback_(callback),
71         weak_factory_(this) {
72     base::MessageLoop::current()->PostDelayedTask(
73         FROM_HERE,
74         base::Bind(&CancelableTimer::Fire, weak_factory_.GetWeakPtr()),
75         delay);
76   };
77 
78  private:
Fire()79   void Fire() { callback_.Run(); }
80 
81   base::Closure callback_;
82   base::WeakPtrFactory<CancelableTimer> weak_factory_;
83 };
84 
85 // RenderViewHostTargetsUIHandler ---------------------------------------------
86 
87 class RenderViewHostTargetsUIHandler
88     : public DevToolsTargetsUIHandler,
89       public content::NotificationObserver {
90  public:
91   explicit RenderViewHostTargetsUIHandler(Callback callback);
92   virtual ~RenderViewHostTargetsUIHandler();
93  private:
94   // content::NotificationObserver overrides.
95   virtual void Observe(int type,
96                        const content::NotificationSource& source,
97                        const content::NotificationDetails& details) OVERRIDE;
98 
99   void UpdateTargets();
100 
101   content::NotificationRegistrar notification_registrar_;
102   scoped_ptr<CancelableTimer> timer_;
103 };
104 
RenderViewHostTargetsUIHandler(Callback callback)105 RenderViewHostTargetsUIHandler::RenderViewHostTargetsUIHandler(
106     Callback callback)
107     : DevToolsTargetsUIHandler(kTargetSourceRenderer, callback) {
108   notification_registrar_.Add(this,
109                               content::NOTIFICATION_WEB_CONTENTS_CONNECTED,
110                               content::NotificationService::AllSources());
111   notification_registrar_.Add(this,
112                               content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
113                               content::NotificationService::AllSources());
114   notification_registrar_.Add(this,
115                               content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
116                               content::NotificationService::AllSources());
117   UpdateTargets();
118 }
119 
~RenderViewHostTargetsUIHandler()120 RenderViewHostTargetsUIHandler::~RenderViewHostTargetsUIHandler() {
121   notification_registrar_.RemoveAll();
122 }
123 
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)124 void RenderViewHostTargetsUIHandler::Observe(
125     int type,
126     const content::NotificationSource& source,
127     const content::NotificationDetails& details) {
128   const int kUpdateDelay = 100;
129   timer_.reset(
130       new CancelableTimer(
131           base::Bind(&RenderViewHostTargetsUIHandler::UpdateTargets,
132                      base::Unretained(this)),
133           base::TimeDelta::FromMilliseconds(kUpdateDelay)));
134 }
135 
UpdateTargets()136 void RenderViewHostTargetsUIHandler::UpdateTargets() {
137   scoped_ptr<ListValue> list_value(new ListValue());
138 
139   std::map<WebContents*, DictionaryValue*> web_contents_to_descriptor_;
140   std::vector<DevToolsTargetImpl*> guest_targets;
141 
142   DevToolsTargetImpl::List targets =
143       DevToolsTargetImpl::EnumerateRenderViewHostTargets();
144 
145   STLDeleteValues(&targets_);
146   for (DevToolsTargetImpl::List::iterator it = targets.begin();
147       it != targets.end(); ++it) {
148     scoped_ptr<DevToolsTargetImpl> target(*it);
149     content::RenderViewHost* rvh = target->GetRenderViewHost();
150     if (!rvh)
151       continue;
152     WebContents* web_contents = WebContents::FromRenderViewHost(rvh);
153     if (!web_contents)
154       continue;
155 
156     DevToolsTargetImpl* target_ptr = target.get();
157     targets_[target_ptr->GetId()] = target.release();
158     if (rvh->GetProcess()->IsGuest()) {
159       guest_targets.push_back(target_ptr);
160     } else {
161       DictionaryValue* descriptor = Serialize(*target_ptr);
162       list_value->Append(descriptor);
163       web_contents_to_descriptor_[web_contents] = descriptor;
164     }
165   }
166 
167   // Add the list of guest-views to each of its embedders.
168   for (std::vector<DevToolsTargetImpl*>::iterator it(guest_targets.begin());
169        it != guest_targets.end(); ++it) {
170     DevToolsTargetImpl* guest = (*it);
171     WebContents* guest_web_contents =
172         WebContents::FromRenderViewHost(guest->GetRenderViewHost());
173     WebContents* embedder = guest_web_contents->GetEmbedderWebContents();
174     if (embedder && web_contents_to_descriptor_.count(embedder) > 0) {
175       DictionaryValue* parent = web_contents_to_descriptor_[embedder];
176       ListValue* guests = NULL;
177       if (!parent->GetList(kGuestList, &guests)) {
178         guests = new ListValue();
179         parent->Set(kGuestList, guests);
180       }
181       guests->Append(Serialize(*guest));
182     }
183   }
184 
185   SendSerializedTargets(list_value.Pass());
186 }
187 
188 // WorkerObserver -------------------------------------------------------------
189 
190 class WorkerObserver
191     : public content::WorkerServiceObserver,
192       public base::RefCountedThreadSafe<WorkerObserver> {
193  public:
WorkerObserver()194   WorkerObserver() {}
195 
Start(DevToolsTargetImpl::Callback callback)196   void Start(DevToolsTargetImpl::Callback callback) {
197     DCHECK(callback_.is_null());
198     DCHECK(!callback.is_null());
199     callback_ = callback;
200     BrowserThread::PostTask(
201         BrowserThread::IO, FROM_HERE,
202         base::Bind(&WorkerObserver::StartOnIOThread, this));
203   }
204 
Stop()205   void Stop() {
206     DCHECK(!callback_.is_null());
207     callback_ = DevToolsTargetImpl::Callback();
208     BrowserThread::PostTask(
209         BrowserThread::IO, FROM_HERE,
210         base::Bind(&WorkerObserver::StopOnIOThread, this));
211   }
212 
Enumerate()213   void Enumerate() {
214     BrowserThread::PostTask(
215         BrowserThread::IO, FROM_HERE,
216         base::Bind(&WorkerObserver::EnumerateOnIOThread,
217                    this));
218   }
219 
220  private:
221   friend class base::RefCountedThreadSafe<WorkerObserver>;
~WorkerObserver()222   virtual ~WorkerObserver() {}
223 
224   // content::WorkerServiceObserver overrides:
WorkerCreated(const GURL & url,const base::string16 & name,int process_id,int route_id)225   virtual void WorkerCreated(
226       const GURL& url,
227       const base::string16& name,
228       int process_id,
229       int route_id) OVERRIDE {
230     EnumerateOnIOThread();
231   }
232 
WorkerDestroyed(int process_id,int route_id)233   virtual void WorkerDestroyed(int process_id, int route_id) OVERRIDE {
234     EnumerateOnIOThread();
235   }
236 
StartOnIOThread()237   void StartOnIOThread() {
238     content::WorkerService::GetInstance()->AddObserver(this);
239     EnumerateOnIOThread();
240   }
241 
StopOnIOThread()242   void StopOnIOThread() {
243     content::WorkerService::GetInstance()->RemoveObserver(this);
244   }
245 
EnumerateOnIOThread()246   void EnumerateOnIOThread() {
247     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
248     DevToolsTargetImpl::EnumerateWorkerTargets(
249         base::Bind(&WorkerObserver::RespondOnUIThread, this));
250   }
251 
RespondOnUIThread(const DevToolsTargetImpl::List & targets)252   void RespondOnUIThread(const DevToolsTargetImpl::List& targets) {
253     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
254     if (callback_.is_null())
255       return;
256     callback_.Run(targets);
257   }
258 
259   DevToolsTargetImpl::Callback callback_;
260 };
261 
262 // WorkerTargetsUIHandler -----------------------------------------------------
263 
264 class WorkerTargetsUIHandler
265     : public DevToolsTargetsUIHandler,
266       public content::BrowserChildProcessObserver {
267  public:
268   explicit WorkerTargetsUIHandler(Callback callback);
269   virtual ~WorkerTargetsUIHandler();
270 
271  private:
272   // content::BrowserChildProcessObserver overrides.
273   virtual void BrowserChildProcessHostConnected(
274       const content::ChildProcessData& data) OVERRIDE;
275   virtual void BrowserChildProcessHostDisconnected(
276       const content::ChildProcessData& data) OVERRIDE;
277 
278   void UpdateTargets(const DevToolsTargetImpl::List& targets);
279 
280   scoped_refptr<WorkerObserver> observer_;
281 };
282 
WorkerTargetsUIHandler(Callback callback)283 WorkerTargetsUIHandler::WorkerTargetsUIHandler(Callback callback)
284     : DevToolsTargetsUIHandler(kTargetSourceWorker, callback),
285       observer_(new WorkerObserver()) {
286   observer_->Start(base::Bind(&WorkerTargetsUIHandler::UpdateTargets,
287                               base::Unretained(this)));
288   BrowserChildProcessObserver::Add(this);
289 }
290 
~WorkerTargetsUIHandler()291 WorkerTargetsUIHandler::~WorkerTargetsUIHandler() {
292   BrowserChildProcessObserver::Remove(this);
293   observer_->Stop();
294 }
295 
BrowserChildProcessHostConnected(const content::ChildProcessData & data)296 void WorkerTargetsUIHandler::BrowserChildProcessHostConnected(
297     const content::ChildProcessData& data) {
298   if (data.process_type == content::PROCESS_TYPE_WORKER)
299     observer_->Enumerate();
300 }
301 
BrowserChildProcessHostDisconnected(const content::ChildProcessData & data)302 void WorkerTargetsUIHandler::BrowserChildProcessHostDisconnected(
303     const content::ChildProcessData& data) {
304   if (data.process_type == content::PROCESS_TYPE_WORKER)
305     observer_->Enumerate();
306 }
307 
UpdateTargets(const DevToolsTargetImpl::List & targets)308 void WorkerTargetsUIHandler::UpdateTargets(
309     const DevToolsTargetImpl::List& targets) {
310   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
311   scoped_ptr<ListValue> list_value(new ListValue());
312   STLDeleteValues(&targets_);
313   for (DevToolsTargetImpl::List::const_iterator it = targets.begin();
314       it != targets.end(); ++it) {
315     DevToolsTargetImpl* target = *it;
316     list_value->Append(Serialize(*target));
317     targets_[target->GetId()] = target;
318   }
319   SendSerializedTargets(list_value.Pass());
320 }
321 
322 // AdbTargetsUIHandler --------------------------------------------------------
323 
324 class AdbTargetsUIHandler
325     : public DevToolsRemoteTargetsUIHandler,
326       public DevToolsAdbBridge::Listener {
327  public:
328   AdbTargetsUIHandler(Callback callback, Profile* profile);
329   virtual ~AdbTargetsUIHandler();
330 
331   virtual void Open(const std::string& browser_id,
332                     const std::string& url) OVERRIDE;
333 
334  private:
335   // DevToolsAdbBridge::Listener overrides.
336   virtual void RemoteDevicesChanged(
337       DevToolsAdbBridge::RemoteDevices* devices) OVERRIDE;
338 
339   Profile* profile_;
340 
341   typedef std::map<std::string,
342       scoped_refptr<DevToolsAdbBridge::RemoteBrowser> > RemoteBrowsers;
343   RemoteBrowsers remote_browsers_;
344 };
345 
AdbTargetsUIHandler(Callback callback,Profile * profile)346 AdbTargetsUIHandler::AdbTargetsUIHandler(Callback callback, Profile* profile)
347     : DevToolsRemoteTargetsUIHandler(kTargetSourceAdb, callback),
348       profile_(profile) {
349   DevToolsAdbBridge* adb_bridge =
350       DevToolsAdbBridge::Factory::GetForProfile(profile_);
351   if (adb_bridge)
352     adb_bridge->AddListener(this);
353 }
354 
~AdbTargetsUIHandler()355 AdbTargetsUIHandler::~AdbTargetsUIHandler() {
356   DevToolsAdbBridge* adb_bridge =
357       DevToolsAdbBridge::Factory::GetForProfile(profile_);
358   if (adb_bridge)
359     adb_bridge->RemoveListener(this);
360 }
361 
Open(const std::string & browser_id,const std::string & url)362 void AdbTargetsUIHandler::Open(const std::string& browser_id,
363                            const std::string& url) {
364   RemoteBrowsers::iterator it = remote_browsers_.find(browser_id);
365   if (it !=  remote_browsers_.end())
366     it->second->Open(url);
367 }
368 
RemoteDevicesChanged(DevToolsAdbBridge::RemoteDevices * devices)369 void AdbTargetsUIHandler::RemoteDevicesChanged(
370     DevToolsAdbBridge::RemoteDevices* devices) {
371   PortForwardingController* port_forwarding_controller =
372       PortForwardingController::Factory::GetForProfile(profile_);
373   PortForwardingController::DevicesStatus port_forwarding_status;
374   if (port_forwarding_controller)
375     port_forwarding_status =
376         port_forwarding_controller->UpdateDeviceList(*devices);
377 
378   remote_browsers_.clear();
379   STLDeleteValues(&targets_);
380 
381   scoped_ptr<ListValue> device_list(new ListValue());
382   for (DevToolsAdbBridge::RemoteDevices::iterator dit = devices->begin();
383        dit != devices->end(); ++dit) {
384     DevToolsAdbBridge::RemoteDevice* device = dit->get();
385     DictionaryValue* device_data = new DictionaryValue();
386     device_data->SetString(kAdbModelField, device->GetModel());
387     device_data->SetString(kAdbSerialField, device->GetSerial());
388     device_data->SetBoolean(kAdbConnectedField, device->IsConnected());
389     std::string device_id = base::StringPrintf(
390         "device:%s",
391         device->GetSerial().c_str());
392     device_data->SetString(kTargetIdField, device_id);
393     ListValue* browser_list = new ListValue();
394     device_data->Set(kAdbBrowsersList, browser_list);
395 
396     DevToolsAdbBridge::RemoteBrowsers& browsers = device->browsers();
397     for (DevToolsAdbBridge::RemoteBrowsers::iterator bit =
398         browsers.begin(); bit != browsers.end(); ++bit) {
399       DevToolsAdbBridge::RemoteBrowser* browser = bit->get();
400       DictionaryValue* browser_data = new DictionaryValue();
401       browser_data->SetString(kAdbBrowserNameField, browser->display_name());
402       browser_data->SetString(kAdbBrowserVersionField, browser->version());
403       DevToolsAdbBridge::RemoteBrowser::ParsedVersion parsed =
404           browser->GetParsedVersion();
405       browser_data->SetInteger(
406           kAdbBrowserChromeVersionField,
407           browser->IsChrome() && !parsed.empty() ? parsed[0] : 0);
408       std::string browser_id = base::StringPrintf(
409           "browser:%s:%s:%s:%s",
410           device->GetSerial().c_str(), // Ensure uniqueness across devices.
411           browser->display_name().c_str(),  // Sort by display name.
412           browser->version().c_str(),  // Then by version.
413           browser->socket().c_str());  // Ensure uniqueness on the device.
414       browser_data->SetString(kTargetIdField, browser_id);
415       browser_data->SetString(kTargetSourceField, source_id());
416       remote_browsers_[browser_id] = browser;
417       ListValue* page_list = new ListValue();
418       browser_data->Set(kAdbPagesList, page_list);
419 
420       DevToolsTargetImpl::List pages = browser->CreatePageTargets();
421       for (DevToolsTargetImpl::List::iterator it =
422           pages.begin(); it != pages.end(); ++it) {
423         DevToolsTargetImpl* target =  *it;
424         DictionaryValue* target_data = Serialize(*target);
425         target_data->SetBoolean(
426             kAdbAttachedForeignField,
427             target->IsAttached() &&
428                 !DevToolsAdbBridge::HasDevToolsWindow(target->GetId()));
429         // Pass the screen size in the target object to make sure that
430         // the caching logic does not prevent the target item from updating
431         // when the screen size changes.
432         gfx::Size screen_size = device->screen_size();
433         target_data->SetInteger(kAdbScreenWidthField, screen_size.width());
434         target_data->SetInteger(kAdbScreenHeightField, screen_size.height());
435         targets_[target->GetId()] = target;
436         page_list->Append(target_data);
437       }
438       browser_list->Append(browser_data);
439     }
440 
441     if (port_forwarding_controller) {
442       PortForwardingController::DevicesStatus::iterator sit =
443           port_forwarding_status.find(device->GetSerial());
444       if (sit != port_forwarding_status.end()) {
445         DictionaryValue* port_status_dict = new DictionaryValue();
446         typedef PortForwardingController::PortStatusMap StatusMap;
447         const StatusMap& port_status = sit->second;
448         for (StatusMap::const_iterator it = port_status.begin();
449              it != port_status.end(); ++it) {
450           port_status_dict->SetInteger(
451               base::StringPrintf("%d", it->first), it->second);
452         }
453         device_data->Set(kAdbPortStatus, port_status_dict);
454       }
455     }
456 
457     device_list->Append(device_data);
458   }
459   SendSerializedTargets(device_list.Pass());
460 }
461 
462 } // namespace
463 
464 // DevToolsTargetsUIHandler ---------------------------------------------------
465 
DevToolsTargetsUIHandler(const std::string & source_id,Callback callback)466 DevToolsTargetsUIHandler::DevToolsTargetsUIHandler(
467     const std::string& source_id,
468     Callback callback)
469     : source_id_(source_id),
470       callback_(callback) {
471 }
472 
~DevToolsTargetsUIHandler()473 DevToolsTargetsUIHandler::~DevToolsTargetsUIHandler() {
474   STLDeleteValues(&targets_);
475 }
476 
477 // static
478 scoped_ptr<DevToolsTargetsUIHandler>
CreateForRenderers(DevToolsTargetsUIHandler::Callback callback)479 DevToolsTargetsUIHandler::CreateForRenderers(
480     DevToolsTargetsUIHandler::Callback callback) {
481   return scoped_ptr<DevToolsTargetsUIHandler>(
482       new RenderViewHostTargetsUIHandler(callback));
483 }
484 
485 // static
486 scoped_ptr<DevToolsTargetsUIHandler>
CreateForWorkers(DevToolsTargetsUIHandler::Callback callback)487 DevToolsTargetsUIHandler::CreateForWorkers(
488     DevToolsTargetsUIHandler::Callback callback) {
489   return scoped_ptr<DevToolsTargetsUIHandler>(
490       new WorkerTargetsUIHandler(callback));
491 }
492 
Inspect(const std::string & target_id,Profile * profile)493 void DevToolsTargetsUIHandler::Inspect(const std::string& target_id,
494                                             Profile* profile) {
495   TargetMap::iterator it = targets_.find(target_id);
496   if (it != targets_.end())
497     it->second->Inspect(profile);
498 }
499 
Activate(const std::string & target_id)500 void DevToolsTargetsUIHandler::Activate(const std::string& target_id) {
501   TargetMap::iterator it = targets_.find(target_id);
502   if (it != targets_.end())
503     it->second->Activate();
504 }
505 
Close(const std::string & target_id)506 void DevToolsTargetsUIHandler::Close(const std::string& target_id) {
507   TargetMap::iterator it = targets_.find(target_id);
508   if (it != targets_.end())
509     it->second->Close();
510 }
511 
Reload(const std::string & target_id)512 void DevToolsTargetsUIHandler::Reload(const std::string& target_id) {
513   TargetMap::iterator it = targets_.find(target_id);
514   if (it != targets_.end())
515     it->second->Reload();
516 }
517 
518 base::DictionaryValue*
Serialize(const DevToolsTargetImpl & target)519 DevToolsTargetsUIHandler::Serialize(
520     const DevToolsTargetImpl& target) {
521   DictionaryValue* target_data = new DictionaryValue();
522   target_data->SetString(kTargetSourceField, source_id_);
523   target_data->SetString(kTargetIdField, target.GetId());
524   target_data->SetString(kTargetTypeField, target.GetType());
525   target_data->SetBoolean(kAttachedField, target.IsAttached());
526   target_data->SetString(kUrlField, target.GetUrl().spec());
527   target_data->SetString(kNameField, net::EscapeForHTML(target.GetTitle()));
528   target_data->SetString(kFaviconUrlField, target.GetFaviconUrl().spec());
529   target_data->SetString(kDescriptionField, target.GetDescription());
530   return target_data;
531 }
532 
SendSerializedTargets(scoped_ptr<ListValue> list)533 void DevToolsTargetsUIHandler::SendSerializedTargets(
534     scoped_ptr<ListValue> list) {
535   callback_.Run(source_id_, list.Pass());
536 }
537 
538 // DevToolsRemoteTargetsUIHandler ---------------------------------------------
539 
DevToolsRemoteTargetsUIHandler(const std::string & source_id,Callback callback)540 DevToolsRemoteTargetsUIHandler::DevToolsRemoteTargetsUIHandler(
541     const std::string& source_id,
542     Callback callback)
543     : DevToolsTargetsUIHandler(source_id, callback) {
544 }
545 
546 // static
547 scoped_ptr<DevToolsRemoteTargetsUIHandler>
CreateForAdb(DevToolsTargetsUIHandler::Callback callback,Profile * profile)548 DevToolsRemoteTargetsUIHandler::CreateForAdb(
549     DevToolsTargetsUIHandler::Callback callback, Profile* profile) {
550   return scoped_ptr<DevToolsRemoteTargetsUIHandler>(
551       new AdbTargetsUIHandler(callback, profile));
552 }
553