Commit 8b86c254 authored by Muyuan Li's avatar Muyuan Li Committed by Commit Bot

Reland: Refactor AXSnapshotNodeAndroid into mojo.

This is a reland of CL:
https://chromium-review.googlesource.com/c/chromium/src/+/1014464
which was reverted by:
https://chromium-review.googlesource.com/c/chromium/src/+/1043187
due to compilation error.

Original commit message:
> Refactor AXSnapshotNodeAndroid into mojo.
>
> Test: browser_tests --gtest_filter=ArcVoiceInteractionArcHomeServiceTest.*
TBR=dmazzoni@chromium.org,sky@chromium.org,dcheng@chromium.org

Change-Id: Ieca4bf35f3aa913683e8d1ce34160731317a797f
Reviewed-on: https://chromium-review.googlesource.com/1045487Reviewed-by: default avatarMuyuan Li <muyuanli@chromium.org>
Commit-Queue: Muyuan Li <muyuanli@chromium.org>
Cr-Commit-Position: refs/heads/master@{#556983}
parent 865fffd1
......@@ -33,6 +33,7 @@ source_set("chromeos") {
"//chromeos:vm_applications_apps_proto",
"//components/policy/proto",
"//content/app/resources",
"//ui/accessibility/mojom",
"//ui/chromeos/resources",
"//ui/chromeos/strings",
"//ui/resources",
......
......@@ -31,7 +31,8 @@
#include "components/arc/connection_holder.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/web_contents.h"
#include "ui/accessibility/platform/ax_snapshot_node_android_platform.h"
#include "ui/accessibility/ax_assistant_structure.h"
#include "ui/accessibility/mojom/ax_assistant_structure.mojom.h"
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host.h"
#include "ui/gfx/geometry/dip_util.h"
......@@ -49,30 +50,33 @@ constexpr base::TimeDelta kWizardCompletedTimeout =
base::TimeDelta::FromMinutes(1);
mojom::VoiceInteractionStructurePtr CreateVoiceInteractionStructure(
const ui::AXSnapshotNodeAndroid& view_structure) {
const ui::AssistantTree& tree,
const ui::AssistantNode& node) {
auto structure = mojom::VoiceInteractionStructure::New();
structure->text = view_structure.text;
structure->text_size = view_structure.text_size;
structure->text = node.text;
structure->text_size = node.text_size;
structure->bold = view_structure.bold;
structure->italic = view_structure.italic;
structure->underline = view_structure.underline;
structure->line_through = view_structure.line_through;
structure->color = view_structure.color;
structure->bgcolor = view_structure.bgcolor;
structure->bold = node.bold;
structure->italic = node.italic;
structure->underline = node.underline;
structure->line_through = node.line_through;
structure->color = node.color;
structure->bgcolor = node.bgcolor;
structure->role = view_structure.role;
structure->role = node.role;
structure->class_name = view_structure.class_name;
structure->rect = view_structure.rect;
structure->class_name = node.class_name;
structure->rect = node.rect;
if (view_structure.has_selection) {
structure->selection = gfx::Range(view_structure.start_selection,
view_structure.end_selection);
if (node.selection.has_value()) {
structure->selection =
gfx::Range(node.selection->start(), node.selection->end());
}
for (auto& child : view_structure.children)
structure->children.push_back(CreateVoiceInteractionStructure(*child));
for (int child : node.children_indices) {
structure->children.push_back(
CreateVoiceInteractionStructure(tree, *tree.nodes[child]));
}
return structure;
}
......@@ -97,8 +101,10 @@ void RequestVoiceInteractionStructureCallback(
title_node->rect = gfx::Rect(bounds.size());
title_node->class_name = "android.view.dummy.WebTitle";
title_node->text = title;
auto assistant_tree = ui::CreateAssistantTree(update, false);
title_node->children.push_back(CreateVoiceInteractionStructure(
*ui::AXSnapshotNodeAndroid::Create(update, false)));
*assistant_tree, *assistant_tree->nodes.front()));
root->children.push_back(std::move(title_node));
std::move(callback).Run(std::move(root));
}
......@@ -334,8 +340,9 @@ void ArcVoiceInteractionArcHomeService::OnVoiceInteractionOobeSetupComplete() {
// static
mojom::VoiceInteractionStructurePtr
ArcVoiceInteractionArcHomeService::CreateVoiceInteractionStructureForTesting(
const ui::AXSnapshotNodeAndroid& view_structure) {
return CreateVoiceInteractionStructure(view_structure);
const ui::AssistantTree& tree,
const ui::AssistantNode& node) {
return CreateVoiceInteractionStructure(tree, node);
}
} // namespace arc
......@@ -20,8 +20,9 @@ class BrowserContext;
} // namespace content
namespace ui {
struct AXSnapshotNodeAndroid;
} // ui
struct AssistantTree;
struct AssistantNode;
} // namespace ui
namespace arc {
......@@ -66,8 +67,8 @@ class ArcVoiceInteractionArcHomeService
void OnVoiceInteractionOobeSetupComplete() override;
static mojom::VoiceInteractionStructurePtr
CreateVoiceInteractionStructureForTesting(
const ui::AXSnapshotNodeAndroid& view_structure);
CreateVoiceInteractionStructureForTesting(const ui::AssistantTree& tree,
const ui::AssistantNode& node);
void set_assistant_started_timeout_for_testing(
const base::TimeDelta& timeout) {
......
......@@ -17,8 +17,8 @@
#include "chrome/test/base/interactive_test_utils.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/test_utils.h"
#include "ui/accessibility/ax_assistant_structure.h"
#include "ui/accessibility/ax_tree_update.h"
#include "ui/accessibility/platform/ax_snapshot_node_android_platform.h"
namespace arc {
......@@ -63,9 +63,11 @@ class ArcVoiceInteractionArcHomeServiceTest : public InProcessBrowserTest {
base::Unretained(&waiter)),
ui::kAXModeComplete);
waiter.Wait();
auto node = ui::AXSnapshotNodeAndroid::Create(waiter.snapshot(), false);
std::unique_ptr<ui::AssistantTree> tree =
ui::CreateAssistantTree(waiter.snapshot(), false);
return ArcVoiceInteractionArcHomeService::
CreateVoiceInteractionStructureForTesting(*node);
CreateVoiceInteractionStructureForTesting(*tree, *tree->nodes.front());
}
private:
......
......@@ -2152,6 +2152,8 @@ jumbo_source_set("browser") {
"//media",
"//media/capture/content/android",
"//media/capture/video/android",
"//ui/accessibility:ax_assistant",
"//ui/accessibility/mojom",
"//ui/android",
"//ui/compositor",
]
......
......@@ -16,9 +16,9 @@
#include "content/common/accessibility_messages.h"
#include "content/public/common/content_client.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/accessibility/ax_assistant_structure.h"
#include "ui/accessibility/ax_role_properties.h"
#include "ui/accessibility/platform/ax_android_constants.h"
#include "ui/accessibility/platform/ax_snapshot_node_android_platform.h"
#include "ui/accessibility/platform/ax_unique_id.h"
namespace {
......@@ -265,7 +265,7 @@ bool BrowserAccessibilityAndroid::IsHierarchical() const {
}
bool BrowserAccessibilityAndroid::IsLink() const {
return ui::AXSnapshotNodeAndroid::AXRoleIsLink(GetRole());
return ui::AXRoleIsLink(GetRole());
}
bool BrowserAccessibilityAndroid::IsMultiLine() const {
......@@ -370,8 +370,8 @@ bool BrowserAccessibilityAndroid::CanOpenPopup() const {
}
const char* BrowserAccessibilityAndroid::GetClassName() const {
return ui::AXSnapshotNodeAndroid::AXRoleToAndroidClassName(
GetRole(), PlatformGetParent() != nullptr);
return ui::AXRoleToAndroidClassName(GetRole(),
PlatformGetParent() != nullptr);
}
base::string16 BrowserAccessibilityAndroid::GetText() const {
......@@ -421,7 +421,7 @@ base::string16 BrowserAccessibilityAndroid::GetText() const {
if (text.empty() && (IsLink() || GetRole() == ax::mojom::Role::kImage) &&
!HasExplicitlyEmptyName()) {
base::string16 url = GetString16Attribute(ax::mojom::StringAttribute::kUrl);
text = ui::AXSnapshotNodeAndroid::AXUrlBaseText(url);
text = ui::AXUrlBaseText(url);
}
return text;
......
......@@ -41,8 +41,9 @@
#include "content/public/common/content_switches.h"
#include "jni/WebContentsImpl_jni.h"
#include "net/android/network_library.h"
#include "ui/accessibility/ax_assistant_structure.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/accessibility/platform/ax_snapshot_node_android_platform.h"
#include "ui/accessibility/mojom/ax_assistant_structure.mojom.h"
#include "ui/android/overscroll_refresh_handler.h"
#include "ui/android/window_android.h"
#include "ui/gfx/android/java_bitmap.h"
......@@ -90,7 +91,8 @@ void SmartClipCallback(const ScopedJavaGlobalRef<jobject>& callback,
ScopedJavaLocalRef<jobject> JNI_WebContentsImpl_CreateJavaAXSnapshot(
JNIEnv* env,
const ui::AXSnapshotNodeAndroid* node,
const ui::AssistantTree* tree,
const ui::AssistantNode* node,
bool is_root) {
ScopedJavaLocalRef<jstring> j_text =
ConvertUTF16ToJavaString(env, node->text);
......@@ -103,15 +105,16 @@ ScopedJavaLocalRef<jobject> JNI_WebContentsImpl_CreateJavaAXSnapshot(
node->text_size, node->bold, node->italic, node->underline,
node->line_through, j_class);
if (node->has_selection) {
if (node->selection.has_value()) {
Java_WebContentsImpl_setAccessibilitySnapshotSelection(
env, j_node, node->start_selection, node->end_selection);
env, j_node, node->selection->start(), node->selection->end());
}
for (auto& child : node->children) {
for (int child : node->children_indices) {
Java_WebContentsImpl_addAccessibilityNodeAsChild(
env, j_node,
JNI_WebContentsImpl_CreateJavaAXSnapshot(env, child.get(), false));
JNI_WebContentsImpl_CreateJavaAXSnapshot(
env, tree, tree->nodes[child].get(), false));
}
return j_node;
}
......@@ -127,10 +130,10 @@ void AXTreeSnapshotCallback(const ScopedJavaGlobalRef<jobject>& callback,
std::unique_ptr<BrowserAccessibilityManagerAndroid> manager(
static_cast<BrowserAccessibilityManagerAndroid*>(
BrowserAccessibilityManager::Create(result, nullptr)));
auto snapshot = ui::AXSnapshotNodeAndroid::Create(
result, manager->ShouldExposePasswordText());
ScopedJavaLocalRef<jobject> j_root =
JNI_WebContentsImpl_CreateJavaAXSnapshot(env, snapshot.get(), true);
std::unique_ptr<ui::AssistantTree> assistant_tree =
ui::CreateAssistantTree(result, manager->ShouldExposePasswordText());
ScopedJavaLocalRef<jobject> j_root = JNI_WebContentsImpl_CreateJavaAXSnapshot(
env, assistant_tree.get(), assistant_tree->nodes.front().get(), true);
Java_WebContentsImpl_onAccessibilitySnapshot(env, j_root, callback);
}
......
......@@ -73,8 +73,6 @@ component("accessibility") {
"platform/ax_android_constants.h",
"platform/ax_platform_node.cc",
"platform/ax_platform_node.h",
"platform/ax_snapshot_node_android_platform.cc",
"platform/ax_snapshot_node_android_platform.h",
"platform/ax_unique_id.cc",
"platform/ax_unique_id.h",
]
......@@ -151,6 +149,16 @@ component("accessibility") {
}
}
source_set("ax_assistant") {
sources = [
"ax_assistant_structure.cc",
"ax_assistant_structure.h",
]
deps = [
":accessibility",
]
}
static_library("test_support") {
testonly = true
sources = [
......
......@@ -13,5 +13,8 @@ include_rules = [
specific_include_rules = {
"run_all_unittests.cc": [
"+mojo/edk/embedder",
]
],
"ax_assistant_util.h": [
"+ui/accessibility/mojom",
],
}
// Copyright 2018 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 UI_ACCESSIBILITY_AX_ASSISTANT_STRUCTURE_H_
#define UI_ACCESSIBILITY_AX_ASSISTANT_STRUCTURE_H_
#include <cstdint>
#include <vector>
#include "base/macros.h"
#include "base/optional.h"
#include "base/strings/string16.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_export.h"
#include "ui/accessibility/ax_tree_update.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/range/range.h"
namespace ui {
struct AssistantNode {
AssistantNode();
AssistantNode(const AssistantNode& other);
~AssistantNode();
std::vector<int32_t> children_indices;
// Geometry of the view in pixels
gfx::Rect rect;
// Text of the view.
base::string16 text;
// Text properties
float text_size;
uint32_t color;
uint32_t bgcolor;
bool bold;
bool italic;
bool underline;
bool line_through;
// Selected portion of the text.
base::Optional<gfx::Range> selection;
// Fake Android view class name of the element. Each node is assigned
// a closest approximation of Android's views to keep the server happy.
std::string class_name;
// Accessibility functionality of the node inferred from DOM or based on HTML
// role attribute.
base::Optional<std::string> role;
};
struct AssistantTree {
AssistantTree();
~AssistantTree();
std::vector<std::unique_ptr<AssistantNode>> nodes;
private:
DISALLOW_COPY_AND_ASSIGN(AssistantTree);
};
std::unique_ptr<AssistantTree> CreateAssistantTree(const AXTreeUpdate& update,
bool show_password);
bool AXRoleIsLink(ax::mojom::Role role);
base::string16 AXUrlBaseText(base::string16 url);
const char* AXRoleToAndroidClassName(ax::mojom::Role role, bool has_parent);
} // namespace ui
#endif // UI_ACCESSIBILITY_AX_ASSISTANT_STRUCTURE_H_
......@@ -6,6 +6,7 @@ import("//mojo/public/tools/bindings/mojom.gni")
mojom("mojom") {
sources = [
"ax_assistant_structure.mojom",
"ax_node_data.mojom",
"ax_tree_data.mojom",
"ax_tree_update.mojom",
......@@ -16,5 +17,6 @@ mojom("mojom") {
"//ui/accessibility:ax_enums_mojo",
"//ui/gfx/geometry/mojo",
"//ui/gfx/mojo",
"//ui/gfx/range/mojo",
]
}
// Copyright 2018 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.
module ax.mojom;
import "mojo/public/mojom/base/string16.mojom";
import "ui/gfx/geometry/mojo/geometry.mojom";
import "ui/gfx/range/mojo/range.mojom";
// Tree structure for assistant. The tree is represented as a flat array of
// nodes, each containing a children_indices vector that points to its child
// nodes. The purpose is to work around max depth restriction of recursive
// data structure in mojo.
struct AssistantTree {
array<AssistantNode> nodes;
};
// Represents view structure to be passed to assistant. The view structure is
// synthesized from the AXNode.
struct AssistantNode {
array<int32> children_indices;
// Geometry of the view in pixels
gfx.mojom.Rect rect;
// Text of the view.
mojo_base.mojom.String16 text;
// Text properties
float text_size;
uint32 color;
uint32 bgcolor;
bool bold;
bool italic;
bool underline;
bool line_through;
// Selected portion of the text.
gfx.mojom.Range? selection;
// Fake Android view class name of the element. Each node is assigned
// a closest approximation of Android's views to keep the server happy.
string class_name;
// Accessibility functionality of the node inferred from DOM or based on HTML
// role attribute.
string? role;
};
# Copyright 2018 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.
mojom = "//ui/accessibility/mojom/ax_assistant_structure.mojom"
public_headers = [ "//ui/accessibility/ax_assistant_structure.h" ]
traits_headers =
[ "//ui/accessibility/mojom/ax_assistant_structure_mojom_traits.h" ]
sources = [
"ax_assistant_structure_mojom_traits.cc",
"ax_assistant_structure_mojom_traits.h",
]
public_deps = [
"//ui/accessibility:ax_assistant",
"//ui/gfx",
"//ui/gfx/geometry/mojo",
"//ui/gfx/geometry/mojo:struct_traits",
"//ui/gfx/range/mojo",
"//ui/gfx/range/mojo:struct_traits",
]
type_mappings = [
"ax.mojom.AssistantTree=ui::AssistantTree[move_only]",
"ax.mojom.AssistantNode=std::unique_ptr<ui::AssistantNode>[move_only]",
]
// Copyright 2018 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 "ui/accessibility/mojom/ax_assistant_structure_mojom_traits.h"
#include "mojo/public/cpp/base/string16_mojom_traits.h"
#include "ui/gfx/geometry/mojo/geometry_struct_traits.h"
#include "ui/gfx/range/mojo/range_struct_traits.h"
namespace mojo {
// static
bool StructTraits<ax::mojom::AssistantTreeDataView, ui::AssistantTree>::Read(
ax::mojom::AssistantTreeDataView data,
ui::AssistantTree* out) {
if (!data.ReadNodes(&out->nodes))
return false;
for (size_t i = 0; i < out->nodes.size(); i++) {
// Each child's index should be greater than its parent and within the array
// bounds. This implies that there is no circle in the tree.
for (size_t child_index : out->nodes[i]->children_indices) {
if (child_index <= i || child_index >= out->nodes.size())
return false;
}
}
return true;
}
// static
bool StructTraits<ax::mojom::AssistantNodeDataView,
std::unique_ptr<ui::AssistantNode>>::
Read(ax::mojom::AssistantNodeDataView data,
std::unique_ptr<ui::AssistantNode>* out) {
DCHECK(!out->get());
*out = std::make_unique<ui::AssistantNode>();
(*out)->bgcolor = data.bgcolor();
(*out)->bold = data.bold();
(*out)->color = data.color();
(*out)->italic = data.italic();
(*out)->line_through = data.line_through();
(*out)->underline = data.underline();
if (!data.ReadRect(&(*out)->rect) || !data.ReadText(&(*out)->text) ||
!data.ReadRole(&(*out)->role) ||
!data.ReadSelection(&(*out)->selection) ||
!data.ReadChildrenIndices(&(*out)->children_indices) ||
!data.ReadClassName(&(*out)->class_name)) {
return false;
}
return true;
}
} // namespace mojo
// Copyright 2018 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 UI_ACCESSIBILITY_MOJOM_AX_ASSISTANT_STRUCTURE_MOJOM_TRAITS_H_
#define UI_ACCESSIBILITY_MOJOM_AX_ASSISTANT_STRUCTURE_MOJOM_TRAITS_H_
#include <memory>
#include "ui/accessibility/ax_assistant_structure.h"
#include "ui/accessibility/mojom/ax_assistant_structure.mojom-shared.h"
#include "ui/accessibility/mojom/ax_assistant_structure.mojom.h"
namespace mojo {
template <>
struct StructTraits<ax::mojom::AssistantTreeDataView, ui::AssistantTree> {
static bool Read(ax::mojom::AssistantTreeDataView data,
ui::AssistantTree* out);
};
template <>
struct StructTraits<ax::mojom::AssistantNodeDataView,
std::unique_ptr<ui::AssistantNode>> {
static std::vector<int32_t> children_indices(
const std::unique_ptr<ui::AssistantNode>& node) {
return node->children_indices;
}
static gfx::Rect rect(const std::unique_ptr<ui::AssistantNode>& node) {
return node->rect;
}
static base::string16 text(const std::unique_ptr<ui::AssistantNode>& node) {
return node->text;
}
static float text_size(const std::unique_ptr<ui::AssistantNode>& node) {
return node->text_size;
}
static uint32_t color(const std::unique_ptr<ui::AssistantNode>& node) {
return node->color;
}
static uint32_t bgcolor(const std::unique_ptr<ui::AssistantNode>& node) {
return node->bgcolor;
}
static bool bold(const std::unique_ptr<ui::AssistantNode>& node) {
return node->bold;
}
static bool italic(const std::unique_ptr<ui::AssistantNode>& node) {
return node->italic;
}
static bool underline(const std::unique_ptr<ui::AssistantNode>& node) {
return node->underline;
}
static bool line_through(const std::unique_ptr<ui::AssistantNode>& node) {
return node->line_through;
}
static base::Optional<gfx::Range> selection(
const std::unique_ptr<ui::AssistantNode>& node) {
return node->selection;
}
static std::string class_name(
const std::unique_ptr<ui::AssistantNode>& node) {
return node->class_name;
}
static base::Optional<std::string> role(
const std::unique_ptr<ui::AssistantNode>& node) {
return node->role;
}
static bool Read(ax::mojom::AssistantNodeDataView data,
std::unique_ptr<ui::AssistantNode>* out);
};
} // namespace mojo
#endif // UI_ACCESSIBILITY_MOJOM_AX_ASSISTANT_STRUCTURE_MOJOM_TRAITS_H_
......@@ -6,4 +6,5 @@ typemaps = [
"//ui/accessibility/mojom/ax_node_data.typemap",
"//ui/accessibility/mojom/ax_tree_data.typemap",
"//ui/accessibility/mojom/ax_tree_update.typemap",
"//ui/accessibility/mojom/ax_assistant_structure.typemap",
]
// Copyright 2017 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 UI_ACCESSIBILITY_PLATFORM_AX_SNAPSHOT_ANDROID_H_
#define UI_ACCESSIBILITY_PLATFORM_AX_SNAPSHOT_ANDROID_H_
#include <cstdint>
#include <memory>
#include <vector>
#include "base/optional.h"
#include "base/strings/string16.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_export.h"
#include "ui/accessibility/ax_tree_update.h"
#include "ui/gfx/geometry/rect.h"
namespace ui {
class AXNode;
class AXTree;
// An intermediate representation of the accessibility snapshot
// in order to share code between ARC and Android. The field names
// are kept consistent with AccessibilitySnapshotNode.java.
struct AXSnapshotNodeAndroid {
// Builds a whole tree of AXSnapshotNodeAndroid objects from
// an AXTreeUpdate.
AX_EXPORT static std::unique_ptr<AXSnapshotNodeAndroid> Create(
const AXTreeUpdate& update,
bool show_password);
// Returns a fake Android view class that is a closest
// approximation of the ax::mojom::Role.
AX_EXPORT static const char* AXRoleToAndroidClassName(ax::mojom::Role role,
bool has_parent);
AX_EXPORT static bool AXRoleIsLink(ax::mojom::Role role);
AX_EXPORT static base::string16 AXUrlBaseText(base::string16 url);
AX_EXPORT ~AXSnapshotNodeAndroid();
gfx::Rect rect;
base::string16 text;
float text_size;
int32_t color;
int32_t bgcolor;
bool bold;
bool italic;
bool underline;
bool line_through;
bool has_selection;
int32_t start_selection;
int32_t end_selection;
base::Optional<std::string> role;
std::string class_name;
std::vector<std::unique_ptr<AXSnapshotNodeAndroid>> children;
private:
AXSnapshotNodeAndroid();
struct WalkAXTreeConfig {
bool should_select_leaf;
const bool show_password;
};
static std::unique_ptr<AXSnapshotNodeAndroid> WalkAXTreeDepthFirst(
const AXNode* node,
gfx::Rect rect,
const AXTreeUpdate& update,
const AXTree* tree,
WalkAXTreeConfig& config);
DISALLOW_COPY_AND_ASSIGN(AXSnapshotNodeAndroid);
};
} // namespace ui
#endif // UI_ACCESSIBILITY_PLATFORM_AX_SNAPSHOT_ANDROID_H_
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