Commit bd149bb9 authored by Dave Vandyke's avatar Dave Vandyke Committed by Chromium LUCI CQ

[DNR] Add tabUpdate option to setExtensionActionOptions API

Add the tabUpdate option to the setExtensionActionOptions API which
provides a way for extension developers to manually increase or decrease
the action count for a given tab.

Skipping presubmit since this seems to be hitting crbug.com/956368.

Bug: 1123296
No-Presubmit: True
Change-Id: Ie235f12841029aa45f92ed61b2e300d78d4d0fab
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2489913
Commit-Queue: Karan Bhatia <karandeepb@chromium.org>
Reviewed-by: default avatarKaran Bhatia <karandeepb@chromium.org>
Cr-Commit-Position: refs/heads/master@{#832110}
parent 5c3ba407
...@@ -98,6 +98,7 @@ ...@@ -98,6 +98,7 @@
#include "extensions/common/api/declarative_net_request/test_utils.h" #include "extensions/common/api/declarative_net_request/test_utils.h"
#include "extensions/common/api/extension_action/action_info.h" #include "extensions/common/api/extension_action/action_info.h"
#include "extensions/common/constants.h" #include "extensions/common/constants.h"
#include "extensions/common/error_utils.h"
#include "extensions/common/extension_features.h" #include "extensions/common/extension_features.h"
#include "extensions/common/extension_id.h" #include "extensions/common/extension_id.h"
#include "extensions/common/file_util.h" #include "extensions/common/file_util.h"
...@@ -396,17 +397,20 @@ class DeclarativeNetRequestBrowserTest ...@@ -396,17 +397,20 @@ class DeclarativeNetRequestBrowserTest
UnorderedElementsAreArray(expected_ruleset_ids)); UnorderedElementsAreArray(expected_ruleset_ids));
} }
void SetActionsAsBadgeText(const ExtensionId& extension_id, bool pref) { std::string SetExtensionActionOptions(const ExtensionId& extension_id,
const char* pref_string = pref ? "true" : "false"; const std::string& options) {
static constexpr char kSetExtensionActionOptionsScript[] = R"( static constexpr char kSetExtensionActionOptionsScript[] = R"(
chrome.declarativeNetRequest.setExtensionActionOptions( chrome.declarativeNetRequest.setExtensionActionOptions(%s,
{displayActionCountAsBadgeText: %s}); () => {
window.domAutomationController.send("done"); window.domAutomationController.send(chrome.runtime.lastError ?
chrome.runtime.lastError.message : 'success');
}
);
)"; )";
ExecuteScriptInBackgroundPage( return ExecuteScriptInBackgroundPage(
extension_id, extension_id,
base::StringPrintf(kSetExtensionActionOptionsScript, pref_string)); base::StringPrintf(kSetExtensionActionOptionsScript, options.c_str()));
} }
// Navigates frame with name |frame_name| to |url|. // Navigates frame with name |frame_name| to |url|.
...@@ -3160,8 +3164,12 @@ IN_PROC_BROWSER_TEST_P(DeclarativeNetRequestBrowserTest, ...@@ -3160,8 +3164,12 @@ IN_PROC_BROWSER_TEST_P(DeclarativeNetRequestBrowserTest,
EXPECT_EQ(default_badge_text, EXPECT_EQ(default_badge_text,
query_badge_text(extension_2->id(), first_tab_id)); query_badge_text(extension_2->id(), first_tab_id));
SetActionsAsBadgeText(extension_1->id(), true); EXPECT_EQ(SetExtensionActionOptions(extension_1->id(),
SetActionsAsBadgeText(extension_2->id(), true); "{displayActionCountAsBadgeText: true}"),
"success");
EXPECT_EQ(SetExtensionActionOptions(extension_2->id(),
"{displayActionCountAsBadgeText: true}"),
"success");
// After enabling the preference the visible badge text should remain as the // After enabling the preference the visible badge text should remain as the
// default initially, as the action count for the tab is still 0 for both // default initially, as the action count for the tab is still 0 for both
...@@ -3215,8 +3223,12 @@ IN_PROC_BROWSER_TEST_P(DeclarativeNetRequestBrowserTest, ...@@ -3215,8 +3223,12 @@ IN_PROC_BROWSER_TEST_P(DeclarativeNetRequestBrowserTest,
EXPECT_EQ(declarative_net_request::kActionCountPlaceholderBadgeText, EXPECT_EQ(declarative_net_request::kActionCountPlaceholderBadgeText,
query_badge_text(extension_1->id(), first_tab_id)); query_badge_text(extension_1->id(), first_tab_id));
SetActionsAsBadgeText(extension_1->id(), false); EXPECT_EQ(SetExtensionActionOptions(extension_1->id(),
SetActionsAsBadgeText(extension_2->id(), false); "{displayActionCountAsBadgeText: false}"),
"success");
EXPECT_EQ(SetExtensionActionOptions(extension_2->id(),
"{displayActionCountAsBadgeText: false}"),
"success");
// Switching the preference off should cause the extension queried badge text // Switching the preference off should cause the extension queried badge text
// to be the explicitly set badge text for this tab if it exists. In this // to be the explicitly set badge text for this tab if it exists. In this
...@@ -3286,7 +3298,9 @@ IN_PROC_BROWSER_TEST_P(DeclarativeNetRequestBrowserTest, ...@@ -3286,7 +3298,9 @@ IN_PROC_BROWSER_TEST_P(DeclarativeNetRequestBrowserTest,
TestExtensionActionAPIObserver test_api_observer( TestExtensionActionAPIObserver test_api_observer(
profile(), extension_id, {web_contents(), second_browser_contents}); profile(), extension_id, {web_contents(), second_browser_contents});
SetActionsAsBadgeText(extension_id, true); EXPECT_EQ(SetExtensionActionOptions(extension_id,
"{displayActionCountAsBadgeText: true}"),
"success");
// Wait until ExtensionActionAPI::NotifyChange is called, then perform a // Wait until ExtensionActionAPI::NotifyChange is called, then perform a
// sanity check that one action was matched, and this is reflected in the // sanity check that one action was matched, and this is reflected in the
...@@ -3594,6 +3608,124 @@ IN_PROC_BROWSER_TEST_P(DeclarativeNetRequestBrowserTest, ...@@ -3594,6 +3608,124 @@ IN_PROC_BROWSER_TEST_P(DeclarativeNetRequestBrowserTest,
} }
} }
// Test that the setExtensionActionOptions tabUpdate option works correctly.
IN_PROC_BROWSER_TEST_P(DeclarativeNetRequestBrowserTest,
ActionsMatchedCountAsBadgeTextTabUpdate) {
// Load the extension with a background script so scripts can be run from its
// generated background page.
set_config_flags(ConfigFlag::kConfig_HasBackgroundScript);
// Set up an extension with a blocking rule.
TestRule rule = CreateGenericRule();
rule.condition->url_filter = std::string("||abc.com");
rule.condition->resource_types = std::vector<std::string>({"sub_frame"});
ASSERT_NO_FATAL_FAILURE(LoadExtensionWithRules(
{rule}, "extension", {URLPattern::kAllUrlsPattern}));
const Extension* extension = last_loaded_extension();
ExtensionAction* action =
ExtensionActionManager::Get(web_contents()->GetBrowserContext())
->GetExtensionAction(*extension);
const std::string default_badge_text = "asdf";
action->SetBadgeText(ExtensionAction::kDefaultTabId, default_badge_text);
// Display action count as badge text for |extension|.
EXPECT_EQ(SetExtensionActionOptions(extension->id(),
"{displayActionCountAsBadgeText: true}"),
"success");
// Navigate to a page with two frames, the same-origin one should be blocked.
const GURL page_url =
embedded_test_server()->GetURL("abc.com", "/page_with_two_frames.html");
ui_test_utils::NavigateToURL(browser(), page_url);
int tab_id = ExtensionTabUtil::GetTabId(web_contents());
// Verify that the initial badge text reflects that the same-origin frame was
// blocked.
EXPECT_EQ("1", action->GetDisplayBadgeText(tab_id));
// Increment the action count.
EXPECT_EQ(SetExtensionActionOptions(
extension->id(),
base::StringPrintf("{tabUpdate: {tabId: %d, increment: 10}}",
tab_id)),
"success");
EXPECT_EQ("11", action->GetDisplayBadgeText(tab_id));
// An increment of 0 is ignored.
EXPECT_EQ(
SetExtensionActionOptions(
extension->id(),
base::StringPrintf("{tabUpdate: {tabId: %d, increment: 0}}", tab_id)),
"success");
EXPECT_EQ("11", action->GetDisplayBadgeText(tab_id));
// If the tab doesn't exist an error should be shown.
EXPECT_EQ(
SetExtensionActionOptions(
extension->id(),
base::StringPrintf("{tabUpdate: {tabId: %d, increment: 10}}", 999)),
ErrorUtils::FormatErrorMessage(declarative_net_request::kTabNotFoundError,
"999"));
EXPECT_EQ("11", action->GetDisplayBadgeText(tab_id));
// The action count should continue to increment when an action is taken.
NavigateFrame("frame1", page_url);
EXPECT_EQ("12", action->GetDisplayBadgeText(tab_id));
// The action count can be decremented.
EXPECT_EQ(SetExtensionActionOptions(
extension->id(),
base::StringPrintf("{tabUpdate: {tabId: %d, increment: -5}}",
tab_id)),
"success");
EXPECT_EQ("7", action->GetDisplayBadgeText(tab_id));
// Check that the action count cannot be decremented below 0. We fallback to
// displaying the default badge text when the action count is 0.
EXPECT_EQ(SetExtensionActionOptions(
extension->id(),
base::StringPrintf("{tabUpdate: {tabId: %d, increment: -10}}",
tab_id)),
"success");
EXPECT_EQ(default_badge_text, action->GetDisplayBadgeText(tab_id));
EXPECT_EQ(SetExtensionActionOptions(
extension->id(),
base::StringPrintf("{tabUpdate: {tabId: %d, increment: -1}}",
tab_id)),
"success");
EXPECT_EQ(default_badge_text, action->GetDisplayBadgeText(tab_id));
EXPECT_EQ(
SetExtensionActionOptions(
extension->id(),
base::StringPrintf("{tabUpdate: {tabId: %d, increment: 3}}", tab_id)),
"success");
EXPECT_EQ("3", action->GetDisplayBadgeText(tab_id));
// The action count cannot be incremented if the display action as badge text
// feature is not enabled.
EXPECT_EQ(
SetExtensionActionOptions(
extension->id(),
base::StringPrintf("{displayActionCountAsBadgeText: false, "
"tabUpdate: {tabId: %d, increment: 10}}",
tab_id)),
declarative_net_request::kIncrementActionCountWithoutUseAsBadgeTextError);
EXPECT_EQ(default_badge_text, action->GetDisplayBadgeText(tab_id));
// Any increment to the action count should not be ignored if we're enabling
// the preference.
EXPECT_EQ(SetExtensionActionOptions(
extension->id(),
base::StringPrintf("{displayActionCountAsBadgeText: true, "
"tabUpdate: {tabId: %d, increment: 5}}",
tab_id)),
"success");
EXPECT_EQ("8", action->GetDisplayBadgeText(tab_id));
}
// Test that the onRuleMatchedDebug event is only available for unpacked // Test that the onRuleMatchedDebug event is only available for unpacked
// extensions. // extensions.
IN_PROC_BROWSER_TEST_P(DeclarativeNetRequestBrowserTest, IN_PROC_BROWSER_TEST_P(DeclarativeNetRequestBrowserTest,
......
...@@ -35,6 +35,7 @@ ...@@ -35,6 +35,7 @@
#include "chrome/browser/extensions/event_router_forwarder.h" #include "chrome/browser/extensions/event_router_forwarder.h"
#include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_system_factory.h" #include "chrome/browser/extensions/extension_system_factory.h"
#include "chrome/browser/extensions/extension_tab_util.h"
#include "chrome/browser/extensions/extension_util.h" #include "chrome/browser/extensions/extension_util.h"
#include "chrome/browser/extensions/menu_manager.h" #include "chrome/browser/extensions/menu_manager.h"
#include "chrome/browser/extensions/updater/chrome_update_client_config.h" #include "chrome/browser/extensions/updater/chrome_update_client_config.h"
...@@ -581,6 +582,13 @@ bool ChromeExtensionsBrowserClient::IsScreenshotRestricted( ...@@ -581,6 +582,13 @@ bool ChromeExtensionsBrowserClient::IsScreenshotRestricted(
return tabs_util::IsScreenshotRestricted(web_contents); return tabs_util::IsScreenshotRestricted(web_contents);
} }
bool ChromeExtensionsBrowserClient::IsValidTabId(
content::BrowserContext* context,
int tab_id) const {
return ExtensionTabUtil::GetTabById(
tab_id, context, true /* include_incognito */, nullptr /* contents */);
}
// static // static
void ChromeExtensionsBrowserClient::SetMediaRouterAccessLoggerForTesting( void ChromeExtensionsBrowserClient::SetMediaRouterAccessLoggerForTesting(
MediaRouterExtensionAccessLogger* media_router_access_logger) { MediaRouterExtensionAccessLogger* media_router_access_logger) {
......
...@@ -159,6 +159,8 @@ class ChromeExtensionsBrowserClient : public ExtensionsBrowserClient { ...@@ -159,6 +159,8 @@ class ChromeExtensionsBrowserClient : public ExtensionsBrowserClient {
content::BrowserContext* context) override; content::BrowserContext* context) override;
bool IsScreenshotRestricted( bool IsScreenshotRestricted(
content::WebContents* web_contents) const override; content::WebContents* web_contents) const override;
bool IsValidTabId(content::BrowserContext* context,
int tab_id) const override;
static void set_did_chrome_update_for_testing(bool did_update); static void set_did_chrome_update_for_testing(bool did_update);
......
...@@ -322,6 +322,23 @@ int ActionTracker::GetPendingRuleCountForTest(const ExtensionId& extension_id, ...@@ -322,6 +322,23 @@ int ActionTracker::GetPendingRuleCountForTest(const ExtensionId& extension_id,
: tracked_info->second.matched_rules.size(); : tracked_info->second.matched_rules.size();
} }
void ActionTracker::IncrementActionCountForTab(const ExtensionId& extension_id,
int tab_id,
int increment) {
TrackedInfo& tracked_info = rules_tracked_[{extension_id, tab_id}];
size_t new_action_count =
std::max<int>(tracked_info.action_count + increment, 0);
if (tracked_info.action_count == new_action_count)
return;
DCHECK(ExtensionsAPIClient::Get());
ExtensionsAPIClient::Get()->UpdateActionCount(browser_context_, extension_id,
tab_id, new_action_count,
false /* clear_badge_text */);
tracked_info.action_count = new_action_count;
}
template <typename T> template <typename T>
ActionTracker::TrackedInfoContextKey<T>::TrackedInfoContextKey( ActionTracker::TrackedInfoContextKey<T>::TrackedInfoContextKey(
ExtensionId extension_id, ExtensionId extension_id,
......
...@@ -103,6 +103,13 @@ class ActionTracker { ...@@ -103,6 +103,13 @@ class ActionTracker {
int GetPendingRuleCountForTest(const ExtensionId& extension_id, int GetPendingRuleCountForTest(const ExtensionId& extension_id,
int64_t navigation_id); int64_t navigation_id);
// Increments the action count for the given |extension_id| and |tab_id|.
// A negative value for |increment| will decrement the action count, but the
// action count will never be less than 0.
void IncrementActionCountForTab(const ExtensionId& extension_id,
int tab_id,
int increment);
private: private:
// Template key type used for TrackedInfo, specified by an extension_id and // Template key type used for TrackedInfo, specified by an extension_id and
// another ID. // another ID.
......
...@@ -106,6 +106,11 @@ const char kEnabledRulesetsRegexRuleCountExceeded[] = ...@@ -106,6 +106,11 @@ const char kEnabledRulesetsRegexRuleCountExceeded[] =
"limit."; "limit.";
const char kInternalErrorUpdatingEnabledRulesets[] = "Internal error."; const char kInternalErrorUpdatingEnabledRulesets[] = "Internal error.";
const char kTabNotFoundError[] = "No tab with id: *.";
const char kIncrementActionCountWithoutUseAsBadgeTextError[] =
"Cannot increment action count unless displaying action count as badge "
"text.";
const char kIndexAndPersistRulesTimeHistogram[] = const char kIndexAndPersistRulesTimeHistogram[] =
"Extensions.DeclarativeNetRequest.IndexAndPersistRulesTime"; "Extensions.DeclarativeNetRequest.IndexAndPersistRulesTime";
const char kManifestRulesCountHistogram[] = const char kManifestRulesCountHistogram[] =
......
...@@ -174,6 +174,10 @@ extern const char kEnabledRulesetsRuleCountExceeded[]; ...@@ -174,6 +174,10 @@ extern const char kEnabledRulesetsRuleCountExceeded[];
extern const char kEnabledRulesetsRegexRuleCountExceeded[]; extern const char kEnabledRulesetsRegexRuleCountExceeded[];
extern const char kInternalErrorUpdatingEnabledRulesets[]; extern const char kInternalErrorUpdatingEnabledRulesets[];
// setExtensionActionOptions API errors.
extern const char kTabNotFoundError[];
extern const char kIncrementActionCountWithoutUseAsBadgeTextError[];
// Histogram names. // Histogram names.
extern const char kIndexAndPersistRulesTimeHistogram[]; extern const char kIndexAndPersistRulesTimeHistogram[];
extern const char kManifestRulesCountHistogram[]; extern const char kManifestRulesCountHistogram[];
......
...@@ -30,6 +30,7 @@ ...@@ -30,6 +30,7 @@
#include "extensions/browser/api/extensions_api_client.h" #include "extensions/browser/api/extensions_api_client.h"
#include "extensions/browser/extension_file_task_runner.h" #include "extensions/browser/extension_file_task_runner.h"
#include "extensions/browser/extension_prefs.h" #include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extensions_browser_client.h"
#include "extensions/browser/quota_service.h" #include "extensions/browser/quota_service.h"
#include "extensions/common/api/declarative_net_request.h" #include "extensions/common/api/declarative_net_request.h"
#include "extensions/common/api/declarative_net_request/constants.h" #include "extensions/common/api/declarative_net_request/constants.h"
...@@ -392,34 +393,58 @@ DeclarativeNetRequestSetExtensionActionOptionsFunction::Run() { ...@@ -392,34 +393,58 @@ DeclarativeNetRequestSetExtensionActionOptionsFunction::Run() {
EXTENSION_FUNCTION_VALIDATE(params); EXTENSION_FUNCTION_VALIDATE(params);
EXTENSION_FUNCTION_VALIDATE(error.empty()); EXTENSION_FUNCTION_VALIDATE(error.empty());
bool use_action_count_as_badge_text =
params->options.display_action_count_as_badge_text;
ExtensionPrefs* prefs = ExtensionPrefs::Get(browser_context());
if (use_action_count_as_badge_text ==
prefs->GetDNRUseActionCountAsBadgeText(extension_id()))
return RespondNow(NoArguments());
prefs->SetDNRUseActionCountAsBadgeText(extension_id(),
use_action_count_as_badge_text);
// If the preference is switched on, update the extension's badge text with
// the number of actions matched for this extension. Otherwise, clear the
// action count for the extension's icon and show the default badge text if
// set.
if (use_action_count_as_badge_text) {
declarative_net_request::RulesMonitorService* rules_monitor_service = declarative_net_request::RulesMonitorService* rules_monitor_service =
declarative_net_request::RulesMonitorService::Get(browser_context()); declarative_net_request::RulesMonitorService::Get(browser_context());
DCHECK(rules_monitor_service); DCHECK(rules_monitor_service);
const declarative_net_request::ActionTracker& action_tracker = ExtensionPrefs* prefs = ExtensionPrefs::Get(browser_context());
declarative_net_request::ActionTracker& action_tracker =
rules_monitor_service->action_tracker(); rules_monitor_service->action_tracker();
bool use_action_count_as_badge_text =
prefs->GetDNRUseActionCountAsBadgeText(extension_id());
if (params->options.display_action_count_as_badge_text &&
*params->options.display_action_count_as_badge_text !=
use_action_count_as_badge_text) {
use_action_count_as_badge_text =
*params->options.display_action_count_as_badge_text;
prefs->SetDNRUseActionCountAsBadgeText(extension_id(),
use_action_count_as_badge_text);
// If the preference is switched on, update the extension's badge text
// with the number of actions matched for this extension. Otherwise, clear
// the action count for the extension's icon and show the default badge
// text if set.
if (use_action_count_as_badge_text)
action_tracker.OnPreferenceEnabled(extension_id()); action_tracker.OnPreferenceEnabled(extension_id());
} else { else {
DCHECK(ExtensionsAPIClient::Get()); DCHECK(ExtensionsAPIClient::Get());
ExtensionsAPIClient::Get()->ClearActionCount(browser_context(), ExtensionsAPIClient::Get()->ClearActionCount(browser_context(),
*extension()); *extension());
} }
}
if (params->options.tab_update) {
if (!use_action_count_as_badge_text) {
return RespondNow(
Error(declarative_net_request::
kIncrementActionCountWithoutUseAsBadgeTextError));
}
const auto& update_options = *params->options.tab_update;
int tab_id = update_options.tab_id;
if (!ExtensionsBrowserClient::Get()->IsValidTabId(browser_context(),
tab_id)) {
return RespondNow(Error(ErrorUtils::FormatErrorMessage(
declarative_net_request::kTabNotFoundError,
base::NumberToString(tab_id))));
}
action_tracker.IncrementActionCountForTab(extension_id(), tab_id,
update_options.increment);
}
return RespondNow(NoArguments()); return RespondNow(NoArguments());
} }
......
...@@ -124,4 +124,9 @@ bool ExtensionsBrowserClient::IsScreenshotRestricted( ...@@ -124,4 +124,9 @@ bool ExtensionsBrowserClient::IsScreenshotRestricted(
return false; return false;
} }
bool ExtensionsBrowserClient::IsValidTabId(content::BrowserContext* context,
int tab_id) const {
return false;
}
} // namespace extensions } // namespace extensions
...@@ -370,6 +370,9 @@ class ExtensionsBrowserClient { ...@@ -370,6 +370,9 @@ class ExtensionsBrowserClient {
// Protection policy. // Protection policy.
virtual bool IsScreenshotRestricted(content::WebContents* web_contents) const; virtual bool IsScreenshotRestricted(content::WebContents* web_contents) const;
// Returns true if the given |tab_id| exists.
virtual bool IsValidTabId(content::BrowserContext* context, int tab_id) const;
private: private:
std::vector<std::unique_ptr<ExtensionsBrowserAPIProvider>> providers_; std::vector<std::unique_ptr<ExtensionsBrowserAPIProvider>> providers_;
}; };
......
...@@ -425,10 +425,20 @@ namespace declarativeNetRequest { ...@@ -425,10 +425,20 @@ namespace declarativeNetRequest {
DOMString[]? enableRulesetIds; DOMString[]? enableRulesetIds;
}; };
dictionary TabActionCountUpdate {
// The tab for which to update the action count.
long tabId;
// The amount to increment the tab's action count by. Negative values will
// decrement the count.
long increment;
};
dictionary ExtensionActionOptions { dictionary ExtensionActionOptions {
// Whether to automatically display the action count for a page as the // Whether to automatically display the action count for a page as the
// extension's badge text. False by default. // extension's badge text. This preference is persisted across sessions.
boolean displayActionCountAsBadgeText; boolean? displayActionCountAsBadgeText;
// Details of how the tab's action count should be adjusted.
TabActionCountUpdate? tabUpdate;
}; };
callback EmptyCallback = void(); callback EmptyCallback = void();
...@@ -531,10 +541,12 @@ namespace declarativeNetRequest { ...@@ -531,10 +541,12 @@ namespace declarativeNetRequest {
static void getMatchedRules(optional MatchedRulesFilter filter, static void getMatchedRules(optional MatchedRulesFilter filter,
GetMatchedRulesCallback callback); GetMatchedRulesCallback callback);
// Configures how matched actions will be displayed on the extension action. // Configures if the action count for tabs should be displayed as the
// This preference is persisted across sessions. // extension action's badge text and provides a way for that action count to
// be incremented.
static void setExtensionActionOptions( static void setExtensionActionOptions(
ExtensionActionOptions options); ExtensionActionOptions options,
optional EmptyCallback callback);
// Checks if the given regular expression will be supported as a // Checks if the given regular expression will be supported as a
// <code>regexFilter</code> rule condition. // <code>regexFilter</code> rule condition.
......
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