Commit a8cf3312 authored by Victor Hugo Vianna Silva's avatar Victor Hugo Vianna Silva Committed by Commit Bot

[b4p] Introduce JS APIs for batch deletions of passwords/blacklists

This CL introduces removeSavedPasswords and removePasswordExceptions
APIs to chrome.passwordsPrivate, which allow deleting batches of
passwords/blacklists that are entirely restored upon call to
undoRemoveSavedPasswordOrException.

This is achieved by plumbing the APIs introduced in crrev.com/c/2193695.
In order to minimize unnecessary function duplication, all the C++
intermediary APIs now take batches as arguments. Only JS still exposes
APIs for individual deletion.

In future CLs, the JS APIs will be used to allow simultaneously deleting
versions of a same password stored in different PasswordStore's.

Bug: 1049141
Change-Id: I5852b1c4375caef4ac3e167ccd0fa9c30eeef9e0
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2199148
Commit-Queue: Victor Vianna <victorvianna@google.com>
Reviewed-by: default avatarSteven Bennetts <stevenjb@chromium.org>
Reviewed-by: default avatarJan Wilken Dörrie <jdoerrie@chromium.org>
Reviewed-by: default avatarFriedrich [CET] <fhorschig@chromium.org>
Cr-Commit-Position: refs/heads/master@{#770034}
parent a634d446
......@@ -77,7 +77,16 @@ ResponseAction PasswordsPrivateRemoveSavedPasswordFunction::Run() {
auto parameters =
api::passwords_private::RemoveSavedPassword::Params::Create(*args_);
EXTENSION_FUNCTION_VALIDATE(parameters);
GetDelegate(browser_context())->RemoveSavedPassword(parameters->id);
GetDelegate(browser_context())->RemoveSavedPasswords({parameters->id});
return RespondNow(NoArguments());
}
// PasswordsPrivateRemoveSavedPasswordsFunction
ResponseAction PasswordsPrivateRemoveSavedPasswordsFunction::Run() {
auto parameters =
api::passwords_private::RemoveSavedPasswords::Params::Create(*args_);
EXTENSION_FUNCTION_VALIDATE(parameters);
GetDelegate(browser_context())->RemoveSavedPasswords(parameters->ids);
return RespondNow(NoArguments());
}
......@@ -86,7 +95,16 @@ ResponseAction PasswordsPrivateRemovePasswordExceptionFunction::Run() {
auto parameters =
api::passwords_private::RemovePasswordException::Params::Create(*args_);
EXTENSION_FUNCTION_VALIDATE(parameters);
GetDelegate(browser_context())->RemovePasswordException(parameters->id);
GetDelegate(browser_context())->RemovePasswordExceptions({parameters->id});
return RespondNow(NoArguments());
}
// PasswordsPrivateRemovePasswordExceptionsFunction
ResponseAction PasswordsPrivateRemovePasswordExceptionsFunction::Run() {
auto parameters =
api::passwords_private::RemovePasswordExceptions::Params::Create(*args_);
EXTENSION_FUNCTION_VALIDATE(parameters);
GetDelegate(browser_context())->RemovePasswordExceptions(parameters->ids);
return RespondNow(NoArguments());
}
......
......@@ -54,6 +54,18 @@ class PasswordsPrivateRemoveSavedPasswordFunction : public ExtensionFunction {
ResponseAction Run() override;
};
class PasswordsPrivateRemoveSavedPasswordsFunction : public ExtensionFunction {
public:
DECLARE_EXTENSION_FUNCTION("passwordsPrivate.removeSavedPasswords",
PASSWORDSPRIVATE_REMOVESAVEDPASSWORDS)
protected:
~PasswordsPrivateRemoveSavedPasswordsFunction() override = default;
// ExtensionFunction overrides.
ResponseAction Run() override;
};
class PasswordsPrivateRemovePasswordExceptionFunction
: public ExtensionFunction {
public:
......@@ -67,6 +79,19 @@ class PasswordsPrivateRemovePasswordExceptionFunction
ResponseAction Run() override;
};
class PasswordsPrivateRemovePasswordExceptionsFunction
: public ExtensionFunction {
public:
DECLARE_EXTENSION_FUNCTION("passwordsPrivate.removePasswordExceptions",
PASSWORDSPRIVATE_REMOVEPASSWORDEXCEPTIONS)
protected:
~PasswordsPrivateRemovePasswordExceptionsFunction() override = default;
// ExtensionFunction overrides.
ResponseAction Run() override;
};
class PasswordsPrivateUndoRemoveSavedPasswordOrExceptionFunction
: public ExtensionFunction {
public:
......
......@@ -126,12 +126,24 @@ IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest,
<< message_;
}
IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest,
RemoveAndUndoRemoveSavedPasswordsBatch) {
EXPECT_TRUE(RunPasswordsSubtest("removeAndUndoRemoveSavedPasswordsBatch"))
<< message_;
}
IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest,
RemoveAndUndoRemovePasswordException) {
EXPECT_TRUE(RunPasswordsSubtest("removeAndUndoRemovePasswordException"))
<< message_;
}
IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest,
RemoveAndUndoRemovePasswordExceptionsBatch) {
EXPECT_TRUE(RunPasswordsSubtest("removeAndUndoRemovePasswordExceptionsBatch"))
<< message_;
}
IN_PROC_BROWSER_TEST_F(PasswordsPrivateApiTest, RequestPlaintextPassword) {
EXPECT_TRUE(RunPasswordsSubtest("requestPlaintextPassword")) << message_;
}
......
......@@ -65,15 +65,13 @@ class PasswordsPrivateDelegate : public KeyedService {
base::string16 new_username,
base::Optional<base::string16> new_password) = 0;
// Removes the saved password entry corresponding to the |id| generated for
// each entry of the password list.
// |id| the id created when going over the list of saved passwords.
virtual void RemoveSavedPassword(int id) = 0;
// Removes the saved password entries corresponding to the |ids| generated for
// each entry of the password list. Any invalid id will be ignored.
virtual void RemoveSavedPasswords(const std::vector<int>& ids) = 0;
// Removes the saved password exception entry corresponding set in the
// given |id|
// |id| The id for the exception url entry being removed.
virtual void RemovePasswordException(int id) = 0;
// Removes the password exceptions entries corresponding corresponding to
// |ids|. Any invalid id will be ignored.
virtual void RemovePasswordExceptions(const std::vector<int>& ids) = 0;
// Undoes the last removal of a saved password or exception.
virtual void UndoRemoveSavedPasswordOrException() = 0;
......
......@@ -122,6 +122,20 @@ password_manager::PlaintextReason ConvertPlaintextReason(
return password_manager::PlaintextReason::kView;
}
// Gets all the existing keys in |generator| corresponding to |ids|. If no key
// is found for an id, it is simply ignored.
std::vector<std::string> GetSortKeys(
const extensions::IdGenerator<std::string>& generator,
const std::vector<int> ids) {
std::vector<std::string> sort_keys;
sort_keys.reserve(ids.size());
for (int id : ids) {
if (const std::string* sort_key = generator.TryGetKey(id))
sort_keys.emplace_back(*sort_key);
}
return sort_keys;
}
} // namespace
namespace extensions {
......@@ -197,28 +211,30 @@ void PasswordsPrivateDelegateImpl::ChangeSavedPassword(
*sort_key, std::move(new_username), std::move(new_password));
}
void PasswordsPrivateDelegateImpl::RemoveSavedPassword(int id) {
void PasswordsPrivateDelegateImpl::RemoveSavedPasswords(
const std::vector<int>& ids) {
ExecuteFunction(
base::Bind(&PasswordsPrivateDelegateImpl::RemoveSavedPasswordInternal,
base::Unretained(this), id));
base::Bind(&PasswordsPrivateDelegateImpl::RemoveSavedPasswordsInternal,
base::Unretained(this), ids));
}
void PasswordsPrivateDelegateImpl::RemoveSavedPasswordInternal(int id) {
const std::string* sort_key = password_id_generator_.TryGetKey(id);
if (sort_key)
password_manager_presenter_->RemoveSavedPassword(*sort_key);
void PasswordsPrivateDelegateImpl::RemoveSavedPasswordsInternal(
const std::vector<int>& ids) {
password_manager_presenter_->RemoveSavedPasswords(
GetSortKeys(password_id_generator_, ids));
}
void PasswordsPrivateDelegateImpl::RemovePasswordException(int id) {
ExecuteFunction(
base::Bind(&PasswordsPrivateDelegateImpl::RemovePasswordExceptionInternal,
base::Unretained(this), id));
void PasswordsPrivateDelegateImpl::RemovePasswordExceptions(
const std::vector<int>& ids) {
ExecuteFunction(base::Bind(
&PasswordsPrivateDelegateImpl::RemovePasswordExceptionsInternal,
base::Unretained(this), ids));
}
void PasswordsPrivateDelegateImpl::RemovePasswordExceptionInternal(int id) {
const std::string* sort_key = exception_id_generator_.TryGetKey(id);
if (sort_key)
password_manager_presenter_->RemovePasswordException(*sort_key);
void PasswordsPrivateDelegateImpl::RemovePasswordExceptionsInternal(
const std::vector<int>& ids) {
password_manager_presenter_->RemovePasswordExceptions(
GetSortKeys(exception_id_generator_, ids));
}
void PasswordsPrivateDelegateImpl::UndoRemoveSavedPasswordOrException() {
......
......@@ -53,8 +53,8 @@ class PasswordsPrivateDelegateImpl : public PasswordsPrivateDelegate,
int id,
base::string16 new_username,
base::Optional<base::string16> new_password) override;
void RemoveSavedPassword(int id) override;
void RemovePasswordException(int id) override;
void RemoveSavedPasswords(const std::vector<int>& ids) override;
void RemovePasswordExceptions(const std::vector<int>& ids) override;
void UndoRemoveSavedPasswordOrException() override;
void RequestPlaintextPassword(int id,
api::passwords_private::PlaintextReason reason,
......@@ -123,8 +123,8 @@ class PasswordsPrivateDelegateImpl : public PasswordsPrivateDelegate,
void SendSavedPasswordsList();
void SendPasswordExceptionsList();
void RemoveSavedPasswordInternal(int id);
void RemovePasswordExceptionInternal(int id);
void RemoveSavedPasswordsInternal(const std::vector<int>& ids);
void RemovePasswordExceptionsInternal(const std::vector<int>& ids);
void UndoRemoveSavedPasswordOrExceptionInternal();
// Callback for when the password list has been written to the destination.
......
......@@ -72,36 +72,55 @@ void TestPasswordsPrivateDelegate::ChangeSavedPassword(
SendSavedPasswordsList();
}
void TestPasswordsPrivateDelegate::RemoveSavedPassword(int id) {
void TestPasswordsPrivateDelegate::RemoveSavedPasswords(
const std::vector<int>& ids) {
if (current_entries_.empty())
return;
// Since this is just mock data, remove the first entry regardless of
// the data contained.
last_deleted_entry_ = std::move(current_entries_.front());
current_entries_.erase(current_entries_.begin());
// Since this is just mock data, remove the first |ids.size()| elements
// regardless of the data contained.
auto first_remaining = (ids.size() <= current_entries_.size())
? current_entries_.begin() + ids.size()
: current_entries_.end();
last_deleted_entries_batch_.assign(
std::make_move_iterator(current_entries_.begin()),
std::make_move_iterator(first_remaining));
current_entries_.erase(current_entries_.begin(), first_remaining);
SendSavedPasswordsList();
}
void TestPasswordsPrivateDelegate::RemovePasswordException(int id) {
// Since this is just mock data, remove the first entry regardless of
// the data contained.
last_deleted_exception_ = std::move(current_exceptions_.front());
current_exceptions_.erase(current_exceptions_.begin());
void TestPasswordsPrivateDelegate::RemovePasswordExceptions(
const std::vector<int>& ids) {
if (current_exceptions_.empty())
return;
// Since this is just mock data, remove the first |ids.size()| elements
// regardless of the data contained.
auto first_remaining = (ids.size() <= current_exceptions_.size())
? current_exceptions_.begin() + ids.size()
: current_exceptions_.end();
last_deleted_exceptions_batch_.assign(
std::make_move_iterator(current_exceptions_.begin()),
std::make_move_iterator(first_remaining));
current_exceptions_.erase(current_exceptions_.begin(), first_remaining);
SendPasswordExceptionsList();
}
// Simplified version of undo logic, only use for testing.
void TestPasswordsPrivateDelegate::UndoRemoveSavedPasswordOrException() {
if (last_deleted_entry_) {
current_entries_.insert(current_entries_.begin(),
std::move(*last_deleted_entry_));
last_deleted_entry_ = base::nullopt;
if (!last_deleted_entries_batch_.empty()) {
current_entries_.insert(
current_entries_.begin(),
std::make_move_iterator(last_deleted_entries_batch_.begin()),
std::make_move_iterator(last_deleted_entries_batch_.end()));
last_deleted_entries_batch_.clear();
SendSavedPasswordsList();
} else if (last_deleted_exception_) {
current_exceptions_.insert(current_exceptions_.begin(),
std::move(*last_deleted_exception_));
last_deleted_exception_ = base::nullopt;
} else if (!last_deleted_exceptions_batch_.empty()) {
current_exceptions_.insert(
current_exceptions_.begin(),
std::make_move_iterator(last_deleted_exceptions_batch_.begin()),
std::make_move_iterator(last_deleted_exceptions_batch_.end()));
last_deleted_exceptions_batch_.clear();
SendPasswordExceptionsList();
}
}
......
......@@ -25,8 +25,8 @@ class TestPasswordsPrivateDelegate : public PasswordsPrivateDelegate {
void ChangeSavedPassword(int id,
base::string16 username,
base::Optional<base::string16> password) override;
void RemoveSavedPassword(int id) override;
void RemovePasswordException(int id) override;
void RemoveSavedPasswords(const std::vector<int>& id) override;
void RemovePasswordExceptions(const std::vector<int>& ids) override;
// Simplified version of undo logic, only use for testing.
void UndoRemoveSavedPasswordOrException() override;
void RequestPlaintextPassword(int id,
......@@ -93,11 +93,15 @@ class TestPasswordsPrivateDelegate : public PasswordsPrivateDelegate {
// having to request them from |password_manager_presenter_| again.
std::vector<api::passwords_private::PasswordUiEntry> current_entries_;
std::vector<api::passwords_private::ExceptionEntry> current_exceptions_;
// Simplified version of a undo manager that only allows undoing and redoing
// the very last deletion.
base::Optional<api::passwords_private::PasswordUiEntry> last_deleted_entry_;
base::Optional<api::passwords_private::ExceptionEntry>
last_deleted_exception_;
// Simplified version of an undo manager that only allows undoing and redoing
// the very last deletion. When the batches are *empty*, this means there is
// no previous deletion to undo.
std::vector<api::passwords_private::PasswordUiEntry>
last_deleted_entries_batch_;
std::vector<api::passwords_private::ExceptionEntry>
last_deleted_exceptions_batch_;
base::Optional<base::string16> plaintext_password_ =
base::ASCIIToUTF16("plaintext");
......
......@@ -330,19 +330,14 @@ void PasswordManagerPresenter::RemoveSavedPassword(size_t index) {
}
}
void PasswordManagerPresenter::RemoveSavedPassword(
const std::string& sort_key) {
if (TryRemovePasswordEntries(&password_map_, sort_key)) {
base::RecordAction(
base::UserMetricsAction("PasswordManager_RemoveSavedPassword"));
}
}
void PasswordManagerPresenter::RemoveSavedPasswords(
const std::vector<std::string>& sort_keys) {
undo_manager_.StartGroupingActions();
for (const std::string& sort_key : sort_keys) {
RemoveSavedPassword(sort_key);
if (TryRemovePasswordEntries(&password_map_, sort_key)) {
base::RecordAction(
base::UserMetricsAction("PasswordManager_RemoveSavedPassword"));
}
}
undo_manager_.EndGroupingActions();
}
......@@ -354,19 +349,14 @@ void PasswordManagerPresenter::RemovePasswordException(size_t index) {
}
}
void PasswordManagerPresenter::RemovePasswordException(
const std::string& sort_key) {
if (TryRemovePasswordEntries(&exception_map_, sort_key)) {
base::RecordAction(
base::UserMetricsAction("PasswordManager_RemovePasswordException"));
}
}
void PasswordManagerPresenter::RemovePasswordExceptions(
const std::vector<std::string>& sort_keys) {
undo_manager_.StartGroupingActions();
for (const std::string& sort_key : sort_keys) {
RemovePasswordException(sort_key);
if (TryRemovePasswordEntries(&exception_map_, sort_key)) {
base::RecordAction(
base::UserMetricsAction("PasswordManager_RemovePasswordException"));
}
}
undo_manager_.EndGroupingActions();
}
......
......@@ -80,7 +80,6 @@ class PasswordManagerPresenter
// TODO(https://crbug.com/778146): Unify these methods and the implementation
// across Desktop and Android.
void RemoveSavedPassword(size_t index);
void RemoveSavedPassword(const std::string& sort_key);
void RemoveSavedPasswords(const std::vector<std::string>& sort_keys);
// Removes the saved exception entries at |index|, or corresponding to
......@@ -88,7 +87,6 @@ class PasswordManagerPresenter
// TODO(https://crbug.com/778146): Unify these methods and the implementation
// across Desktop and Android.
void RemovePasswordException(size_t index);
void RemovePasswordException(const std::string& sort_key);
void RemovePasswordExceptions(const std::vector<std::string>& sort_keys);
// Undoes the last saved password or exception removal.
......
......@@ -443,8 +443,8 @@ TEST_F(PasswordManagerPresenterTest, TestPasswordRemovalAndUndo) {
UnorderedElementsAre(Pair(kUsername, kPassword),
Pair(kUsername2, kPassword2)));
GetUIController().GetPasswordManagerPresenter()->RemoveSavedPassword(
password_manager::CreateSortKey(password1));
GetUIController().GetPasswordManagerPresenter()->RemoveSavedPasswords(
{password_manager::CreateSortKey(password1)});
UpdatePasswordLists();
EXPECT_THAT(GetUsernamesAndPasswords(GetStoredPasswordsForRealm(kExampleCom)),
UnorderedElementsAre(Pair(kUsername2, kPassword2)));
......@@ -463,8 +463,8 @@ TEST_F(PasswordManagerPresenterTest, TestExceptionRemovalAndUndo) {
autofill::PasswordForm exception2 = AddPasswordException(GURL(kExampleOrg));
UpdatePasswordLists();
GetUIController().GetPasswordManagerPresenter()->RemovePasswordException(
password_manager::CreateSortKey(exception1));
GetUIController().GetPasswordManagerPresenter()->RemovePasswordExceptions(
{password_manager::CreateSortKey(exception1)});
EXPECT_CALL(GetUIController(), SetPasswordExceptionList(UnorderedElementsAre(
HasOrigin(exception2.origin))));
UpdatePasswordLists();
......
......@@ -206,12 +206,24 @@ namespace passwordsPrivate {
// password entry being removed.
static void removeSavedPassword(long id);
// Removes the saved password exception corresponding to |exceptionUrl|. If
// no exception with this URL exists, this function is a no-op.
// Removes the saved password corresponding to |ids|. If no saved password
// exists for a certain id, that id is ignored. Undoing this operation via
// undoRemoveSavedPasswordOrException will restore all the removed passwords
// in the batch.
static void removeSavedPasswords(long[] ids);
// Removes the saved password exception corresponding to |id|. If
// no exception with this id exists, this function is a no-op.
// |id|: The id for the exception url entry being removed.
static void removePasswordException(long id);
// Undoes the last removal of a saved password or exception.
// Removes the saved password exceptions corresponding to |ids|. If
// no exception exists for a certain id, that id is ignored. Undoing this
// operation via undoRemoveSavedPasswordOrException will restore all the
// removed exceptions in the batch.
static void removePasswordExceptions(long[] ids);
// Undoes the last removal of saved password(s) or exception(s).
static void undoRemoveSavedPasswordOrException();
// Returns the plaintext password corresponding to |id|. Note that on
......
......@@ -56,6 +56,34 @@ var availableTests = [
chrome.passwordsPrivate.getSavedPasswordList(callback);
},
function removeAndUndoRemoveSavedPasswordsBatch() {
var numCalls = 0;
var numSavedPasswords;
var callback = function(savedPasswordsList) {
numCalls++;
if (numCalls == 1) {
numSavedPasswords = savedPasswordsList.length;
// There should be at least two passwords for this test to make sense.
chrome.test.assertTrue(numSavedPasswords >= 2);
chrome.passwordsPrivate.removeSavedPasswords(
Array(savedPasswordsList[0].id, savedPasswordsList[1].id));
} else if (numCalls == 2) {
chrome.test.assertEq(savedPasswordsList.length, numSavedPasswords - 2);
chrome.passwordsPrivate.undoRemoveSavedPasswordOrException();
} else if (numCalls == 3) {
chrome.test.assertEq(savedPasswordsList.length, numSavedPasswords);
chrome.test.succeed();
} else {
chrome.test.fail();
}
};
chrome.passwordsPrivate.onSavedPasswordsListChanged.addListener(callback);
chrome.passwordsPrivate.getSavedPasswordList(callback);
},
function removeAndUndoRemovePasswordException() {
var numCalls = 0;
var numPasswordExceptions;
......@@ -84,6 +112,36 @@ var availableTests = [
chrome.passwordsPrivate.getPasswordExceptionList(callback);
},
function removeAndUndoRemovePasswordExceptionsBatch() {
var numCalls = 0;
var numPasswordExceptions;
var callback = function(passwordExceptionsList) {
numCalls++;
if (numCalls == 1) {
numPasswordExceptions = passwordExceptionsList.length;
// There should be at least two exceptions for this test to make sense.
chrome.test.assertTrue(numPasswordExceptions >= 2);
chrome.passwordsPrivate.removePasswordExceptions(
Array(passwordExceptionsList[0].id, passwordExceptionsList[1].id));
} else if (numCalls == 2) {
chrome.test.assertEq(
passwordExceptionsList.length, numPasswordExceptions - 2);
chrome.passwordsPrivate.undoRemoveSavedPasswordOrException();
} else if (numCalls == 3) {
chrome.test.assertEq(
passwordExceptionsList.length, numPasswordExceptions);
chrome.test.succeed();
} else {
chrome.test.fail();
}
};
chrome.passwordsPrivate.onPasswordExceptionsListChanged.addListener(
callback);
chrome.passwordsPrivate.getPasswordExceptionList(callback);
},
function requestPlaintextPassword() {
chrome.passwordsPrivate.requestPlaintextPassword(
0, chrome.passwordsPrivate.PlaintextReason.VIEW, password => {
......
......@@ -1536,6 +1536,8 @@ enum HistogramValue {
TERMINALPRIVATE_OPENWINDOW = 1473,
AUTOTESTPRIVATE_SETPLUGINVMPOLICY = 1474,
AUTOTESTPRIVATE_SHOWPLUGINVMINSTALLER = 1475,
PASSWORDSPRIVATE_REMOVESAVEDPASSWORDS = 1476,
PASSWORDSPRIVATE_REMOVEPASSWORDEXCEPTIONS = 1477,
// Last entry: Add new entries above, then run:
// python tools/metrics/histograms/update_extension_histograms.py
ENUM_BOUNDARY
......
......@@ -145,12 +145,26 @@ chrome.passwordsPrivate.changeSavedPassword = function(
chrome.passwordsPrivate.removeSavedPassword = function(id) {};
/**
* Removes the saved password exception corresponding to |exceptionUrl|. If no
* exception with this URL exists, this function is a no-op.
* Removes the saved passwords corresponding to |ids|. If no saved password
* exists for a certain id, that id is ignored.
* @param {Array<number>} ids The ids for the password entries being removed.
*/
chrome.passwordsPrivate.removeSavedPasswords = function(ids) {};
/**
* Removes the saved password exception corresponding to |id|. If no
* exception with this id exists, this function is a no-op.
* @param {number} id The id for the exception url entry being removed.
*/
chrome.passwordsPrivate.removePasswordException = function(id) {};
/**
* Removes the saved password exceptions corresponding to |ids|. If no
* exception exists for a certain id, that id is ignored.
* @param {Array<number>} ids The ids for the exception entries being removed.
*/
chrome.passwordsPrivate.removePasswordExceptions = function(ids) {};
/**
* Undoes the last removal of a saved password or exception.
*/
......
......@@ -23002,6 +23002,8 @@ Called by update_extension_histograms.py.-->
<int value="1473" label="TERMINALPRIVATE_OPENWINDOW"/>
<int value="1474" label="AUTOTESTPRIVATE_SETPLUGINVMPOLICY"/>
<int value="1475" label="AUTOTESTPRIVATE_SHOWPLUGINVMINSTALLER"/>
<int value="1476" label="PASSWORDSPRIVATE_REMOVESAVEDPASSWORDS"/>
<int value="1477" label="PASSWORDSPRIVATE_REMOVEPASSWORDEXCEPTIONS"/>
</enum>
<enum name="ExtensionIconState">
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