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

Side by Side Diff: remoting/client/plugin/chromoting_instance.cc

Issue 7355011: Modify Chromoting logging to hook into base logging. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: time Created 9 years, 4 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) 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 "remoting/client/plugin/chromoting_instance.h" 5 #include "remoting/client/plugin/chromoting_instance.h"
6 6
7 #include <string> 7 #include <string>
8 #include <vector> 8 #include <vector>
9 9
10 #include "base/bind.h" 10 #include "base/bind.h"
11 #include "base/logging.h" 11 #include "base/logging.h"
12 #include "base/message_loop.h" 12 #include "base/message_loop.h"
13 #include "base/stringprintf.h" 13 #include "base/stringprintf.h"
14 #include "base/synchronization/waitable_event.h" 14 #include "base/synchronization/waitable_event.h"
15 #include "base/task.h" 15 #include "base/task.h"
16 #include "base/threading/thread.h" 16 #include "base/threading/thread.h"
17 // TODO(sergeyu): We should not depend on renderer here. Instead P2P 17 // TODO(sergeyu): We should not depend on renderer here. Instead P2P
18 // Pepper API should be used. Remove this dependency. 18 // Pepper API should be used. Remove this dependency.
19 // crbug.com/74951 19 // crbug.com/74951
20 #include "content/renderer/p2p/ipc_network_manager.h" 20 #include "content/renderer/p2p/ipc_network_manager.h"
21 #include "content/renderer/p2p/ipc_socket_factory.h" 21 #include "content/renderer/p2p/ipc_socket_factory.h"
22 #include "ppapi/cpp/completion_callback.h" 22 #include "ppapi/cpp/completion_callback.h"
23 #include "ppapi/cpp/input_event.h" 23 #include "ppapi/cpp/input_event.h"
24 #include "ppapi/cpp/rect.h" 24 #include "ppapi/cpp/rect.h"
25 // TODO(wez): Remove this when crbug.com/86353 is complete. 25 // TODO(wez): Remove this when crbug.com/86353 is complete.
26 #include "ppapi/cpp/private/var_private.h" 26 #include "ppapi/cpp/private/var_private.h"
27 #include "remoting/base/task_thread_proxy.h"
28 #include "remoting/base/util.h"
27 #include "remoting/client/client_config.h" 29 #include "remoting/client/client_config.h"
28 #include "remoting/client/chromoting_client.h" 30 #include "remoting/client/chromoting_client.h"
29 #include "remoting/client/rectangle_update_decoder.h" 31 #include "remoting/client/rectangle_update_decoder.h"
30 #include "remoting/client/plugin/chromoting_scriptable_object.h" 32 #include "remoting/client/plugin/chromoting_scriptable_object.h"
31 #include "remoting/client/plugin/pepper_client_logger.h"
32 #include "remoting/client/plugin/pepper_input_handler.h" 33 #include "remoting/client/plugin/pepper_input_handler.h"
33 #include "remoting/client/plugin/pepper_port_allocator_session.h" 34 #include "remoting/client/plugin/pepper_port_allocator_session.h"
34 #include "remoting/client/plugin/pepper_view.h" 35 #include "remoting/client/plugin/pepper_view.h"
35 #include "remoting/client/plugin/pepper_view_proxy.h" 36 #include "remoting/client/plugin/pepper_view_proxy.h"
36 #include "remoting/client/plugin/pepper_util.h" 37 #include "remoting/client/plugin/pepper_util.h"
37 #include "remoting/client/plugin/pepper_xmpp_proxy.h" 38 #include "remoting/client/plugin/pepper_xmpp_proxy.h"
38 #include "remoting/jingle_glue/jingle_thread.h" 39 #include "remoting/jingle_glue/jingle_thread.h"
39 #include "remoting/proto/auth.pb.h" 40 #include "remoting/proto/auth.pb.h"
40 #include "remoting/protocol/connection_to_host.h" 41 #include "remoting/protocol/connection_to_host.h"
41 #include "remoting/protocol/host_stub.h" 42 #include "remoting/protocol/host_stub.h"
42 // TODO(sergeyu): This is a hack: plugin should not depend on webkit 43 // TODO(sergeyu): This is a hack: plugin should not depend on webkit
43 // glue. It is used here to get P2PPacketDispatcher corresponding to 44 // glue. It is used here to get P2PPacketDispatcher corresponding to
44 // the current RenderView. Use P2P Pepper API for connection and 45 // the current RenderView. Use P2P Pepper API for connection and
45 // remove these includes. 46 // remove these includes.
46 // crbug.com/74951 47 // crbug.com/74951
47 #include "webkit/plugins/ppapi/ppapi_plugin_instance.h" 48 #include "webkit/plugins/ppapi/ppapi_plugin_instance.h"
48 #include "webkit/plugins/ppapi/resource_tracker.h" 49 #include "webkit/plugins/ppapi/resource_tracker.h"
49 50
50 namespace remoting { 51 namespace remoting {
51 52
53 // This flag blocks LOGs to the UI if we're already in the middle of logging
54 // to the UI. This prevents a potential infinite loop if we encounter an error
55 // while sending the log message to the UI.
56 static bool g_logging_to_plugin = false;
57 static ChromotingInstance* g_logging_instance = NULL;
58 static logging::LogMessageHandlerFunction g_logging_old_handler = NULL;
59
52 const char* ChromotingInstance::kMimeType = "pepper-application/x-chromoting"; 60 const char* ChromotingInstance::kMimeType = "pepper-application/x-chromoting";
53 61
54 ChromotingInstance::ChromotingInstance(PP_Instance pp_instance) 62 ChromotingInstance::ChromotingInstance(PP_Instance pp_instance)
55 : pp::InstancePrivate(pp_instance), 63 : pp::InstancePrivate(pp_instance),
56 initialized_(false), 64 initialized_(false),
57 scale_to_fit_(false), 65 scale_to_fit_(false) {
58 logger_(this) {
59 RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE | PP_INPUTEVENT_CLASS_WHEEL); 66 RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE | PP_INPUTEVENT_CLASS_WHEEL);
60 RequestFilteringInputEvents(PP_INPUTEVENT_CLASS_KEYBOARD); 67 RequestFilteringInputEvents(PP_INPUTEVENT_CLASS_KEYBOARD);
68
69 log_proxy_ = new TaskThreadProxy(MessageLoop::current());
70
71 // Set up log message handler.
72 // Note that this approach doesn't quite support having multiple instances
73 // of Chromoting running. In that case, the most recently opened tab will
74 // grab all the debug log messages, and when any Chromoting tab is closed
75 // the logging handler will go away.
76 // Since having multiple Chromoting tabs is not a primary use case, and this
77 // is just debug logging, we're punting improving debug log support for that
78 // case.
79 if (g_logging_old_handler == NULL)
80 g_logging_old_handler = logging::GetLogMessageHandler();
81 logging::SetLogMessageHandler(&LogToUI);
82 g_logging_instance = this;
61 } 83 }
62 84
63 ChromotingInstance::~ChromotingInstance() { 85 ChromotingInstance::~ChromotingInstance() {
64 DCHECK(CurrentlyOnPluginThread()); 86 DCHECK(CurrentlyOnPluginThread());
65 87
88 // Detach the log proxy so we don't log anything else to the UI.
89 log_proxy_->Detach();
90
91 // Remove log message handler.
92 logging::SetLogMessageHandler(g_logging_old_handler);
93 g_logging_old_handler = NULL;
94 g_logging_instance = NULL;
95
66 if (client_.get()) { 96 if (client_.get()) {
67 base::WaitableEvent done_event(true, false); 97 base::WaitableEvent done_event(true, false);
68 client_->Stop(base::Bind(&base::WaitableEvent::Signal, 98 client_->Stop(base::Bind(&base::WaitableEvent::Signal,
69 base::Unretained(&done_event))); 99 base::Unretained(&done_event)));
70 done_event.Wait(); 100 done_event.Wait();
71 } 101 }
72 102
73 // Stopping the context shutdown all chromoting threads. This is a requirement 103 // Stopping the context shutdown all chromoting threads. This is a requirement
74 // before we can call Detach() on |view_proxy_|. 104 // before we can call Detach() on |view_proxy_|.
75 context_.Stop(); 105 context_.Stop();
76 106
77 view_proxy_->Detach(); 107 view_proxy_->Detach();
78 } 108 }
79 109
80 bool ChromotingInstance::Init(uint32_t argc, 110 bool ChromotingInstance::Init(uint32_t argc,
81 const char* argn[], 111 const char* argn[],
82 const char* argv[]) { 112 const char* argv[]) {
83 CHECK(!initialized_); 113 CHECK(!initialized_);
84 initialized_ = true; 114 initialized_ = true;
85 115
86 logger_.VLog(1, "Started ChromotingInstance::Init"); 116 VLOG(1) << "Started ChromotingInstance::Init";
87 117
88 // Start all the threads. 118 // Start all the threads.
89 context_.Start(); 119 context_.Start();
90 120
91 webkit::ppapi::PluginInstance* plugin_instance = 121 webkit::ppapi::PluginInstance* plugin_instance =
92 webkit::ppapi::ResourceTracker::Get()->GetInstance(pp_instance()); 122 webkit::ppapi::ResourceTracker::Get()->GetInstance(pp_instance());
93 123
94 P2PSocketDispatcher* socket_dispatcher = 124 P2PSocketDispatcher* socket_dispatcher =
95 plugin_instance->delegate()->GetP2PSocketDispatcher(); 125 plugin_instance->delegate()->GetP2PSocketDispatcher();
96 IpcNetworkManager* network_manager = NULL; 126 IpcNetworkManager* network_manager = NULL;
97 IpcPacketSocketFactory* socket_factory = NULL; 127 IpcPacketSocketFactory* socket_factory = NULL;
98 PortAllocatorSessionFactory* session_factory = 128 PortAllocatorSessionFactory* session_factory =
99 CreatePepperPortAllocatorSessionFactory(this); 129 CreatePepperPortAllocatorSessionFactory(this);
100 130
101 // If we don't have socket dispatcher for IPC (e.g. P2P API is 131 // If we don't have socket dispatcher for IPC (e.g. P2P API is
102 // disabled), then JingleSessionManager will try to use physical sockets. 132 // disabled), then JingleSessionManager will try to use physical sockets.
103 if (socket_dispatcher) { 133 if (socket_dispatcher) {
104 logger_.VLog(1, "Creating IpcNetworkManager and IpcPacketSocketFactory."); 134 VLOG(1) << "Creating IpcNetworkManager and IpcPacketSocketFactory.";
105 network_manager = new IpcNetworkManager(socket_dispatcher); 135 network_manager = new IpcNetworkManager(socket_dispatcher);
106 socket_factory = new IpcPacketSocketFactory(socket_dispatcher); 136 socket_factory = new IpcPacketSocketFactory(socket_dispatcher);
107 } 137 }
108 138
109 // Create the chromoting objects. 139 // Create the chromoting objects.
110 // TODO(sergeyu): Use firewall traversal policy settings here. 140 // TODO(sergeyu): Use firewall traversal policy settings here.
111 host_connection_.reset(new protocol::ConnectionToHost( 141 host_connection_.reset(new protocol::ConnectionToHost(
112 context_.network_message_loop(), network_manager, socket_factory, 142 context_.network_message_loop(), network_manager, socket_factory,
113 session_factory, false)); 143 session_factory, false));
114 view_.reset(new PepperView(this, &context_)); 144 view_.reset(new PepperView(this, &context_));
115 view_proxy_ = new PepperViewProxy(this, view_.get()); 145 view_proxy_ = new PepperViewProxy(this, view_.get());
116 rectangle_decoder_ = new RectangleUpdateDecoder( 146 rectangle_decoder_ = new RectangleUpdateDecoder(
117 context_.decode_message_loop(), view_proxy_); 147 context_.decode_message_loop(), view_proxy_);
118 input_handler_.reset(new PepperInputHandler(&context_, 148 input_handler_.reset(new PepperInputHandler(&context_,
119 host_connection_.get(), 149 host_connection_.get(),
120 view_proxy_)); 150 view_proxy_));
121 151
122 // Default to a medium grey. 152 // Default to a medium grey.
123 view_->SetSolidFill(0xFFCDCDCD); 153 view_->SetSolidFill(0xFFCDCDCD);
124 154
125 return true; 155 return true;
126 } 156 }
127 157
128 void ChromotingInstance::Connect(const ClientConfig& config) { 158 void ChromotingInstance::Connect(const ClientConfig& config) {
129 DCHECK(CurrentlyOnPluginThread()); 159 DCHECK(CurrentlyOnPluginThread());
130 160
131 client_.reset(new ChromotingClient(config, &context_, host_connection_.get(), 161 client_.reset(new ChromotingClient(config, &context_, host_connection_.get(),
132 view_proxy_, rectangle_decoder_.get(), 162 view_proxy_, rectangle_decoder_.get(),
133 input_handler_.get(), &logger_, NULL)); 163 input_handler_.get(), NULL));
134 164
135 logger_.Log(logging::LOG_INFO, "Connecting."); 165 LOG(INFO) << "Connecting.";
136 166
137 // Setup the XMPP Proxy. 167 // Setup the XMPP Proxy.
138 ChromotingScriptableObject* scriptable_object = GetScriptableObject(); 168 ChromotingScriptableObject* scriptable_object = GetScriptableObject();
139 scoped_refptr<PepperXmppProxy> xmpp_proxy = 169 scoped_refptr<PepperXmppProxy> xmpp_proxy =
140 new PepperXmppProxy(scriptable_object->AsWeakPtr(), 170 new PepperXmppProxy(scriptable_object->AsWeakPtr(),
141 context_.jingle_thread()->message_loop()); 171 context_.jingle_thread()->message_loop());
142 scriptable_object->AttachXmppProxy(xmpp_proxy); 172 scriptable_object->AttachXmppProxy(xmpp_proxy);
143 173
144 // Kick off the connection. 174 // Kick off the connection.
145 client_->Start(xmpp_proxy); 175 client_->Start(xmpp_proxy);
146 176
147 logger_.Log(logging::LOG_INFO, "Connection status: Initializing"); 177 LOG(INFO) << "Connection status: Initializing";
148 GetScriptableObject()->SetConnectionInfo(STATUS_INITIALIZING, 178 GetScriptableObject()->SetConnectionInfo(STATUS_INITIALIZING,
149 QUALITY_UNKNOWN); 179 QUALITY_UNKNOWN);
150 } 180 }
151 181
152 void ChromotingInstance::Disconnect() { 182 void ChromotingInstance::Disconnect() {
153 DCHECK(CurrentlyOnPluginThread()); 183 DCHECK(CurrentlyOnPluginThread());
154 184
155 logger_.Log(logging::LOG_INFO, "Disconnecting from host."); 185 LOG(INFO) << "Disconnecting from host.";
156 if (client_.get()) { 186 if (client_.get()) {
157 // TODO(sergeyu): Should we disconnect asynchronously? 187 // TODO(sergeyu): Should we disconnect asynchronously?
158 base::WaitableEvent done_event(true, false); 188 base::WaitableEvent done_event(true, false);
159 client_->Stop(base::Bind(&base::WaitableEvent::Signal, 189 client_->Stop(base::Bind(&base::WaitableEvent::Signal,
160 base::Unretained(&done_event))); 190 base::Unretained(&done_event)));
161 done_event.Wait(); 191 done_event.Wait();
162 } 192 }
163 193
164 GetScriptableObject()->SetConnectionInfo(STATUS_CLOSED, QUALITY_UNKNOWN); 194 GetScriptableObject()->SetConnectionInfo(STATUS_CLOSED, QUALITY_UNKNOWN);
165 } 195 }
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after
212 } 242 }
213 243
214 case PP_INPUTEVENT_TYPE_CONTEXTMENU: { 244 case PP_INPUTEVENT_TYPE_CONTEXTMENU: {
215 // We need to return true here or else we'll get a local (plugin) context 245 // We need to return true here or else we'll get a local (plugin) context
216 // menu instead of the mouseup event for the right click. 246 // menu instead of the mouseup event for the right click.
217 return true; 247 return true;
218 } 248 }
219 249
220 case PP_INPUTEVENT_TYPE_KEYDOWN: { 250 case PP_INPUTEVENT_TYPE_KEYDOWN: {
221 pp::KeyboardInputEvent key = pp::KeyboardInputEvent(event); 251 pp::KeyboardInputEvent key = pp::KeyboardInputEvent(event);
222 logger_.VLog(3, "PP_INPUTEVENT_TYPE_KEYDOWN key=%d", key.GetKeyCode()); 252 VLOG(3) << "PP_INPUTEVENT_TYPE_KEYDOWN" << " key=" << key.GetKeyCode();
223 pih->HandleKeyEvent(true, key); 253 pih->HandleKeyEvent(true, key);
224 return true; 254 return true;
225 } 255 }
226 256
227 case PP_INPUTEVENT_TYPE_KEYUP: { 257 case PP_INPUTEVENT_TYPE_KEYUP: {
228 pp::KeyboardInputEvent key = pp::KeyboardInputEvent(event); 258 pp::KeyboardInputEvent key = pp::KeyboardInputEvent(event);
229 logger_.VLog(3, "PP_INPUTEVENT_TYPE_KEYUP key=%d", key.GetKeyCode()); 259 VLOG(3) << "PP_INPUTEVENT_TYPE_KEYUP" << " key=" << key.GetKeyCode();
230 pih->HandleKeyEvent(false, key); 260 pih->HandleKeyEvent(false, key);
231 return true; 261 return true;
232 } 262 }
233 263
234 case PP_INPUTEVENT_TYPE_CHAR: { 264 case PP_INPUTEVENT_TYPE_CHAR: {
235 pih->HandleCharacterEvent(pp::KeyboardInputEvent(event)); 265 pih->HandleCharacterEvent(pp::KeyboardInputEvent(event));
236 return true; 266 return true;
237 } 267 }
238 268
239 default: { 269 default: {
240 LOG(INFO) << "Unhandled input event: " << event.GetType(); 270 LOG(INFO) << "Unhandled input event: " << event.GetType();
241 break; 271 break;
242 } 272 }
243 } 273 }
244 274
245 return false; 275 return false;
246 } 276 }
247 277
248 ChromotingScriptableObject* ChromotingInstance::GetScriptableObject() { 278 ChromotingScriptableObject* ChromotingInstance::GetScriptableObject() {
249 pp::VarPrivate object = GetInstanceObject(); 279 pp::VarPrivate object = GetInstanceObject();
250 if (!object.is_undefined()) { 280 if (!object.is_undefined()) {
251 pp::deprecated::ScriptableObject* so = object.AsScriptableObject(); 281 pp::deprecated::ScriptableObject* so = object.AsScriptableObject();
252 DCHECK(so != NULL); 282 DCHECK(so != NULL);
253 return static_cast<ChromotingScriptableObject*>(so); 283 return static_cast<ChromotingScriptableObject*>(so);
254 } 284 }
255 logger_.Log(logging::LOG_ERROR, 285 LOG(ERROR) << "Unable to get ScriptableObject for Chromoting plugin.";
256 "Unable to get ScriptableObject for Chromoting plugin.");
257 return NULL; 286 return NULL;
258 } 287 }
259 288
260 void ChromotingInstance::SubmitLoginInfo(const std::string& username, 289 void ChromotingInstance::SubmitLoginInfo(const std::string& username,
261 const std::string& password) { 290 const std::string& password) {
262 if (host_connection_->state() != 291 if (host_connection_->state() !=
263 protocol::ConnectionToHost::STATE_CONNECTED) { 292 protocol::ConnectionToHost::STATE_CONNECTED) {
264 logger_.Log(logging::LOG_INFO, 293 LOG(INFO) << "Client not connected or already authenticated.";
265 "Client not connected or already authenticated.");
266 return; 294 return;
267 } 295 }
268 296
269 protocol::LocalLoginCredentials* credentials = 297 protocol::LocalLoginCredentials* credentials =
270 new protocol::LocalLoginCredentials(); 298 new protocol::LocalLoginCredentials();
271 credentials->set_type(protocol::PASSWORD); 299 credentials->set_type(protocol::PASSWORD);
272 credentials->set_username(username); 300 credentials->set_username(username);
273 credentials->set_credential(password.data(), password.length()); 301 credentials->set_credential(password.data(), password.length());
274 302
275 host_connection_->host_stub()->BeginSessionRequest( 303 host_connection_->host_stub()->BeginSessionRequest(
(...skipping 10 matching lines...) Expand all
286 scale_to_fit_ = scale_to_fit; 314 scale_to_fit_ = scale_to_fit;
287 if (scale_to_fit) { 315 if (scale_to_fit) {
288 rectangle_decoder_->SetScaleRatios(view_->GetHorizontalScaleRatio(), 316 rectangle_decoder_->SetScaleRatios(view_->GetHorizontalScaleRatio(),
289 view_->GetVerticalScaleRatio()); 317 view_->GetVerticalScaleRatio());
290 } else { 318 } else {
291 rectangle_decoder_->SetScaleRatios(1.0, 1.0); 319 rectangle_decoder_->SetScaleRatios(1.0, 1.0);
292 } 320 }
293 rectangle_decoder_->RefreshFullFrame(); 321 rectangle_decoder_->RefreshFullFrame();
294 } 322 }
295 323
296 void ChromotingInstance::Log(int severity, const char* format, ...) { 324 // static
297 va_list ap; 325 bool ChromotingInstance::LogToUI(int severity, const char* file, int line,
298 va_start(ap, format); 326 size_t message_start,
299 logger_.va_Log(severity, format, ap); 327 const std::string& str) {
300 va_end(ap); 328 if (g_logging_instance) {
329 std::string message = remoting::GetTimestampString();
330 message += (str.c_str() + message_start);
331 g_logging_instance->log_proxy_->Call(
332 base::Bind(&ChromotingInstance::ProcessLogToUI,
333 base::Unretained(g_logging_instance), message));
334 }
335
336 if (g_logging_old_handler)
337 return (g_logging_old_handler)(severity, file, line, message_start, str);
338 return false;
301 } 339 }
302 340
303 void ChromotingInstance::VLog(int verboselevel, const char* format, ...) { 341 void ChromotingInstance::ProcessLogToUI(const std::string& message) {
304 va_list ap; 342 if (!g_logging_to_plugin) {
305 va_start(ap, format); 343 ChromotingScriptableObject* cso = GetScriptableObject();
306 logger_.va_VLog(verboselevel, format, ap); 344 if (cso) {
307 va_end(ap); 345 g_logging_to_plugin = true;
346 cso->LogDebugInfo(message);
347 g_logging_to_plugin = false;
348 }
349 }
308 } 350 }
309 351
310 pp::Var ChromotingInstance::GetInstanceObject() { 352 pp::Var ChromotingInstance::GetInstanceObject() {
311 if (instance_object_.is_undefined()) { 353 if (instance_object_.is_undefined()) {
312 ChromotingScriptableObject* object = new ChromotingScriptableObject(this); 354 ChromotingScriptableObject* object = new ChromotingScriptableObject(this);
313 object->Init(); 355 object->Init();
314 356
315 // The pp::Var takes ownership of object here. 357 // The pp::Var takes ownership of object here.
316 instance_object_ = pp::VarPrivate(this, object); 358 instance_object_ = pp::VarPrivate(this, object);
317 } 359 }
318 360
319 return instance_object_; 361 return instance_object_;
320 } 362 }
321 363
322 ChromotingStats* ChromotingInstance::GetStats() { 364 ChromotingStats* ChromotingInstance::GetStats() {
323 if (!client_.get()) 365 if (!client_.get())
324 return NULL; 366 return NULL;
325 return client_->GetStats(); 367 return client_->GetStats();
326 } 368 }
327 369
328 void ChromotingInstance::ReleaseAllKeys() { 370 void ChromotingInstance::ReleaseAllKeys() {
329 input_handler_->ReleaseAllKeys(); 371 input_handler_->ReleaseAllKeys();
330 } 372 }
331 373
332 } // namespace remoting 374 } // namespace remoting
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698