From 16244e42e073a5562e16637b275e43aceef7f0fb Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 23 Apr 2013 12:18:07 +0800 Subject: [PATCH] Use require('ipc').send to communicate between browser and renderer. --- atom.gyp | 6 +++ browser/api/atom_api_browser_ipc.cc | 66 +++++++++++++++++++++++++++ browser/api/atom_api_browser_ipc.h | 29 ++++++++++++ browser/api/atom_browser_bindings.cc | 11 +++-- browser/api/atom_browser_bindings.h | 7 ++- browser/api/lib/ipc.coffee | 12 +++++ browser/default_app/main.js | 6 ++- browser/native_window.cc | 9 +++- browser/native_window.h | 3 +- common/api/api_messages.h | 6 ++- common/api/atom_extensions.h | 3 ++ renderer/api/atom_api_renderer_ipc.cc | 83 ++++++++++++++++++++++++++++++++++ renderer/api/atom_api_renderer_ipc.h | 29 ++++++++++++ renderer/api/atom_renderer_bindings.cc | 64 ++++++++------------------ renderer/api/atom_renderer_bindings.h | 19 +++++--- renderer/api/lib/ipc.coffee | 12 +++++ renderer/api/lib/remote_object.coffee | 3 -- renderer/atom_render_view_observer.cc | 18 +++++++- renderer/atom_render_view_observer.h | 10 ++++ 19 files changed, 329 insertions(+), 67 deletions(-) create mode 100644 browser/api/atom_api_browser_ipc.cc create mode 100644 browser/api/atom_api_browser_ipc.h create mode 100644 browser/api/lib/ipc.coffee create mode 100644 renderer/api/atom_api_renderer_ipc.cc create mode 100644 renderer/api/atom_api_renderer_ipc.h create mode 100644 renderer/api/lib/ipc.coffee delete mode 100644 renderer/api/lib/remote_object.coffee diff --git a/atom.gyp b/atom.gyp index 8182e82..832e3e5 100644 --- 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 index 0000000..0173edc --- /dev/null +++ b/browser/api/atom_api_browser_ipc.cc @@ -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 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_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 converter(new V8ValueConverterImpl()); + scoped_ptr 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(arguments.get()))); + + return v8::Undefined(); +} + +// static +void BrowserIPC::Initialize(v8::Handle 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 index 0000000..5411daf --- /dev/null +++ b/browser/api/atom_api_browser_ipc.h @@ -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 target); + + private: + static v8::Handle Send(const v8::Arguments &args); + + DISALLOW_IMPLICIT_CONSTRUCTORS(BrowserIPC); +}; + +} // namespace api + +} // namespace atom + +#endif // ATOM_BROWSER_API_ATOM_API_BROWSER_IPC_H_ diff --git a/browser/api/atom_browser_bindings.cc b/browser/api/atom_browser_bindings.cc index e6be1db..fb9089d 100644 --- a/browser/api/atom_browser_bindings.cc +++ b/browser/api/atom_browser_bindings.cc @@ -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 context = v8::Context::GetCurrent(); @@ -49,8 +51,9 @@ void AtomBrowserBindings::OnRendererMessage( scoped_ptr converter(new V8ValueConverterImpl()); std::vector> 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++) { diff --git a/browser/api/atom_browser_bindings.h b/browser/api/atom_browser_bindings.h index aa2831e..32b1786 100644 --- a/browser/api/atom_browser_bindings.h +++ b/browser/api/atom_browser_bindings.h @@ -5,6 +5,8 @@ #ifndef ATOM_BROWSER_API_ATOM_BROWSER_BINDINGS_ #define ATOM_BROWSER_API_ATOM_BROWSER_BINDINGS_ +#include + #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 browser_main_parts() { diff --git a/browser/api/lib/ipc.coffee b/browser/api/lib/ipc.coffee new file mode 100644 index 0000000..5f3b3a4 --- /dev/null +++ b/browser/api/lib/ipc.coffee @@ -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 diff --git a/browser/default_app/main.js b/browser/default_app/main.js index ebbd4ca..12873bf 100644 --- a/browser/default_app/main.js +++ b/browser/default_app/main.js @@ -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() { diff --git a/browser/native_window.cc b/browser/native_window.cc index be29cdb..d566511 100644 --- a/browser/native_window.cc +++ b/browser/native_window.cc @@ -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 diff --git a/browser/native_window.h b/browser/native_window.h index b65ff17..0bc1ed3 100644 --- a/browser/native_window.h +++ b/browser/native_window.h @@ -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_; diff --git a/common/api/api_messages.h b/common/api/api_messages.h index 8a3083a..34d4740 100644 --- a/common/api/api_messages.h +++ b/common/api/api_messages.h @@ -15,8 +15,10 @@ #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 */) diff --git a/common/api/atom_extensions.h b/common/api/atom_extensions.h index 47bdd37..2a985e5 100644 --- a/common/api/atom_extensions.h +++ b/common/api/atom_extensions.h @@ -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 index 0000000..4bd2775 --- /dev/null +++ b/renderer/api/atom_api_renderer_ipc.cc @@ -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 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_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 converter(V8ValueConverter::create()); + scoped_ptr 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(arguments.get()))); + + return v8::Undefined(); +} + +// static +void RendererIPC::Initialize(v8::Handle 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 index 0000000..30affee --- /dev/null +++ b/renderer/api/atom_api_renderer_ipc.h @@ -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 target); + + private: + static v8::Handle Send(const v8::Arguments &args); + + DISALLOW_IMPLICIT_CONSTRUCTORS(RendererIPC); +}; + +} // namespace api + +} // namespace atom + +#endif // ATOM_RENDERER_API_ATOM_API_RENDERER_IPC_H_ diff --git a/renderer/api/atom_renderer_bindings.cc b/renderer/api/atom_renderer_bindings.cc index 15276dd..702690c 100644 --- a/renderer/api/atom_renderer_bindings.cc +++ b/renderer/api/atom_renderer_bindings.cc @@ -4,9 +4,10 @@ #include "renderer/api/atom_renderer_bindings.h" +#include + #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 GetProcessObject(v8::Handle 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 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 process = GetProcessObject(context); - - node::SetMethod(process, "send", Send); -} - -// static -v8::Handle AtomRendererBindings::Send(const v8::Arguments &args) { v8::HandleScope scope; - RenderView* render_view = GetCurrentRenderView(); + v8::Local 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_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 process = GetProcessObject(context); scoped_ptr converter(V8ValueConverter::create()); - scoped_ptr arguments( - converter->FromV8Value(v8_args, v8::Context::GetCurrent())); - DCHECK(arguments && arguments->IsType(base::Value::TYPE_LIST)); + std::vector> 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(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 diff --git a/renderer/api/atom_renderer_bindings.h b/renderer/api/atom_renderer_bindings.h index 4be2a64..17bc1a0 100644 --- a/renderer/api/atom_renderer_bindings.h +++ b/renderer/api/atom_renderer_bindings.h @@ -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 #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 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 index 0000000..c36dd03 --- /dev/null +++ b/renderer/api/lib/ipc.coffee @@ -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 index d1d39a2..0000000 --- a/renderer/api/lib/remote_object.coffee +++ /dev/null @@ -1,3 +0,0 @@ -RemoteObject = process.atom_binding('remote_object').RemoteObject - -module.exports = RemoteObject diff --git a/renderer/atom_render_view_observer.cc b/renderer/atom_render_view_observer.cc index cd02505..30007cc 100644 --- a/renderer/atom_render_view_observer.cc +++ b/renderer/atom_render_view_observer.cc @@ -7,7 +7,9 @@ #include #include +#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 diff --git a/renderer/atom_render_view_observer.h b/renderer/atom_render_view_observer.h index 23c23eb..82715f8 100644 --- a/renderer/atom_render_view_observer.h +++ b/renderer/atom_render_view_observer.h @@ -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 atom_bindings_; // Weak reference to renderer client. -- 2.7.4