Commit b9e6a3d3 authored by ben@chromium.org's avatar ben@chromium.org

Allow EmbedRoot to be called multiple times.

Subsequent calls to EmbedRoot are now delegated to the window manager. This allows an application that was not created via the embed flow to obtain a connection to the view manager by asking the view manager init service to embed it somewhere. The init service just forwards this to the window manager. This is effectively like asking the window manager to create a toplevel window.

R=sky@chromium.org
BUG=none

Committed: https://src.chromium.org/viewvc/chrome?view=rev&revision=284322

Review URL: https://codereview.chromium.org/403083002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@284624 0039d316-1c4b-4281-b951-d872f2087c98
parent fcee6820
...@@ -15,9 +15,9 @@ ...@@ -15,9 +15,9 @@
#include "mojo/services/public/cpp/view_manager/node.h" #include "mojo/services/public/cpp/view_manager/node.h"
#include "mojo/services/public/cpp/view_manager/node_observer.h" #include "mojo/services/public/cpp/view_manager/node_observer.h"
#include "mojo/services/public/cpp/view_manager/view.h" #include "mojo/services/public/cpp/view_manager/view.h"
#include "mojo/services/public/cpp/view_manager/view_event_dispatcher.h"
#include "mojo/services/public/cpp/view_manager/view_manager.h" #include "mojo/services/public/cpp/view_manager/view_manager.h"
#include "mojo/services/public/cpp/view_manager/view_manager_delegate.h" #include "mojo/services/public/cpp/view_manager/view_manager_delegate.h"
#include "mojo/services/public/cpp/view_manager/window_manager_delegate.h"
#include "mojo/services/public/interfaces/input_events/input_events.mojom.h" #include "mojo/services/public/interfaces/input_events/input_events.mojom.h"
#include "mojo/services/public/interfaces/launcher/launcher.mojom.h" #include "mojo/services/public/interfaces/launcher/launcher.mojom.h"
#include "mojo/services/public/interfaces/navigation/navigation.mojom.h" #include "mojo/services/public/interfaces/navigation/navigation.mojom.h"
...@@ -34,10 +34,10 @@ using mojo::view_manager::Id; ...@@ -34,10 +34,10 @@ using mojo::view_manager::Id;
using mojo::view_manager::Node; using mojo::view_manager::Node;
using mojo::view_manager::NodeObserver; using mojo::view_manager::NodeObserver;
using mojo::view_manager::View; using mojo::view_manager::View;
using mojo::view_manager::ViewEventDispatcher;
using mojo::view_manager::ViewManager; using mojo::view_manager::ViewManager;
using mojo::view_manager::ViewManagerDelegate; using mojo::view_manager::ViewManagerDelegate;
using mojo::view_manager::ViewObserver; using mojo::view_manager::ViewObserver;
using mojo::view_manager::WindowManagerDelegate;
namespace mojo { namespace mojo {
namespace examples { namespace examples {
...@@ -252,7 +252,7 @@ class RootLayoutManager : public NodeObserver { ...@@ -252,7 +252,7 @@ class RootLayoutManager : public NodeObserver {
class WindowManager : public ApplicationDelegate, class WindowManager : public ApplicationDelegate,
public DebugPanel::Delegate, public DebugPanel::Delegate,
public ViewManagerDelegate, public ViewManagerDelegate,
public ViewEventDispatcher { public WindowManagerDelegate {
public: public:
WindowManager() WindowManager()
: launcher_ui_(NULL), : launcher_ui_(NULL),
...@@ -339,7 +339,7 @@ class WindowManager : public ApplicationDelegate, ...@@ -339,7 +339,7 @@ class WindowManager : public ApplicationDelegate,
virtual void OnRootAdded(ViewManager* view_manager, Node* root) OVERRIDE { virtual void OnRootAdded(ViewManager* view_manager, Node* root) OVERRIDE {
DCHECK(!view_manager_); DCHECK(!view_manager_);
view_manager_ = view_manager; view_manager_ = view_manager;
view_manager_->SetEventDispatcher(this); view_manager_->SetWindowManagerDelegate(this);
Node* node = Node::Create(view_manager_); Node* node = Node::Create(view_manager_);
root->AddChild(node); root->AddChild(node);
...@@ -366,7 +366,12 @@ class WindowManager : public ApplicationDelegate, ...@@ -366,7 +366,12 @@ class WindowManager : public ApplicationDelegate,
base::MessageLoop::current()->Quit(); base::MessageLoop::current()->Quit();
} }
// Overridden from ViewEventDispatcher: // Overridden from WindowManagerDelegate:
virtual void EmbedRoot(const String& url) OVERRIDE {
CreateWindow(url,
navigation::NavigationDetailsPtr().Pass(),
navigation::ResponseDetailsPtr().Pass());
}
virtual void DispatchEvent(View* target, EventPtr event) OVERRIDE { virtual void DispatchEvent(View* target, EventPtr event) OVERRIDE {
// TODO(beng): More sophisticated focus handling than this is required! // TODO(beng): More sophisticated focus handling than this is required!
if (event->action == ui::ET_MOUSE_PRESSED && if (event->action == ui::ET_MOUSE_PRESSED &&
......
...@@ -522,10 +522,10 @@ ...@@ -522,10 +522,10 @@
'services/public/cpp/view_manager/node.h', 'services/public/cpp/view_manager/node.h',
'services/public/cpp/view_manager/node_observer.h', 'services/public/cpp/view_manager/node_observer.h',
'services/public/cpp/view_manager/view.h', 'services/public/cpp/view_manager/view.h',
'services/public/cpp/view_manager/view_event_dispatcher.h',
'services/public/cpp/view_manager/view_manager.h', 'services/public/cpp/view_manager/view_manager.h',
'services/public/cpp/view_manager/view_manager_delegate.h', 'services/public/cpp/view_manager/view_manager_delegate.h',
'services/public/cpp/view_manager/view_observer.h', 'services/public/cpp/view_manager/view_observer.h',
'services/public/cpp/view_manager/window_manager_delegate.h',
], ],
'export_dependent_settings': [ 'export_dependent_settings': [
'mojo_view_manager_bindings', 'mojo_view_manager_bindings',
......
...@@ -13,9 +13,9 @@ ...@@ -13,9 +13,9 @@
#include "mojo/services/public/cpp/view_manager/lib/view_private.h" #include "mojo/services/public/cpp/view_manager/lib/view_private.h"
#include "mojo/services/public/cpp/view_manager/node_observer.h" #include "mojo/services/public/cpp/view_manager/node_observer.h"
#include "mojo/services/public/cpp/view_manager/util.h" #include "mojo/services/public/cpp/view_manager/util.h"
#include "mojo/services/public/cpp/view_manager/view_event_dispatcher.h"
#include "mojo/services/public/cpp/view_manager/view_manager_delegate.h" #include "mojo/services/public/cpp/view_manager/view_manager_delegate.h"
#include "mojo/services/public/cpp/view_manager/view_observer.h" #include "mojo/services/public/cpp/view_manager/view_observer.h"
#include "mojo/services/public/cpp/view_manager/window_manager_delegate.h"
#include "third_party/skia/include/core/SkBitmap.h" #include "third_party/skia/include/core/SkBitmap.h"
#include "ui/gfx/codec/png_codec.h" #include "ui/gfx/codec/png_codec.h"
...@@ -529,7 +529,7 @@ ViewManagerClientImpl::ViewManagerClientImpl(ApplicationConnection* connection, ...@@ -529,7 +529,7 @@ ViewManagerClientImpl::ViewManagerClientImpl(ApplicationConnection* connection,
connection_id_(0), connection_id_(0),
next_id_(1), next_id_(1),
delegate_(delegate), delegate_(delegate),
dispatcher_(NULL) {} window_manager_delegate_(NULL) {}
ViewManagerClientImpl::~ViewManagerClientImpl() { ViewManagerClientImpl::~ViewManagerClientImpl() {
while (!nodes_.empty()) { while (!nodes_.empty()) {
...@@ -676,14 +676,14 @@ void ViewManagerClientImpl::RemoveView(Id view_id) { ...@@ -676,14 +676,14 @@ void ViewManagerClientImpl::RemoveView(Id view_id) {
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// ViewManagerClientImpl, ViewManager implementation: // ViewManagerClientImpl, ViewManager implementation:
void ViewManagerClientImpl::SetEventDispatcher( void ViewManagerClientImpl::SetWindowManagerDelegate(
ViewEventDispatcher* dispatcher) { WindowManagerDelegate* window_manager_delegate) {
CHECK(NULL != GetNodeById(1)); CHECK(NULL != GetNodeById(1));
dispatcher_ = dispatcher; window_manager_delegate_ = window_manager_delegate;
} }
void ViewManagerClientImpl::DispatchEvent(View* target, EventPtr event) { void ViewManagerClientImpl::DispatchEvent(View* target, EventPtr event) {
CHECK(dispatcher_); CHECK(window_manager_delegate_);
service_->DispatchOnViewInputEvent(target->id(), event.Pass()); service_->DispatchOnViewInputEvent(target->id(), event.Pass());
} }
...@@ -824,9 +824,13 @@ void ViewManagerClientImpl::OnFocusChanged(Id gained_focus_id, ...@@ -824,9 +824,13 @@ void ViewManagerClientImpl::OnFocusChanged(Id gained_focus_id,
} }
} }
void ViewManagerClientImpl::EmbedRoot(const String& url) {
window_manager_delegate_->EmbedRoot(url);
}
void ViewManagerClientImpl::DispatchOnViewInputEvent(Id view_id, void ViewManagerClientImpl::DispatchOnViewInputEvent(Id view_id,
EventPtr event) { EventPtr event) {
dispatcher_->DispatchEvent(GetViewById(view_id), event.Pass()); window_manager_delegate_->DispatchEvent(GetViewById(view_id), event.Pass());
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
......
...@@ -21,7 +21,6 @@ namespace mojo { ...@@ -21,7 +21,6 @@ namespace mojo {
class ApplicationConnection; class ApplicationConnection;
namespace view_manager { namespace view_manager {
class ViewEventDispatcher;
class ViewManager; class ViewManager;
class ViewManagerTransaction; class ViewManagerTransaction;
...@@ -88,7 +87,8 @@ class ViewManagerClientImpl : public ViewManager, ...@@ -88,7 +87,8 @@ class ViewManagerClientImpl : public ViewManager,
typedef std::map<Id, View*> IdToViewMap; typedef std::map<Id, View*> IdToViewMap;
// Overridden from ViewManager: // Overridden from ViewManager:
virtual void SetEventDispatcher(ViewEventDispatcher* dispatcher) OVERRIDE; virtual void SetWindowManagerDelegate(
WindowManagerDelegate* delegate) OVERRIDE;
virtual void DispatchEvent(View* target, EventPtr event) OVERRIDE; virtual void DispatchEvent(View* target, EventPtr event) OVERRIDE;
virtual const std::string& GetEmbedderURL() const OVERRIDE; virtual const std::string& GetEmbedderURL() const OVERRIDE;
virtual const std::vector<Node*>& GetRoots() const OVERRIDE; virtual const std::vector<Node*>& GetRoots() const OVERRIDE;
...@@ -123,6 +123,7 @@ class ViewManagerClientImpl : public ViewManager, ...@@ -123,6 +123,7 @@ class ViewManagerClientImpl : public ViewManager,
EventPtr event, EventPtr event,
const Callback<void()>& callback) OVERRIDE; const Callback<void()>& callback) OVERRIDE;
virtual void OnFocusChanged(Id gained_focus_id, Id lost_focus_id) OVERRIDE; virtual void OnFocusChanged(Id gained_focus_id, Id lost_focus_id) OVERRIDE;
virtual void EmbedRoot(const String& url) OVERRIDE;
virtual void DispatchOnViewInputEvent(Id view_id, EventPtr event) OVERRIDE; virtual void DispatchOnViewInputEvent(Id view_id, EventPtr event) OVERRIDE;
// Sync the client model with the service by enumerating the pending // Sync the client model with the service by enumerating the pending
...@@ -147,7 +148,7 @@ class ViewManagerClientImpl : public ViewManager, ...@@ -147,7 +148,7 @@ class ViewManagerClientImpl : public ViewManager,
base::Callback<void(void)> changes_acked_callback_; base::Callback<void(void)> changes_acked_callback_;
ViewManagerDelegate* delegate_; ViewManagerDelegate* delegate_;
ViewEventDispatcher* dispatcher_; WindowManagerDelegate* window_manager_delegate_;
std::vector<Node*> roots_; std::vector<Node*> roots_;
......
...@@ -17,8 +17,8 @@ namespace view_manager { ...@@ -17,8 +17,8 @@ namespace view_manager {
class Node; class Node;
class View; class View;
class ViewEventDispatcher;
class ViewManagerDelegate; class ViewManagerDelegate;
class WindowManagerDelegate;
class ViewManager { class ViewManager {
public: public:
...@@ -26,12 +26,13 @@ class ViewManager { ...@@ -26,12 +26,13 @@ class ViewManager {
static void ConfigureIncomingConnection(ApplicationConnection* connection, static void ConfigureIncomingConnection(ApplicationConnection* connection,
ViewManagerDelegate* delegate); ViewManagerDelegate* delegate);
// Sets the event dispatcher. Can only be called by the app rendering to the // Sets the window manager delegate. Can only be called by the app embedded at
// root Node of the hierarchy. // the service root node.
virtual void SetEventDispatcher(ViewEventDispatcher* dispatcher) = 0; virtual void SetWindowManagerDelegate(
WindowManagerDelegate* window_manager_delegate) = 0;
// Dispatches the supplied event to the specified View. Can be called only // Dispatches the supplied event to the specified View. Can be called only
// by the application that called SetEventDispatcher(). // by the application that called SetWindowManagerDelegate().
virtual void DispatchEvent(View* target, EventPtr event) = 0; virtual void DispatchEvent(View* target, EventPtr event) = 0;
// Returns the URL of the application that embedded this application. // Returns the URL of the application that embedded this application.
......
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
#ifndef MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_VIEW_EVENT_DISPATCHER_H_ #ifndef MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_WINDOW_MANAGER_DELEGATE_H_
#define MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_VIEW_EVENT_DISPATCHER_H_ #define MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_WINDOW_MANAGER_DELEGATE_H_
#include "mojo/services/public/interfaces/input_events/input_events.mojom.h" #include "mojo/services/public/interfaces/input_events/input_events.mojom.h"
...@@ -12,19 +12,22 @@ namespace view_manager { ...@@ -12,19 +12,22 @@ namespace view_manager {
class View; class View;
// A ViewEventDispatcher is provided by the application rendering at the root // A WindowManagerDelegate is provided by the application embedded at the
// of a Node hierarchy. It is responsible for targeting input events to the // service root node.
// relevant Views. This allows window manager features like focus, activation, class WindowManagerDelegate {
// modality, etc. to be implemented.
class ViewEventDispatcher {
public: public:
// Create an appropriate node to embed |url|.
virtual void EmbedRoot(const String& url) = 0;
// Dispatch the supplied input event to the appropriate view (taking into
// account focus, activation, modality, etc.).
virtual void DispatchEvent(View* target, EventPtr event) = 0; virtual void DispatchEvent(View* target, EventPtr event) = 0;
protected: protected:
virtual ~ViewEventDispatcher() {} virtual ~WindowManagerDelegate() {}
}; };
} // namespace view_manager } // namespace view_manager
} // namespace mojo } // namespace mojo
#endif // MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_VIEW_EVENT_DISPATCHER_H_ #endif // MOJO_SERVICES_PUBLIC_CPP_VIEW_MANAGER_WINDOW_MANAGER_DELEGATE_H_
...@@ -22,10 +22,17 @@ enum ErrorCode { ...@@ -22,10 +22,17 @@ enum ErrorCode {
ILLEGAL_ARGUMENT, ILLEGAL_ARGUMENT,
}; };
// ViewManagerInitService is responsible for launching the client that controls // ViewManagerInitService is used to grant an application a connection to the
// the root node. mojo::view_manager returns an instance of this. All other // ViewManager by embedding it at an approriate Node.
// connections are established by the client this creates.
interface ViewManagerInitService { interface ViewManagerInitService {
// Embed the application @ |url| at an appropriate Node.
// The first time this method is called in the lifetime of a View Manager
// application instance, the "appropriate Node" is defined as being the
// service root Node.
// Subsequent times, implementation of this method is delegated to the
// application embedded at the service root Node. This application is
// typically referred to as the "window manager", and will have a specific
// definition of where within its Node hierarchy to embed an unparented URL.
EmbedRoot(string url) => (bool success); EmbedRoot(string url) => (bool success);
}; };
...@@ -193,8 +200,16 @@ interface ViewManagerClient { ...@@ -193,8 +200,16 @@ interface ViewManagerClient {
// removed. // removed.
OnFocusChanged(uint32 gained_focus_id, uint32 lost_focus_id); OnFocusChanged(uint32 gained_focus_id, uint32 lost_focus_id);
// TODO(sky): move to separate interface when FIFO sorted out. // TODO(sky): The following methods represent an interface between the view
// manager and the application embedded at the service root node
// (i.e. the window manager). These methods are not called on
// any other clients. They should be moved to a separate interface
// once support for derived FIFOs is landed.
// Requests the window manager create a "top level" node embedding |url|.
EmbedRoot(string url);
// Requests the view manager dispatch the event targeted at |view|.
DispatchOnViewInputEvent(uint32 view, mojo.Event event); DispatchOnViewInputEvent(uint32 view, mojo.Event event);
}; };
......
...@@ -88,8 +88,12 @@ void RootNodeManager::RemoveConnection(ViewManagerServiceImpl* connection) { ...@@ -88,8 +88,12 @@ void RootNodeManager::RemoveConnection(ViewManagerServiceImpl* connection) {
} }
void RootNodeManager::EmbedRoot(const std::string& url) { void RootNodeManager::EmbedRoot(const std::string& url) {
CHECK(connection_map_.empty()); if (connection_map_.empty()) {
EmbedImpl(kRootConnection, String::From(url), InvalidNodeId()); EmbedImpl(kRootConnection, String::From(url), InvalidNodeId());
return;
}
ViewManagerServiceImpl* connection = GetConnection(kWindowManagerConnection);
connection->client()->EmbedRoot(url);
} }
void RootNodeManager::Embed(ConnectionSpecificId creator_id, void RootNodeManager::Embed(ConnectionSpecificId creator_id,
......
...@@ -80,6 +80,8 @@ std::string ChangeToDescription1(const Change& change) { ...@@ -80,6 +80,8 @@ std::string ChangeToDescription1(const Change& change) {
"InputEvent view=%s event_action=%d", "InputEvent view=%s event_action=%d",
NodeIdToString(change.view_id).c_str(), NodeIdToString(change.view_id).c_str(),
change.event_action); change.event_action);
case CHANGE_TYPE_EMBED_ROOT:
return base::StringPrintf("EmbedRoot url=%s", change.embed_url.data());
} }
return std::string(); return std::string();
} }
...@@ -223,6 +225,13 @@ void TestChangeTracker::OnViewInputEvent(Id view_id, EventPtr event) { ...@@ -223,6 +225,13 @@ void TestChangeTracker::OnViewInputEvent(Id view_id, EventPtr event) {
AddChange(change); AddChange(change);
} }
void TestChangeTracker::OnEmbedRoot(const String& url) {
Change change;
change.type = CHANGE_TYPE_EMBED_ROOT;
change.embed_url = url;
AddChange(change);
}
void TestChangeTracker::AddChange(const Change& change) { void TestChangeTracker::AddChange(const Change& change) {
changes_.push_back(change); changes_.push_back(change);
if (delegate_) if (delegate_)
......
...@@ -27,6 +27,7 @@ enum ChangeType { ...@@ -27,6 +27,7 @@ enum ChangeType {
CHANGE_TYPE_VIEW_DELETED, CHANGE_TYPE_VIEW_DELETED,
CHANGE_TYPE_VIEW_REPLACED, CHANGE_TYPE_VIEW_REPLACED,
CHANGE_TYPE_INPUT_EVENT, CHANGE_TYPE_INPUT_EVENT,
CHANGE_TYPE_EMBED_ROOT,
}; };
// TODO(sky): consider nuking and converting directly to NodeData. // TODO(sky): consider nuking and converting directly to NodeData.
...@@ -57,6 +58,7 @@ struct Change { ...@@ -57,6 +58,7 @@ struct Change {
gfx::Rect bounds2; gfx::Rect bounds2;
int32 event_action; int32 event_action;
String creator_url; String creator_url;
String embed_url;
OrderDirection direction; OrderDirection direction;
}; };
...@@ -111,6 +113,7 @@ class TestChangeTracker { ...@@ -111,6 +113,7 @@ class TestChangeTracker {
void OnViewDeleted(Id view_id); void OnViewDeleted(Id view_id);
void OnNodeViewReplaced(Id node_id, Id new_view_id, Id old_view_id); void OnNodeViewReplaced(Id node_id, Id new_view_id, Id old_view_id);
void OnViewInputEvent(Id view_id, EventPtr event); void OnViewInputEvent(Id view_id, EventPtr event);
void OnEmbedRoot(const String& url);
private: private:
void AddChange(const Change& change); void AddChange(const Change& change);
......
...@@ -44,11 +44,8 @@ void ViewManagerInitServiceImpl::MaybeEmbedRoot( ...@@ -44,11 +44,8 @@ void ViewManagerInitServiceImpl::MaybeEmbedRoot(
void ViewManagerInitServiceImpl::EmbedRoot( void ViewManagerInitServiceImpl::EmbedRoot(
const String& url, const String& url,
const Callback<void(bool)>& callback) { const Callback<void(bool)>& callback) {
if (connect_params_) { // TODO(beng): This means you can only have one EmbedRoot in flight at a time.
DVLOG(1) << "Ignoring second connect"; // Keep a vector of these around instead.
callback.Run(false);
return;
}
connect_params_.reset(new ConnectParams); connect_params_.reset(new ConnectParams);
connect_params_->url = url.To<std::string>(); connect_params_->url = url.To<std::string>();
connect_params_->callback = callback; connect_params_->callback = callback;
......
...@@ -318,7 +318,7 @@ class TestViewManagerClientConnection ...@@ -318,7 +318,7 @@ class TestViewManagerClientConnection
connection_.set_view_manager(client()); connection_.set_view_manager(client());
} }
// ViewMangerClient: // ViewManagerClient:
virtual void OnViewManagerConnectionEstablished( virtual void OnViewManagerConnectionEstablished(
ConnectionSpecificId connection_id, ConnectionSpecificId connection_id,
const String& creator_url, const String& creator_url,
...@@ -363,6 +363,9 @@ class TestViewManagerClientConnection ...@@ -363,6 +363,9 @@ class TestViewManagerClientConnection
} }
virtual void OnFocusChanged(Id gained_focus_id, virtual void OnFocusChanged(Id gained_focus_id,
Id lost_focus_id) OVERRIDE {} Id lost_focus_id) OVERRIDE {}
virtual void EmbedRoot(const String& url) OVERRIDE {
tracker_.OnEmbedRoot(url);
}
virtual void DispatchOnViewInputEvent(Id view_id, virtual void DispatchOnViewInputEvent(Id view_id,
mojo::EventPtr event) OVERRIDE { mojo::EventPtr event) OVERRIDE {
} }
...@@ -517,6 +520,12 @@ class ViewManagerTest : public testing::Test { ...@@ -517,6 +520,12 @@ class ViewManagerTest : public testing::Test {
DISALLOW_COPY_AND_ASSIGN(ViewManagerTest); DISALLOW_COPY_AND_ASSIGN(ViewManagerTest);
}; };
TEST_F(ViewManagerTest, SecondEmbedRoot) {
ASSERT_TRUE(EmbedRoot(view_manager_init_.get(), kTestServiceURL));
connection_->DoRunLoopUntilChangesCount(1);
EXPECT_EQ(kTestServiceURL, connection_->changes()[0].embed_url);
}
// Verifies client gets a valid id. // Verifies client gets a valid id.
TEST_F(ViewManagerTest, ValidId) { TEST_F(ViewManagerTest, ValidId) {
// TODO(beng): this should really have the URL of the application that // TODO(beng): this should really have the URL of the application that
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment