Use require('ipc').send to communicate between browser and renderer.
authorCheng Zhao <zcbenz@gmail.com>
Tue, 23 Apr 2013 04:18:07 +0000 (12:18 +0800)
committerCheng Zhao <zcbenz@gmail.com>
Tue, 23 Apr 2013 04:18:07 +0000 (12:18 +0800)
19 files changed:
atom.gyp
browser/api/atom_api_browser_ipc.cc [new file with mode: 0644]
browser/api/atom_api_browser_ipc.h [new file with mode: 0644]
browser/api/atom_browser_bindings.cc
browser/api/atom_browser_bindings.h
browser/api/lib/ipc.coffee [new file with mode: 0644]
browser/default_app/main.js
browser/native_window.cc
browser/native_window.h
common/api/api_messages.h
common/api/atom_extensions.h
renderer/api/atom_api_renderer_ipc.cc [new file with mode: 0644]
renderer/api/atom_api_renderer_ipc.h [new file with mode: 0644]
renderer/api/atom_renderer_bindings.cc
renderer/api/atom_renderer_bindings.h
renderer/api/lib/ipc.coffee [new file with mode: 0644]
renderer/api/lib/remote_object.coffee [deleted file]
renderer/atom_render_view_observer.cc
renderer/atom_render_view_observer.h

index 8182e82..832e3e5 100644 (file)
--- a/atom.gyp
+++ b/atom.gyp
@@ -7,12 +7,16 @@
     ],
     'coffee_sources': [
       'browser/api/lib/atom.coffee',
+      'browser/api/lib/ipc.coffee',
       'browser/api/lib/window.coffee',
       'browser/atom/atom.coffee',
+      'renderer/api/lib/ipc.coffee',
     ],
     'lib_sources': [
       'app/atom_main_delegate.cc',
       'app/atom_main_delegate.h',
+      'browser/api/atom_api_browser_ipc.cc',
+      'browser/api/atom_api_browser_ipc.h',
       'browser/api/atom_api_event.cc',
       'browser/api/atom_api_event.h',
       'browser/api/atom_api_event_emitter.cc',
@@ -52,6 +56,8 @@
       'common/options_switches.h',
       'common/v8_value_converter_impl.cc',
       'common/v8_value_converter_impl.h',
+      'renderer/api/atom_api_renderer_ipc.cc',
+      'renderer/api/atom_api_renderer_ipc.h',
       'renderer/api/atom_renderer_bindings.cc',
       'renderer/api/atom_renderer_bindings.h',
       'renderer/atom_render_view_observer.cc',
diff --git a/browser/api/atom_api_browser_ipc.cc b/browser/api/atom_api_browser_ipc.cc
new file mode 100644 (file)
index 0000000..0173edc
--- /dev/null
@@ -0,0 +1,66 @@
+// Copyright (c) 2013 GitHub, Inc. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "browser/api/atom_api_browser_ipc.h"
+
+#include "base/values.h"
+#include "common/api/api_messages.h"
+#include "common/v8_value_converter_impl.h"
+#include "content/public/browser/render_view_host.h"
+#include "vendor/node/src/node.h"
+#include "vendor/node/src/node_internals.h"
+
+using content::RenderViewHost;
+using content::V8ValueConverter;
+
+namespace atom {
+
+namespace api {
+
+// static
+v8::Handle<v8::Value> BrowserIPC::Send(const v8::Arguments &args) {
+  v8::HandleScope scope;
+
+  if (!args[0]->IsNumber() || !args[1]->IsNumber() || !args[2]->IsString())
+    return node::ThrowTypeError("Bad argument");
+
+  int process_id = args[0]->IntegerValue();
+  int routing_id = args[1]->IntegerValue();
+  std::string channel(*v8::String::Utf8Value(args[2]));
+
+  RenderViewHost* render_view_host(RenderViewHost::FromID(
+      process_id, routing_id));
+  if (!render_view_host)
+    return node::ThrowError("Invalid render view host");
+
+  // Convert Arguments to Array, so we can use V8ValueConverter to convert it
+  // to ListValue.
+  v8::Local<v8::Array> v8_args = v8::Array::New(args.Length() - 3);
+  for (int i = 0; i < args.Length() - 3; ++i)
+    v8_args->Set(i, args[i + 3]);
+
+  scoped_ptr<V8ValueConverter> converter(new V8ValueConverterImpl());
+  scoped_ptr<base::Value> arguments(
+      converter->FromV8Value(v8_args, v8::Context::GetCurrent()));
+
+  DCHECK(arguments && arguments->IsType(base::Value::TYPE_LIST));
+
+  render_view_host->Send(new AtomViewMsg_Message(
+      routing_id,
+      channel,
+      *static_cast<base::ListValue*>(arguments.get())));
+
+  return v8::Undefined();
+}
+
+// static
+void BrowserIPC::Initialize(v8::Handle<v8::Object> target) {
+  node::SetMethod(target, "send", Send);
+}
+
+}  // namespace api
+
+}  // namespace atom
+
+NODE_MODULE(atom_browser_ipc, atom::api::BrowserIPC::Initialize)
diff --git a/browser/api/atom_api_browser_ipc.h b/browser/api/atom_api_browser_ipc.h
new file mode 100644 (file)
index 0000000..5411daf
--- /dev/null
@@ -0,0 +1,29 @@
+// Copyright (c) 2013 GitHub, Inc. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ATOM_BROWSER_API_ATOM_API_BROWSER_IPC_H_
+#define ATOM_BROWSER_API_ATOM_API_BROWSER_IPC_H_
+
+#include "base/basictypes.h"
+#include "v8/include/v8.h"
+
+namespace atom {
+
+namespace api {
+
+class BrowserIPC {
+ public:
+  static void Initialize(v8::Handle<v8::Object> target);
+
+ private:
+  static v8::Handle<v8::Value> Send(const v8::Arguments &args);
+
+  DISALLOW_IMPLICIT_CONSTRUCTORS(BrowserIPC);
+};
+
+}  // namespace api
+
+}  // namespace atom
+
+#endif  // ATOM_BROWSER_API_ATOM_API_BROWSER_IPC_H_
index e6be1db..fb9089d 100644 (file)
@@ -40,8 +40,10 @@ void AtomBrowserBindings::AfterLoad() {
   DCHECK(!browser_main_parts_.IsEmpty());
 }
 
-void AtomBrowserBindings::OnRendererMessage(
-    int routing_id, const base::ListValue& args) {
+void AtomBrowserBindings::OnRendererMessage(int process_id,
+                                            int routing_id,
+                                            const std::string& channel,
+                                            const base::ListValue& args) {
   v8::HandleScope scope;
 
   v8::Handle<v8::Context> context = v8::Context::GetCurrent();
@@ -49,8 +51,9 @@ void AtomBrowserBindings::OnRendererMessage(
   scoped_ptr<V8ValueConverter> converter(new V8ValueConverterImpl());
 
   std::vector<v8::Handle<v8::Value>> arguments;
-  arguments.reserve(2 + args.GetSize());
-  arguments.push_back(v8::String::New("message"));
+  arguments.reserve(3 + args.GetSize());
+  arguments.push_back(v8::String::New(channel.c_str(), channel.size()));
+  arguments.push_back(v8::Integer::New(process_id));
   arguments.push_back(v8::Integer::New(routing_id));
 
   for (size_t i = 0; i < args.GetSize(); i++) {
index aa2831e..32b1786 100644 (file)
@@ -5,6 +5,8 @@
 #ifndef ATOM_BROWSER_API_ATOM_BROWSER_BINDINGS_
 #define ATOM_BROWSER_API_ATOM_BROWSER_BINDINGS_
 
+#include <iosfwd>
+
 #include "common/api/atom_bindings.h"
 
 namespace base {
@@ -22,7 +24,10 @@ class AtomBrowserBindings : public AtomBindings {
   virtual void AfterLoad();
 
   // Called when received a message from renderer.
-  void OnRendererMessage(int routing_id, const base::ListValue& args);
+  void OnRendererMessage(int process_id,
+                         int routing_id,
+                         const std::string& channel,
+                         const base::ListValue& args);
 
   // The require('atom').browserMainParts object.
   v8::Handle<v8::Object> browser_main_parts() {
diff --git a/browser/api/lib/ipc.coffee b/browser/api/lib/ipc.coffee
new file mode 100644 (file)
index 0000000..5f3b3a4
--- /dev/null
@@ -0,0 +1,12 @@
+EventEmitter = require('events').EventEmitter
+send = process.atom_binding('ipc').send
+
+class Ipc extends EventEmitter
+  constructor: ->
+    process.on 'ATOM_INTERNAL_MESSAGE', (args...) =>
+      @emit('message', args...)
+
+  send: (process_id, routing_id, args...) ->
+    send(process_id, routing_id, 'ATOM_INTERNAL_MESSAGE', args...)
+
+module.exports = new Ipc
index ebbd4ca..12873bf 100644 (file)
@@ -1,10 +1,12 @@
 var atom = require('atom');
+var ipc = require('ipc');
 var Window = require('window');
 
 var mainWindow = null;
 
-process.on('message', function() {
-  console.log.apply(this, arguments);
+ipc.on('message', function(process_id, routing_id) {
+  console.log('message from', process_id, routing_id);
+  ipc.send.apply(ipc, arguments);
 });
 
 atom.browserMainParts.preMainMessageLoopRun = function() {
index be29cdb..d566511 100644 (file)
@@ -17,6 +17,7 @@
 #include "content/public/browser/notification_details.h"
 #include "content/public/browser/notification_source.h"
 #include "content/public/browser/notification_types.h"
+#include "content/public/browser/render_process_host.h"
 #include "common/api/api_messages.h"
 #include "common/options_switches.h"
 #include "ipc/ipc_message_macros.h"
@@ -159,9 +160,13 @@ void NativeWindow::Observe(int type,
   }
 }
 
-void NativeWindow::OnRendererMessage(const base::ListValue& args) {
+void NativeWindow::OnRendererMessage(const std::string& channel,
+                                     const base::ListValue& args) {
   AtomBrowserMainParts::Get()->atom_bindings()->OnRendererMessage(
-      GetWebContents()->GetRoutingID(), args);
+      GetWebContents()->GetRenderProcessHost()->GetID(),
+      GetWebContents()->GetRoutingID(),
+      channel,
+      args);
 }
 
 }  // namespace atom
index b65ff17..0bc1ed3 100644 (file)
@@ -120,7 +120,8 @@ class NativeWindow : public content::WebContentsDelegate,
                        const content::NotificationDetails& details) OVERRIDE;
 
  private:
-  void OnRendererMessage(const base::ListValue& args);
+  void OnRendererMessage(const std::string& channel,
+                         const base::ListValue& args);
 
   // Notification manager.
   content::NotificationRegistrar registrar_;
index 8a3083a..34d4740 100644 (file)
 
 #define IPC_MESSAGE_START ShellMsgStart
 
-IPC_MESSAGE_ROUTED1(AtomViewHostMsg_Message,
+IPC_MESSAGE_ROUTED2(AtomViewHostMsg_Message,
+                    std::string /* channel */,
                     ListValue /* arguments */)
 
-IPC_MESSAGE_ROUTED1(AtomViewMsg_Message,
+IPC_MESSAGE_ROUTED2(AtomViewMsg_Message,
+                    std::string /* channel */,
                     ListValue /* arguments */)
index 47bdd37..2a985e5 100644 (file)
@@ -8,6 +8,9 @@
 
 NODE_EXT_LIST_START
 
+NODE_EXT_LIST_ITEM(atom_browser_ipc)
 NODE_EXT_LIST_ITEM(atom_browser_window)
 
+NODE_EXT_LIST_ITEM(atom_renderer_ipc)
+
 NODE_EXT_LIST_END
diff --git a/renderer/api/atom_api_renderer_ipc.cc b/renderer/api/atom_api_renderer_ipc.cc
new file mode 100644 (file)
index 0000000..4bd2775
--- /dev/null
@@ -0,0 +1,83 @@
+// Copyright (c) 2013 GitHub, Inc. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "renderer/api/atom_api_renderer_ipc.h"
+
+#include "base/values.h"
+#include "common/api/api_messages.h"
+#include "content/public/renderer/render_view.h"
+#include "content/public/renderer/v8_value_converter.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h"
+#include "vendor/node/src/node.h"
+
+using content::RenderView;
+using content::V8ValueConverter;
+using WebKit::WebFrame;
+using WebKit::WebView;
+
+namespace atom {
+
+namespace api {
+
+namespace {
+
+RenderView* GetCurrentRenderView() {
+  WebFrame* frame = WebFrame::frameForCurrentContext();
+  DCHECK(frame);
+  if (!frame)
+    return NULL;
+
+  WebView* view = frame->view();
+  if (!view)
+    return NULL;  // can happen during closing.
+
+  RenderView* render_view = RenderView::FromWebView(view);
+  DCHECK(render_view);
+  return render_view;
+}
+
+}  // namespace
+
+// static
+v8::Handle<v8::Value> RendererIPC::Send(const v8::Arguments &args) {
+  v8::HandleScope scope;
+
+  if (!args[0]->IsString())
+    return node::ThrowTypeError("Bad argument");
+
+  std::string channel(*v8::String::Utf8Value(args[0]));
+
+  RenderView* render_view = GetCurrentRenderView();
+
+  // Convert Arguments to Array, so we can use V8ValueConverter to convert it
+  // to ListValue.
+  v8::Local<v8::Array> v8_args = v8::Array::New(args.Length() - 1);
+  for (int i = 0; i < args.Length() - 1; ++i)
+    v8_args->Set(i, args[i + 1]);
+
+  scoped_ptr<V8ValueConverter> converter(V8ValueConverter::create());
+  scoped_ptr<base::Value> arguments(
+      converter->FromV8Value(v8_args, v8::Context::GetCurrent()));
+
+  DCHECK(arguments && arguments->IsType(base::Value::TYPE_LIST));
+
+  render_view->Send(new AtomViewHostMsg_Message(
+      render_view->GetRoutingID(),
+      channel,
+      *static_cast<base::ListValue*>(arguments.get())));
+
+  return v8::Undefined();
+}
+
+// static
+void RendererIPC::Initialize(v8::Handle<v8::Object> target) {
+  node::SetMethod(target, "send", Send);
+}
+
+}  // namespace api
+
+}  // namespace atom
+
+NODE_MODULE(atom_renderer_ipc, atom::api::RendererIPC::Initialize)
diff --git a/renderer/api/atom_api_renderer_ipc.h b/renderer/api/atom_api_renderer_ipc.h
new file mode 100644 (file)
index 0000000..30affee
--- /dev/null
@@ -0,0 +1,29 @@
+// Copyright (c) 2013 GitHub, Inc. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ATOM_RENDERER_API_ATOM_API_RENDERER_IPC_H_
+#define ATOM_RENDERER_API_ATOM_API_RENDERER_IPC_H_
+
+#include "base/basictypes.h"
+#include "v8/include/v8.h"
+
+namespace atom {
+
+namespace api {
+
+class RendererIPC {
+ public:
+  static void Initialize(v8::Handle<v8::Object> target);
+
+ private:
+  static v8::Handle<v8::Value> Send(const v8::Arguments &args);
+
+  DISALLOW_IMPLICIT_CONSTRUCTORS(RendererIPC);
+};
+
+}  // namespace api
+
+}  // namespace atom
+
+#endif  // ATOM_RENDERER_API_ATOM_API_RENDERER_IPC_H_
index 15276dd..702690c 100644 (file)
@@ -4,9 +4,10 @@
 
 #include "renderer/api/atom_renderer_bindings.h"
 
+#include <vector>
+
 #include "base/logging.h"
 #include "base/values.h"
-#include "common/api/api_messages.h"
 #include "content/public/renderer/render_view.h"
 #include "content/public/renderer/v8_value_converter.h"
 #include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h"
@@ -16,7 +17,6 @@
 using content::RenderView;
 using content::V8ValueConverter;
 using WebKit::WebFrame;
-using WebKit::WebView;
 
 namespace atom {
 
@@ -30,21 +30,6 @@ v8::Handle<v8::Object> GetProcessObject(v8::Handle<v8::Context> context) {
   return process;
 }
 
-RenderView* GetCurrentRenderView() {
-  WebFrame* frame = WebFrame::frameForCurrentContext();
-  DCHECK(frame);
-  if (!frame)
-    return NULL;
-
-  WebView* view = frame->view();
-  if (!view)
-    return NULL;  // can happen during closing.
-
-  RenderView* render_view = RenderView::FromWebView(view);
-  DCHECK(render_view);
-  return render_view;
-}
-
 }  // namespace
 
 AtomRendererBindings::AtomRendererBindings(RenderView* render_view)
@@ -66,43 +51,34 @@ void AtomRendererBindings::BindToFrame(WebFrame* frame) {
   AtomBindings::BindTo(GetProcessObject(context));
 }
 
-void AtomRendererBindings::AddIPCBindings(WebFrame* frame) {
-  v8::HandleScope handle_scope;
-
-  v8::Handle<v8::Context> context = frame->mainWorldScriptContext();
-  if (context.IsEmpty())
+void AtomRendererBindings::OnRendererMessage(const std::string& channel,
+                                             const base::ListValue& args) {
+  if (!render_view_->GetWebView())
     return;
 
-  v8::Context::Scope scope(context);
-
-  v8::Handle<v8::Object> process = GetProcessObject(context);
-
-  node::SetMethod(process, "send", Send);
-}
-
-// static
-v8::Handle<v8::Value> AtomRendererBindings::Send(const v8::Arguments &args) {
   v8::HandleScope scope;
 
-  RenderView* render_view = GetCurrentRenderView();
+  v8::Local<v8::Context> context =
+      render_view_->GetWebView()->mainFrame()->mainWorldScriptContext();
+  if (context.IsEmpty())
+    return;
 
-  // Convert Arguments to Array, so we can use V8ValueConverter to convert it
-  // to ListValue.
-  v8::Local<v8::Array> v8_args = v8::Array::New(args.Length());
-  for (int i = 0; i < args.Length(); ++i)
-    v8_args->Set(i, args[i]);
+  v8::Context::Scope context_scope(context);
 
+  v8::Handle<v8::Object> process = GetProcessObject(context);
   scoped_ptr<V8ValueConverter> converter(V8ValueConverter::create());
-  scoped_ptr<base::Value> arguments(
-      converter->FromV8Value(v8_args, v8::Context::GetCurrent()));
 
-  DCHECK(arguments && arguments->IsType(base::Value::TYPE_LIST));
+  std::vector<v8::Handle<v8::Value>> arguments;
+  arguments.reserve(1 + args.GetSize());
+  arguments.push_back(v8::String::New(channel.c_str(), channel.size()));
 
-  render_view->Send(new AtomViewHostMsg_Message(
-      render_view->GetRoutingID(),
-      *static_cast<base::ListValue*>(arguments.get())));
+  for (size_t i = 0; i < args.GetSize(); i++) {
+    const base::Value* value;
+    if (args.Get(i, &value))
+      arguments.push_back(converter->ToV8Value(value, context));
+  }
 
-  return v8::Undefined();
+  node::MakeCallback(process, "emit", arguments.size(), &arguments[0]);
 }
 
 }  // namespace atom
index 4be2a64..17bc1a0 100644 (file)
@@ -2,11 +2,17 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef ATOM_RENDERER_API_ATOM_RENDERER_BINDINGS_
-#define ATOM_RENDERER_API_ATOM_RENDERER_BINDINGS_
+#ifndef ATOM_RENDERER_API_ATOM_RENDERER_BINDINGS_H_
+#define ATOM_RENDERER_API_ATOM_RENDERER_BINDINGS_H_
+
+#include <iosfwd>
 
 #include "common/api/atom_bindings.h"
 
+namespace base {
+class ListValue;
+}
+
 namespace content {
 class RenderView;
 }
@@ -25,12 +31,11 @@ class AtomRendererBindings : public AtomBindings {
   // Call BindTo for process object of the frame.
   void BindToFrame(WebKit::WebFrame* frame);
 
-  // Add process.send and make process.on accept IPC message.
-  void AddIPCBindings(WebKit::WebFrame* frame);
+  // Dispatch messages from browser.
+  void OnRendererMessage(const std::string& channel,
+                         const base::ListValue& args);
 
  private:
-  static v8::Handle<v8::Value> Send(const v8::Arguments &args);
-
   content::RenderView* render_view_;
 
   DISALLOW_COPY_AND_ASSIGN(AtomRendererBindings);
@@ -38,4 +43,4 @@ class AtomRendererBindings : public AtomBindings {
 
 }  // namespace atom
 
-#endif  // ATOM_RENDERER_API_ATOM_BINDINGS_
+#endif  // ATOM_RENDERER_API_ATOM_BINDINGS_H_
diff --git a/renderer/api/lib/ipc.coffee b/renderer/api/lib/ipc.coffee
new file mode 100644 (file)
index 0000000..c36dd03
--- /dev/null
@@ -0,0 +1,12 @@
+EventEmitter = require('events').EventEmitter
+send = process.atom_binding('ipc').send
+
+class Ipc extends EventEmitter
+  constructor: ->
+    process.on 'ATOM_INTERNAL_MESSAGE', (args...) =>
+      @emit('message', args...)
+
+  send: (args...) ->
+    send('ATOM_INTERNAL_MESSAGE', args...)
+
+module.exports = new Ipc
diff --git a/renderer/api/lib/remote_object.coffee b/renderer/api/lib/remote_object.coffee
deleted file mode 100644 (file)
index d1d39a2..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-RemoteObject = process.atom_binding('remote_object').RemoteObject
-
-module.exports = RemoteObject
index cd02505..30007cc 100644 (file)
@@ -7,7 +7,9 @@
 #include <algorithm>
 #include <vector>
 
+#include "common/api/api_messages.h"
 #include "common/node_bindings.h"
+#include "ipc/ipc_message_macros.h"
 #include "renderer/api/atom_renderer_bindings.h"
 #include "renderer/atom_renderer_client.h"
 #include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h"
@@ -70,7 +72,6 @@ void AtomRenderViewObserver::DidClearWindowObject(WebFrame* frame) {
 
   renderer_client_->node_bindings()->BindTo(frame);
   atom_bindings()->BindToFrame(frame);
-  atom_bindings()->AddIPCBindings(frame);
 }
 
 void AtomRenderViewObserver::FrameWillClose(WebFrame* frame) {
@@ -78,4 +79,19 @@ void AtomRenderViewObserver::FrameWillClose(WebFrame* frame) {
   vec.erase(std::remove(vec.begin(), vec.end(), frame), vec.end());
 }
 
+bool AtomRenderViewObserver::OnMessageReceived(const IPC::Message& message) {
+  bool handled = true;
+  IPC_BEGIN_MESSAGE_MAP(AtomRenderViewObserver, message)
+    IPC_MESSAGE_HANDLER(AtomViewMsg_Message, OnRendererMessage)
+    IPC_MESSAGE_UNHANDLED(handled = false)
+  IPC_END_MESSAGE_MAP()
+
+  return handled;
+}
+
+void AtomRenderViewObserver::OnRendererMessage(const std::string& channel,
+                                               const base::ListValue& args) {
+  atom_bindings()->OnRendererMessage(channel, args);
+}
+
 }  // namespace atom
index 23c23eb..82715f8 100644 (file)
@@ -8,6 +8,10 @@
 #include "base/memory/scoped_ptr.h"
 #include "content/public/renderer/render_view_observer.h"
 
+namespace base {
+class ListValue;
+}
+
 namespace atom {
 
 class AtomRendererBindings;
@@ -27,6 +31,12 @@ class AtomRenderViewObserver : content::RenderViewObserver {
   virtual void FrameWillClose(WebKit::WebFrame*) OVERRIDE;
 
  private:
+  // content::RenderViewObserver implementation.
+  virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
+
+  void OnRendererMessage(const std::string& channel,
+                         const base::ListValue& args);
+
   scoped_ptr<AtomRendererBindings> atom_bindings_;
 
   // Weak reference to renderer client.