Commit 3a6fa9bb authored by jdufault's avatar jdufault Committed by Commit bot

cros: Enable WebUILoginView reuse.

This CL also includes a few fixes for loading a WebUILoginView instance by itself without associated helper classes also being initialized.

There are three child CLs associated with this one:
- [1] contains fixes on the HTML side.
- [2] makes the lock screen show up correctly in the task manager.
- [3] preloads and reuses the WebUIScreenLocker instance (derived from WebUILoginView).

1: https://codereview.chromium.org/2555453003/
2: https://codereview.chromium.org/2550263002/
3: https://codereview.chromium.org/2553863002/

There is additional context at go/cros-lock-screen-optimization.

BUG=669638
CQ_INCLUDE_TRYBOTS=master.tryserver.chromium.linux:closure_compilation

Review-Url: https://codereview.chromium.org/2512473004
Cr-Commit-Position: refs/heads/master@{#437942}
parent 8bcf27f1
......@@ -877,6 +877,10 @@ source_set("chromeos") {
"login/ui/models/user_board_model.cc",
"login/ui/models/user_board_model.h",
"login/ui/proxy_settings_dialog.cc",
"login/ui/shared_web_view.cc",
"login/ui/shared_web_view.h",
"login/ui/shared_web_view_factory.cc",
"login/ui/shared_web_view_factory.h",
"login/ui/simple_web_view_dialog.cc",
"login/ui/simple_web_view_dialog.h",
"login/ui/user_adding_screen.cc",
......@@ -885,6 +889,8 @@ source_set("chromeos") {
"login/ui/user_adding_screen_input_methods_controller.h",
"login/ui/web_contents_set_background_color.cc",
"login/ui/web_contents_set_background_color.h",
"login/ui/web_view_handle.cc",
"login/ui/web_view_handle.h",
"login/ui/webui_login_display.cc",
"login/ui/webui_login_display.h",
"login/ui/webui_login_view.cc",
......
......@@ -61,7 +61,8 @@ namespace chromeos {
// WebUIScreenLocker implementation.
WebUIScreenLocker::WebUIScreenLocker(ScreenLocker* screen_locker)
: screen_locker_(screen_locker),
: WebUILoginView(WebViewSettings()),
screen_locker_(screen_locker),
network_state_helper_(new login::NetworkStateHelper),
weak_factory_(this) {
set_should_emit_login_prompt_visible(false);
......@@ -88,7 +89,7 @@ WebUIScreenLocker::~WebUIScreenLocker() {
}
// If LockScreen() was called, we need to clear the signin screen handler
// delegate set in ShowSigninScreen so that it no longer points to us.
if (login_display_.get())
if (login_display_.get() && GetOobeUI())
GetOobeUI()->ResetSigninScreenHandlerDelegate();
if (keyboard::KeyboardController::GetInstance() && is_observing_keyboard_) {
......@@ -107,7 +108,7 @@ void WebUIScreenLocker::LockScreen() {
lock_window_->AddObserver(this);
Init();
content::WebContentsObserver::Observe(webui_login_->GetWebContents());
content::WebContentsObserver::Observe(web_view()->GetWebContents());
lock_window_->SetContentsView(this);
lock_window_->SetBounds(bounds);
......@@ -171,7 +172,7 @@ gfx::NativeWindow WebUIScreenLocker::GetNativeWindow() const {
void WebUIScreenLocker::FocusUserPod() {
if (!webui_ready_)
return;
webui_login_->RequestFocus();
web_view()->RequestFocus();
GetWebUI()->CallJavascriptFunctionUnsafe(
"cr.ui.Oobe.forceLockedUserPodFocus");
}
......
......@@ -1141,7 +1141,7 @@ void LoginDisplayHostImpl::InitLoginWindowAndView() {
params.delegate = new LoginWidgetDelegate(login_window_);
login_window_->Init(params);
login_view_ = new WebUILoginView();
login_view_ = new WebUILoginView(WebUILoginView::WebViewSettings());
login_view_->Init();
if (login_view_->webui_visible())
OnLoginPromptVisible();
......
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/chromeos/login/ui/shared_web_view.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/chromeos/login/ui/web_view_handle.h"
#include "chrome/browser/profiles/profile.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/web_contents.h"
#include "ui/views/controls/webview/webview.h"
namespace chromeos {
SharedWebView::SharedWebView(Profile* profile) : profile_(profile) {
registrar_.Add(this, chrome::NOTIFICATION_CLOSE_ALL_BROWSERS_REQUEST,
content::NotificationService::AllSources());
memory_pressure_listener_ = base::MakeUnique<base::MemoryPressureListener>(
base::Bind(&SharedWebView::OnMemoryPressure, base::Unretained(this)));
}
SharedWebView::~SharedWebView() {}
void SharedWebView::Shutdown() {
web_view_handle_ = nullptr;
}
bool SharedWebView::Get(const GURL& url,
scoped_refptr<WebViewHandle>* out_handle) {
// At the moment only one shared WebView instance is supported per profile.
DCHECK(web_view_url_.is_empty() || web_view_url_ == url);
web_view_url_ = url;
// Ensure the WebView is not being reused simultaneously.
DCHECK(!web_view_handle_ || web_view_handle_->HasOneRef());
// Clear cached reference if it is no longer valid (ie, destroyed in task
// manager).
if (web_view_handle_ &&
!web_view_handle_->web_view()
->GetWebContents()
->GetRenderViewHost()
->GetWidget()
->GetView()) {
web_view_handle_ = nullptr;
}
// Create WebView if needed.
bool reused = true;
if (!web_view_handle_) {
web_view_handle_ = new WebViewHandle(profile_);
reused = false;
}
*out_handle = web_view_handle_;
return reused;
}
void SharedWebView::Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
DCHECK_EQ(chrome::NOTIFICATION_CLOSE_ALL_BROWSERS_REQUEST, type);
web_view_handle_ = nullptr;
}
void SharedWebView::OnMemoryPressure(
base::MemoryPressureListener::MemoryPressureLevel level) {
switch (level) {
case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE:
break;
case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE:
case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL:
web_view_handle_ = nullptr;
break;
}
}
} // namespace chromeos
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_CHROMEOS_LOGIN_UI_SHARED_WEB_VIEW_H_
#define CHROME_BROWSER_CHROMEOS_LOGIN_UI_SHARED_WEB_VIEW_H_
#include <memory>
#include "base/macros.h"
#include "base/memory/memory_pressure_listener.h"
#include "base/memory/ref_counted.h"
#include "components/keyed_service/core/keyed_service.h"
#include "content/public/browser/notification_observer.h"
#include "content/public/browser/notification_registrar.h"
#include "url/gurl.h"
class Profile;
namespace chromeos {
class WebViewHandle;
// Stores and fetches a views::WebView instance that is ulimately owned by the
// signin profile. This allows for a WebView to be reused over time or
// preloaded. Use SharedWebViewFactory to get an instance of this class.
class SharedWebView : public KeyedService,
public content::NotificationObserver {
public:
explicit SharedWebView(Profile* profile);
~SharedWebView() override;
void Shutdown() override;
// Stores a webview instance inside of |out_handle|. |url| is the initial
// URL that the webview stores (note: this function does not perform the
// navigation). This returns true if the webview was reused, false if it
// was freshly created.
bool Get(const GURL& url, scoped_refptr<WebViewHandle>* out_handle);
private:
// content::NotificationObserver:
void Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) override;
// Called when there is a memory pressure event.
void OnMemoryPressure(
base::MemoryPressureListener::MemoryPressureLevel level);
content::NotificationRegistrar registrar_;
// Profile used for creating the views::WebView instance.
Profile* const profile_;
// Cached URL that the |web_view_| instance should point to used for
// validation.
GURL web_view_url_;
// Shared web-view instance. Callers may take a reference on the handle so it
// could outlive this class.
scoped_refptr<WebViewHandle> web_view_handle_;
std::unique_ptr<base::MemoryPressureListener> memory_pressure_listener_;
DISALLOW_COPY_AND_ASSIGN(SharedWebView);
};
} // namespace chromeos
#endif // CHROME_BROWSER_CHROMEOS_LOGIN_UI_SHARED_WEB_VIEW_H_
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/chromeos/login/ui/shared_web_view_factory.h"
#include "chrome/browser/chromeos/login/ui/shared_web_view.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h"
#include "chrome/browser/profiles/incognito_helpers.h"
#include "chrome/browser/profiles/profile.h"
#include "components/keyed_service/content/browser_context_dependency_manager.h"
namespace chromeos {
// static
SharedWebView* SharedWebViewFactory::GetForProfile(Profile* profile) {
auto result = static_cast<SharedWebView*>(
GetInstance()->GetServiceForBrowserContext(profile, true));
return result;
}
// static
SharedWebViewFactory* SharedWebViewFactory::GetInstance() {
return base::Singleton<SharedWebViewFactory>::get();
}
SharedWebViewFactory::SharedWebViewFactory()
: BrowserContextKeyedServiceFactory(
"SharedWebViewFactory",
BrowserContextDependencyManager::GetInstance()) {}
SharedWebViewFactory::~SharedWebViewFactory() {}
content::BrowserContext* SharedWebViewFactory::GetBrowserContextToUse(
content::BrowserContext* context) const {
// Make sure that only the SigninProfile is using a shared webview.
if (Profile::FromBrowserContext(context) != ProfileHelper::GetSigninProfile())
return nullptr;
return context;
}
KeyedService* SharedWebViewFactory::BuildServiceInstanceFor(
content::BrowserContext* context) const {
return new SharedWebView(Profile::FromBrowserContext(context));
}
} // namespace chromeos
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_CHROMEOS_LOGIN_UI_SHARED_WEB_VIEW_FACTORY_H_
#define CHROME_BROWSER_CHROMEOS_LOGIN_UI_SHARED_WEB_VIEW_FACTORY_H_
#include "base/macros.h"
#include "base/memory/singleton.h"
#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
class Profile;
namespace chromeos {
class SharedWebView;
// Fetches a SharedWebView instance for the signin profile.
class SharedWebViewFactory : public BrowserContextKeyedServiceFactory {
public:
static SharedWebView* GetForProfile(Profile* profile);
static SharedWebViewFactory* GetInstance();
private:
friend struct base::DefaultSingletonTraits<SharedWebViewFactory>;
SharedWebViewFactory();
~SharedWebViewFactory() override;
// BrowserContextKeyedServiceFactory:
content::BrowserContext* GetBrowserContextToUse(
content::BrowserContext* context) const override;
KeyedService* BuildServiceInstanceFor(
content::BrowserContext* profile) const override;
DISALLOW_COPY_AND_ASSIGN(SharedWebViewFactory);
};
} // namespace chromeos
#endif // CHROME_BROWSER_CHROMEOS_LOGIN_UI_SHARED_WEB_VIEW_FACTORY_H_
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/chromeos/login/ui/web_view_handle.h"
#include "chrome/browser/profiles/profile.h"
#include "ui/views/controls/webview/webview.h"
namespace chromeos {
WebViewHandle::WebViewHandle(Profile* profile)
: web_view_(base::MakeUnique<views::WebView>(profile)) {
web_view_->set_owned_by_client();
}
WebViewHandle::~WebViewHandle() {}
} // namespace chromeos
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_CHROMEOS_LOGIN_UI_WEB_VIEW_HANDLE_H_
#define CHROME_BROWSER_CHROMEOS_LOGIN_UI_WEB_VIEW_HANDLE_H_
#include <memory>
#include "base/macros.h"
#include "base/memory/ref_counted.h"
class Profile;
namespace views {
class WebView;
}
namespace chromeos {
// Owns a views::WebView instance. Any caller actively using the views::WebView
// instance should increase/decrease the refcount.
class WebViewHandle : public base::RefCounted<WebViewHandle> {
public:
explicit WebViewHandle(Profile* profile);
// Returns an unowned pointer to the stored |web_view| instance.
views::WebView* web_view() { return web_view_.get(); }
private:
friend class base::RefCounted<WebViewHandle>;
~WebViewHandle();
std::unique_ptr<views::WebView> web_view_;
DISALLOW_COPY_AND_ASSIGN(WebViewHandle);
};
} // namespace chromeos
#endif // CHROME_BROWSER_CHROMEOS_LOGIN_UI_WEB_VIEW_HANDLE_H_
......@@ -22,7 +22,10 @@
#include "chrome/browser/chromeos/app_mode/kiosk_app_manager.h"
#include "chrome/browser/chromeos/login/ui/login_display_host_impl.h"
#include "chrome/browser/chromeos/login/ui/proxy_settings_dialog.h"
#include "chrome/browser/chromeos/login/ui/shared_web_view.h"
#include "chrome/browser/chromeos/login/ui/shared_web_view_factory.h"
#include "chrome/browser/chromeos/login/ui/web_contents_set_background_color.h"
#include "chrome/browser/chromeos/login/ui/web_view_handle.h"
#include "chrome/browser/chromeos/login/ui/webui_login_display.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h"
#include "chrome/browser/chromeos/settings/cros_settings.h"
......@@ -176,7 +179,8 @@ class WebUILoginView::StatusAreaFocusTraversable
// WebUILoginView public: ------------------------------------------------------
WebUILoginView::WebUILoginView() {
WebUILoginView::WebUILoginView(const WebViewSettings& settings)
: settings_(settings) {
registrar_.Add(this,
chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE,
content::NotificationService::AllSources());
......@@ -252,15 +256,24 @@ WebUILoginView::~WebUILoginView() {
} else {
NOTIMPLEMENTED();
}
// If the WebView is going to be reused, make sure we call teardown.
if (!webui_login_->HasOneRef())
GetWebUI()->CallJavascriptFunctionUnsafe("cr.ui.Oobe.teardown");
// Clear any delegates we have set on the WebView.
WebContents* web_contents = web_view()->GetWebContents();
WebContentsModalDialogManager::FromWebContents(web_contents)
->SetDelegate(nullptr);
web_contents->SetDelegate(nullptr);
}
void WebUILoginView::Init() {
Profile* signin_profile = ProfileHelper::GetSigninProfile();
webui_login_ = new views::WebView(signin_profile);
webui_login_->set_allow_accelerators(true);
AddChildView(webui_login_);
// static
void WebUILoginView::InitializeWebView(views::WebView* web_view) {
WebContents* web_contents = web_view->GetWebContents();
WebContents* web_contents = webui_login_->GetWebContents();
WebContentsSetBackgroundColor::CreateForWebContentsWithColor(
web_contents, SK_ColorTRANSPARENT);
// Ensure that the login UI has a tab ID, which will allow the GAIA auth
// extension's background script to tell it apart from a captive portal window
......@@ -274,16 +287,38 @@ void WebUILoginView::Init() {
// LoginHandlerViews uses a constrained window for the password manager view.
WebContentsModalDialogManager::CreateForWebContents(web_contents);
WebContentsModalDialogManager::FromWebContents(web_contents)->
SetDelegate(this);
web_contents->SetDelegate(this);
extensions::SetViewType(web_contents, extensions::VIEW_TYPE_COMPONENT);
extensions::ChromeExtensionWebContentsObserver::CreateForWebContents(
web_contents);
content::RendererPreferences* prefs = web_contents->GetMutableRendererPrefs();
renderer_preferences_util::UpdateFromSystemSettings(
prefs, signin_profile, web_contents);
prefs, ProfileHelper::GetSigninProfile(), web_contents);
}
void WebUILoginView::Init() {
Profile* signin_profile = ProfileHelper::GetSigninProfile();
if (!settings_.preloaded_url.is_empty()) {
SharedWebView* shared_web_view =
SharedWebViewFactory::GetForProfile(signin_profile);
is_reusing_webview_ =
shared_web_view->Get(settings_.preloaded_url, &webui_login_);
} else {
webui_login_ = new WebViewHandle(signin_profile);
is_reusing_webview_ = false;
}
WebContents* web_contents = web_view()->GetWebContents();
if (!is_reusing_webview_)
InitializeWebView(web_view());
web_view()->set_allow_accelerators(true);
AddChildView(web_view());
WebContentsModalDialogManager::FromWebContents(web_contents)
->SetDelegate(this);
web_contents->SetDelegate(this);
status_area_widget_host_ = new views::View;
AddChildView(status_area_widget_host_);
......@@ -294,7 +329,7 @@ const char* WebUILoginView::GetClassName() const {
}
void WebUILoginView::RequestFocus() {
webui_login_->RequestFocus();
web_view()->RequestFocus();
}
web_modal::WebContentsModalDialogHost*
......@@ -334,7 +369,7 @@ bool WebUILoginView::AcceleratorPressed(
if (entry == accel_map_.end())
return false;
if (!webui_login_)
if (!web_view())
return true;
content::WebUI* web_ui = GetWebUI();
......@@ -351,12 +386,15 @@ gfx::NativeWindow WebUILoginView::GetNativeWindow() const {
return GetWidget()->GetNativeWindow();
}
void WebUILoginView::LoadURL(const GURL & url) {
webui_login_->LoadInitialURL(url);
webui_login_->RequestFocus();
void WebUILoginView::LoadURL(const GURL& url) {
// If a preloaded_url is provided then |url| must match it.
DCHECK(settings_.preloaded_url.is_empty() || url == settings_.preloaded_url);
WebContentsSetBackgroundColor::CreateForWebContentsWithColor(
GetWebContents(), SK_ColorTRANSPARENT);
if (is_reusing_webview_ && !settings_.preloaded_url.is_empty())
GetWebUI()->CallJavascriptFunctionUnsafe("cr.ui.Oobe.reload");
else
web_view()->LoadInitialURL(url);
web_view()->RequestFocus();
// There is no Shell instance while running in mash.
if (chrome::IsRunningInMash())
......@@ -373,14 +411,17 @@ void WebUILoginView::LoadURL(const GURL & url) {
}
content::WebUI* WebUILoginView::GetWebUI() {
return webui_login_->web_contents()->GetWebUI();
return web_view()->web_contents()->GetWebUI();
}
content::WebContents* WebUILoginView::GetWebContents() {
return webui_login_->web_contents();
return web_view()->web_contents();
}
OobeUI* WebUILoginView::GetOobeUI() {
if (!GetWebUI())
return nullptr;
return static_cast<OobeUI*>(GetWebUI()->GetController());
}
......@@ -442,8 +483,8 @@ void WebUILoginView::SetUIEnabled(bool enabled) {
// WebUILoginView protected: ---------------------------------------------------
void WebUILoginView::Layout() {
DCHECK(webui_login_);
webui_login_->SetBoundsRect(bounds());
DCHECK(web_view());
web_view()->SetBoundsRect(bounds());
for (auto& observer : observer_list_)
observer.OnPositionRequiresUpdate();
......@@ -459,9 +500,9 @@ void WebUILoginView::ChildPreferredSizeChanged(View* child) {
void WebUILoginView::AboutToRequestFocusFromTabTraversal(bool reverse) {
// Return the focus to the web contents.
webui_login_->web_contents()->FocusThroughTabTraversal(reverse);
web_view()->web_contents()->FocusThroughTabTraversal(reverse);
GetWidget()->Activate();
webui_login_->web_contents()->Focus();
web_view()->web_contents()->Focus();
}
void WebUILoginView::Observe(int type,
......@@ -479,6 +520,10 @@ void WebUILoginView::Observe(int type,
}
}
views::WebView* WebUILoginView::web_view() {
return webui_login_->web_view();
}
// WebUILoginView private: -----------------------------------------------------
bool WebUILoginView::HandleContextMenu(
......
......@@ -9,6 +9,7 @@
#include <string>
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/observer_list.h"
#include "chrome/browser/ui/chrome_web_modal_dialog_manager_delegate.h"
#include "components/web_modal/web_contents_modal_dialog_host.h"
......@@ -18,8 +19,7 @@
#include "ui/views/controls/webview/unhandled_keyboard_event_handler.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"
class GURL;
#include "url/gurl.h"
namespace content {
class WebUI;
......@@ -34,6 +34,7 @@ class Widget;
namespace chromeos {
class OobeUI;
class WebViewHandle;
// View used to render a WebUI supporting Widget. This widget is used for the
// WebUI based start up and lock screens. It contains a WebView.
......@@ -43,10 +44,15 @@ class WebUILoginView : public views::View,
public ChromeWebModalDialogManagerDelegate,
public web_modal::WebContentsModalDialogHost {
public:
struct WebViewSettings {
// URL of the WebView to preload and reuse across WebUILoginView instances.
GURL preloaded_url;
};
// Internal class name.
static const char kViewClassName[];
WebUILoginView();
explicit WebUILoginView(const WebViewSettings& settings);
~WebUILoginView() override;
// Initializes the webui login view.
......@@ -105,6 +111,8 @@ class WebUILoginView : public views::View,
}
protected:
static void InitializeWebView(views::WebView* web_view);
// Overridden from views::View:
void Layout() override;
void OnLocaleChanged() override;
......@@ -116,8 +124,7 @@ class WebUILoginView : public views::View,
const content::NotificationSource& source,
const content::NotificationDetails& details) override;
// WebView for rendering a webpage as a webui login.
views::WebView* webui_login_ = nullptr;
views::WebView* web_view();
private:
// Map type for the accelerator-to-identifier map.
......@@ -148,6 +155,15 @@ class WebUILoginView : public views::View,
content::NotificationRegistrar registrar_;
// WebView configuration options.
const WebViewSettings settings_;
// WebView for rendering a webpage as a webui login.
scoped_refptr<WebViewHandle> webui_login_;
// True if the current webview instance (ie, GetWebUI()) has been reused.
bool is_reusing_webview_ = false;
// Converts keyboard events on the WebContents to accelerators.
views::UnhandledKeyboardEventHandler unhandled_keyboard_event_handler_;
......
......@@ -894,12 +894,7 @@ void SigninScreenHandler::ReloadGaia(bool force_reload) {
}
void SigninScreenHandler::Initialize() {
// If delegate_ is nullptr here (e.g. WebUIScreenLocker has been destroyed),
// don't do anything, just return.
if (!delegate_)
return;
if (show_on_init_) {
if (delegate_ && show_on_init_) {
show_on_init_ = false;
ShowImpl();
}
......
......@@ -117,13 +117,13 @@ ScreenlockBridge* ScreenlockBridge::Get() {
}
void ScreenlockBridge::SetLockHandler(LockHandler* lock_handler) {
DCHECK(lock_handler_ == nullptr || lock_handler == nullptr);
// Don't notify observers if there is no change -- i.e. if the screen was
// already unlocked, and is remaining unlocked.
if (lock_handler == lock_handler_)
return;
DCHECK(lock_handler_ == nullptr || lock_handler == nullptr);
// TODO(isherman): If |lock_handler| is null, then |lock_handler_| might have
// been freed. Cache the screen type rather than querying it below.
LockHandler::ScreenType screen_type;
......
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