[go: nahoru, domu]

blob: f4231878cc013fe9714068d03b679182abbe974b [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.h"
#import <WebKit/WebKit.h>
#import "base/apple/foundation_util.h"
#import "base/command_line.h"
#import "base/containers/contains.h"
#import "base/files/file.h"
#import "base/files/file_util.h"
#import "base/ios/ios_util.h"
#import "base/json/json_string_value_serializer.h"
#import "base/strings/sys_string_conversions.h"
#import "base/test/ios/wait_util.h"
#import "base/test/scoped_feature_list.h"
#import "base/threading/thread_restrictions.h"
#import "base/values.h"
#import "components/autofill/core/browser/personal_data_manager.h"
#import "components/autofill/core/common/autofill_features.h"
#import "components/browsing_data/core/pref_names.h"
#import "components/content_settings/core/browser/host_content_settings_map.h"
#import "components/metrics/demographics/demographic_metrics_provider.h"
#import "components/password_manager/core/common/password_manager_features.h"
#import "components/prefs/pref_service.h"
#import "components/search_engines/template_url_service.h"
#import "components/sync/base/features.h"
#import "components/sync/base/pref_names.h"
#import "components/unified_consent/unified_consent_service.h"
#import "components/variations/variations_associated_data.h"
#import "components/variations/variations_ids_provider.h"
#import "ios/chrome/app/main_controller.h"
#import "ios/chrome/browser/autofill/personal_data_manager_factory.h"
#import "ios/chrome/browser/content_settings/host_content_settings_map_factory.h"
#import "ios/chrome/browser/default_browser/utils.h"
#import "ios/chrome/browser/default_browser/utils_test_support.h"
#import "ios/chrome/browser/first_run/first_run.h"
#import "ios/chrome/browser/ntp/features.h"
#import "ios/chrome/browser/search_engines/search_engines_util.h"
#import "ios/chrome/browser/search_engines/template_url_service_factory.h"
#import "ios/chrome/browser/sessions/session_restoration_browser_agent.h"
#import "ios/chrome/browser/sessions/session_service_ios.h"
#import "ios/chrome/browser/shared/coordinator/scene/scene_state.h"
#import "ios/chrome/browser/shared/model/application_context/application_context.h"
#import "ios/chrome/browser/shared/model/browser/browser_provider.h"
#import "ios/chrome/browser/shared/model/browser/browser_provider_interface.h"
#import "ios/chrome/browser/shared/model/browser_state/chrome_browser_state.h"
#import "ios/chrome/browser/shared/model/web_state_list/web_state_list.h"
#import "ios/chrome/browser/shared/public/commands/application_commands.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/shared/ui/symbols/symbols.h"
#import "ios/chrome/browser/shared/ui/util/rtl_geometry.h"
#import "ios/chrome/browser/signin/fake_system_identity.h"
#import "ios/chrome/browser/sync/sync_service_factory.h"
#import "ios/chrome/browser/ui/ntp/new_tab_page_feature.h"
#import "ios/chrome/browser/ui/popup_menu/overflow_menu/feature_flags.h"
#import "ios/chrome/browser/unified_consent/unified_consent_service_factory.h"
#import "ios/chrome/browser/web/web_navigation_browser_agent.h"
#import "ios/chrome/test/app/browsing_data_test_util.h"
#import "ios/chrome/test/app/chrome_test_util.h"
#import "ios/chrome/test/app/navigation_test_util.h"
#import "ios/chrome/test/app/signin_test_util.h"
#import "ios/chrome/test/app/sync_test_util.h"
#import "ios/chrome/test/app/tab_test_util.h"
#import "ios/chrome/test/app/window_test_util.h"
#import "ios/chrome/test/earl_grey/accessibility_util.h"
#import "ios/public/provider/chrome/browser/lens/lens_api.h"
#import "ios/testing/hardware_keyboard_util.h"
#import "ios/testing/nserror_util.h"
#import "ios/testing/open_url_context.h"
#import "ios/testing/verify_custom_webkit.h"
#import "ios/web/common/features.h"
#import "ios/web/js_messaging/web_view_js_utils.h"
#import "ios/web/public/js_messaging/web_frame.h"
#import "ios/web/public/js_messaging/web_frames_manager.h"
#import "ios/web/public/navigation/navigation_manager.h"
#import "ios/web/public/test/element_selector.h"
#import "ios/web/public/test/url_test_util.h"
#import "ios/web/public/test/web_view_content_test_util.h"
#import "ios/web/public/test/web_view_interaction_test_util.h"
#import "ios/web/public/ui/crw_web_view_proxy.h"
#import "ios/web/public/ui/crw_web_view_scroll_view_proxy.h"
#import "ios/web/public/web_client.h"
#import "ios/web/public/web_state.h"
#import "net/base/mac/url_conversions.h"
#import "services/metrics/public/cpp/ukm_recorder.h"
#import "ui/base/device_form_factor.h"
// To get access to UseSessionSerializationOptimizations().
// TODO(crbug.com/1383087): remove once the feature is fully launched.
#import "ios/web/common/features.h"
using base::test::ios::kWaitForActionTimeout;
using base::test::ios::kWaitForJSCompletionTimeout;
using base::test::ios::kWaitForPageLoadTimeout;
using base::test::ios::WaitUntilConditionOrTimeout;
namespace {
// Returns a JSON-encoded string representing the given `pref`. If `pref` is
// nullptr, returns a string representing a base::Value of type NONE.
NSString* SerializedPref(const PrefService::Preference* pref) {
base::Value none_value(base::Value::Type::NONE);
const base::Value* value = pref ? pref->GetValue() : &none_value;
DCHECK(value);
std::string serialized_value;
JSONStringValueSerializer serializer(&serialized_value);
serializer.Serialize(*value);
return base::SysUTF8ToNSString(serialized_value);
}
// Returns a JSON-encoded string representing the given `value`. If `value` is
// nullptr, returns a string representing a base::Value of type NONE.
NSString* SerializedValue(const base::Value* value) {
base::Value none_value(base::Value::Type::NONE);
const base::Value* result = value ? value : &none_value;
DCHECK(result);
std::string serialized_value;
JSONStringValueSerializer serializer(&serialized_value);
serializer.Serialize(*result);
return base::SysUTF8ToNSString(serialized_value);
}
}
@implementation JavaScriptExecutionResult
- (instancetype)initWithResult:(NSString*)result
successfulExecution:(BOOL)outcome {
self = [super init];
if (self) {
_result = result;
_success = outcome;
}
return self;
}
@end
@implementation ChromeEarlGreyAppInterface
+ (BOOL)isRTL {
return UseRTLLayout();
}
+ (NSError*)clearBrowsingHistory {
if (chrome_test_util::ClearBrowsingHistory()) {
return nil;
}
[self killWebKitNetworkProcess];
return testing::NSErrorWithLocalizedDescription(
@"Clearing browser history timed out");
}
+ (void)killWebKitNetworkProcess {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
if (@available(iOS 15, *)) {
[[WKWebsiteDataStore defaultDataStore]
performSelector:@selector(_terminateNetworkProcess)];
}
#pragma clang diagnostic pop
}
+ (NSInteger)browsingHistoryEntryCountWithError:
(NSError* __autoreleasing*)error {
return chrome_test_util::GetBrowsingHistoryEntryCount(error);
}
+ (NSInteger)navigationBackListItemsCount {
web::WebState* webState = chrome_test_util::GetCurrentWebState();
if (!webState)
return -1;
return webState->GetNavigationManager()->GetBackwardItems().size();
}
+ (NSError*)removeBrowsingCache {
if (chrome_test_util::RemoveBrowsingCache()) {
return nil;
}
return testing::NSErrorWithLocalizedDescription(
@"Clearing browser cache for main tabs timed out");
}
+ (void)saveSessionImmediately {
if (!web::features::UseSessionSerializationOptimizations()) {
SessionRestorationBrowserAgent::FromBrowser(
chrome_test_util::GetMainBrowser())
->SaveSession(/*immediately=*/true);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
ProceduralBlock completionBlock = ^{
dispatch_semaphore_signal(semaphore);
};
[[SessionServiceIOS sharedService] shutdownWithCompletion:completionBlock];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
}
+ (NSError*)clearAllWebStateBrowsingData {
if (chrome_test_util::ClearAllWebStateBrowsingData()) {
return nil;
}
return testing::NSErrorWithLocalizedDescription(
@"Clearing web state browsing data for main tabs timed out");
}
+ (void)sceneOpenURL:(NSString*)spec {
NSURL* url = [NSURL URLWithString:spec];
TestOpenURLContext* context = [[TestOpenURLContext alloc] init];
context.URL = url;
UIApplication* application = UIApplication.sharedApplication;
UIScene* scene = application.connectedScenes.anyObject;
[scene.delegate scene:scene openURLContexts:[NSSet setWithObject:context]];
}
+ (void)startLoadingURL:(NSString*)spec {
chrome_test_util::LoadUrl(GURL(base::SysNSStringToUTF8(spec)));
}
+ (bool)isLoading {
return chrome_test_util::IsLoading();
}
+ (void)startReloading {
WebNavigationBrowserAgent::FromBrowser(chrome_test_util::GetMainBrowser())
->Reload();
}
+ (void)openURLFromExternalApp:(NSString*)URL {
chrome_test_util::OpenChromeFromExternalApp(
GURL(base::SysNSStringToUTF8(URL)));
}
+ (void)dismissSettings {
[chrome_test_util::HandlerForActiveBrowser() closeSettingsUI];
}
#pragma mark - Tab Utilities (EG2)
+ (void)selectTabAtIndex:(NSUInteger)index {
chrome_test_util::SelectTabAtIndexInCurrentMode(index);
}
+ (BOOL)isIncognitoMode {
return chrome_test_util::IsIncognitoMode();
}
+ (void)closeTabAtIndex:(NSUInteger)index {
chrome_test_util::CloseTabAtIndex(index);
}
+ (NSUInteger)mainTabCount {
return chrome_test_util::GetMainTabCount();
}
+ (NSUInteger)inactiveTabCount {
return chrome_test_util::GetInactiveTabCount();
}
+ (NSUInteger)incognitoTabCount {
return chrome_test_util::GetIncognitoTabCount();
}
+ (NSUInteger)browserCount {
return chrome_test_util::RegularBrowserCount();
}
+ (NSUInteger)evictedMainTabCount {
return chrome_test_util::GetEvictedMainTabCount();
}
+ (void)evictOtherBrowserTabs {
chrome_test_util::EvictOtherBrowserTabs();
}
+ (NSError*)simulateTabsBackgrounding {
if (!chrome_test_util::SimulateTabsBackgrounding()) {
return testing::NSErrorWithLocalizedDescription(
@"Fail to simulate tab backgrounding.");
}
return nil;
}
+ (NSError*)setCurrentTabsToBeColdStartTabs {
if (!chrome_test_util::SetCurrentTabsToBeColdStartTabs()) {
return testing::NSErrorWithLocalizedDescription(
@"Fail to state tabs as cold start tabs");
}
return nil;
}
+ (NSError*)resetTabUsageRecorder {
if (!chrome_test_util::ResetTabUsageRecorder()) {
return testing::NSErrorWithLocalizedDescription(
@"Fail to reset the TabUsageRecorder");
}
return nil;
}
+ (void)openNewTab {
chrome_test_util::OpenNewTab();
}
+ (void)simulateExternalAppURLOpeningWithURL:(NSURL*)URL {
chrome_test_util::SimulateExternalAppURLOpeningWithURL(URL);
}
+ (void)simulateAddAccountFromWeb {
chrome_test_util::SimulateAddAccountFromWeb();
}
+ (void)closeCurrentTab {
chrome_test_util::CloseCurrentTab();
}
+ (void)pinCurrentTab {
chrome_test_util::PinCurrentTab();
}
+ (void)openNewIncognitoTab {
chrome_test_util::OpenNewIncognitoTab();
}
+ (NSString*)currentTabTitle {
return chrome_test_util::GetCurrentTabTitle();
}
+ (NSString*)nextTabTitle {
return chrome_test_util::GetNextTabTitle();
}
+ (void)closeAllTabsInCurrentMode {
chrome_test_util::CloseAllTabsInCurrentMode();
}
+ (NSError*)closeAllNormalTabs {
bool success = chrome_test_util::CloseAllNormalTabs();
if (!success) {
return testing::NSErrorWithLocalizedDescription(
@"Could not close all normal tabs");
}
return nil;
}
+ (NSError*)closeAllIncognitoTabs {
bool success = chrome_test_util::CloseAllIncognitoTabs();
if (!success) {
return testing::NSErrorWithLocalizedDescription(
@"Could not close all Incognito tabs");
}
return nil;
}
+ (void)closeAllTabs {
chrome_test_util::CloseAllTabs();
}
+ (void)startGoingBack {
WebNavigationBrowserAgent::FromBrowser(chrome_test_util::GetMainBrowser())
->GoBack();
}
+ (void)startGoingForward {
WebNavigationBrowserAgent::FromBrowser(chrome_test_util::GetMainBrowser())
->GoForward();
}
+ (NSString*)currentTabID {
web::WebState* web_state = chrome_test_util::GetCurrentWebState();
return web_state->GetStableIdentifier();
}
+ (NSString*)nextTabID {
web::WebState* web_state = chrome_test_util::GetNextWebState();
return web_state->GetStableIdentifier();
}
+ (NSUInteger)indexOfActiveNormalTab {
return chrome_test_util::GetIndexOfActiveNormalTab();
}
#pragma mark - Window utilities (EG2)
+ (UIWindow*)windowWithNumber:(int)windowNumber {
NSArray<SceneState*>* connectedScenes =
chrome_test_util::GetMainController().appState.connectedScenes;
NSString* accessibilityIdentifier =
[NSString stringWithFormat:@"%ld", (long)windowNumber];
for (SceneState* state in connectedScenes) {
if ([state.window.accessibilityIdentifier
isEqualToString:accessibilityIdentifier]) {
return state.window;
}
}
return nil;
}
// Returns screen position of the given `windowNumber`
+ (CGRect)screenPositionOfScreenWithNumber:(int)windowNumber {
NSArray<SceneState*>* connectedScenes =
chrome_test_util::GetMainController().appState.connectedScenes;
NSString* accessibilityIdentifier =
[NSString stringWithFormat:@"%ld", (long)windowNumber];
for (SceneState* state in connectedScenes) {
if ([state.window.accessibilityIdentifier
isEqualToString:accessibilityIdentifier]) {
return
[state.window convertRect:state.window.frame
toCoordinateSpace:state.window.screen.fixedCoordinateSpace];
}
}
return CGRectZero;
}
+ (NSUInteger)windowCount [[nodiscard]] {
// If the scene API is in use, return the count of open sessions.
return UIApplication.sharedApplication.openSessions.count;
}
+ (NSUInteger)foregroundWindowCount [[nodiscard]] {
// If the scene API is in use, look at all the connected scenes and count
// those in the foreground.
NSUInteger count = 0;
for (UIScene* scene in UIApplication.sharedApplication.connectedScenes) {
if (scene.activationState == UISceneActivationStateForegroundActive ||
scene.activationState == UISceneActivationStateForegroundInactive) {
count++;
}
}
return count;
}
+ (NSError*)openNewWindow {
if (!base::ios::IsMultipleScenesSupported()) {
return testing::NSErrorWithLocalizedDescription(
@"Multiwindow not supported");
}
// Always disable default browser promo in new window, to avoid
// messages to be closed too early.
[self disableDefaultBrowserPromo];
NSUserActivity* activity =
[[NSUserActivity alloc] initWithActivityType:@"EG2NewWindow"];
UISceneActivationRequestOptions* options =
[[UISceneActivationRequestOptions alloc] init];
[UIApplication.sharedApplication
requestSceneSessionActivation:nil /* make a new scene */
userActivity:activity
options:options
errorHandler:nil];
return nil;
}
+ (void)openNewTabInWindowWithNumber:(int)windowNumber {
chrome_test_util::OpenNewTabInWindowWithNumber(windowNumber);
}
+ (void)changeWindowWithNumber:(int)windowNumber
toNewNumber:(int)newWindowNumber {
NSArray<SceneState*>* connectedScenes =
chrome_test_util::GetMainController().appState.connectedScenes;
NSString* accessibilityIdentifier =
[NSString stringWithFormat:@"%ld", (long)windowNumber];
NSString* newAccessibilityIdentifier =
[NSString stringWithFormat:@"%ld", (long)newWindowNumber];
for (SceneState* state in connectedScenes) {
if ([state.window.accessibilityIdentifier
isEqualToString:accessibilityIdentifier]) {
state.window.accessibilityIdentifier = newAccessibilityIdentifier;
break;
}
}
}
+ (void)closeWindowWithNumber:(int)windowNumber {
NSArray<SceneState*>* connectedScenes =
chrome_test_util::GetMainController().appState.connectedScenes;
NSString* accessibilityIdentifier =
[NSString stringWithFormat:@"%ld", (long)windowNumber];
for (SceneState* state in connectedScenes) {
if ([state.window.accessibilityIdentifier
isEqualToString:accessibilityIdentifier]) {
UIWindowSceneDestructionRequestOptions* options =
[[UIWindowSceneDestructionRequestOptions alloc] init];
options.windowDismissalAnimation =
UIWindowSceneDismissalAnimationStandard;
[UIApplication.sharedApplication
requestSceneSessionDestruction:state.scene.session
options:options
errorHandler:nil];
}
}
}
+ (void)closeAllExtraWindows {
if (!base::ios::IsMultipleScenesSupported())
return;
SceneState* foreground_scene_state =
chrome_test_util::GetMainController().appState.foregroundActiveScene;
// New windows get an accessibilityIdentifier equal to the number of windows
// when they are created.
// Renumber the remaining window to avoid conflicts with future windows.
foreground_scene_state.window.accessibilityIdentifier = @"0";
NSSet<UISceneSession*>* sessions =
UIApplication.sharedApplication.openSessions;
if (sessions.count <= 1)
return;
for (UISceneSession* session in sessions) {
if (foreground_scene_state.scene == session.scene) {
continue;
}
UIWindowSceneDestructionRequestOptions* options =
[[UIWindowSceneDestructionRequestOptions alloc] init];
options.windowDismissalAnimation = UIWindowSceneDismissalAnimationStandard;
[UIApplication.sharedApplication requestSceneSessionDestruction:session
options:options
errorHandler:nil];
}
}
+ (void)startLoadingURL:(NSString*)spec inWindowWithNumber:(int)windowNumber {
chrome_test_util::LoadUrlInWindowWithNumber(
GURL(base::SysNSStringToUTF8(spec)), windowNumber);
}
+ (BOOL)isLoadingInWindowWithNumber:(int)windowNumber {
return chrome_test_util::IsLoadingInWindowWithNumber(windowNumber);
}
+ (BOOL)webStateContainsText:(NSString*)text
inWindowWithNumber:(int)windowNumber {
web::WebState* webState =
chrome_test_util::GetCurrentWebStateForWindowWithNumber(windowNumber);
return webState ? web::test::IsWebViewContainingText(
webState, base::SysNSStringToUTF8(text))
: NO;
}
+ (NSUInteger)mainTabCountInWindowWithNumber:(int)windowNumber {
return chrome_test_util::GetMainTabCountForWindowWithNumber(windowNumber);
}
+ (NSUInteger)incognitoTabCountInWindowWithNumber:(int)windowNumber {
return chrome_test_util::GetIncognitoTabCountForWindowWithNumber(
windowNumber);
}
+ (UIWindow*)keyWindow {
NSSet<UIScene*>* scenes = UIApplication.sharedApplication.connectedScenes;
for (UIScene* scene in scenes) {
UIWindowScene* windowScene =
base::apple::ObjCCastStrict<UIWindowScene>(scene);
for (UIWindow* window in windowScene.windows) {
if (window.isKeyWindow) {
return window;
}
}
}
return nil;
}
#pragma mark - WebState Utilities (EG2)
+ (NSError*)tapWebStateElementInIFrameWithID:(NSString*)elementID {
bool success = web::test::TapWebViewElementWithIdInIframe(
chrome_test_util::GetCurrentWebState(),
base::SysNSStringToUTF8(elementID));
if (!success) {
return testing::NSErrorWithLocalizedDescription([NSString
stringWithFormat:@"Failed to tap element with ID=%@", elementID]);
}
return nil;
}
+ (NSError*)tapWebStateElementWithID:(NSString*)elementID {
NSError* error = nil;
bool success = web::test::TapWebViewElementWithId(
chrome_test_util::GetCurrentWebState(),
base::SysNSStringToUTF8(elementID), &error);
if (!success || error) {
NSString* errorDescription =
[NSString stringWithFormat:
@"Failed to tap web state element with ID=%@! Error: %@",
elementID, error];
return testing::NSErrorWithLocalizedDescription(errorDescription);
}
return nil;
}
+ (NSError*)waitForWebStateContainingElement:(ElementSelector*)selector {
bool success = WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^bool {
return web::test::IsWebViewContainingElement(
chrome_test_util::GetCurrentWebState(), selector);
});
if (!success) {
NSString* NSErrorDescription = [NSString
stringWithFormat:@"Failed waiting for web state containing element %@",
selector.selectorDescription];
return testing::NSErrorWithLocalizedDescription(NSErrorDescription);
}
return nil;
}
+ (NSError*)waitForWebStateNotContainingElement:(ElementSelector*)selector {
bool success = WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^bool {
return !web::test::IsWebViewContainingElement(
chrome_test_util::GetCurrentWebState(), selector);
});
if (!success) {
NSString* NSErrorDescription = [NSString
stringWithFormat:@"Failed waiting for web state without element %@",
selector.selectorDescription];
return testing::NSErrorWithLocalizedDescription(NSErrorDescription);
}
return nil;
}
+ (NSError*)waitForWebStateContainingTextInIFrame:(NSString*)text {
std::string stringText = base::SysNSStringToUTF8(text);
bool success = WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^bool {
return web::test::IsWebViewContainingTextInFrame(
chrome_test_util::GetCurrentWebState(), stringText);
});
if (!success) {
NSString* NSErrorDescription = [NSString
stringWithFormat:
@"Failed waiting for web state's iframes containing text %@", text];
return testing::NSErrorWithLocalizedDescription(NSErrorDescription);
}
return nil;
}
+ (NSError*)submitWebStateFormWithID:(NSString*)formID {
bool success = web::test::SubmitWebViewFormWithId(
chrome_test_util::GetCurrentWebState(), base::SysNSStringToUTF8(formID));
if (!success) {
NSString* errorString =
[NSString stringWithFormat:@"Failed to submit form with ID=%@", formID];
return testing::NSErrorWithLocalizedDescription(errorString);
}
return nil;
}
+ (BOOL)webStateContainsElement:(ElementSelector*)selector {
return web::test::IsWebViewContainingElement(
chrome_test_util::GetCurrentWebState(), selector);
}
+ (BOOL)webStateContainsText:(NSString*)text {
return web::test::IsWebViewContainingText(
chrome_test_util::GetCurrentWebState(), base::SysNSStringToUTF8(text));
}
+ (NSError*)waitForWebStateContainingLoadedImage:(NSString*)imageID {
bool success = web::test::WaitForWebViewContainingImage(
base::SysNSStringToUTF8(imageID), chrome_test_util::GetCurrentWebState(),
web::test::IMAGE_STATE_LOADED);
if (!success) {
NSString* errorString = [NSString
stringWithFormat:@"Failed waiting for web view loaded image %@",
imageID];
return testing::NSErrorWithLocalizedDescription(errorString);
}
return nil;
}
+ (NSError*)waitForWebStateContainingBlockedImage:(NSString*)imageID {
bool success = web::test::WaitForWebViewContainingImage(
base::SysNSStringToUTF8(imageID), chrome_test_util::GetCurrentWebState(),
web::test::IMAGE_STATE_BLOCKED);
if (!success) {
NSString* errorString = [NSString
stringWithFormat:@"Failed waiting for web view blocked image %@",
imageID];
return testing::NSErrorWithLocalizedDescription(errorString);
}
return nil;
}
+ (NSError*)waitForWebStateZoomScale:(CGFloat)scale {
bool success = WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^bool {
web::WebState* web_state = chrome_test_util::GetCurrentWebState();
if (!web_state) {
return false;
}
CGFloat current_scale =
[[web_state->GetWebViewProxy() scrollViewProxy] zoomScale];
return (current_scale > (scale - 0.05)) && (current_scale < (scale + 0.05));
});
if (!success) {
NSString* NSErrorDescription = [NSString
stringWithFormat:@"Failed waiting for web state zoom scale %f", scale];
return testing::NSErrorWithLocalizedDescription(NSErrorDescription);
}
return nil;
}
+ (void)signOutAndClearIdentitiesWithCompletion:(ProceduralBlock)completion {
chrome_test_util::SignOutAndClearIdentities(completion);
}
+ (BOOL)hasIdentities {
return chrome_test_util::HasIdentities();
}
+ (NSString*)webStateVisibleURL {
return base::SysUTF8ToNSString(
chrome_test_util::GetCurrentWebState()->GetVisibleURL().spec());
}
+ (NSString*)webStateLastCommittedURL {
return base::SysUTF8ToNSString(
chrome_test_util::GetCurrentWebState()->GetLastCommittedURL().spec());
}
+ (NSError*)purgeCachedWebViewPages {
web::WebState* web_state = chrome_test_util::GetCurrentWebState();
web_state->SetWebUsageEnabled(false);
if (!chrome_test_util::RemoveBrowsingCache()) {
return testing::NSErrorWithLocalizedDescription(
@"Fail to purge cached web view pages.");
}
// Attempt to deflake WebKit sometimes still holding on to the browser cache
// with a larger hammer.
base::ScopedAllowBlockingForTesting allow_blocking;
base::FilePath library_dir = base::apple::GetUserLibraryPath();
base::FilePath webkit_cache_dir = library_dir.Append("WebKit");
DeletePathRecursively(webkit_cache_dir);
web_state->SetWebUsageEnabled(true);
web_state->GetNavigationManager()->LoadIfNecessary();
return nil;
}
+ (BOOL)isRestoreSessionInProgress {
web::WebState* web_state = chrome_test_util::GetCurrentWebState();
return web_state->GetNavigationManager()->IsRestoreSessionInProgress();
}
+ (BOOL)webStateWebViewUsesContentInset {
web::WebState* web_state = chrome_test_util::GetCurrentWebState();
return web_state->GetWebViewProxy().shouldUseViewContentInset;
}
+ (CGSize)webStateWebViewSize {
web::WebState* web_state = chrome_test_util::GetCurrentWebState();
return [web_state->GetWebViewProxy() bounds].size;
}
+ (void)stopAllWebStatesLoading {
WebStateList* web_state_list =
chrome_test_util::GetMainController()
.browserProviderInterface.currentBrowserProvider.browser
->GetWebStateList();
for (int index = 0; index < web_state_list->count(); ++index) {
web::WebState* web_state = web_state_list->GetWebStateAt(index);
if (web_state->IsLoading()) {
web_state->Stop();
}
}
}
#pragma mark - URL Utilities (EG2)
+ (NSString*)displayTitleForURL:(NSString*)URL {
return base::SysUTF16ToNSString(
web::GetDisplayTitleForUrl(GURL(base::SysNSStringToUTF8(URL))));
}
#pragma mark - Sync Utilities (EG2)
+ (int)numberOfSyncEntitiesWithType:(syncer::ModelType)type {
return chrome_test_util::GetNumberOfSyncEntities(type);
}
+ (void)addFakeSyncServerBookmarkWithURL:(NSString*)URL title:(NSString*)title {
chrome_test_util::AddBookmarkToFakeSyncServer(base::SysNSStringToUTF8(URL),
base::SysNSStringToUTF8(title));
}
+ (void)addFakeSyncServerLegacyBookmarkWithURL:(NSString*)URL
title:(NSString*)title
originator_client_item_id:
(NSString*)originator_client_item_id {
chrome_test_util::AddLegacyBookmarkToFakeSyncServer(
base::SysNSStringToUTF8(URL), base::SysNSStringToUTF8(title),
base::SysNSStringToUTF8(originator_client_item_id));
}
+ (void)addFakeSyncServerTypedURL:(NSString*)URL {
chrome_test_util::AddTypedURLToFakeSyncServer(base::SysNSStringToUTF8(URL));
}
+ (void)addFakeSyncServerHistoryVisit:(NSURL*)URL {
chrome_test_util::AddHistoryVisitToFakeSyncServer(net::GURLWithNSURL(URL));
}
+ (void)addFakeSyncServerDeviceInfo:(NSString*)deviceName
lastUpdatedTimestamp:(base::Time)lastUpdatedTimestamp {
chrome_test_util::AddDeviceInfoToFakeSyncServer(
base::SysNSStringToUTF8(deviceName), lastUpdatedTimestamp);
}
+ (void)addHistoryServiceTypedURL:(NSString*)URL {
chrome_test_util::AddTypedURLToClient(GURL(base::SysNSStringToUTF8(URL)));
}
+ (void)deleteHistoryServiceTypedURL:(NSString*)URL {
chrome_test_util::DeleteTypedUrlFromClient(
GURL(base::SysNSStringToUTF8(URL)));
}
+ (BOOL)isURL:(NSString*)spec presentOnClient:(BOOL)expectPresent {
NSError* error = nil;
GURL URL(base::SysNSStringToUTF8(spec));
BOOL success =
chrome_test_util::IsUrlPresentOnClient(URL, expectPresent, &error);
return success && !error;
}
+ (void)triggerSyncCycleForType:(syncer::ModelType)type {
chrome_test_util::TriggerSyncCycle(type);
}
+ (void)
addUserDemographicsToSyncServerWithBirthYear:(int)rawBirthYear
gender:
(metrics::UserDemographicsProto::
Gender)gender {
chrome_test_util::AddUserDemographicsToSyncServer(rawBirthYear, gender);
}
+ (void)clearAutofillProfileWithGUID:(NSString*)GUID {
std::string utfGUID = base::SysNSStringToUTF8(GUID);
chrome_test_util::ClearAutofillProfile(utfGUID);
}
+ (void)addAutofillProfileToFakeSyncServerWithGUID:(NSString*)GUID
autofillProfileName:(NSString*)fullName {
std::string utfGUID = base::SysNSStringToUTF8(GUID);
std::string utfFullName = base::SysNSStringToUTF8(fullName);
chrome_test_util::AddAutofillProfileToFakeSyncServer(utfGUID, utfFullName);
}
+ (BOOL)isAutofillProfilePresentWithGUID:(NSString*)GUID
autofillProfileName:(NSString*)fullName {
std::string utfGUID = base::SysNSStringToUTF8(GUID);
std::string utfFullName = base::SysNSStringToUTF8(fullName);
return chrome_test_util::IsAutofillProfilePresent(utfGUID, utfFullName);
}
+ (void)deleteAutofillProfileFromFakeSyncServerWithGUID:(NSString*)GUID {
chrome_test_util::DeleteAutofillProfileFromFakeSyncServer(
base::SysNSStringToUTF8(GUID));
}
+ (void)signInWithoutSyncWithIdentity:(FakeSystemIdentity*)identity {
chrome_test_util::SignInWithoutSync(identity);
}
+ (void)clearSyncServerData {
chrome_test_util::ClearSyncServerData();
}
+ (NSError*)waitForSyncEngineInitialized:(BOOL)isInitialized
syncTimeout:(base::TimeDelta)timeout {
bool success = WaitUntilConditionOrTimeout(timeout, ^{
return chrome_test_util::IsSyncEngineInitialized() == isInitialized;
});
if (!success) {
NSString* errorDescription =
[NSString stringWithFormat:@"Sync must be initialized: %@",
isInitialized ? @"YES" : @"NO"];
return testing::NSErrorWithLocalizedDescription(errorDescription);
}
return nil;
}
+ (NSError*)waitForSyncFeatureEnabled:(BOOL)isEnabled
syncTimeout:(base::TimeDelta)timeout {
bool success = WaitUntilConditionOrTimeout(timeout, ^{
ChromeBrowserState* browser_state =
chrome_test_util::GetOriginalBrowserState();
DCHECK(browser_state);
syncer::SyncService* syncService =
SyncServiceFactory::GetForBrowserState(browser_state);
return syncService->IsSyncFeatureEnabled() == isEnabled;
});
if (!success) {
NSString* errorDescription =
[NSString stringWithFormat:@"Sync feature must be enabled: %@",
isEnabled ? @"YES" : @"NO"];
return testing::NSErrorWithLocalizedDescription(errorDescription);
}
return nil;
}
+ (NSError*)waitForSyncTransportStateActiveWithTimeout:
(base::TimeDelta)timeout {
bool success = WaitUntilConditionOrTimeout(timeout, ^{
ChromeBrowserState* browser_state =
chrome_test_util::GetOriginalBrowserState();
DCHECK(browser_state);
syncer::SyncService* syncService =
SyncServiceFactory::GetForBrowserState(browser_state);
return syncService->GetTransportState() ==
syncer::SyncService::TransportState::ACTIVE;
});
if (!success) {
ChromeBrowserState* browser_state =
chrome_test_util::GetOriginalBrowserState();
NSString* errorDescription = [NSString
stringWithFormat:
@"Sync transport must be active, but actual state was: %d",
(int)SyncServiceFactory::GetForBrowserState(browser_state)
->GetTransportState()];
return testing::NSErrorWithLocalizedDescription(errorDescription);
}
return nil;
}
+ (NSString*)syncCacheGUID {
return base::SysUTF8ToNSString(chrome_test_util::GetSyncCacheGuid());
}
+ (NSError*)waitForSyncInvalidationFields {
const bool success = WaitUntilConditionOrTimeout(kWaitForActionTimeout, ^{
return chrome_test_util::VerifySyncInvalidationFieldsPopulated();
});
if (!success) {
return testing::NSErrorWithLocalizedDescription(
@"The local DeviceInfo doesn't have invalidation fields");
}
return nil;
}
+ (BOOL)isFakeSyncServerSetUp {
return chrome_test_util::IsFakeSyncServerSetUp();
}
+ (void)setUpFakeSyncServer {
chrome_test_util::SetUpFakeSyncServer();
}
+ (void)tearDownFakeSyncServer {
chrome_test_util::TearDownFakeSyncServer();
}
+ (NSError*)verifyNumberOfSyncEntitiesWithType:(NSUInteger)type
name:(NSString*)name
count:(NSUInteger)count {
std::string UTF8Name = base::SysNSStringToUTF8(name);
NSError* __autoreleasing tempError = nil;
bool success = chrome_test_util::VerifyNumberOfSyncEntitiesWithName(
(syncer::ModelType)type, UTF8Name, count, &tempError);
NSError* error = tempError;
if (!success and !error) {
NSString* errorString =
[NSString stringWithFormat:@"Expected %zu entities of the %d type.",
count, (syncer::ModelType)type];
return testing::NSErrorWithLocalizedDescription(errorString);
}
return error;
}
+ (NSError*)verifySessionsOnSyncServerWithSpecs:(NSArray<NSString*>*)specs {
std::multiset<std::string> multisetSpecs;
for (NSString* spec in specs) {
multisetSpecs.insert(base::SysNSStringToUTF8(spec));
}
NSError* __autoreleasing tempError = nil;
bool success =
chrome_test_util::VerifySessionsOnSyncServer(multisetSpecs, &tempError);
NSError* error = tempError;
if (!success && !error) {
error = testing::NSErrorWithLocalizedDescription(
@"Error occurred during verification sessions.");
}
return error;
}
+ (NSError*)verifyHistoryOnSyncServerWithURLs:(NSArray<NSURL*>*)URLs {
std::multiset<GURL> multisetUrls;
for (NSURL* url in URLs) {
multisetUrls.insert(net::GURLWithNSURL(url));
}
NSError* __autoreleasing tempError = nil;
bool success =
chrome_test_util::VerifyHistoryOnSyncServer(multisetUrls, &tempError);
NSError* error = tempError;
if (!success && !error) {
error = testing::NSErrorWithLocalizedDescription(
@"Error occurred during verifying history URLs.");
}
return error;
}
+ (void)addBookmarkWithSyncPassphrase:(NSString*)syncPassphrase {
chrome_test_util::AddBookmarkWithSyncPassphrase(
base::SysNSStringToUTF8(syncPassphrase));
}
#pragma mark - JavaScript Utilities (EG2)
+ (JavaScriptExecutionResult*)executeJavaScript:(NSString*)javaScript {
__block web::WebFrame* main_frame = nullptr;
bool completed =
WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^bool {
main_frame = chrome_test_util::GetCurrentWebState()
->GetPageWorldWebFramesManager()
->GetMainWebFrame();
return main_frame != nullptr;
});
if (!main_frame) {
DLOG(ERROR) << "No main web frame exists.";
return [[JavaScriptExecutionResult alloc] initWithResult:nil
successfulExecution:NO];
}
std::u16string script = base::SysNSStringToUTF16(javaScript);
__block bool handlerCalled = false;
__block NSString* blockResult = nil;
__block NSError* blockError = nil;
main_frame->ExecuteJavaScript(
script, base::BindOnce(^(const base::Value* value, NSError* error) {
handlerCalled = true;
blockError = error;
blockResult = SerializedValue(value);
}));
completed = WaitUntilConditionOrTimeout(kWaitForJSCompletionTimeout, ^{
return handlerCalled;
});
DLOG_IF(ERROR, !completed) << "JavaScript execution timed out.";
DLOG_IF(ERROR, blockError) << "JavaScript execution of:\n"
<< script << "\nfailed with error:\n"
<< base::SysNSStringToUTF8(blockError.description);
BOOL success = completed && !blockError;
JavaScriptExecutionResult* result =
[[JavaScriptExecutionResult alloc] initWithResult:blockResult
successfulExecution:success];
return result;
}
+ (NSString*)mobileUserAgentString {
return base::SysUTF8ToNSString(
web::GetWebClient()->GetUserAgent(web::UserAgentType::MOBILE));
}
#pragma mark - Accessibility Utilities (EG2)
+ (NSError*)verifyAccessibilityForCurrentScreen {
NSError* error = nil;
BOOL success = chrome_test_util::VerifyAccessibilityForCurrentScreen(&error);
if (!success || error) {
NSString* errorDescription = [NSString
stringWithFormat:@"Accessibility checks failed! Error: %@", error];
return testing::NSErrorWithLocalizedDescription(errorDescription);
}
return nil;
}
#pragma mark - Check features (EG2)
+ (BOOL)isVariationEnabled:(int)variationID {
variations::VariationsIdsProvider* provider =
variations::VariationsIdsProvider::GetInstance();
std::vector<variations::VariationID> ids = provider->GetVariationsVector(
{variations::GOOGLE_WEB_PROPERTIES_ANY_CONTEXT,
variations::GOOGLE_WEB_PROPERTIES_FIRST_PARTY});
return base::Contains(ids, variationID);
}
+ (BOOL)isTriggerVariationEnabled:(int)variationID {
variations::VariationsIdsProvider* provider =
variations::VariationsIdsProvider::GetInstance();
std::vector<variations::VariationID> ids = provider->GetVariationsVector(
{variations::GOOGLE_WEB_PROPERTIES_TRIGGER_ANY_CONTEXT,
variations::GOOGLE_WEB_PROPERTIES_TRIGGER_FIRST_PARTY});
return base::Contains(ids, variationID);
}
+ (BOOL)isUKMEnabled {
return base::FeatureList::IsEnabled(ukm::kUkmFeature);
}
+ (BOOL)isSynthesizedRestoreSessionEnabled {
return base::FeatureList::IsEnabled(
web::features::kSynthesizedRestoreSession);
}
+ (BOOL)isTestFeatureEnabled {
return base::FeatureList::IsEnabled(kTestFeature);
}
+ (BOOL)isDemographicMetricsReportingEnabled {
return base::FeatureList::IsEnabled(metrics::kDemographicMetricsReporting);
}
+ (BOOL)isSyncHistoryDataTypeEnabled {
return base::FeatureList::IsEnabled(syncer::kSyncEnableHistoryDataType);
}
+ (BOOL)appHasLaunchSwitch:(NSString*)launchSwitch {
return base::CommandLine::ForCurrentProcess()->HasSwitch(
base::SysNSStringToUTF8(launchSwitch));
}
+ (BOOL)isCustomWebKitLoadedIfRequested {
return IsCustomWebKitLoadedIfRequested();
}
+ (BOOL)isLoadSimulatedRequestAPIEnabled {
return web::features::IsLoadSimulatedRequestAPIEnabled();
}
+ (BOOL)isMobileModeByDefault {
web::UserAgentType webClientUserAgent =
web::GetWebClient()->GetDefaultUserAgent(
chrome_test_util::GetCurrentWebState(), GURL());
return webClientUserAgent == web::UserAgentType::MOBILE;
}
+ (BOOL)areMultipleWindowsSupported {
return base::ios::IsMultipleScenesSupported();
}
+ (BOOL)isNewOverflowMenuEnabled {
return IsNewOverflowMenuEnabled();
}
+ (BOOL)isUseLensToSearchForImageEnabled {
TemplateURLService* service =
ios::TemplateURLServiceFactory::GetForBrowserState(
chrome_test_util::GetOriginalBrowserState());
return ios::provider::IsLensSupported() &&
ui::GetDeviceFormFactor() != ui::DEVICE_FORM_FACTOR_TABLET &&
search_engines::SupportsSearchImageWithLens(service);
}
+ (BOOL)isWebChannelsEnabled {
return base::FeatureList::IsEnabled(kEnableWebChannels);
}
+ (BOOL)isUIButtonConfigurationEnabled {
return IsUIButtonConfigurationEnabled();
}
+ (BOOL)isBottomOmniboxSteadyStateEnabled {
return IsBottomOmniboxSteadyStateEnabled();
}
#pragma mark - ContentSettings
+ (ContentSetting)popupPrefValue {
return ios::HostContentSettingsMapFactory::GetForBrowserState(
chrome_test_util::GetOriginalBrowserState())
->GetDefaultContentSetting(ContentSettingsType::POPUPS, NULL);
}
+ (void)setPopupPrefValue:(ContentSetting)value {
ios::HostContentSettingsMapFactory::GetForBrowserState(
chrome_test_util::GetOriginalBrowserState())
->SetDefaultContentSetting(ContentSettingsType::POPUPS, value);
}
+ (void)resetDesktopContentSetting {
ios::HostContentSettingsMapFactory::GetForBrowserState(
chrome_test_util::GetOriginalBrowserState())
->SetDefaultContentSetting(ContentSettingsType::REQUEST_DESKTOP_SITE,
CONTENT_SETTING_BLOCK);
}
+ (void)setContentSetting:(ContentSetting)setting
forContentSettingsType:(ContentSettingsType)type {
ios::HostContentSettingsMapFactory::GetForBrowserState(
chrome_test_util::GetOriginalBrowserState())
->SetDefaultContentSetting(type, setting);
}
#pragma mark - Default Utilities (EG2)
+ (void)setUserDefaultObject:(id)value forKey:(NSString*)defaultName {
[[NSUserDefaults standardUserDefaults] setObject:value forKey:defaultName];
}
+ (void)removeUserDefaultObjectForKey:(NSString*)key {
[[NSUserDefaults standardUserDefaults] removeObjectForKey:key];
}
#pragma mark - Pref Utilities (EG2)
+ (NSString*)localStatePrefValue:(NSString*)prefName {
std::string path = base::SysNSStringToUTF8(prefName);
const PrefService::Preference* pref =
GetApplicationContext()->GetLocalState()->FindPreference(path);
return SerializedPref(pref);
}
+ (void)setIntegerValue:(int)value forLocalStatePref:(NSString*)prefName {
std::string path = base::SysNSStringToUTF8(prefName);
PrefService* prefService = GetApplicationContext()->GetLocalState();
prefService->SetInteger(path, value);
}
+ (void)setTimeValue:(base::Time)value forLocalStatePref:(NSString*)prefName {
std::string path = base::SysNSStringToUTF8(prefName);
PrefService* prefService = GetApplicationContext()->GetLocalState();
prefService->SetTime(path, value);
}
+ (void)setStringValue:(NSString*)value forLocalStatePref:(NSString*)prefName {
std::string UTF8Value = base::SysNSStringToUTF8(value);
std::string path = base::SysNSStringToUTF8(prefName);
PrefService* prefService = GetApplicationContext()->GetLocalState();
prefService->SetString(path, UTF8Value);
}
+ (NSString*)userPrefValue:(NSString*)prefName {
std::string path = base::SysNSStringToUTF8(prefName);
const PrefService::Preference* pref =
chrome_test_util::GetOriginalBrowserState()->GetPrefs()->FindPreference(
path);
return SerializedPref(pref);
}
+ (void)setBoolValue:(BOOL)value forUserPref:(NSString*)prefName {
chrome_test_util::SetBooleanUserPref(
chrome_test_util::GetOriginalBrowserState(),
base::SysNSStringToUTF8(prefName).c_str(), value);
}
+ (void)setIntegerValue:(int)value forUserPref:(NSString*)prefName {
chrome_test_util::SetIntegerUserPref(
chrome_test_util::GetOriginalBrowserState(),
base::SysNSStringToUTF8(prefName).c_str(), value);
}
+ (void)clearUserPrefWithName:(NSString*)prefName {
PrefService* prefs = chrome_test_util::GetOriginalBrowserState()->GetPrefs();
prefs->ClearPref(base::SysNSStringToUTF8(prefName));
}
+ (void)commitPendingUserPrefsWrite {
PrefService* prefs = chrome_test_util::GetOriginalBrowserState()->GetPrefs();
prefs->CommitPendingWrite();
}
+ (void)resetBrowsingDataPrefs {
PrefService* prefs = chrome_test_util::GetOriginalBrowserState()->GetPrefs();
prefs->ClearPref(browsing_data::prefs::kDeleteBrowsingHistory);
prefs->ClearPref(browsing_data::prefs::kDeleteCookies);
prefs->ClearPref(browsing_data::prefs::kDeleteCache);
prefs->ClearPref(browsing_data::prefs::kDeletePasswords);
prefs->ClearPref(browsing_data::prefs::kDeleteFormData);
}
+ (void)resetDataForLocalStatePref:(NSString*)prefName {
std::string path = base::SysNSStringToUTF8(prefName);
PrefService* prefService = GetApplicationContext()->GetLocalState();
prefService->ClearPref(path);
}
#pragma mark - Unified Consent utilities
+ (void)setURLKeyedAnonymizedDataCollectionEnabled:(BOOL)enabled {
UnifiedConsentServiceFactory::GetForBrowserState(
chrome_test_util::GetOriginalBrowserState())
->SetUrlKeyedAnonymizedDataCollectionEnabled(enabled);
}
#pragma mark - Keyboard Command Utilities
+ (NSInteger)registeredKeyCommandCount {
UIViewController* browserViewController =
chrome_test_util::GetMainController()
.browserProviderInterface.mainBrowserProvider.viewController;
// The BVC delegates its key commands to its next responder,
// KeyCommandsProvider.
return browserViewController.nextResponder.keyCommands.count;
}
+ (void)simulatePhysicalKeyboardEvent:(NSString*)input
flags:(UIKeyModifierFlags)flags {
chrome_test_util::SimulatePhysicalKeyboardEvent(flags, input);
}
#pragma mark - Pasteboard utilities
+ (void)clearPasteboardURLs {
[[UIPasteboard generalPasteboard] setURLs:nil];
}
+ (void)clearPasteboard {
[[UIPasteboard generalPasteboard] setItems:@[]];
}
+ (BOOL)pasteboardHasImages {
return [UIPasteboard.generalPasteboard hasImages];
}
+ (NSArray<NSString*>*)pasteboardStrings {
return [UIPasteboard generalPasteboard].strings;
}
+ (NSString*)pasteboardURLSpec {
return [UIPasteboard generalPasteboard].URL.absoluteString;
}
+ (void)copyTextToPasteboard:(NSString*)text {
[UIPasteboard.generalPasteboard setString:text];
}
#pragma mark - Watcher utilities
// Delay between two watch cycles.
constexpr base::TimeDelta kWatcherCycleDelay = base::Milliseconds(200);
// Set of buttons being watched for.
NSMutableSet* watchingButtons;
// Set of buttons that were actually watched.
NSMutableSet* watchedButtons;
// Current watch number, to allow terminating older scheduled runs.
int watchRunNumber = 0;
+ (void)watchForButtonsWithLabels:(NSArray<NSString*>*)labels
timeout:(base::TimeDelta)timeout {
watchRunNumber++;
watchedButtons = [NSMutableSet set];
watchingButtons = [NSMutableSet set];
for (NSString* label in labels) {
[watchingButtons addObject:label];
}
[self scheduleNextWatchForButtonsWithTimeout:timeout
runNumber:watchRunNumber];
}
+ (BOOL)watcherDetectedButtonWithLabel:(NSString*)label {
return [watchedButtons containsObject:label];
}
+ (void)stopWatcher {
[watchingButtons removeAllObjects];
[watchedButtons removeAllObjects];
}
// Schedule the next watch cycles from a background thread, that will dispatch
// the actual check, async, on the main thread, since UI objects can only
// be accessed from there. Scheduling directly on the main
// thread would let EG to try to drain the main thread queue without
// success.
+ (void)scheduleNextWatchForButtonsWithTimeout:(base::TimeDelta)timeout
runNumber:(int)runNumber {
dispatch_queue_t background_queue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_after(
dispatch_time(DISPATCH_TIME_NOW, kWatcherCycleDelay.InNanoseconds()),
background_queue, ^{
dispatch_async(dispatch_get_main_queue(), ^{
if (!watchingButtons.count || runNumber != watchRunNumber)
return;
NSMutableArray<UIWindow*>* windows = [[NSMutableArray alloc] init];
for (UIScene* scene in UIApplication.sharedApplication
.connectedScenes) {
UIWindowScene* windowScene =
base::apple::ObjCCastStrict<UIWindowScene>(scene);
[windows addObjectsFromArray:windowScene.windows];
}
[self findButtonsWithLabelsInViews:windows];
if (watchingButtons.count && timeout.is_positive()) {
[self scheduleNextWatchForButtonsWithTimeout:timeout -
kWatcherCycleDelay
runNumber:runNumber];
} else {
[watchingButtons removeAllObjects];
}
});
});
}
// Looks for a button (based on traits) with the given `label`,
// recursively in the given `views`.
+ (void)findButtonsWithLabelsInViews:(NSArray<UIView*>*)views {
if (!watchingButtons.count)
return;
for (UIView* view in views) {
[self buttonsWithLabelsMatchView:view];
[self findButtonsWithLabelsInViews:view.subviews];
}
}
// Checks if the given `view` is a button (based on traits) with the
// given accessibility label.
+ (void)buttonsWithLabelsMatchView:(UIView*)view {
if (![view respondsToSelector:@selector(accessibilityLabel)])
return;
if (([view accessibilityTraits] & UIAccessibilityTraitButton) == 0)
return;
if ([watchingButtons containsObject:view.accessibilityLabel]) {
[watchedButtons addObject:view.accessibilityLabel];
[watchingButtons removeObject:view.accessibilityLabel];
}
}
#pragma mark - Default Browser Promo Utilities
+ (void)clearDefaultBrowserPromoData {
ClearDefaultBrowserPromoData();
}
+ (void)copyURLToPasteBoard {
UIPasteboard* pasteboard = UIPasteboard.generalPasteboard;
pasteboard.URL = [NSURL URLWithString:@"chrome://version"];
}
+ (void)disableDefaultBrowserPromo {
chrome_test_util::GetMainController().appState.shouldShowDefaultBrowserPromo =
NO;
LogUserInteractionWithFullscreenPromo();
}
#pragma mark - First Run Utilities
+ (void)writeFirstRunSentinel {
base::ScopedAllowBlockingForTesting allow_blocking;
FirstRun::RemoveSentinel();
base::File::Error fileError;
FirstRun::CreateSentinel(&fileError);
FirstRun::LoadSentinelInfo();
}
+ (void)removeFirstRunSentinel {
base::ScopedAllowBlockingForTesting allow_blocking;
if (FirstRun::RemoveSentinel()) {
FirstRun::LoadSentinelInfo();
}
}
@end