diff --git a/DEPS b/DEPS index bd5a815c..d9c1ae7 100644 --- a/DEPS +++ b/DEPS
@@ -40,11 +40,11 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling Skia # and whatever else without interference from each other. - 'skia_revision': 'ba7196c0c9844bc885692cf3abee7e62a2e5ec7b', + 'skia_revision': '6d3af6faa23a931be1404cd3fcba02a3c271151c', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling V8 # and whatever else without interference from each other. - 'v8_revision': '643d02d9ce587dba7d0d944c01a953e99397158c', + 'v8_revision': '547d6a869d38e6c08a5d038abd1477530d01f9cc', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling swarming_client # and whatever else without interference from each other.
diff --git a/chrome/browser/captive_portal/captive_portal_tab_helper_unittest.cc b/chrome/browser/captive_portal/captive_portal_tab_helper_unittest.cc index 6c67e59..f536a0c 100644 --- a/chrome/browser/captive_portal/captive_portal_tab_helper_unittest.cc +++ b/chrome/browser/captive_portal/captive_portal_tab_helper_unittest.cc
@@ -284,8 +284,9 @@ if (content::IsBrowserSideNavigationEnabled()) { // This chain of event is not possible with PlzNavigate. For the same-site // navigation not to be cancelled by the second navigation, it needs to get - // to ReadyToCommit stage. This is not currently possible to simulate with - // the content/ test API. + // to ReadyToCommit stage. Navigation failure after the ReadyToCommit stage + // is something we want to suppress, so it is not implemented in the + // NavigationSimulator. return; } GURL same_site_url = GURL(kHttpUrl); @@ -322,13 +323,6 @@ // Similar to the above test, except the original RenderViewHost manages to // commit before its navigation is aborted. TEST_F(CaptivePortalTabHelperTest, UnexpectedCommit) { - if (content::IsBrowserSideNavigationEnabled()) { - // This chain of event is not possible with PlzNavigate. For the same-site - // navigation not to be cancelled by the second navigation, it needs to get - // to ReadyToCommit stage. This is not currently possible to simulate with - // the content/ test API. - return; - } GURL same_site_url = GURL(kHttpUrl); GURL cross_process_url = GURL(kHttpsUrl2); @@ -337,10 +331,10 @@ OnLoadStart(same_site_url.SchemeIsCryptographic())).Times(1); std::unique_ptr<NavigationSimulator> same_site_navigation = NavigationSimulator::CreateRendererInitiated(same_site_url, main_rfh()); - same_site_navigation->Start(); + same_site_navigation->ReadyToCommit(); // It's unexpectedly interrupted by a cross-process navigation, which starts - // navigating before the old navigation cancels. + // navigating before the old navigation commits. EXPECT_CALL(mock_reloader(), OnAbort()).Times(1); EXPECT_CALL(mock_reloader(), OnLoadStart(cross_process_url.SchemeIsCryptographic())).Times(1);
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn index 89ddfc0..b31a4a0 100644 --- a/chrome/browser/chromeos/BUILD.gn +++ b/chrome/browser/chromeos/BUILD.gn
@@ -782,6 +782,7 @@ "lock_screen_apps/app_manager.h", "lock_screen_apps/app_manager_impl.cc", "lock_screen_apps/app_manager_impl.h", + "lock_screen_apps/focus_cycler_delegate.h", "lock_screen_apps/state_controller.cc", "lock_screen_apps/state_controller.h", "lock_screen_apps/state_observer.h",
diff --git a/chrome/browser/chromeos/lock_screen_apps/focus_cycler_delegate.h b/chrome/browser/chromeos/lock_screen_apps/focus_cycler_delegate.h new file mode 100644 index 0000000..3ffff39 --- /dev/null +++ b/chrome/browser/chromeos/lock_screen_apps/focus_cycler_delegate.h
@@ -0,0 +1,37 @@ +// 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 CHROME_BROWSER_CHROMEOS_LOCK_SCREEN_APPS_FOCUS_CYCLER_DELEGATE_H_ +#define CHROME_BROWSER_CHROMEOS_LOCK_SCREEN_APPS_FOCUS_CYCLER_DELEGATE_H_ + +#include "base/callback_forward.h" + +namespace lock_screen_apps { + +// Used by the StateController to inject a lock screen app window in the tab +// order cycle with the lock screen UI and system tray, i.e. to move the focus +// away from the app window when the app window is tabbed through, and to +// register a handler for receiving focus from the lock screen UI. +class FocusCyclerDelegate { + public: + virtual ~FocusCyclerDelegate() = default; + + // Registers a callback that should be called when the focus should be moved + // to the app window. + using LockScreenAppFocusCallback = base::Callback<void(bool reverse)>; + virtual void RegisterLockScreenAppFocusHandler( + const LockScreenAppFocusCallback& focus_handler) = 0; + + // Unregister the callback that should be called to move the focus to the + // app window, if one was registered. + virtual void UnregisterLockScreenAppFocusHandler() = 0; + + // Called when the focus leaves the lock screen app window. The delegate + // should move the focus to the next appropriate UI element. + virtual void HandleLockScreenAppFocusOut(bool reverse) = 0; +}; + +} // namespace lock_screen_apps + +#endif // CHROME_BROWSER_CHROMEOS_LOCK_SCREEN_APPS_FOCUS_CYCLER_DELEGATE_H_
diff --git a/chrome/browser/chromeos/lock_screen_apps/state_controller.cc b/chrome/browser/chromeos/lock_screen_apps/state_controller.cc index 03463b0..843225e6 100644 --- a/chrome/browser/chromeos/lock_screen_apps/state_controller.cc +++ b/chrome/browser/chromeos/lock_screen_apps/state_controller.cc
@@ -15,6 +15,7 @@ #include "base/strings/string16.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/chromeos/lock_screen_apps/app_manager_impl.h" +#include "chrome/browser/chromeos/lock_screen_apps/focus_cycler_delegate.h" #include "chrome/browser/chromeos/note_taking_helper.h" #include "chrome/browser/chromeos/profiles/profile_helper.h" #include "chrome/browser/profiles/profile_manager.h" @@ -25,6 +26,7 @@ #include "components/prefs/pref_registry_simple.h" #include "components/prefs/pref_service.h" #include "components/session_manager/core/session_manager.h" +#include "content/public/browser/web_contents.h" #include "content/public/common/service_manager_connection.h" #include "crypto/symmetric_key.h" #include "extensions/browser/api/lock_screen_data/lock_screen_item_storage.h" @@ -145,6 +147,7 @@ ResetNoteTakingWindowAndMoveToNextState(true /*close_window*/); app_manager_.reset(); } + focus_cycler_delegate_ = nullptr; power_manager_client_observer_.RemoveAll(); input_devices_observer_.RemoveAll(); binding_.Close(); @@ -240,6 +243,20 @@ observers_.RemoveObserver(observer); } +void StateController::SetFocusCyclerDelegate(FocusCyclerDelegate* delegate) { + DCHECK(!focus_cycler_delegate_ || !delegate); + + if (focus_cycler_delegate_ && note_app_window_) + focus_cycler_delegate_->UnregisterLockScreenAppFocusHandler(); + + focus_cycler_delegate_ = delegate; + + if (focus_cycler_delegate_ && note_app_window_) { + focus_cycler_delegate_->RegisterLockScreenAppFocusHandler(base::Bind( + &StateController::FocusAppWindow, weak_ptr_factory_.GetWeakPtr())); + } +} + TrayActionState StateController::GetLockScreenNoteState() const { return lock_screen_note_state_; } @@ -323,9 +340,26 @@ app_window_observer_.Add( extensions::AppWindowRegistry::Get(lock_screen_profile_)); UpdateLockScreenNoteState(TrayActionState::kActive); + if (focus_cycler_delegate_) { + focus_cycler_delegate_->RegisterLockScreenAppFocusHandler(base::Bind( + &StateController::FocusAppWindow, weak_ptr_factory_.GetWeakPtr())); + } return note_app_window_; } +bool StateController::HandleTakeFocus(content::WebContents* web_contents, + bool reverse) { + if (!focus_cycler_delegate_ || + (GetLockScreenNoteState() != TrayActionState::kActive && + GetLockScreenNoteState() != TrayActionState::kBackground) || + note_app_window_->web_contents() != web_contents) { + return false; + } + + focus_cycler_delegate_->HandleLockScreenAppFocusOut(reverse); + return true; +} + void StateController::MoveToBackground() { if (GetLockScreenNoteState() == TrayActionState::kLaunching) { UpdateLockScreenNoteState(TrayActionState::kAvailable); @@ -352,11 +386,34 @@ UpdateLockScreenNoteState(TrayActionState::kAvailable); } +void StateController::FocusAppWindow(bool reverse) { + // If the app window is in background, move it to foreground (moving the + // window to foreground should also active it). + if (GetLockScreenNoteState() == TrayActionState::kBackground) { + note_app_window_->web_contents()->FocusThroughTabTraversal(reverse); + MoveToForeground(); + return; + } + + // If the app window is not active, pass the focus on to the delegate.. + if (GetLockScreenNoteState() != TrayActionState::kActive) { + focus_cycler_delegate_->HandleLockScreenAppFocusOut(reverse); + return; + } + + note_app_window_->web_contents()->FocusThroughTabTraversal(reverse); + note_app_window_->GetBaseWindow()->Activate(); + note_app_window_->web_contents()->Focus(); +} + void StateController::ResetNoteTakingWindowAndMoveToNextState( bool close_window) { app_window_observer_.RemoveAll(); if (note_app_window_) { + if (focus_cycler_delegate_) + focus_cycler_delegate_->UnregisterLockScreenAppFocusHandler(); + if (close_window && note_app_window_->GetBaseWindow()) note_app_window_->GetBaseWindow()->Close(); note_app_window_ = nullptr;
diff --git a/chrome/browser/chromeos/lock_screen_apps/state_controller.h b/chrome/browser/chromeos/lock_screen_apps/state_controller.h index 8893e6fa..a1ee49c 100644 --- a/chrome/browser/chromeos/lock_screen_apps/state_controller.h +++ b/chrome/browser/chromeos/lock_screen_apps/state_controller.h
@@ -49,6 +49,7 @@ namespace lock_screen_apps { +class FocusCyclerDelegate; class StateObserver; // Manages state of lock screen action handler apps, and notifies @@ -107,6 +108,10 @@ void AddObserver(StateObserver* observer); void RemoveObserver(StateObserver* observer); + // Sets the focus cycler delegate the state controller should use to pass on + // from and give focus to the active lock screen app window. + void SetFocusCyclerDelegate(FocusCyclerDelegate* delegate); + // Gets current state assiciated with the lock screen note action. ash::mojom::TrayActionState GetLockScreenNoteState() const; @@ -138,6 +143,12 @@ extensions::api::app_runtime::ActionType action, std::unique_ptr<extensions::AppDelegate> app_delegate); + // Should be called when the active app window is tabbed through. If needed, + // the method will take focus from the app window and pass it on using + // |focus_cycler_delegate_|. + // Returns whether the focus has been taken from the app window. + bool HandleTakeFocus(content::WebContents* web_contents, bool reverse); + // If there are any active lock screen action handlers, moved their windows // to background, to ensure lock screen UI is visible. void MoveToBackground(); @@ -183,6 +194,11 @@ // Notifies observers that the lock screen note action state changed. void NotifyLockScreenNoteStateChanged(); + // Passed as a focus handler to |focus_cycler_delegate_| when the assiciated + // app window is visible (active or in background). + // It focuses the app window. + void FocusAppWindow(bool reverse); + // Lock screen note action state. ash::mojom::TrayActionState lock_screen_note_state_ = ash::mojom::TrayActionState::kNotAvailable; @@ -199,6 +215,8 @@ std::unique_ptr<AppManager> app_manager_; + FocusCyclerDelegate* focus_cycler_delegate_ = nullptr; + extensions::AppWindow* note_app_window_ = nullptr; ScopedObserver<extensions::AppWindowRegistry,
diff --git a/chrome/browser/chromeos/lock_screen_apps/state_controller_unittest.cc b/chrome/browser/chromeos/lock_screen_apps/state_controller_unittest.cc index c995973..1ad6828 100644 --- a/chrome/browser/chromeos/lock_screen_apps/state_controller_unittest.cc +++ b/chrome/browser/chromeos/lock_screen_apps/state_controller_unittest.cc
@@ -16,6 +16,7 @@ #include "base/test/scoped_command_line.h" #include "chrome/browser/chromeos/arc/arc_session_manager.h" #include "chrome/browser/chromeos/lock_screen_apps/app_manager.h" +#include "chrome/browser/chromeos/lock_screen_apps/focus_cycler_delegate.h" #include "chrome/browser/chromeos/lock_screen_apps/state_observer.h" #include "chrome/browser/chromeos/note_taking_helper.h" #include "chrome/browser/chromeos/profiles/profile_helper.h" @@ -45,6 +46,7 @@ #include "extensions/common/extension_builder.h" #include "extensions/common/value_builder.h" #include "testing/gtest/include/gtest/gtest.h" +#include "ui/aura/window.h" #include "ui/events/devices/input_device_manager.h" #include "ui/events/devices/stylus_state.h" #include "ui/events/test/device_data_manager_test_api.h" @@ -94,6 +96,44 @@ .Build(); } +class TestFocusCyclerDelegate : public lock_screen_apps::FocusCyclerDelegate { + public: + TestFocusCyclerDelegate() = default; + ~TestFocusCyclerDelegate() override = default; + + void RegisterLockScreenAppFocusHandler( + const LockScreenAppFocusCallback& handler) override { + focus_handler_ = handler; + lock_screen_app_focused_ = true; + } + + void UnregisterLockScreenAppFocusHandler() override { + ASSERT_FALSE(focus_handler_.is_null()); + focus_handler_.Reset(); + } + + void HandleLockScreenAppFocusOut(bool reverse) override { + ASSERT_FALSE(focus_handler_.is_null()); + lock_screen_app_focused_ = false; + } + + void RequestAppFocus(bool reverse) { + ASSERT_FALSE(focus_handler_.is_null()); + lock_screen_app_focused_ = true; + focus_handler_.Run(reverse); + } + + bool HasHandler() const { return !focus_handler_.is_null(); } + + bool lock_screen_app_focused() const { return lock_screen_app_focused_; } + + private: + bool lock_screen_app_focused_ = false; + LockScreenAppFocusCallback focus_handler_; + + DISALLOW_COPY_AND_ASSIGN(TestFocusCyclerDelegate); +}; + class TestAppManager : public lock_screen_apps::AppManager { public: enum class State { @@ -380,6 +420,8 @@ profile(), lock_screen_profile()->GetOriginalProfile()); app_manager_ = app_manager.get(); + focus_cycler_delegate_ = base::MakeUnique<TestFocusCyclerDelegate>(); + state_controller_ = base::MakeUnique<lock_screen_apps::StateController>(); state_controller_->SetTrayActionPtrForTesting( tray_action_.CreateInterfacePtrAndBind()); @@ -387,6 +429,7 @@ state_controller_->SetReadyCallbackForTesting(ready_waiter_.QuitClosure()); state_controller_->Initialize(); state_controller_->FlushTrayActionForTesting(); + state_controller_->SetFocusCyclerDelegate(focus_cycler_delegate_.get()); state_controller_->AddObserver(&observer_); } @@ -404,6 +447,7 @@ app_window_.reset(); BrowserWithTestWindowTest::TearDown(); DestroyProfile(lock_screen_profile()); + focus_cycler_delegate_.reset(); } TestingProfile* CreateProfile() override { @@ -548,6 +592,10 @@ TestAppWindow* app_window() { return app_window_.get(); } const extensions::Extension* app() { return app_.get(); } + TestFocusCyclerDelegate* focus_cycler_delegate() { + return focus_cycler_delegate_.get(); + } + private: std::unique_ptr<base::test::ScopedCommandLine> command_line_; TestingProfileManager profile_manager_; @@ -575,6 +623,8 @@ std::unique_ptr<lock_screen_apps::StateController> state_controller_; + std::unique_ptr<TestFocusCyclerDelegate> focus_cycler_delegate_; + TestStateObserver observer_; TestTrayAction tray_action_; TestAppManager* app_manager_ = nullptr; @@ -1208,3 +1258,101 @@ base::RunLoop().RunUntilIdle(); EXPECT_TRUE(secondary_app_window->closed()); } + +// Goes through different states with no focus cycler set; mainly to check +// there are no crashes. +TEST_F(LockScreenAppStateTest, NoFocusCyclerDelegate) { + lock_screen_apps::StateController::Get()->SetFocusCyclerDelegate(nullptr); + + ASSERT_TRUE(InitializeNoteTakingApp(TrayActionState::kActive, + true /* enable_app_launch */)); + + state_controller()->MoveToBackground(); + state_controller()->FlushTrayActionForTesting(); + + EXPECT_EQ(TrayActionState::kBackground, + state_controller()->GetLockScreenNoteState()); + + state_controller()->MoveToForeground(); + state_controller()->FlushTrayActionForTesting(); + + EXPECT_EQ(TrayActionState::kActive, + state_controller()->GetLockScreenNoteState()); + + base::RunLoop().RunUntilIdle(); + EXPECT_FALSE(app_window()->closed()); +} + +TEST_F(LockScreenAppStateTest, ResetFocusCyclerDelegateWhileActive) { + ASSERT_TRUE(InitializeNoteTakingApp(TrayActionState::kActive, + true /* enable_app_launch */)); + + lock_screen_apps::StateController::Get()->SetFocusCyclerDelegate(nullptr); + ASSERT_FALSE(focus_cycler_delegate()->HasHandler()); + + lock_screen_apps::StateController::Get()->SetFocusCyclerDelegate( + focus_cycler_delegate()); + EXPECT_TRUE(focus_cycler_delegate()->HasHandler()); +} + +TEST_F(LockScreenAppStateTest, FocusCyclerDelegateGetsSetOnAppWindowCreation) { + ASSERT_TRUE(InitializeNoteTakingApp(TrayActionState::kAvailable, + true /* enable_app_launch */)); + + tray_action()->SendNewNoteRequest(); + state_controller()->FlushTrayActionForTesting(); + + EXPECT_FALSE(focus_cycler_delegate()->HasHandler()); + + std::unique_ptr<TestAppWindow> app_window = + CreateNoteTakingWindow(lock_screen_profile(), app()); + app_window->Initialize(true /* shown */); + + EXPECT_TRUE(focus_cycler_delegate()->HasHandler()); + + state_controller()->MoveToBackground(); + + EXPECT_TRUE(focus_cycler_delegate()->HasHandler()); + + app_window->Close(); + EXPECT_FALSE(focus_cycler_delegate()->HasHandler()); +} + +TEST_F(LockScreenAppStateTest, TakeFocus) { + ASSERT_TRUE(InitializeNoteTakingApp(TrayActionState::kActive, + true /* enable_app_launch */)); + + auto regular_app_window = base::MakeUnique<TestAppWindow>( + profile(), + new extensions::AppWindow(profile(), new ChromeAppDelegate(true), app())); + EXPECT_FALSE(state_controller()->HandleTakeFocus( + regular_app_window->window()->web_contents(), true)); + EXPECT_TRUE(focus_cycler_delegate()->lock_screen_app_focused()); + + ASSERT_TRUE(state_controller()->HandleTakeFocus( + app_window()->window()->web_contents(), true)); + EXPECT_FALSE(focus_cycler_delegate()->lock_screen_app_focused()); + + focus_cycler_delegate()->RequestAppFocus(true); + EXPECT_TRUE(focus_cycler_delegate()->lock_screen_app_focused()); +} + +TEST_F(LockScreenAppStateTest, RequestFocusFromBackgroundMovesAppToForeground) { + ASSERT_TRUE(InitializeNoteTakingApp(TrayActionState::kActive, + true /* enable_app_launch */)); + + ASSERT_TRUE(state_controller()->HandleTakeFocus( + app_window()->window()->web_contents(), true)); + EXPECT_FALSE(focus_cycler_delegate()->lock_screen_app_focused()); + + state_controller()->MoveToBackground(); + + EXPECT_EQ(TrayActionState::kBackground, + state_controller()->GetLockScreenNoteState()); + + focus_cycler_delegate()->RequestAppFocus(true); + EXPECT_TRUE(focus_cycler_delegate()->lock_screen_app_focused()); + + EXPECT_EQ(TrayActionState::kActive, + state_controller()->GetLockScreenNoteState()); +}
diff --git a/chrome/browser/chromeos/login/lock/webui_screen_locker.cc b/chrome/browser/chromeos/login/lock/webui_screen_locker.cc index 5f8ec9b..7b4b7d6 100644 --- a/chrome/browser/chromeos/login/lock/webui_screen_locker.cc +++ b/chrome/browser/chromeos/login/lock/webui_screen_locker.cc
@@ -143,6 +143,8 @@ if (login_display_.get() && GetOobeUI()) GetOobeUI()->ResetSigninScreenHandlerDelegate(); + ClearLockScreenAppFocusCyclerDelegate(); + ResetKeyboardOverscrollOverride(); RequestPreload(); @@ -175,6 +177,8 @@ GetOobeUI()->ShowSigninScreen( LoginScreenContext(), login_display_.get(), login_display_.get()); + SetLockScreenAppFocusCyclerDelegate(); + DisableKeyboardOverscroll(); }
diff --git a/chrome/browser/chromeos/login/ui/webui_login_view.cc b/chrome/browser/chromeos/login/ui/webui_login_view.cc index dee953d..d24bb8b 100644 --- a/chrome/browser/chromeos/login/ui/webui_login_view.cc +++ b/chrome/browser/chromeos/login/ui/webui_login_view.cc
@@ -20,6 +20,7 @@ #include "chrome/browser/chrome_notification_types.h" #include "chrome/browser/chromeos/accessibility/accessibility_util.h" #include "chrome/browser/chromeos/app_mode/kiosk_app_manager.h" +#include "chrome/browser/chromeos/lock_screen_apps/state_controller.h" #include "chrome/browser/chromeos/login/enrollment/auto_enrollment_controller.h" #include "chrome/browser/chromeos/login/ui/login_display_host_impl.h" #include "chrome/browser/chromeos/login/ui/preloaded_web_view.h" @@ -440,6 +441,20 @@ return webui_login_.get(); } +void WebUILoginView::SetLockScreenAppFocusCyclerDelegate() { + if (lock_screen_apps::StateController::IsEnabled()) { + delegates_lock_screen_app_focus_cycle_ = true; + lock_screen_apps::StateController::Get()->SetFocusCyclerDelegate(this); + } +} + +void WebUILoginView::ClearLockScreenAppFocusCyclerDelegate() { + if (!delegates_lock_screen_app_focus_cycle_) + return; + lock_screen_apps::StateController::Get()->SetFocusCyclerDelegate(nullptr); + delegates_lock_screen_app_focus_cycle_ = false; +} + //////////////////////////////////////////////////////////////////////////////// // ash::ShellObserver: @@ -519,21 +534,28 @@ if (!forward_keyboard_event_) return false; - // Focus is accepted, but the Ash system tray is not available in Mash, so - // exit early. - if (ash_util::IsRunningInMash()) + // For default tab order, after login UI, try focusing the system tray. + if (!reverse && MoveFocusToSystemTray(reverse)) return true; - ash::SystemTray* tray = ash::Shell::Get()->GetPrimarySystemTray(); - if (tray && tray->GetWidget()->IsVisible() && tray->visible()) { - ash::StatusAreaWidgetDelegate::GetPrimaryInstance() - ->set_default_last_focusable_child(reverse); - ash::Shell::Get()->focus_cycler()->RotateFocus( - reverse ? ash::FocusCycler::BACKWARD : ash::FocusCycler::FORWARD); - } else { - AboutToRequestFocusFromTabTraversal(reverse); + // Either if tab order is reversed (in which case system tray focus was not + // attempted), or system tray focus failed (in which case focusing an app + // window is preferrable to focus returning to login UI), try moving focus + // to the app window. + if (!lock_screen_app_focus_handler_.is_null()) { + lock_screen_app_focus_handler_.Run(reverse); + return true; } + // If initial MoveFocusToSystemTray was skipped due to lock screen app being + // a preferred option (due to traversal direction), try focusing system tray + // again. + if (reverse && MoveFocusToSystemTray(reverse)) + return true; + + // Since neither system tray nor a lock screen app window was focusable, the + // focus should stay in the login UI. + AboutToRequestFocusFromTabTraversal(reverse); return true; } @@ -562,10 +584,48 @@ return blink::WebInputEvent::IsPinchGestureEventType(event.GetType()); } -void WebUILoginView::OnFocusOut(bool reverse) { +void WebUILoginView::RegisterLockScreenAppFocusHandler( + const LockScreenAppFocusCallback& focus_handler) { + lock_screen_app_focus_handler_ = focus_handler; +} + +void WebUILoginView::UnregisterLockScreenAppFocusHandler() { + lock_screen_app_focus_handler_.Reset(); +} + +void WebUILoginView::HandleLockScreenAppFocusOut(bool reverse) { + if (reverse && MoveFocusToSystemTray(reverse)) + return; + AboutToRequestFocusFromTabTraversal(reverse); } +void WebUILoginView::OnFocusOut(bool reverse) { + if (!reverse && !lock_screen_app_focus_handler_.is_null()) { + lock_screen_app_focus_handler_.Run(reverse); + return; + } + + AboutToRequestFocusFromTabTraversal(reverse); +} + +bool WebUILoginView::MoveFocusToSystemTray(bool reverse) { + // Focus is accepted, but the Ash system tray is not available in Mash, so + // exit early. + if (ash_util::IsRunningInMash()) + return true; + + ash::SystemTray* tray = ash::Shell::Get()->GetPrimarySystemTray(); + if (!tray || !tray->GetWidget()->IsVisible() || !tray->visible()) + return false; + + ash::StatusAreaWidgetDelegate::GetPrimaryInstance() + ->set_default_last_focusable_child(reverse); + ash::Shell::Get()->focus_cycler()->RotateFocus( + reverse ? ash::FocusCycler::BACKWARD : ash::FocusCycler::FORWARD); + return true; +} + void WebUILoginView::OnLoginPromptVisible() { // If we're hidden than will generate this signal once we're shown. if (is_hidden_ || webui_visible_) {
diff --git a/chrome/browser/chromeos/login/ui/webui_login_view.h b/chrome/browser/chromeos/login/ui/webui_login_view.h index 0e3964b..932219d17 100644 --- a/chrome/browser/chromeos/login/ui/webui_login_view.h +++ b/chrome/browser/chromeos/login/ui/webui_login_view.h
@@ -13,6 +13,7 @@ #include "base/macros.h" #include "base/memory/ref_counted.h" #include "base/observer_list.h" +#include "chrome/browser/chromeos/lock_screen_apps/focus_cycler_delegate.h" #include "chrome/browser/ui/chrome_web_modal_dialog_manager_delegate.h" #include "components/web_modal/web_contents_modal_dialog_host.h" #include "content/public/browser/notification_observer.h" @@ -47,6 +48,7 @@ public content::NotificationObserver, public ChromeWebModalDialogManagerDelegate, public web_modal::WebContentsModalDialogHost, + public lock_screen_apps::FocusCyclerDelegate, public ash::StatusAreaFocusObserver { public: struct WebViewSettings { @@ -136,6 +138,14 @@ views::WebView* web_view(); + // Sets |this| as lock_screen_apps::StateController's + // lock_screen_apps::FocusCyclerDelegate. + void SetLockScreenAppFocusCyclerDelegate(); + + // Resets the lock_screen_apps::StateController's FocusCyclerDelegate, + // provided that |this| was set as the delegate. + void ClearLockScreenAppFocusCyclerDelegate(); + private: // Map type for the accelerator-to-identifier map. typedef std::map<ui::Accelerator, std::string> AccelMap; @@ -165,9 +175,19 @@ bool PreHandleGestureEvent(content::WebContents* source, const blink::WebGestureEvent& event) override; + // lock_screen_apps::FocusCyclerDelegate: + void RegisterLockScreenAppFocusHandler( + const LockScreenAppFocusCallback& focus_handler) override; + void UnregisterLockScreenAppFocusHandler() override; + void HandleLockScreenAppFocusOut(bool reverse) override; + // Overridden from ash::StatusAreaFocusObserver. void OnFocusOut(bool reverse) override; + // Attempts to move focus to system tray. Returns whether the attempt was + // successful (it might fail if the system tray is not visible). + bool MoveFocusToSystemTray(bool reverse); + // Performs series of actions when login prompt is considered // to be ready and visible. // 1. Emits LoginPromptVisible signal if needed @@ -204,6 +224,15 @@ // True to forward keyboard event. bool forward_keyboard_event_ = true; + // If set, the callback that should be called when focus should be moved to + // a lock screen app window. + // It gets registered using |RegisterLockScreenAppFocusHandler|. + LockScreenAppFocusCallback lock_screen_app_focus_handler_; + + // Whether this was set as lock_screen_apps::StateController's + // FocusCyclerDelegate. + bool delegates_lock_screen_app_focus_cycle_ = false; + base::ObserverList<web_modal::ModalDialogHostObserver> observer_list_; DISALLOW_COPY_AND_ASSIGN(WebUILoginView);
diff --git a/chrome/browser/resources/chromeos/login/md_top_header_bar.js b/chrome/browser/resources/chromeos/login/md_top_header_bar.js index b46b5b7a..7108c2e 100644 --- a/chrome/browser/resources/chromeos/login/md_top_header_bar.js +++ b/chrome/browser/resources/chromeos/login/md_top_header_bar.js
@@ -391,13 +391,12 @@ this.lockScreenAppsState_ != LOCK_SCREEN_APPS_STATE.AVAILABLE && this.lockScreenAppsState_ != LOCK_SCREEN_APPS_STATE.FOREGROUND; + var newNoteActionEnabled = + this.lockScreenAppsState_ == LOCK_SCREEN_APPS_STATE.AVAILABLE; + $('new-note-action').classList.toggle('disabled', !newNoteActionEnabled); $('new-note-action') - .classList.toggle( - 'disabled', - this.lockScreenAppsState_ != LOCK_SCREEN_APPS_STATE.AVAILABLE); - - $('new-note-action-icon').hidden = - this.lockScreenAppsState_ != LOCK_SCREEN_APPS_STATE.AVAILABLE; + .setAttribute('tabIndex', newNoteActionEnabled ? '0' : '-1'); + $('new-note-action-icon').hidden = !newNoteActionEnabled; // This might get set when the action is activated - reset it when the // lock screen action is updated.
diff --git a/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_dialog_controller_impl_browsertest_win.cc b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_dialog_controller_impl_browsertest_win.cc index 3286e75d..9aab983b 100644 --- a/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_dialog_controller_impl_browsertest_win.cc +++ b/chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_dialog_controller_impl_browsertest_win.cc
@@ -80,7 +80,7 @@ } IN_PROC_BROWSER_TEST_F(ChromeCleanerPromptUserTest, - OnInfectedBrowserNotAvailable) { + DISABLED_OnInfectedBrowserNotAvailable) { browser()->window()->Minimize(); base::RunLoop().RunUntilIdle(); dialog_controller_->OnInfected(std::set<base::FilePath>());
diff --git a/chrome/browser/search/one_google_bar/one_google_bar_service.cc b/chrome/browser/search/one_google_bar/one_google_bar_service.cc index d812105..5e0e278 100644 --- a/chrome/browser/search/one_google_bar/one_google_bar_service.cc +++ b/chrome/browser/search/one_google_bar/one_google_bar_service.cc
@@ -71,8 +71,11 @@ } void OneGoogleBarService::SigninStatusChanged() { - one_google_bar_data_ = base::nullopt; - NotifyObservers(); + // If we have cached data, clear it and notify observers. + if (one_google_bar_data_.has_value()) { + one_google_bar_data_ = base::nullopt; + NotifyObservers(); + } } void OneGoogleBarService::OneGoogleBarDataFetched(
diff --git a/chrome/browser/search/one_google_bar/one_google_bar_service_unittest.cc b/chrome/browser/search/one_google_bar/one_google_bar_service_unittest.cc index c91b8d62..da86d60 100644 --- a/chrome/browser/search/one_google_bar/one_google_bar_service_unittest.cc +++ b/chrome/browser/search/one_google_bar/one_google_bar_service_unittest.cc
@@ -225,9 +225,15 @@ fetcher()->RespondToAllCallbacks(OneGoogleBarFetcher::Status::OK, data); ASSERT_THAT(service()->one_google_bar_data(), Eq(data)); - // Sign in. This should clear the cached data. + StrictMock<MockOneGoogleBarServiceObserver> observer; + service()->AddObserver(&observer); + + // Sign in. This should clear the cached data and notify the observer. + EXPECT_CALL(observer, OnOneGoogleBarDataUpdated()); SignIn(); EXPECT_THAT(service()->one_google_bar_data(), Eq(base::nullopt)); + + service()->RemoveObserver(&observer); } TEST_F(OneGoogleBarServiceTest, ResetsOnSignOut) { @@ -240,7 +246,27 @@ fetcher()->RespondToAllCallbacks(OneGoogleBarFetcher::Status::OK, data); ASSERT_THAT(service()->one_google_bar_data(), Eq(data)); - // Sign out. This should clear the cached data. + StrictMock<MockOneGoogleBarServiceObserver> observer; + service()->AddObserver(&observer); + + // Sign in. This should clear the cached data and notify the observer. + EXPECT_CALL(observer, OnOneGoogleBarDataUpdated()); SignOut(); EXPECT_THAT(service()->one_google_bar_data(), Eq(base::nullopt)); + + service()->RemoveObserver(&observer); +} + +TEST_F(OneGoogleBarServiceTest, DoesNotNotifyObserverOnSignInIfNoCachedData) { + ASSERT_THAT(service()->one_google_bar_data(), Eq(base::nullopt)); + + StrictMock<MockOneGoogleBarServiceObserver> observer; + service()->AddObserver(&observer); + + // Sign in. This should *not* notify the observer, since there was no cached + // data before. + SignIn(); + EXPECT_THAT(service()->one_google_bar_data(), Eq(base::nullopt)); + + service()->RemoveObserver(&observer); }
diff --git a/chrome/browser/ui/apps/chrome_app_delegate.cc b/chrome/browser/ui/apps/chrome_app_delegate.cc index 619d53c..f18af9b3 100644 --- a/chrome/browser/ui/apps/chrome_app_delegate.cc +++ b/chrome/browser/ui/apps/chrome_app_delegate.cc
@@ -47,6 +47,10 @@ #include "ash/shelf/shelf_constants.h" // nogncheck #endif +#if defined(OS_CHROMEOS) +#include "chrome/browser/chromeos/lock_screen_apps/state_controller.h" +#endif + #if BUILDFLAG(ENABLE_PRINTING) #if BUILDFLAG(ENABLE_PRINT_PREVIEW) #include "chrome/browser/printing/print_preview_message_handler.h" @@ -164,6 +168,7 @@ ChromeAppDelegate::ChromeAppDelegate(bool keep_alive) : has_been_shown_(false), is_hidden_(true), + for_lock_screen_app_(false), new_window_contents_delegate_(new NewWindowContentsDelegate()), weak_factory_(this) { if (keep_alive) { @@ -348,6 +353,18 @@ KeepAliveRestartOption::DISABLED)); } +bool ChromeAppDelegate::TakeFocus(content::WebContents* web_contents, + bool reverse) { + if (!for_lock_screen_app_) + return false; +#if defined(OS_CHROMEOS) + return lock_screen_apps::StateController::Get()->HandleTakeFocus(web_contents, + reverse); +#else + return false; +#endif +} + void ChromeAppDelegate::Observe(int type, const content::NotificationSource& source, const content::NotificationDetails& details) {
diff --git a/chrome/browser/ui/apps/chrome_app_delegate.h b/chrome/browser/ui/apps/chrome_app_delegate.h index 31ec3de..a98b0f8 100644 --- a/chrome/browser/ui/apps/chrome_app_delegate.h +++ b/chrome/browser/ui/apps/chrome_app_delegate.h
@@ -28,6 +28,10 @@ static void DisableExternalOpenForTesting(); + void set_for_lock_screen_app(bool for_lock_screen_app) { + for_lock_screen_app_ = for_lock_screen_app; + } + private: static void RelinquishKeepAliveAfterTimeout( const base::WeakPtr<ChromeAppDelegate>& chrome_app_delegate); @@ -70,6 +74,7 @@ void SetTerminatingCallback(const base::Closure& callback) override; void OnHide() override; void OnShow() override; + bool TakeFocus(content::WebContents* web_contents, bool reverse) override; // content::NotificationObserver: void Observe(int type, @@ -78,6 +83,7 @@ bool has_been_shown_; bool is_hidden_; + bool for_lock_screen_app_; std::unique_ptr<ScopedKeepAlive> keep_alive_; std::unique_ptr<NewWindowContentsDelegate> new_window_contents_delegate_; base::Closure terminating_callback_;
diff --git a/chrome/browser/ui/apps/chrome_app_window_client.cc b/chrome/browser/ui/apps/chrome_app_window_client.cc index b13496b..99ca6d2 100644 --- a/chrome/browser/ui/apps/chrome_app_window_client.cc +++ b/chrome/browser/ui/apps/chrome_app_window_client.cc
@@ -59,10 +59,12 @@ if (!lock_screen_apps::StateController::IsEnabled()) return nullptr; + auto app_delegate = base::MakeUnique<ChromeAppDelegate>(true /*keep_alive*/); + app_delegate->set_for_lock_screen_app(true); + return lock_screen_apps::StateController::Get() - ->CreateAppWindowForLockScreenAction( - context, extension, action, - base::MakeUnique<ChromeAppDelegate>(true /* keep_alive */)); + ->CreateAppWindowForLockScreenAction(context, extension, action, + std::move(app_delegate)); #else return nullptr; #endif
diff --git a/components/ntp_snippets/contextual_suggestions_source_unittest.cc b/components/ntp_snippets/contextual_suggestions_source_unittest.cc index c632242..c713fe9 100644 --- a/components/ntp_snippets/contextual_suggestions_source_unittest.cc +++ b/components/ntp_snippets/contextual_suggestions_source_unittest.cc
@@ -13,6 +13,7 @@ #include "base/memory/ptr_util.h" #include "base/message_loop/message_loop.h" #include "base/run_loop.h" +#include "base/test/mock_callback.h" #include "components/image_fetcher/core/image_fetcher_impl.h" #include "components/ntp_snippets/category_info.h" #include "components/ntp_snippets/content_suggestion.h" @@ -22,12 +23,17 @@ #include "components/ntp_snippets/remote/remote_suggestion.h" #include "components/ntp_snippets/remote/remote_suggestion_builder.h" #include "components/ntp_snippets/remote/remote_suggestions_database.h" +#include "components/prefs/pref_registry_simple.h" +#include "components/prefs/testing_pref_service.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/image/image.h" +#include "ui/gfx/image/image_unittest_util.h" using testing::_; using testing::AllOf; using testing::ElementsAre; +using testing::IsEmpty; using testing::Mock; using testing::Pointee; using testing::Property; @@ -36,21 +42,26 @@ namespace { -const char kFromURL[] = "http://localhost"; -const char kSuggestionURL[] = "http://url.test"; +ACTION_TEMPLATE(MoveArg, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_1_VALUE_PARAMS(out)) { + *out = std::move(*::testing::get<k>(args)); +}; -// Always fetches one valid RemoteSuggestion. +// Always fetches the result that was set by SetFakeResponse. class FakeContextualSuggestionsFetcher : public ContextualSuggestionsFetcher { + public: void FetchContextualSuggestions( const GURL& url, SuggestionsAvailableCallback callback) override { - OptionalSuggestions suggestions = RemoteSuggestion::PtrVector(); - suggestions->push_back(test::RemoteSuggestionBuilder() - .AddId(kSuggestionURL) - .SetUrl(kSuggestionURL) - .SetAmpUrl(kSuggestionURL) - .Build()); - std::move(callback).Run(Status::Success(), std::move(suggestions)); + std::move(callback).Run(fake_status_, std::move(fake_suggestions_)); + fake_suggestions_ = base::nullopt; + } + + void SetFakeResponse(Status fake_status, + OptionalSuggestions fake_suggestions) { + fake_status_ = fake_status; + fake_suggestions_ = std::move(fake_suggestions); } const std::string& GetLastStatusForTesting() const override { return empty_; } @@ -60,6 +71,27 @@ private: std::string empty_; GURL empty_url_; + Status fake_status_ = Status::Success(); + OptionalSuggestions fake_suggestions_; +}; + +// Always fetches a fake image if the given URL is valid. +class FakeCachedImageFetcher : public CachedImageFetcher { + public: + FakeCachedImageFetcher(PrefService* pref_service) + : CachedImageFetcher(std::unique_ptr<image_fetcher::ImageFetcher>(), + pref_service, + nullptr){}; + + void FetchSuggestionImage(const ContentSuggestion::ID&, + const GURL& image_url, + ImageFetchedCallback callback) override { + gfx::Image image; + if (image_url.is_valid()) { + image = gfx::test::CreateImage(); + } + std::move(callback).Run(image); + } }; // GMock does not support movable-only types (ContentSuggestion). @@ -90,16 +122,23 @@ class ContextualSuggestionsSourceTest : public testing::Test { public: ContextualSuggestionsSourceTest() { + RequestThrottler::RegisterProfilePrefs(pref_service_.registry()); + std::unique_ptr<FakeContextualSuggestionsFetcher> fetcher = + base::MakeUnique<FakeContextualSuggestionsFetcher>(); + fetcher_ = fetcher.get(); source_ = base::MakeUnique<ContextualSuggestionsSource>( - base::MakeUnique<FakeContextualSuggestionsFetcher>(), - std::unique_ptr<CachedImageFetcher>(), + std::move(fetcher), + base::MakeUnique<FakeCachedImageFetcher>(&pref_service_), std::unique_ptr<RemoteSuggestionsDatabase>()); } + FakeContextualSuggestionsFetcher* fetcher() { return fetcher_; } ContextualSuggestionsSource* source() { return source_.get(); } private: + FakeContextualSuggestionsFetcher* fetcher_; base::MessageLoop message_loop_; + TestingPrefServiceSimple pref_service_; std::unique_ptr<ContextualSuggestionsSource> source_; DISALLOW_COPY_AND_ASSIGN(ContextualSuggestionsSourceTest); @@ -107,17 +146,95 @@ TEST_F(ContextualSuggestionsSourceTest, ShouldFetchContextualSuggestion) { MockFetchContextualSuggestionsCallback mock_suggestions_callback; - EXPECT_CALL( - mock_suggestions_callback, - Run(Property(&Status::IsSuccess, true), GURL(kFromURL), - Pointee(ElementsAre(AllOf( - Property(&ContentSuggestion::id, - Property(&ContentSuggestion::ID::category, - Category::FromKnownCategory( - KnownCategories::CONTEXTUAL))), - Property(&ContentSuggestion::url, GURL(kSuggestionURL))))))); + const std::string kValidFromUrl = "http://some.url"; + const std::string kToUrl = "http://another.url"; + ContextualSuggestionsFetcher::OptionalSuggestions remote_suggestions = + RemoteSuggestion::PtrVector(); + remote_suggestions->push_back(test::RemoteSuggestionBuilder() + .AddId(kToUrl) + .SetUrl(kToUrl) + .SetAmpUrl(kToUrl) + .Build()); + fetcher()->SetFakeResponse(Status::Success(), std::move(remote_suggestions)); + EXPECT_CALL(mock_suggestions_callback, + Run(Property(&Status::IsSuccess, true), GURL(kValidFromUrl), + Pointee(ElementsAre(AllOf( + Property(&ContentSuggestion::id, + Property(&ContentSuggestion::ID::category, + Category::FromKnownCategory( + KnownCategories::CONTEXTUAL))), + Property(&ContentSuggestion::url, GURL(kToUrl))))))); source()->FetchContextualSuggestions( - GURL(kFromURL), mock_suggestions_callback.ToOnceCallback()); + GURL(kValidFromUrl), mock_suggestions_callback.ToOnceCallback()); + base::RunLoop().RunUntilIdle(); +} + +TEST_F(ContextualSuggestionsSourceTest, ShouldRunCallbackOnEmptyResults) { + MockFetchContextualSuggestionsCallback mock_suggestions_callback; + const std::string kEmpty; + fetcher()->SetFakeResponse(Status::Success(), RemoteSuggestion::PtrVector()); + EXPECT_CALL(mock_suggestions_callback, Run(Property(&Status::IsSuccess, true), + GURL(kEmpty), Pointee(IsEmpty()))); + source()->FetchContextualSuggestions( + GURL(kEmpty), mock_suggestions_callback.ToOnceCallback()); + base::RunLoop().RunUntilIdle(); +} + +TEST_F(ContextualSuggestionsSourceTest, ShouldRunCallbackOnError) { + MockFetchContextualSuggestionsCallback mock_suggestions_callback; + const std::string kEmpty; + fetcher()->SetFakeResponse(Status(StatusCode::TEMPORARY_ERROR, ""), + RemoteSuggestion::PtrVector()); + EXPECT_CALL(mock_suggestions_callback, + Run(Property(&Status::IsSuccess, false), GURL(kEmpty), + Pointee(IsEmpty()))); + source()->FetchContextualSuggestions( + GURL(kEmpty), mock_suggestions_callback.ToOnceCallback()); + base::RunLoop().RunUntilIdle(); +} + +TEST_F(ContextualSuggestionsSourceTest, ShouldFetchEmptyImageIfNotFound) { + base::MockCallback<ImageFetchedCallback> mock_image_fetched_callback; + const std::string kEmpty; + ContentSuggestion::ID id( + Category::FromKnownCategory(KnownCategories::CONTEXTUAL), kEmpty); + EXPECT_CALL(mock_image_fetched_callback, + Run(Property(&gfx::Image::IsEmpty, true))); + source()->FetchContextualSuggestionImage(id, + mock_image_fetched_callback.Get()); + // TODO(gaschler): Verify with a mock that the image fetcher is not called if + // the id is unknown. + base::RunLoop().RunUntilIdle(); +} + +TEST_F(ContextualSuggestionsSourceTest, + ShouldFetchImageForPreviouslyFetchedSuggestion) { + const std::string kValidFromUrl = "http://some.url"; + const std::string kToUrl = "http://another.url"; + const std::string kValidImageUrl = "http://some.url/image.png"; + ContextualSuggestionsFetcher::OptionalSuggestions remote_suggestions = + RemoteSuggestion::PtrVector(); + remote_suggestions->push_back(test::RemoteSuggestionBuilder() + .AddId(kToUrl) + .SetUrl(kToUrl) + .SetAmpUrl(kToUrl) + .SetImageUrl(kValidImageUrl) + .Build()); + fetcher()->SetFakeResponse(Status::Success(), std::move(remote_suggestions)); + MockFetchContextualSuggestionsCallback mock_suggestions_callback; + std::vector<ContentSuggestion> suggestions; + EXPECT_CALL(mock_suggestions_callback, Run(_, _, _)) + .WillOnce(MoveArg<2>(&suggestions)); + source()->FetchContextualSuggestions( + GURL(kValidFromUrl), mock_suggestions_callback.ToOnceCallback()); + base::RunLoop().RunUntilIdle(); + + ASSERT_THAT(suggestions, Not(IsEmpty())); + base::MockCallback<ImageFetchedCallback> mock_image_fetched_callback; + EXPECT_CALL(mock_image_fetched_callback, + Run(Property(&gfx::Image::IsEmpty, false))); + source()->FetchContextualSuggestionImage(suggestions[0].id(), + mock_image_fetched_callback.Get()); base::RunLoop().RunUntilIdle(); }
diff --git a/components/ntp_snippets/remote/cached_image_fetcher.h b/components/ntp_snippets/remote/cached_image_fetcher.h index bf8ce56..51eef498 100644 --- a/components/ntp_snippets/remote/cached_image_fetcher.h +++ b/components/ntp_snippets/remote/cached_image_fetcher.h
@@ -46,9 +46,9 @@ // Fetches the image for a suggestion. The fetcher will first issue a lookup // to the underlying cache with a fallback to the network. - void FetchSuggestionImage(const ContentSuggestion::ID& suggestion_id, - const GURL& image_url, - ImageFetchedCallback callback); + virtual void FetchSuggestionImage(const ContentSuggestion::ID& suggestion_id, + const GURL& image_url, + ImageFetchedCallback callback); private: // image_fetcher::ImageFetcherDelegate implementation.
diff --git a/content/public/test/navigation_simulator.cc b/content/public/test/navigation_simulator.cc index 339a2f7..8569b6a94 100644 --- a/content/public/test/navigation_simulator.cc +++ b/content/public/test/navigation_simulator.cc
@@ -174,13 +174,13 @@ } void NavigationSimulator::Redirect(const GURL& new_url) { - CHECK(state_ <= STARTED) << "NavigationSimulator::Redirect should be " - "called before Fail or Commit"; + CHECK_LE(state_, STARTED) << "NavigationSimulator::Redirect should be " + "called before Fail or Commit"; CHECK_EQ(0, num_did_finish_navigation_called_) << "NavigationSimulator::Redirect cannot be called after the " "navigation has finished"; - if (state_ == INITIALIZATION) { + if (state_ < STARTED) { Start(); if (state_ == FAILED) return; @@ -232,15 +232,15 @@ } } -void NavigationSimulator::Commit() { - CHECK_LE(state_, STARTED) << "NavigationSimulator::Commit can only be " - "called once, and cannot be called after " +void NavigationSimulator::ReadyToCommit() { + CHECK_LE(state_, STARTED) << "NavigationSimulator::ReadyToCommit can only " + "be called once, and cannot be called after " "NavigationSimulator::Fail"; CHECK_EQ(0, num_did_finish_navigation_called_) - << "NavigationSimulator::Commit cannot be called after the " + << "NavigationSimulator::ReadyToCommit cannot be called after the " "navigation has finished"; - if (state_ == INITIALIZATION) { + if (state_ < STARTED) { Start(); if (state_ == FAILED) return; @@ -308,6 +308,22 @@ CHECK(!handle_->is_transferring()); } render_frame_host_ = new_render_frame_host; + state_ = READY_TO_COMMIT; +} + +void NavigationSimulator::Commit() { + CHECK_LE(state_, READY_TO_COMMIT) << "NavigationSimulator::Commit can only " + "be called once, and cannot be called " + "after NavigationSimulator::Fail"; + CHECK_EQ(0, num_did_finish_navigation_called_) + << "NavigationSimulator::Commit cannot be called after the navigation " + "has finished"; + + if (state_ < READY_TO_COMMIT) { + ReadyToCommit(); + if (state_ == FAILED) + return; + } // Keep a pointer to the current RenderFrameHost that may be pending deletion // after commit. @@ -648,7 +664,7 @@ } RenderFrameHost* NavigationSimulator::GetFinalRenderFrameHost() { - CHECK_EQ(state_, FINISHED); + CHECK_GE(state_, READY_TO_COMMIT); return render_frame_host_; }
diff --git a/content/public/test/navigation_simulator.h b/content/public/test/navigation_simulator.h index 29799b88..7b11024 100644 --- a/content/public/test/navigation_simulator.h +++ b/content/public/test/navigation_simulator.h
@@ -115,6 +115,10 @@ // Simulates a redirect to |new_url| for the navigation. virtual void Redirect(const GURL& new_url); + // Simulates receiving the navigation response and choosing a final + // RenderFrameHost to commit it. + virtual void ReadyToCommit(); + // Simulates the commit of the navigation in the RenderFrameHost. virtual void Commit(); @@ -215,6 +219,7 @@ enum State { INITIALIZATION, STARTED, + READY_TO_COMMIT, FAILED, FINISHED, };
diff --git a/extensions/browser/app_window/app_delegate.h b/extensions/browser/app_window/app_delegate.h index 1cdd50f2..ec5b8a3 100644 --- a/extensions/browser/app_window/app_delegate.h +++ b/extensions/browser/app_window/app_delegate.h
@@ -83,6 +83,11 @@ // Called when the app is hidden or shown. virtual void OnHide() = 0; virtual void OnShow() = 0; + + // Called when app web contents finishes focus traversal - gives the delegate + // a chance to handle the focus change. + // Return whether focus has been handled. + virtual bool TakeFocus(content::WebContents* web_contents, bool reverse) = 0; }; } // namespace extensions
diff --git a/extensions/browser/app_window/app_window.cc b/extensions/browser/app_window/app_window.cc index 80cc0ae..ed4ba26 100644 --- a/extensions/browser/app_window/app_window.cc +++ b/extensions/browser/app_window/app_window.cc
@@ -440,6 +440,10 @@ event_handler); } +bool AppWindow::TakeFocus(WebContents* source, bool reverse) { + return app_delegate_->TakeFocus(source, reverse); +} + void AppWindow::RenderViewCreated(content::RenderViewHost* render_view_host) { app_delegate_->RenderViewCreated(render_view_host); }
diff --git a/extensions/browser/app_window/app_window.h b/extensions/browser/app_window/app_window.h index 6022852..7d07ae89 100644 --- a/extensions/browser/app_window/app_window.h +++ b/extensions/browser/app_window/app_window.h
@@ -437,6 +437,7 @@ std::unique_ptr<content::BluetoothChooser> RunBluetoothChooser( content::RenderFrameHost* frame, const content::BluetoothChooser::EventHandler& event_handler) override; + bool TakeFocus(content::WebContents* source, bool reverse) override; // content::WebContentsObserver implementation. void RenderViewCreated(content::RenderViewHost* render_view_host) override;
diff --git a/extensions/shell/browser/shell_app_delegate.cc b/extensions/shell/browser/shell_app_delegate.cc index 245cc2f..7509db5 100644 --- a/extensions/shell/browser/shell_app_delegate.cc +++ b/extensions/shell/browser/shell_app_delegate.cc
@@ -101,4 +101,9 @@ // manually or should it use a browser termination callback like Chrome? } +bool ShellAppDelegate::TakeFocus(content::WebContents* web_contents, + bool reverse) { + return false; +} + } // namespace extensions
diff --git a/extensions/shell/browser/shell_app_delegate.h b/extensions/shell/browser/shell_app_delegate.h index ff124c1c..95ae1103 100644 --- a/extensions/shell/browser/shell_app_delegate.h +++ b/extensions/shell/browser/shell_app_delegate.h
@@ -53,6 +53,7 @@ void SetTerminatingCallback(const base::Closure& callback) override; void OnHide() override {} void OnShow() override {} + bool TakeFocus(content::WebContents* web_contents, bool reverse) override; private: DISALLOW_COPY_AND_ASSIGN(ShellAppDelegate);
diff --git a/ios/chrome/app/main_controller.mm b/ios/chrome/app/main_controller.mm index ba738e58..9b64ed2 100644 --- a/ios/chrome/app/main_controller.mm +++ b/ios/chrome/app/main_controller.mm
@@ -2467,6 +2467,8 @@ } - (UIViewController*)topPresentedViewController { + // TODO(crbug.com/754642): Implement TopPresentedViewControllerFrom() + // privately. return top_view_controller::TopPresentedViewControllerFrom( self.mainViewController); }
diff --git a/ios/chrome/browser/ui/authentication/signin_interaction_controller.mm b/ios/chrome/browser/ui/authentication/signin_interaction_controller.mm index 87e1799..84d7504e 100644 --- a/ios/chrome/browser/ui/authentication/signin_interaction_controller.mm +++ b/ios/chrome/browser/ui/authentication/signin_interaction_controller.mm
@@ -221,6 +221,7 @@ [weakSelf runCompletionCallbackWithSuccess:NO showAccountsSettings:NO]; }; + // TODO(crbug.com/754642): Stop using TopPresentedViewControllerFrom(). alertCoordinator_ = ios_internal::ErrorCoordinator( error, dismissAction, top_view_controller::TopPresentedViewControllerFrom(viewController));
diff --git a/ios/chrome/browser/ui/payments/payment_request_can_make_payment_egtest.mm b/ios/chrome/browser/ui/payments/payment_request_can_make_payment_egtest.mm index 53ef0bd7..dd86a6b 100644 --- a/ios/chrome/browser/ui/payments/payment_request_can_make_payment_egtest.mm +++ b/ios/chrome/browser/ui/payments/payment_request_can_make_payment_egtest.mm
@@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "base/ios/ios_util.h" #include "components/autofill/core/browser/autofill_test_utils.h" #include "components/autofill/core/browser/credit_card.h" #import "ios/chrome/browser/ui/payments/payment_request_egtest_base.h" @@ -111,6 +112,12 @@ // Tests canMakePayment() exceeds query quota when different payment methods are // queried one after another. - (void)testCanMakePaymentExceedsQueryQuota { + if (!base::ios::IsRunningOnOrLater(10, 3, 0)) { + EARL_GREY_TEST_DISABLED( + @"Disabled on iOS versions below 10.3 because DOMException is not " + @"available."); + } + [ChromeEarlGrey loadURL:web::test::HttpServer::MakeUrl(kCanMakePaymentCreditCardPage)]; @@ -161,6 +168,12 @@ // Tests canMakePayment() exceeds query quota when different payment methods are // queried one after another. - (void)testCanMakePaymentExceedsQueryQuotaBasicaCard { + if (!base::ios::IsRunningOnOrLater(10, 3, 0)) { + EARL_GREY_TEST_DISABLED( + @"Disabled on iOS versions below 10.3 because DOMException is not " + @"available."); + } + [ChromeEarlGrey loadURL:web::test::HttpServer::MakeUrl( kCanMakePaymentMethodIdentifierPage)];
diff --git a/ios/chrome/browser/ui/payments/payment_request_cancel_pay_abort_egtest.mm b/ios/chrome/browser/ui/payments/payment_request_cancel_pay_abort_egtest.mm index d075615..1d386452 100644 --- a/ios/chrome/browser/ui/payments/payment_request_cancel_pay_abort_egtest.mm +++ b/ios/chrome/browser/ui/payments/payment_request_cancel_pay_abort_egtest.mm
@@ -4,6 +4,7 @@ #include <vector> +#include "base/ios/ios_util.h" #include "components/autofill/core/browser/autofill_profile.h" #include "components/autofill/core/browser/autofill_test_utils.h" #include "components/autofill/core/browser/credit_card.h" @@ -107,6 +108,12 @@ // Tests that tapping the cancel button closes the Payment Request UI and // rejects the Promise returned by request.show() with the appropriate error. - (void)testOpenAndCancel { + if (!base::ios::IsRunningOnOrLater(10, 3, 0)) { + EARL_GREY_TEST_DISABLED( + @"Disabled on iOS versions below 10.3 because DOMException is not " + @"available."); + } + [ChromeEarlGrey loadURL:web::test::HttpServer::MakeUrl(kAbortPage)]; [ChromeEarlGrey tapWebViewElementWithID:@"buy"]; @@ -133,6 +140,12 @@ // rejects the Promise returned by request.show() with the appropriate error, // and displays the Autofill Settings UI. - (void)testOpenAndNavigateToSettings { + if (!base::ios::IsRunningOnOrLater(10, 3, 0)) { + EARL_GREY_TEST_DISABLED( + @"Disabled on iOS versions below 10.3 because DOMException is not " + @"available."); + } + [ChromeEarlGrey loadURL:web::test::HttpServer::MakeUrl(kAbortPage)]; [ChromeEarlGrey tapWebViewElementWithID:@"buy"];
diff --git a/ios/chrome/browser/ui/payments/payment_request_data_url_egtest.mm b/ios/chrome/browser/ui/payments/payment_request_data_url_egtest.mm index 284c482..14dcb39 100644 --- a/ios/chrome/browser/ui/payments/payment_request_data_url_egtest.mm +++ b/ios/chrome/browser/ui/payments/payment_request_data_url_egtest.mm
@@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "base/ios/ios_util.h" #import "ios/chrome/browser/ui/payments/payment_request_egtest_base.h" #import "ios/chrome/test/app/chrome_test_util.h" #import "ios/chrome/test/earl_grey/chrome_earl_grey.h" @@ -44,6 +45,12 @@ // Tests that the Promise returned by show() gets rejected with a // NotSupportedError if the JS isContextSecure variable is not set in time. - (void)testShowDataURL { + if (!base::ios::IsRunningOnOrLater(10, 3, 0)) { + EARL_GREY_TEST_DISABLED( + @"Disabled on iOS versions below 10.3 because DOMException is not " + @"available."); + } + [ChromeEarlGrey loadURL:GURL("data:text/html,<html><head><script>(new " "PaymentRequest([{supportedMethods: ['basic-card']}], "
diff --git a/ios/chrome/browser/ui/qr_scanner/BUILD.gn b/ios/chrome/browser/ui/qr_scanner/BUILD.gn index eac9145..7e74d40 100644 --- a/ios/chrome/browser/ui/qr_scanner/BUILD.gn +++ b/ios/chrome/browser/ui/qr_scanner/BUILD.gn
@@ -52,6 +52,7 @@ "//ios/chrome/app:app_internal", "//ios/chrome/app/strings", "//ios/chrome/browser", + "//ios/chrome/browser/ui:ui", "//ios/chrome/browser/ui:ui_internal", "//ios/chrome/browser/ui/commands", "//ios/chrome/browser/ui/icons",
diff --git a/ios/chrome/browser/ui/qr_scanner/qr_scanner_view_controller_egtest.mm b/ios/chrome/browser/ui/qr_scanner/qr_scanner_view_controller_egtest.mm index 962b623..9644ef01b 100644 --- a/ios/chrome/browser/ui/qr_scanner/qr_scanner_view_controller_egtest.mm +++ b/ios/chrome/browser/ui/qr_scanner/qr_scanner_view_controller_egtest.mm
@@ -6,6 +6,7 @@ #import <EarlGrey/EarlGrey.h> #import <UIKit/UIKit.h> +#include "base/ios/ios_util.h" #include "base/strings/sys_string_conversions.h" #include "base/strings/utf_string_conversions.h" #include "components/strings/grit/components_strings.h" @@ -19,6 +20,7 @@ #include "ios/chrome/browser/ui/qr_scanner/qr_scanner_view.h" #include "ios/chrome/browser/ui/qr_scanner/qr_scanner_view_controller.h" #include "ios/chrome/browser/ui/toolbar/web_toolbar_controller.h" +#include "ios/chrome/browser/ui/ui_util.h" #include "ios/chrome/grit/ios_chromium_strings.h" #include "ios/chrome/grit/ios_strings.h" #import "ios/chrome/test/app/chrome_test_util.h" @@ -808,6 +810,12 @@ // Test that the correct page is loaded if the scanner result is a URL which is // then manually edited. - (void)testReceivingQRScannerURLResultAndEditingTheURL { + // TODO(crbug.com/753098): Re-enable this test on iOS 11 iPad once + // grey_typeText works on iOS 11. + if (base::ios::IsRunningOnIOS11OrLater() && IsIPadIdiom()) { + EARL_GREY_TEST_DISABLED(@"Test disabled on iOS 11."); + } + [self doTestReceivingResult:_testURL.GetContent() response:kTestURLEditedResponse edit:@"\b\bedited/"]; @@ -822,6 +830,12 @@ // Test that the correct page is loaded if the scanner result is a search query // which is then manually edited. - (void)testReceivingQRScannerSearchQueryResultAndEditingTheQuery { + // TODO(crbug.com/753098): Re-enable this test on iOS 11 iPad once + // grey_typeText works on iOS 11. + if (base::ios::IsRunningOnIOS11OrLater() && IsIPadIdiom()) { + EARL_GREY_TEST_DISABLED(@"Test disabled on iOS 11."); + } + [self swizzleWebToolbarControllerLoadGURLFromLocationBar:_testQueryEdited]; [self doTestReceivingResult:kTestQuery response:kTestQueryEditedResponse
diff --git a/ios/chrome/browser/ui/settings/passwords_settings_egtest.mm b/ios/chrome/browser/ui/settings/passwords_settings_egtest.mm index e413c9e..ea10af5 100644 --- a/ios/chrome/browser/ui/settings/passwords_settings_egtest.mm +++ b/ios/chrome/browser/ui/settings/passwords_settings_egtest.mm
@@ -376,6 +376,7 @@ MockReauthenticationModule* SetUpAndReturnMockReauthenticationModule() { MockReauthenticationModule* mock_reauthentication_module = [[MockReauthenticationModule alloc] init]; + // TODO(crbug.com/754642): Stop using TopPresentedViewController(); SettingsNavigationController* settings_navigation_controller = base::mac::ObjCCastStrict<SettingsNavigationController>( top_view_controller::TopPresentedViewController());
diff --git a/ios/chrome/browser/ui/util/top_view_controller.h b/ios/chrome/browser/ui/util/top_view_controller.h index c0d3a343..0e3b2af3 100644 --- a/ios/chrome/browser/ui/util/top_view_controller.h +++ b/ios/chrome/browser/ui/util/top_view_controller.h
@@ -9,9 +9,12 @@ namespace top_view_controller { +// DEPRECATED -- do not add further usage of these functions. +// TODO(crbug.com/754642): Remove TopPresentedViewControllerFrom(). UIViewController* TopPresentedViewControllerFrom( UIViewController* base_view_controller); +// TODO(crbug.com/754642): Remove TopPresentedViewController(). UIViewController* TopPresentedViewController(); } // namespace top_view_controller
diff --git a/ios/chrome/browser/web/repost_form_tab_helper.mm b/ios/chrome/browser/web/repost_form_tab_helper.mm index 51fdb8bf..ea4e198e 100644 --- a/ios/chrome/browser/web/repost_form_tab_helper.mm +++ b/ios/chrome/browser/web/repost_form_tab_helper.mm
@@ -17,6 +17,7 @@ void RepostFormTabHelper::PresentDialog( CGPoint location, const base::Callback<void(bool)>& callback) { + // TODO(crbug.com/754642): Stop using TopPresentedViewControllerFrom(). UIViewController* top_controller = top_view_controller::TopPresentedViewControllerFrom( [UIApplication sharedApplication].keyWindow.rootViewController);
diff --git a/ios/clean/chrome/app/BUILD.gn b/ios/clean/chrome/app/BUILD.gn index 115ce97..e261dfe 100644 --- a/ios/clean/chrome/app/BUILD.gn +++ b/ios/clean/chrome/app/BUILD.gn
@@ -98,13 +98,14 @@ "application_phase.h", "application_state.h", "application_state.mm", - "application_step.h", ] configs += [ "//build/config/compiler:enable_arc" ] deps = [ "//base", + "//ios/clean/chrome/app/steps:step_runner", + "//ios/clean/chrome/app/steps:steps", "//ios/shared/chrome/browser/ui/coordinators", "//ios/testing/perf:startup", ] @@ -120,7 +121,10 @@ deps = [ ":application_state", - "//ios/clean/chrome/app/steps", + "//base", + "//ios/clean/chrome/app/steps:step_runner", + "//ios/clean/chrome/app/steps:steps", + "//ios/clean/chrome/browser", "//ios/testing/perf:startup", ] }
diff --git a/ios/clean/chrome/app/app_delegate.mm b/ios/clean/chrome/app/app_delegate.mm index 1325e27..bc319283 100644 --- a/ios/clean/chrome/app/app_delegate.mm +++ b/ios/clean/chrome/app/app_delegate.mm
@@ -4,11 +4,10 @@ #import "ios/clean/chrome/app/app_delegate.h" +#include "base/logging.h" +#include "base/strings/sys_string_conversions.h" #import "ios/clean/chrome/app/application_state.h" -#import "ios/clean/chrome/app/steps/launch_to_background.h" -#import "ios/clean/chrome/app/steps/launch_to_basic.h" -#import "ios/clean/chrome/app/steps/launch_to_foreground.h" -#import "ios/clean/chrome/app/steps/root_coordinator+application_step.h" +#import "ios/clean/chrome/browser/url_opening.h" #import "ios/testing/perf/startupLoggers.h" #if !defined(__has_feature) || !__has_feature(objc_arc) @@ -29,10 +28,24 @@ didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { startup_loggers::RegisterAppDidFinishLaunchingTime(); self.applicationState = [[ApplicationState alloc] init]; - self.applicationState.application = application; - [self configureApplicationState]; + [self.applicationState configure]; + self.applicationState.launchOptions = launchOptions; - [self.applicationState launchWithOptions:launchOptions]; + switch (application.applicationState) { + case UIApplicationStateBackground: + // The app is launching in the background. + self.applicationState.phase = APPLICATION_BACKGROUNDED; + break; + case UIApplicationStateInactive: + // The app is launching in the foreground but hasn't become active yet. + self.applicationState.phase = APPLICATION_FOREGROUNDED; + break; + case UIApplicationStateActive: + // This should never happen. + NOTREACHED() << "Application unexpectedly active in " + << base::SysNSStringToUTF8(NSStringFromSelector(_cmd)); + } + return YES; } @@ -50,6 +63,7 @@ } - (void)applicationWillTerminate:(UIApplication*)application { + self.applicationState.phase = APPLICATION_TERMINATING; } - (void)applicationDidReceiveMemoryWarning:(UIApplication*)application { @@ -94,27 +108,4 @@ return YES; } -#pragma mark - Private methods - -// Configures the application state for application launch by setting the launch -// steps. -// Future architecture/refactoring note: configuring the application state in -// this way is outside the scope of responsibility of the object as defined in -// the header file. The correct solution is probably a helper object that can -// perform all of the configuration necessary, and that can be adjusted as -// needed. -- (void)configureApplicationState { - [self.applicationState.launchSteps addObjectsFromArray:@[ - [[ProviderInitializer alloc] init], - [[SetupBundleAndUserDefaults alloc] init], - [[StartChromeMain alloc] init], - [[SetBrowserState alloc] init], - [[BeginForegrounding alloc] init], - [[PrepareForUI alloc] init], - [[CompleteForegrounding alloc] init], - [[RootCoordinator alloc] init], - [[DebuggingInformationOverlay alloc] init], - ]]; -} - @end
diff --git a/ios/clean/chrome/app/application_phase.h b/ios/clean/chrome/app/application_phase.h index c7d91eaf..c7b82790 100644 --- a/ios/clean/chrome/app/application_phase.h +++ b/ios/clean/chrome/app/application_phase.h
@@ -9,7 +9,7 @@ // states. typedef NS_ENUM(NSUInteger, ApplicationPhase) { // The initial state of the application performing a cold launch. - APPLICATION_COLD, + APPLICATION_COLD = 0, // The minimal initialization that must be completed before any further // startup can happen. |applicationDidFinishLaunching:withOptions:| must // bring the appication to at least this phase before returning.
diff --git a/ios/clean/chrome/app/application_state.h b/ios/clean/chrome/app/application_state.h index 00863261..38d07ca 100644 --- a/ios/clean/chrome/app/application_state.h +++ b/ios/clean/chrome/app/application_state.h
@@ -8,101 +8,53 @@ #import <UIKit/UIKit.h> #import "ios/clean/chrome/app/application_phase.h" +#import "ios/clean/chrome/app/steps/phased_step_runner.h" -namespace base { -class SupportsUserData; -} +// This class manages the application's transitions from one state to another, +// and keeps pointers to some global objects. +// This class is a specialization of PhasedStepRunner (and thus it conforms to +// StepContext). It uses the ApplicationPhase enum as the type of its |phase| +// property, and all of its phase transitions should be expressed in terms of +// those phases. +// +// See PhasedStepRunner's header documentation for information on how steps +// are ordered and executed, and the distinctions between "features", "steps", +// and "jobs" in this context. +// +// This class's -configure method will create all of the neccessary application +// steps, and define the steps to execute in response to each phase change. +// +// The general process to add new steps that will be handled by this object is: +// (1) Implement the step as class in chrome/app/steps/, either subclassing +// SimpleApplicationStep or just conforming to ApplicationStep. +// (2) Add a feature string for the feature the step enables to step_features.h +// (3) Be sure your implementation sets this feature as its |providedFeature|. +// (4) Be sure your implementation sets the features it requires as its +// |requiredFeatures|. +// (5) If other features directly require your feature, be sure to add your +// feature to their |requiredFeatures| arrays. +// (6) Update [StepCollections +allApplicationSteps] to include an instance of +// your step. +// (7) If your step must run in one or more phases, and isn't dependent on +// a feature already (transitively) required for that phase, add the +// necessare -requireFeature:forPhase: calls into this class's -configure +// method. -namespace ios { -class ChromeBrowserState; -} +@interface ApplicationState : PhasedStepRunner -@protocol ApplicationStep; -@protocol URLOpening; +// StepContext property redeclared readwrite. +@property(nonatomic, readwrite, copy) NSDictionary* launchOptions; -typedef NSMutableArray<id<ApplicationStep>> ApplicationStepArray; +// StepContext property redeclared with enum type. +@property(nonatomic) ApplicationPhase phase; -// An ApplicationState object stores information about Chrome's status in the -// iOS application lifecycle and owns root-level global objects needed -// throughout the app (specifically, |browserState| and |window|). -// Additionally, an ApplicationState object keeps arrays of steps to perform -// in response to various changes in the overall application states. -// These steps are grouped into launch (used when the app is cold starting), -// termination (used when the app is shutting down), background (used when the -// app is backgrounding but staying resident), and foreground (used when the app -// is warm starting). Each group of steps is an ordered array of objects -// implementing the ApplicationStep protocol. -// Some methods (indicated below) will cause the ApplicationState to run one of -// these lists of steps; this consists of: -// (1) Calling -canRunInState: on the first item in the list, passing in the -// running ApplicationState object. If this returns NO, stop. Otherwise: -// (2) Removing the first item from the list and then calling -runInState:, -// again passing in the current state object. State objects can assume -// that they are no longer in the step array once -runInState: is called. -// (3) When runInState: completes, going back to (1). Since the list of steps -// had ownership of the application step that just ran, that step will -// then no longer be strongly held, and will thus be deallocated unless -// it is being strongly held elsewhere. -// Steps cannot themselves store state, since they don't persist after running -// by default. Steps can write to the ApplicationState's |state| object to -// store state. -@interface ApplicationState : NSObject +// Optional StepContext properties explicitly declared here so +// direct users of this class don't need to check for support. +@property(nonatomic) ios::ChromeBrowserState* browserState; -// The UIApplication instance this object is storing state for. -@property(nonatomic, weak) UIApplication* application; - -// The launch options dictionary for this application, set via -// -launchWithOptions: -@property(nonatomic, readonly) NSDictionary* launchOptions; - -// The browser state this object stores; many launch steps are expected to -// make use of this. At least one launch step must assign to this property at -// some point in the launch sequence. -@property(nonatomic, assign) ios::ChromeBrowserState* browserState; - -// The current phase the application is in. Changing this doesn't trigger -// any launch steps; one of the phase change methods must be called to do that. -// Steps invoked during the phase change methods must update this property. -@property(nonatomic, assign) ApplicationPhase phase; - -// Persistent storage for application steps that need to have objects live -// beyond the execution of the step. This should be used sparingly for objects -// that actually need to persist for the lifetime of the app. -@property(nonatomic, readonly) base::SupportsUserData* persistentState; - -// The window for this application. Launch steps must create this, make it key -// and make it visible, although those three actions may be spread across -// more than one step. @property(nonatomic, strong) UIWindow* window; -// An object that can open URLs when the application is asked to do so by the -// operating system. -@property(nonatomic, weak) id<URLOpening> URLOpener; - -// Steps for each phase. Steps may add more steps to these arrays, and steps -// added in this way become strongly held by this object. -@property(nonatomic, readonly) ApplicationStepArray* launchSteps; -@property(nonatomic, readonly) ApplicationStepArray* terminationSteps; -@property(nonatomic, readonly) ApplicationStepArray* backgroundSteps; -@property(nonatomic, readonly) ApplicationStepArray* foregroundSteps; - -// Phase change methods. - -// Sets the launchOptions property to |launchOptions| and then runs the launch -// steps. -- (void)launchWithOptions:(NSDictionary*)launchOptions; - -// Runs the launch steps. -- (void)continueLaunch; - -// Runs the termination steps. -- (void)terminate; - -// Runs the background steps. -- (void)background; - -// Runs the foreground steps. -- (void)foreground; +- (void)configure; @end
diff --git a/ios/clean/chrome/app/application_state.mm b/ios/clean/chrome/app/application_state.mm index e41f397b..1716e831 100644 --- a/ios/clean/chrome/app/application_state.mm +++ b/ios/clean/chrome/app/application_state.mm
@@ -6,101 +6,31 @@ #include <memory> -#include "base/logging.h" -#include "base/memory/ptr_util.h" -#include "base/supports_user_data.h" -#import "ios/clean/chrome/app/application_step.h" +#import "ios/clean/chrome/app/application_state.h" +#import "ios/clean/chrome/app/steps/step_collections.h" +#import "ios/clean/chrome/app/steps/step_features.h" #if !defined(__has_feature) || !__has_feature(objc_arc) #error "This file requires ARC support." #endif -namespace { +@implementation ApplicationState +@dynamic phase; -// Specialization of base::SupportsUserData for use in this class. -class PersistentApplicationState : public base::SupportsUserData { - public: - ~PersistentApplicationState() override {} -}; - -} // namespace - -@interface ApplicationState () -@property(nonatomic, readwrite, copy) NSDictionary* launchOptions; -@end - -@implementation ApplicationState { - std::unique_ptr<PersistentApplicationState> _persistentState; -} - -@synthesize application = _application; @synthesize browserState = _browserState; -@synthesize URLOpener = _URLOpener; -@synthesize phase = _phase; @synthesize window = _window; -@synthesize launchSteps = _launchSteps; -@synthesize terminationSteps = _terminationSteps; -@synthesize backgroundSteps = _backgroundSteps; -@synthesize foregroundSteps = _foregroundSteps; @synthesize launchOptions = _launchOptions; -#pragma mark - Object lifecycle - -- (instancetype)init { - if ((self = [super init])) { - _phase = APPLICATION_COLD; - _launchSteps = [ApplicationStepArray array]; - _terminationSteps = [ApplicationStepArray array]; - _backgroundSteps = [ApplicationStepArray array]; - _foregroundSteps = [ApplicationStepArray array]; - _persistentState = base::MakeUnique<PersistentApplicationState>(); - } - return self; +- (void)configure { + [self addSteps:[StepCollections allApplicationSteps]]; + [self requireFeature:step_features::kBrowserState + forPhase:APPLICATION_BACKGROUNDED]; + [self requireFeature:step_features::kRootCoordinatorStarted + forPhase:APPLICATION_FOREGROUNDED]; + [self requireFeature:step_features::kChromeMainStopped + forPhase:APPLICATION_TERMINATING]; } -#pragma mark - Public API -- (base::SupportsUserData*)persistentState { - return _persistentState.get(); -} - -- (void)launchWithOptions:(NSDictionary*)launchOptions { - self.launchOptions = launchOptions; - [self continueLaunch]; -} - -- (void)continueLaunch { - [self runSteps:self.launchSteps]; -} - -- (void)terminate { - CHECK(self.phase != APPLICATION_TERMINATING); - self.phase = APPLICATION_TERMINATING; - [self runSteps:self.terminationSteps]; - CHECK(self.terminationSteps.count == 0); -} - -- (void)background { - [self runSteps:self.backgroundSteps]; -} - -- (void)foreground { - [self runSteps:self.foregroundSteps]; -} - -#pragma mark - Running steps - -// While the first step in |steps| can run in |self|, pop it, run it, and -// release ownership of it. -- (void)runSteps:(ApplicationStepArray*)steps { - while ([steps.firstObject canRunInState:self]) { - id<ApplicationStep> nextStep = steps.firstObject; - [steps removeObject:nextStep]; - // |nextStep| should not be in |steps| when -runInState is called. - // (Some steps may re-insert themselves into |steps|, for example). - DCHECK(![steps containsObject:nextStep]); - [nextStep runInState:self]; - } -} @end
diff --git a/ios/clean/chrome/app/application_step.h b/ios/clean/chrome/app/application_step.h deleted file mode 100644 index be05b08..0000000 --- a/ios/clean/chrome/app/application_step.h +++ /dev/null
@@ -1,26 +0,0 @@ -// Copyright 2016 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef IOS_CLEAN_CHROME_APP_APPLICATION_STEP_H_ -#define IOS_CLEAN_CHROME_APP_APPLICATION_STEP_H_ - -#import <Foundation/Foundation.h> - -@class ApplicationState; - -// Objects that perform application state change steps must conform to this -// protocol. -@protocol ApplicationStep<NSObject> -// Implementors should not modify |state| in -canRunInState. -- (BOOL)canRunInState:(ApplicationState*)state; - -// Implementors should expect to be deallocated after -runInState is called. -// If an implementor creates objects that need to persist longer than that, they -// should be stored in |state.state|. -// Implementors can assume that they are not in any of |state|'s step arrays -// when this method is called. -- (void)runInState:(ApplicationState*)state; -@end - -#endif // IOS_CLEAN_CHROME_APP_APPLICATION_STEP_H_
diff --git a/ios/clean/chrome/app/steps/BUILD.gn b/ios/clean/chrome/app/steps/BUILD.gn index f78ecdc..a9a6b5e 100644 --- a/ios/clean/chrome/app/steps/BUILD.gn +++ b/ios/clean/chrome/app/steps/BUILD.gn
@@ -2,22 +2,50 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -source_set("steps") { +source_set("step_runner") { sources = [ - "launch_to_background.h", - "launch_to_background.mm", - "launch_to_basic.h", - "launch_to_basic.mm", - "launch_to_foreground.h", - "launch_to_foreground.mm", - "root_coordinator+application_step.h", - "root_coordinator+application_step.mm", + "application_step.h", + "phased_step_runner.h", + "phased_step_runner.mm", + "step_context.h", ] configs += [ "//build/config/compiler:enable_arc" ] deps = [ "//base", + ] +} + +source_set("steps") { + sources = [ + "browser_state_setter.h", + "browser_state_setter.mm", + "bundle_and_defaults_configurator.h", + "bundle_and_defaults_configurator.mm", + "chrome_main.h", + "chrome_main.mm", + "foregrounder.h", + "foregrounder.mm", + "provider_initializer.h", + "provider_initializer.mm", + "root_coordinator_initializer.h", + "root_coordinator_initializer.mm", + "simple_application_step.h", + "simple_application_step.mm", + "step_collections.h", + "step_collections.mm", + "step_features.h", + "step_features.mm", + "ui_initializer.h", + "ui_initializer.mm", + ] + + configs += [ "//build/config/compiler:enable_arc" ] + + deps = [ + ":step_runner", + "//base", "//components/content_settings/core/browser", "//ios/chrome/app:app_internal", "//ios/chrome/app/startup", @@ -29,7 +57,6 @@ "//ios/chrome/browser/content_settings", "//ios/chrome/browser/web:web_internal", "//ios/chrome/browser/web_state_list", - "//ios/clean/chrome/app:application_state", "//ios/clean/chrome/browser/ui/root", "//ios/net", "//ios/shared/chrome/browser/ui/browser_list", @@ -42,14 +69,16 @@ testonly = true sources = [ - "root_coordinator+application_step_unittest.mm", + "phased_step_runner_unittest.mm", ] configs += [ "//build/config/compiler:enable_arc" ] deps = [ + ":step_runner", ":steps", "//base", + "//ios/chrome/test/base", "//ios/clean/chrome/app:application_state", "//testing/gtest", "//third_party/ocmock",
diff --git a/ios/clean/chrome/app/steps/application_step.h b/ios/clean/chrome/app/steps/application_step.h new file mode 100644 index 0000000..07beeb2b --- /dev/null +++ b/ios/clean/chrome/app/steps/application_step.h
@@ -0,0 +1,52 @@ +// 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 IOS_CLEAN_CHROME_APP_STEPS_APPLICATION_STEP_H_ +#define IOS_CLEAN_CHROME_APP_STEPS_APPLICATION_STEP_H_ + +#import <Foundation/Foundation.h> + +@protocol StepContext; + +// Objects that perform application state change steps must conform to this +// protocol. +@protocol ApplicationStep<NSObject> + +// The feature(s) that running this step provides. Other steps that require +// one of these features will cause this step to be run before they run. +// Steps that do not return at least one value for this property will +// never be run. +// Steps should always return the same values for this property each time it +// is accessed. +@property(nonatomic, readonly) NSArray<NSString*>* providedFeatures; + +// The features that this step requires in order to run. This array can be +// empty (or nil), indicating that this step has no dependencies. +// Steps should always return the same values for this property each time it +// is accessed. +@property(nonatomic, readonly) NSArray<NSString*>* requiredFeatures; + +// YES if this step must run synchronously with other steps on the main thread. +// NO if this step can run on another thread. Steps will wait for their +// dependencies to complete regardless of which threads they are running on. +// When in doubt, return YES. +// Steps should always return the same values for this property each time it +// is accessed. +@property(nonatomic, readonly) BOOL synchronous; + +// Runs the step. +// This method will be called when the step is executing. A given step +// may have this method called multiple times in its lifetime, depending on +// how the step runner is configured. It is up to the step's implementation +// to manage being run multiple times. +// A step will only be called once for each phase change, however. +// |feature| will be the feature this step was run to satisfy a requirement +// for. If a feature has only one |prividedFeature|, |feature| will always +// be that value. +// |context| is the StepContext object the step can operate on. +- (void)runFeature:(NSString*)feature withContext:(id<StepContext>)context; + +@end + +#endif // IOS_CLEAN_CHROME_APP_STEPS_APPLICATION_STEP_H_
diff --git a/ios/clean/chrome/app/steps/browser_state_setter.h b/ios/clean/chrome/app/steps/browser_state_setter.h new file mode 100644 index 0000000..14180c1 --- /dev/null +++ b/ios/clean/chrome/app/steps/browser_state_setter.h
@@ -0,0 +1,16 @@ +// 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 IOS_CLEAN_CHROME_APP_STEPS_BROWSER_STATE_SETTER_H_ +#define IOS_CLEAN_CHROME_APP_STEPS_BROWSER_STATE_SETTER_H_ + +#import <Foundation/Foundation.h> + +#import "ios/clean/chrome/app/steps/application_step.h" +#import "ios/clean/chrome/app/steps/simple_application_step.h" + +@interface BrowserStateSetter : SimpleApplicationStep<ApplicationStep> +@end + +#endif // IOS_CLEAN_CHROME_APP_STEPS_BROWSER_STATE_SETTER_H_
diff --git a/ios/clean/chrome/app/steps/browser_state_setter.mm b/ios/clean/chrome/app/steps/browser_state_setter.mm new file mode 100644 index 0000000..52f406c --- /dev/null +++ b/ios/clean/chrome/app/steps/browser_state_setter.mm
@@ -0,0 +1,36 @@ +// 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. + +#import "ios/clean/chrome/app/steps/browser_state_setter.h" + +#include "base/logging.h" +#include "ios/chrome/browser/application_context.h" +#include "ios/chrome/browser/browser_state/chrome_browser_state_manager.h" +#import "ios/clean/chrome/app/steps/step_context.h" +#import "ios/clean/chrome/app/steps/step_features.h" + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +@implementation BrowserStateSetter + +- (instancetype)init { + if ((self = [super init])) { + self.providedFeature = step_features::kBrowserState; + self.requiredFeatures = @[ + step_features::kChromeMainStarted, step_features::kBundleAndDefaults + ]; + } + return self; +} + +- (void)runFeature:(NSString*)feature withContext:(id<StepContext>)context { + DCHECK([context respondsToSelector:@selector(setBrowserState:)]); + context.browserState = GetApplicationContext() + ->GetChromeBrowserStateManager() + ->GetLastUsedBrowserState(); +} + +@end
diff --git a/ios/clean/chrome/app/steps/bundle_and_defaults_configurator.h b/ios/clean/chrome/app/steps/bundle_and_defaults_configurator.h new file mode 100644 index 0000000..a7849f66 --- /dev/null +++ b/ios/clean/chrome/app/steps/bundle_and_defaults_configurator.h
@@ -0,0 +1,17 @@ +// 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 IOS_CLEAN_CHROME_APP_STEPS_BUNDLE_AND_DEFAULTS_CONFIGURATOR_H_ +#define IOS_CLEAN_CHROME_APP_STEPS_BUNDLE_AND_DEFAULTS_CONFIGURATOR_H_ + +#import <Foundation/Foundation.h> + +#import "ios/clean/chrome/app/steps/application_step.h" +#import "ios/clean/chrome/app/steps/simple_application_step.h" + +@interface BundleAndDefaultsConfigurator + : SimpleApplicationStep<ApplicationStep> +@end + +#endif // IOS_CLEAN_CHROME_APP_STEPS_BUNDLE_AND_DEFAULTS_CONFIGURATOR_H_
diff --git a/ios/clean/chrome/app/steps/bundle_and_defaults_configurator.mm b/ios/clean/chrome/app/steps/bundle_and_defaults_configurator.mm new file mode 100644 index 0000000..5c7e928 --- /dev/null +++ b/ios/clean/chrome/app/steps/bundle_and_defaults_configurator.mm
@@ -0,0 +1,40 @@ +// 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. + +#import "ios/clean/chrome/app/steps/bundle_and_defaults_configurator.h" + +#include "base/mac/bundle_locations.h" +#include "base/mac/foundation_util.h" +#include "base/strings/sys_string_conversions.h" +#import "ios/chrome/app/startup/register_experimental_settings.h" +#import "ios/clean/chrome/app/steps/step_features.h" + +@protocol StepContext; + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +@implementation BundleAndDefaultsConfigurator + +- (instancetype)init { + if ((self = [super init])) { + self.providedFeature = step_features::kBundleAndDefaults; + } + return self; +} + +- (void)runFeature:(NSString*)feature withContext:(id<StepContext>)context { + NSBundle* baseBundle = base::mac::OuterBundle(); + base::mac::SetBaseBundleID( + base::SysNSStringToUTF8([baseBundle bundleIdentifier]).c_str()); + + [RegisterExperimentalSettings + registerExperimentalSettingsWithUserDefaults:[NSUserDefaults + standardUserDefaults] + bundle:base::mac:: + FrameworkBundle()]; +} + +@end
diff --git a/ios/clean/chrome/app/steps/chrome_main.h b/ios/clean/chrome/app/steps/chrome_main.h new file mode 100644 index 0000000..8f0429e --- /dev/null +++ b/ios/clean/chrome/app/steps/chrome_main.h
@@ -0,0 +1,16 @@ +// 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 IOS_CLEAN_CHROME_APP_STEPS_CHROME_MAIN_H_ +#define IOS_CLEAN_CHROME_APP_STEPS_CHROME_MAIN_H_ + +#import <Foundation/Foundation.h> + +#import "ios/clean/chrome/app/steps/application_step.h" +#import "ios/clean/chrome/app/steps/simple_application_step.h" + +@interface ChromeMain : SimpleApplicationStep<ApplicationStep> +@end + +#endif // IOS_CLEAN_CHROME_APP_STEPS_CHROME_MAIN_H_
diff --git a/ios/clean/chrome/app/steps/chrome_main.mm b/ios/clean/chrome/app/steps/chrome_main.mm new file mode 100644 index 0000000..3a2bd0c --- /dev/null +++ b/ios/clean/chrome/app/steps/chrome_main.mm
@@ -0,0 +1,43 @@ +// 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. + +#import "ios/clean/chrome/app/steps/chrome_main.h" + +#include "base/memory/ptr_util.h" +#include "ios/chrome/app/startup/ios_chrome_main.h" +#import "ios/chrome/browser/web/chrome_web_client.h" +#import "ios/clean/chrome/app/steps/step_features.h" +#include "ios/web/public/web_client.h" + +@protocol StepContext; + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +@implementation ChromeMain { + std::unique_ptr<IOSChromeMain> _chromeMain; +} + +- (instancetype)init { + if ((self = [super init])) { + self.providedFeatures = @[ + step_features::kChromeMainStarted, step_features::kChromeMainStopped + ]; + self.requiredFeatures = @[ step_features::kProviders ]; + } + return self; +} + +- (void)runFeature:(NSString*)feature withContext:(id<StepContext>)context { + if ([feature isEqualToString:step_features::kChromeMainStarted]) { + web::SetWebClient(new ChromeWebClient()); + _chromeMain = base::MakeUnique<IOSChromeMain>(); + } else { + DCHECK([feature isEqualToString:step_features::kChromeMainStopped]); + _chromeMain.reset(); + } +} + +@end
diff --git a/ios/clean/chrome/app/steps/foregrounder.h b/ios/clean/chrome/app/steps/foregrounder.h new file mode 100644 index 0000000..a8db6d8 --- /dev/null +++ b/ios/clean/chrome/app/steps/foregrounder.h
@@ -0,0 +1,16 @@ +// 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 IOS_CLEAN_CHROME_APP_STEPS_FOREGROUNDER_H_ +#define IOS_CLEAN_CHROME_APP_STEPS_FOREGROUNDER_H_ + +#import <Foundation/Foundation.h> + +#import "ios/clean/chrome/app/steps/application_step.h" +#import "ios/clean/chrome/app/steps/simple_application_step.h" + +@interface Foregrounder : SimpleApplicationStep<ApplicationStep> +@end + +#endif // IOS_CLEAN_CHROME_APP_STEPS_FOREGROUNDER_H_
diff --git a/ios/clean/chrome/app/steps/foregrounder.mm b/ios/clean/chrome/app/steps/foregrounder.mm new file mode 100644 index 0000000..6febc46 --- /dev/null +++ b/ios/clean/chrome/app/steps/foregrounder.mm
@@ -0,0 +1,30 @@ +// 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. + +#import "ios/clean/chrome/app/steps/foregrounder.h" + +#include "ios/chrome/browser/application_context.h" +#import "ios/clean/chrome/app/steps/step_features.h" + +@protocol StepContext; +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif +@implementation Foregrounder +- (instancetype)init { + if ((self = [super init])) { + self.providedFeature = step_features::kForeground; + self.requiredFeatures = @[ + step_features::kBundleAndDefaults, step_features::kChromeMainStarted, + step_features::kBrowserState, step_features::kChromeMainStarted + ]; + } + return self; +} + +- (void)runFeature:(NSString*)feature withContext:(id<StepContext>)context { + GetApplicationContext()->OnAppEnterForeground(); +} + +@end
diff --git a/ios/clean/chrome/app/steps/launch_to_background.h b/ios/clean/chrome/app/steps/launch_to_background.h deleted file mode 100644 index 36914a4..0000000 --- a/ios/clean/chrome/app/steps/launch_to_background.h +++ /dev/null
@@ -1,34 +0,0 @@ -// Copyright 2016 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef IOS_CLEAN_CHROME_APP_STEPS_LAUNCH_TO_BACKGROUND_H_ -#define IOS_CLEAN_CHROME_APP_STEPS_LAUNCH_TO_BACKGROUND_H_ - -#import <Foundation/Foundation.h> - -#import "ios/clean/chrome/app/application_step.h" - -// This file includes ApplicationStep objects that perform the steps that bring -// the application up to the background phase of launching. - -// Sets up the user defaults and application bundle. -// Pre: Application phase is APPLICATION_BASIC. -// Post: Application phase is (still) APPLICATION_BASIC. -@interface SetupBundleAndUserDefaults : NSObject<ApplicationStep> -@end - -// Starts the ChromeMain object and sets the global WebClient object. -// Pre: Application phase is APPLICATION_BASIC. -// Post: Application phase is APPLICATION_BACKGROUNDED. -@interface StartChromeMain : NSObject<ApplicationStep> -@end - -// Sets the browserState property in the ApplicationState. -// Pre: Application phase is APPLICATION_BACKGROUNDED and a browser state -// manager is available. -// Post: Application phase is (still) APPLICATION_BACKGROUNDED. -@interface SetBrowserState : NSObject<ApplicationStep> -@end - -#endif // IOS_CLEAN_CHROME_APP_STEPS_LAUNCH_TO_BACKGROUND_H_
diff --git a/ios/clean/chrome/app/steps/launch_to_background.mm b/ios/clean/chrome/app/steps/launch_to_background.mm deleted file mode 100644 index 9858c1d..0000000 --- a/ios/clean/chrome/app/steps/launch_to_background.mm +++ /dev/null
@@ -1,127 +0,0 @@ -// Copyright 2016 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#import "ios/clean/chrome/app/steps/launch_to_background.h" - -#include <memory> - -#include "base/mac/bundle_locations.h" -#include "base/mac/foundation_util.h" -#include "base/memory/ptr_util.h" -#include "base/strings/sys_string_conversions.h" -#include "base/supports_user_data.h" -#include "ios/chrome/app/startup/ios_chrome_main.h" -#include "ios/chrome/app/startup/register_experimental_settings.h" -#include "ios/chrome/browser/application_context.h" -#include "ios/chrome/browser/browser_state/chrome_browser_state_manager.h" -#include "ios/chrome/browser/web/chrome_web_client.h" -#include "ios/clean/chrome/app/application_state.h" -#include "ios/web/public/web_client.h" - -#if !defined(__has_feature) || !__has_feature(objc_arc) -#error "This file requires ARC support." -#endif - -// Step that removes a previously-stored IOSChromeMain instance. -@interface StopChromeMain : NSObject<ApplicationStep> -@end - -namespace { - -// A SupportsUserData::Data struct to store an IOSChromeMain object and preserve -// its lifetime (by storing it in an ApplicationState's |state| property) beyond -// the running time of the -StartChromeMain step. -class ChromeMainContainer : public base::SupportsUserData::Data { - public: - explicit ChromeMainContainer(std::unique_ptr<IOSChromeMain> chrome_main) - : main_(std::move(chrome_main)) {} - - private: - std::unique_ptr<IOSChromeMain> main_; -}; - -// Key for storing the ChromeMainContainer. -const char kChromeMainKey[] = "chrome_main"; - -} // namespace - -@implementation SetupBundleAndUserDefaults - -- (BOOL)canRunInState:(ApplicationState*)state { - return state.phase == APPLICATION_BASIC; -} - -- (void)runInState:(ApplicationState*)state { - NSBundle* baseBundle = base::mac::OuterBundle(); - base::mac::SetBaseBundleID( - base::SysNSStringToUTF8([baseBundle bundleIdentifier]).c_str()); - - [RegisterExperimentalSettings - registerExperimentalSettingsWithUserDefaults:[NSUserDefaults - standardUserDefaults] - bundle:base::mac:: - FrameworkBundle()]; -} - -@end - -@implementation StartChromeMain - -- (BOOL)canRunInState:(ApplicationState*)state { - return state.phase == APPLICATION_BASIC; -} - -- (void)runInState:(ApplicationState*)state { - web::SetWebClient(new ChromeWebClient()); - - // Create and persist an IOSChromeMain instance. - state.persistentState->SetUserData( - kChromeMainKey, - base::MakeUnique<ChromeMainContainer>(base::MakeUnique<IOSChromeMain>())); - - // Add a step to the termination steps of |state| that will stop and remove - // the IOSChromeMain instance. - [[state terminationSteps] addObject:[[StopChromeMain alloc] init]]; - - state.phase = APPLICATION_BACKGROUNDED; -} - -@end - -@implementation StopChromeMain - -- (BOOL)canRunInState:(ApplicationState*)state { - return state.phase == APPLICATION_TERMINATING; -} - -- (void)runInState:(ApplicationState*)state { - // This should be the last step executed while the app is terminating. - if (state.terminationSteps.count) { - // There are other steps waiting to run, so add this to the end and return. - [state.terminationSteps addObject:self]; - return; - } - state.persistentState->RemoveUserData(kChromeMainKey); -} - -@end - -@implementation SetBrowserState - -- (BOOL)canRunInState:(ApplicationState*)state { - ApplicationContext* applicationContext = GetApplicationContext(); - if (!applicationContext) - return NO; - - return applicationContext->GetChromeBrowserStateManager() && - state.phase == APPLICATION_BACKGROUNDED; -} - -- (void)runInState:(ApplicationState*)state { - state.browserState = GetApplicationContext() - ->GetChromeBrowserStateManager() - ->GetLastUsedBrowserState(); -} - -@end
diff --git a/ios/clean/chrome/app/steps/launch_to_basic.h b/ios/clean/chrome/app/steps/launch_to_basic.h deleted file mode 100644 index eac1b603..0000000 --- a/ios/clean/chrome/app/steps/launch_to_basic.h +++ /dev/null
@@ -1,21 +0,0 @@ -// Copyright 2016 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef IOS_CLEAN_CHROME_APP_STEPS_LAUNCH_TO_BASIC_H_ -#define IOS_CLEAN_CHROME_APP_STEPS_LAUNCH_TO_BASIC_H_ - -#import <Foundation/Foundation.h> - -#import "ios/clean/chrome/app/application_step.h" - -// This file includes ApplicationStep objects that perform the very first steps -// of application launch. - -// Initializes the application providers. -// Pre: Application phase is APPLICATION_COLD. -// Post: Application phase is APPLICATION_BASIC. -@interface ProviderInitializer : NSObject<ApplicationStep> -@end - -#endif // IOS_CLEAN_CHROME_APP_STEPS_LAUNCH_TO_BASIC_H_
diff --git a/ios/clean/chrome/app/steps/launch_to_basic.mm b/ios/clean/chrome/app/steps/launch_to_basic.mm deleted file mode 100644 index 72d7943..0000000 --- a/ios/clean/chrome/app/steps/launch_to_basic.mm +++ /dev/null
@@ -1,26 +0,0 @@ -// Copyright 2016 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#import "ios/clean/chrome/app/steps/launch_to_basic.h" - -#include "ios/chrome/app/startup/provider_registration.h" -#include "ios/clean/chrome/app/application_state.h" - -#if !defined(__has_feature) || !__has_feature(objc_arc) -#error "This file requires ARC support." -#endif - -@implementation ProviderInitializer - -- (BOOL)canRunInState:(ApplicationState*)state { - return state.phase == APPLICATION_COLD; -} - -- (void)runInState:(ApplicationState*)state { - [ProviderRegistration registerProviders]; - - state.phase = APPLICATION_BASIC; -} - -@end
diff --git a/ios/clean/chrome/app/steps/launch_to_foreground.h b/ios/clean/chrome/app/steps/launch_to_foreground.h deleted file mode 100644 index c8a1a75..0000000 --- a/ios/clean/chrome/app/steps/launch_to_foreground.h +++ /dev/null
@@ -1,42 +0,0 @@ -// Copyright 2016 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - - -#ifndef IOS_CLEAN_CHROME_APP_STEPS_LAUNCH_TO_FOREGROUND_H_ -#define IOS_CLEAN_CHROME_APP_STEPS_LAUNCH_TO_FOREGROUND_H_ - -#import <Foundation/Foundation.h> - -#import "ios/clean/chrome/app/application_step.h" - -// This file includes ApplicationStep objects that perform the very first steps -// of application launch. - -// Signals to the application context that the app is entering the foreground. -// Pre: Application phase is APPLICATION_BACKGROUNDED. -// Post: Application phase is (still) APPLICATION_BACKGROUNDED. -@interface BeginForegrounding : NSObject<ApplicationStep> -@end - -// Creates the main window and makes it key, but doesn't make it visible yet. -// Pre: Application phase is APPLICATION_BACKGROUNDED. -// Post: Application phase is (still) APPLICATION_BACKGROUNDED. -@interface PrepareForUI : NSObject<ApplicationStep> -@end - -// Performs final foregrounding tasks. -// Creates the main window and makes it key, but doesn't make it visible yet. -// Pre: Application phase is APPLICATION_BACKGROUNDED and the main window is -// key. -// Post: Application phase is APPLICATION_FOREGROUNDED. -@interface CompleteForegrounding : NSObject<ApplicationStep> -@end - -// Performs preparation steps for UIDebuggingInformationOverlay. -// Pre: Application phase is APPLICATION_FOREGROUNDED. -// Post: Application phase is (still) APPLICATION_FOREGROUNDED. -@interface DebuggingInformationOverlay : NSObject<ApplicationStep> -@end - -#endif // IOS_CLEAN_CHROME_APP_STEPS_LAUNCH_TO_FOREGROUND_H_
diff --git a/ios/clean/chrome/app/steps/launch_to_foreground.mm b/ios/clean/chrome/app/steps/launch_to_foreground.mm deleted file mode 100644 index 4647934..0000000 --- a/ios/clean/chrome/app/steps/launch_to_foreground.mm +++ /dev/null
@@ -1,92 +0,0 @@ -// Copyright 2016 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#import "ios/clean/chrome/app/steps/launch_to_foreground.h" - -#include "components/content_settings/core/browser/host_content_settings_map.h" -#include "ios/chrome/browser/application_context.h" -#include "ios/chrome/browser/browser_state/chrome_browser_state.h" -#include "ios/chrome/browser/content_settings/host_content_settings_map_factory.h" -#include "ios/clean/chrome/app/application_state.h" -#include "ios/web/public/web_capabilities.h" -#include "ios/web/public/web_thread.h" - -#if !defined(__has_feature) || !__has_feature(objc_arc) -#error "This file requires ARC support." -#endif - -// Temporary class to trigger URL-opening events from a shake gesture. -@interface ShakeCatchingWindow : UIWindow -@end - -@implementation BeginForegrounding - -- (BOOL)canRunInState:(ApplicationState*)state { - return state.phase == APPLICATION_BACKGROUNDED; -} - -- (void)runInState:(ApplicationState*)state { - GetApplicationContext()->OnAppEnterForeground(); -} - -@end - -@implementation PrepareForUI - -- (BOOL)canRunInState:(ApplicationState*)state { - return state.phase == APPLICATION_BACKGROUNDED; -} - -- (void)runInState:(ApplicationState*)state { - state.window = - [[ShakeCatchingWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; - [state.window makeKeyWindow]; -} - -@end - -@implementation CompleteForegrounding -- (BOOL)canRunInState:(ApplicationState*)state { - return state.window.keyWindow && state.phase == APPLICATION_BACKGROUNDED; -} - -- (void)runInState:(ApplicationState*)state { - state.phase = APPLICATION_FOREGROUNDED; -} - -@end - -@implementation ShakeCatchingWindow - -#pragma mark - UIResponder - -- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent*)event { - if (motion == UIEventSubtypeMotionShake) { - UIApplication* app = [UIApplication sharedApplication]; - [app.delegate application:app - openURL:[NSURL URLWithString:@"chrome://newtab"] - options:@{}]; - } - [super motionEnded:motion withEvent:event]; -} - -@end - -@implementation DebuggingInformationOverlay - -- (BOOL)canRunInState:(ApplicationState*)state { - return state.phase == APPLICATION_FOREGROUNDED; -} - -- (void)runInState:(ApplicationState*)state { -#ifndef NDEBUG -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Warc-performSelector-leaks" - [NSClassFromString(@"UIDebuggingInformationOverlay") - performSelector:NSSelectorFromString(@"prepareDebuggingOverlay")]; -#pragma clang diagnostic pop -#endif // NDEBUG -} - -@end
diff --git a/ios/clean/chrome/app/steps/phased_step_runner.h b/ios/clean/chrome/app/steps/phased_step_runner.h new file mode 100644 index 0000000..591b8f7 --- /dev/null +++ b/ios/clean/chrome/app/steps/phased_step_runner.h
@@ -0,0 +1,71 @@ +// 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 IOS_CLEAN_CHROME_APP_STEPS_PHASED_STEP_RUNNER_H_ +#define IOS_CLEAN_CHROME_APP_STEPS_PHASED_STEP_RUNNER_H_ + +#import <Foundation/Foundation.h> + +#import "ios/clean/chrome/app/steps/application_step.h" +#import "ios/clean/chrome/app/steps/step_context.h" + +// This class implements a generic state machine that will execute one or more +// "steps" in response to changes in its |phase| property. (The numeric |phase| +// property is defined as part of the StepContext protocol.) +// +// Steps, their dependencies, and the mechanisms by which they are executed +// are defined in terms of "features", "steps" and "jobs". +// +// A "feature" in this context is a NSString that refers to some action that a +// step performs, or some global structure that it initializes. Features are +// intentionally indirect labels for steps; they allow for dependencies to +// be expressed between the steps while keeping the step implementations +// encapsulated. +// +// A "step" is an object that conforms to ApplicationStep. A step defines one +// or more features it provides, and may define any number of features it +// requires as well. The step runner will use the dependencies expressed by +// steps to determine the order that steps need to execute in. +// +// Once a step is added to the step runner, the step object will persist for +// the lifetime of the step runner. If a step is run in multiple phases, it +// is the same step object that is run each time. This allows a step to maintain +// internal state. +// +// A "job" is the internal object that is used to encapsulate the steps that +// need to run each time the step runner's phase changes. Jobs are created, +// run at most once, and then disposed of. +// +// "Running a step" consists of calling the step's -runFeature:withContext: +// method, passing in the feature that the step was run for, and the +// current StepContext (the task runner itself). StepContext exposes the +// step runner's phase as readwrite, which means that during the execution of +// the steps triggered by a phase change, the phase might be changed to a new +// value. +// +// If this happens, as soon as the phase is set to another value, all queued +// jobs are cancelled, and new jobs are triggered for the new phase change. +// +// By defualt, steps are executed on the main thread. Steps that advertise +// a |synchronous| property value of |NO| will execute on another thread. +// The -setPhase: method of the phasedStepRunner will wait for all jobs to +// complete before returning, regardless of the threads they run on. +@interface PhasedStepRunner : NSObject<StepContext> + +// Adds |step| as one of the application steps that this runner knows about. +// (The step will only be executed if it is required to do so in the +// dependency graph for a phase change). +// It is an error if |step| provides a feature that has already been provided +// by a previously-added step. +- (void)addStep:(id<ApplicationStep>)step; + +// Adds all of the steps in |steps|. +- (void)addSteps:(NSArray<id<ApplicationStep>>*)steps; + +// Adds |feature| as a requirement when the receiver's phase changes to +// |phase|. +- (void)requireFeature:(NSString*)feature forPhase:(NSUInteger)phase; +@end + +#endif // IOS_CLEAN_CHROME_APP_STEPS_PHASED_STEP_RUNNER_H_
diff --git a/ios/clean/chrome/app/steps/phased_step_runner.mm b/ios/clean/chrome/app/steps/phased_step_runner.mm new file mode 100644 index 0000000..6983b62 --- /dev/null +++ b/ios/clean/chrome/app/steps/phased_step_runner.mm
@@ -0,0 +1,265 @@ +// 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. + +#import "ios/clean/chrome/app/steps/phased_step_runner.h" + +#import "base/logging.h" +#import "base/mac/foundation_util.h" + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +// An ApplicationStep used to anchor the dependency graph for a phase change. +// PhasedStepRunner will create a PhaseChangeStep for each phase that has +// required features. All other steps for that phase change will be dependencies +// of the PhaseChangeStep. Running the PhaseChangeStep will set the +// PhasedStepRunner's |running| property to NO. +@interface PhaseChangeStep : NSObject<ApplicationStep> +// |runner| is the PhasedStepRunner whose |running| property should be unset +// when this step runs. +// |phase| is the phase that the PhasedStepRunner is expected to be in when +// this step is run. +- (instancetype)initWithRunner:(PhasedStepRunner*)runner + phase:(NSUInteger)phase NS_DESIGNATED_INITIALIZER; +- (instancetype)init NS_UNAVAILABLE; +// Phase set in -initWithRunner:phase: +@property(nonatomic, readonly) NSUInteger phase; +// Runner set in -initWithRunner:phase: +@property(nonatomic, weak) PhasedStepRunner* runner; +// Features required for this phase. +@property(nonatomic, readonly) NSMutableArray<NSString*>* requiredFeatures; +@end + +// NSOperation subclass used for jobs. +@interface StepOperation : NSOperation +- (instancetype)initWithStep:(id<ApplicationStep>)step + feature:(NSString*)feature + context:(id<StepContext>)context NS_DESIGNATED_INITIALIZER; +- (instancetype)init NS_UNAVAILABLE; +@property(nonatomic, copy) NSString* feature; +@property(nonatomic, readonly, weak) id<ApplicationStep> step; +@property(nonatomic, readonly, weak) id<StepContext> context; +@end + +@interface PhasedStepRunner () ++ (NSString*)featureForChangeToPhase:(NSUInteger)phase; +@property(nonatomic) + NSMutableDictionary<NSString*, id<ApplicationStep>>* featureProviders; +@property(nonatomic) NSOperationQueue* queue; +@property(nonatomic) NSMutableSet<StepOperation*>* jobs; +@property(nonatomic, readonly) NSSet<StepOperation*>* readyJobs; +@property(nonatomic) BOOL running; +@end + +@implementation PhaseChangeStep +@synthesize phase = _phase; +@synthesize runner = _runner; +@synthesize providedFeatures = _providedFeatures; +@synthesize requiredFeatures = _requiredFeatures; +@synthesize synchronous = _synchronous; + +- (instancetype)initWithRunner:(PhasedStepRunner*)runner + phase:(NSUInteger)phase { + if ((self = [super init])) { + _runner = runner; + _phase = phase; + _providedFeatures = @[ [PhasedStepRunner featureForChangeToPhase:_phase] ]; + _requiredFeatures = [[NSMutableArray alloc] init]; + // Always synchronous. + _synchronous = YES; + } + return self; +} + +- (void)runFeature:(NSString*)feature withContext:(id<StepContext>)context { + self.runner.running = NO; +} + +@end + +@implementation StepOperation +@synthesize step = _step; +@synthesize feature = _feature; +@synthesize context = _context; + +- (instancetype)initWithStep:(id<ApplicationStep>)step + feature:(NSString*)feature + context:(id<StepContext>)context { + if ((self = [super init])) { + _step = step; + _feature = feature; + _context = context; + self.name = feature; + } + return self; +} + +- (void)main { + [self.step runFeature:self.feature withContext:self.context]; +} + +@end + +@implementation PhasedStepRunner + +@synthesize phase = _phase; +@synthesize featureProviders = _featureProviders; +@synthesize jobs = _jobs; +@synthesize queue = _queue; +@synthesize running = _running; +@synthesize URLOpener = _URLOpener; + +- (instancetype)init { + if ((self = [super init])) { + _featureProviders = [[NSMutableDictionary alloc] init]; + _jobs = [[NSMutableSet alloc] init]; + } + return self; +} + ++ (NSString*)featureForChangeToPhase:(NSUInteger)phase { + return [NSString stringWithFormat:@"__PhaseChangeCompleteForPhase_%ld", + static_cast<unsigned long>(phase)]; +} + +- (void)addStep:(id<ApplicationStep>)step { + for (NSString* feature in step.providedFeatures) { + DCHECK(!self.featureProviders[feature]) + << "Step provides duplicated feature."; + self.featureProviders[feature] = step; + } +} + +- (void)addSteps:(NSArray<id<ApplicationStep>>*)steps { + for (id<ApplicationStep> step in steps) { + [self addStep:step]; + } +} + +- (void)requireFeature:(NSString*)feature forPhase:(NSUInteger)phase { + NSString* phaseFeature = [[self class] featureForChangeToPhase:phase]; + PhaseChangeStep* changeStep = + base::mac::ObjCCast<PhaseChangeStep>(self.featureProviders[phaseFeature]); + if (!changeStep) { + changeStep = [[PhaseChangeStep alloc] initWithRunner:self phase:phase]; + [self addStep:changeStep]; + } + [changeStep.requiredFeatures addObject:feature]; +} + +- (void)setPhase:(NSUInteger)phase { + if (phase == _phase) + return; + _phase = phase; + if (self.running) { + [self stop]; + } else { + [self run]; + } +} + +- (NSSet<NSOperation*>*)readyJobs { + return [self.jobs objectsPassingTest:^BOOL(NSOperation* job, BOOL* stop) { + return job.ready && !job.executing; + }]; +} + +- (NSArray<StepOperation*>*)jobsForCurrentPhase { + // Build the jobs. + LOG(ERROR) << "Building jobs for phase " << self.phase; + NSMutableDictionary<NSString*, StepOperation*>* featureJobs = + [[NSMutableDictionary alloc] init]; + NSMutableSet<NSString*>* requiredFeatures = [NSMutableSet<NSString*> + setWithObject:[[self class] featureForChangeToPhase:self.phase]]; + + while (requiredFeatures.count) { + NSString* feature = [requiredFeatures anyObject]; + id<ApplicationStep> step = self.featureProviders[feature]; + DCHECK(step) << "No provider for feature " << feature.UTF8String; + if (!featureJobs[feature]) { + featureJobs[feature] = [[StepOperation alloc] initWithStep:step + feature:feature + context:self]; + } + [requiredFeatures removeObject:feature]; + for (NSString* requirement in step.requiredFeatures) { + [requiredFeatures addObject:requirement]; + } + } + + // Set up dependencies. + for (NSString* feature in featureJobs.allKeys) { + for (NSString* requirement in self.featureProviders[feature] + .requiredFeatures) { + [featureJobs[feature] addDependency:featureJobs[requirement]]; + } + } + + LOG(ERROR) << "Created " << featureJobs.allValues.count << " jobs"; + return featureJobs.allValues; +} + +- (void)stop { + LOG(ERROR) << "Stopping; " << self.jobs.count << " sync jobs, " + << self.queue.operationCount << " async"; + [self.jobs removeAllObjects]; + [self.queue cancelAllOperations]; + [self.queue waitUntilAllOperationsAreFinished]; + self.running = NO; +} + +// Run all of the steps for the current phase. +- (void)run { + [self.jobs removeAllObjects]; + self.queue = [[NSOperationQueue alloc] init]; + self.running = YES; + for (StepOperation* job in [self jobsForCurrentPhase]) { + if (job.step.synchronous) + [self.jobs addObject:job]; + else + [self.queue addOperation:job]; + } + + // Watch all of the jobs running synchronously on this thread. + for (NSOperation* job in self.jobs) { + [job addObserver:self + forKeyPath:@"isFinished" + options:NSKeyValueObservingOptionNew + context:nullptr]; + } + + // Run the synchronous jobs. + LOG(ERROR) << "Running " << self.jobs.count << " sync jobs for phase " + << self.phase; + NSUInteger initialPhase = self.phase; + while (self.jobs.count) { + [[self.readyJobs anyObject] start]; + } + DCHECK_EQ(self.queue.operationCount, 0UL); + DCHECK(!self.running); + self.queue = nil; + + // If the phase was changed while running the jobs, then start over again. + if (initialPhase != self.phase) { + [self run]; + } +} + +- (void)observeValueForKeyPath:(NSString*)keyPath + ofObject:(id)object + change:(NSDictionary<NSKeyValueChangeKey, id>*)change + context:(void*)context { + StepOperation* job = base::mac::ObjCCast<StepOperation>(object); + if (!job) + return; + if (job.finished) { + // When a job finishes, remove it. + [self.jobs removeObject:job]; + // Also stop observing it. + [job removeObserver:self forKeyPath:@"isFinished"]; + } +} + +@end
diff --git a/ios/clean/chrome/app/steps/phased_step_runner_unittest.mm b/ios/clean/chrome/app/steps/phased_step_runner_unittest.mm new file mode 100644 index 0000000..57a1b97af --- /dev/null +++ b/ios/clean/chrome/app/steps/phased_step_runner_unittest.mm
@@ -0,0 +1,396 @@ +// 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. + +#import "ios/clean/chrome/app/steps/phased_step_runner.h" +#import "ios/clean/chrome/app/steps/application_step.h" + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/gtest_mac.h" +#include "testing/platform_test.h" + +// Step that provides a single feature. +@interface TestStep : NSObject<ApplicationStep> +@property(nonatomic) BOOL hasRun; +@property(nonatomic) NSString* providedFeature; +@property(nonatomic) NSMutableSet<TestStep*>* dependencies; +@end + +@interface AsyncTestStep : TestStep +@end + +@interface PhaseChangeTestStep : TestStep +@property(nonatomic) NSUInteger newPhase; +@end + +@implementation TestStep + +@synthesize providedFeature = _providedFeature; +@synthesize hasRun = _hasRun; +@synthesize dependencies = _dependencies; + +- (instancetype)init { + if ((self = [super init])) { + _dependencies = [[NSMutableSet alloc] init]; + } + return self; +} + +- (NSArray<NSString*>*)providedFeatures { + return @[ self.providedFeature ]; +} + +- (BOOL)synchronous { + return YES; +} + +- (NSArray<NSString*>*)requiredFeatures { + NSMutableArray<NSString*>* deps = [[NSMutableArray alloc] init]; + for (TestStep* dependency in self.dependencies) { + [deps addObject:dependency.providedFeature]; + } + return deps; +} + +- (void)checkDeps { + std::string mt = [NSThread isMainThread] ? "(mt) " : "(ot) "; + LOG(ERROR) << "Running " << mt << self.providedFeature.UTF8String; + EXPECT_FALSE(self.hasRun); + for (TestStep* dependency in self.dependencies) { + EXPECT_TRUE(dependency.hasRun); + } +} + +- (void)runFeature:(NSString*)feature withContext:(id<StepContext>)context { + EXPECT_NSEQ(feature, self.providedFeature); + [self checkDeps]; + self.hasRun = YES; +} + +@end + +@implementation AsyncTestStep + +- (BOOL)synchronous { + return NO; +} + +@end + +@implementation PhaseChangeTestStep +@synthesize newPhase = _newPhase; + +- (void)runFeature:(NSString*)feature withContext:(id<StepContext>)context { + EXPECT_NSEQ(feature, self.providedFeature); + [self checkDeps]; + context.phase = self.newPhase; + self.hasRun = YES; +} + +@end + +TEST(PhasedStepRunnerTest, TestUnexecutedSteps) { + TestStep* step1 = [[TestStep alloc] init]; + step1.providedFeature = @"feature_a"; + + TestStep* task2 = [[TestStep alloc] init]; + task2.providedFeature = @"feature_b"; + + TestStep* task3 = [[TestStep alloc] init]; + task3.providedFeature = @"feature_c"; + + NSUInteger testPhase = 1; + NSUInteger untestedPhase = 2; + + PhasedStepRunner* runner = [[PhasedStepRunner alloc] init]; + [runner addSteps:@[ step1, task2, task3 ]]; + EXPECT_NE(testPhase, runner.phase); + [runner requireFeature:@"feature_a" forPhase:testPhase]; + [runner requireFeature:@"feature_b" forPhase:untestedPhase]; + EXPECT_FALSE(step1.hasRun); + EXPECT_FALSE(task2.hasRun); + EXPECT_FALSE(task3.hasRun); + + runner.phase = testPhase; + + EXPECT_EQ(runner.phase, testPhase); + EXPECT_TRUE(step1.hasRun); + EXPECT_FALSE(task2.hasRun); + EXPECT_FALSE(task3.hasRun); +} + +// Simple dependency chain A-> B-> C +TEST(PhasedStepRunnerTest, TestSimpleDependencies) { + TestStep* step1 = [[TestStep alloc] init]; + step1.providedFeature = @"feature_a"; + + TestStep* task2 = [[TestStep alloc] init]; + task2.providedFeature = @"feature_b"; + + TestStep* task3 = [[TestStep alloc] init]; + task3.providedFeature = @"feature_c"; + + [step1.dependencies addObject:task2]; + [task2.dependencies addObject:task3]; + + NSUInteger testPhase = 1; + + PhasedStepRunner* runner = [[PhasedStepRunner alloc] init]; + [runner addSteps:@[ step1, task2, task3 ]]; + EXPECT_NE(testPhase, runner.phase); + [runner requireFeature:@"feature_a" forPhase:testPhase]; + EXPECT_FALSE(step1.hasRun); + EXPECT_FALSE(task2.hasRun); + EXPECT_FALSE(task3.hasRun); + + runner.phase = testPhase; + + EXPECT_EQ(runner.phase, testPhase); + EXPECT_TRUE(step1.hasRun); + EXPECT_TRUE(task2.hasRun); + EXPECT_TRUE(task3.hasRun); +} + +// Dependency graph: A->B; B->C; B->D, C->D. +TEST(PhasedStepRunnerTest, TestDependencyGraph) { + TestStep* step1 = [[TestStep alloc] init]; + step1.providedFeature = @"feature_a"; + + TestStep* task2 = [[TestStep alloc] init]; + task2.providedFeature = @"feature_b"; + + TestStep* task3 = [[TestStep alloc] init]; + task3.providedFeature = @"feature_c"; + + TestStep* task4 = [[TestStep alloc] init]; + task4.providedFeature = @"feature_d"; + + [step1.dependencies addObject:task2]; + [step1.dependencies addObject:task3]; + [task2.dependencies addObject:task4]; + [task3.dependencies addObject:task4]; + + NSUInteger testPhase = 1; + + PhasedStepRunner* runner = [[PhasedStepRunner alloc] init]; + [runner addSteps:@[ step1, task2, task3, task4 ]]; + EXPECT_NE(testPhase, runner.phase); + [runner requireFeature:@"feature_a" forPhase:testPhase]; + EXPECT_FALSE(step1.hasRun); + EXPECT_FALSE(task2.hasRun); + EXPECT_FALSE(task3.hasRun); + EXPECT_FALSE(task4.hasRun); + runner.phase = testPhase; + + EXPECT_EQ(runner.phase, testPhase); + EXPECT_TRUE(step1.hasRun); + EXPECT_TRUE(task2.hasRun); + EXPECT_TRUE(task3.hasRun); + EXPECT_TRUE(task4.hasRun); +} + +TEST(PhasedStepRunnerTest, TestOneAsyncAction) { + AsyncTestStep* step1 = [[AsyncTestStep alloc] init]; + step1.providedFeature = @"feature_a"; + NSUInteger testPhase = 1; + + PhasedStepRunner* runner = [[PhasedStepRunner alloc] init]; + [runner addStep:step1]; + EXPECT_NE(testPhase, runner.phase); + [runner requireFeature:@"feature_a" forPhase:testPhase]; + EXPECT_FALSE(step1.hasRun); + runner.phase = testPhase; + EXPECT_EQ(runner.phase, testPhase); + EXPECT_TRUE(step1.hasRun); +} + +TEST(PhasedStepRunnerTest, TestAsyncDependencies) { + AsyncTestStep* step1 = [[AsyncTestStep alloc] init]; + step1.providedFeature = @"feature_a"; + + TestStep* task2 = [[TestStep alloc] init]; + task2.providedFeature = @"feature_b"; + + AsyncTestStep* task3 = [[AsyncTestStep alloc] init]; + task3.providedFeature = @"feature_c"; + + [step1.dependencies addObject:task2]; + [task2.dependencies addObject:task3]; + + NSUInteger testPhase = 1; + + PhasedStepRunner* runner = [[PhasedStepRunner alloc] init]; + [runner addSteps:@[ step1, task2, task3 ]]; + EXPECT_NE(testPhase, runner.phase); + [runner requireFeature:@"feature_a" forPhase:testPhase]; + EXPECT_FALSE(step1.hasRun); + EXPECT_FALSE(task2.hasRun); + EXPECT_FALSE(task3.hasRun); + + runner.phase = testPhase; + + EXPECT_EQ(runner.phase, testPhase); + EXPECT_TRUE(step1.hasRun); + EXPECT_TRUE(task2.hasRun); + EXPECT_TRUE(task3.hasRun); +} + +TEST(PhasedStepRunnerTest, TestManyDependencies) { + NSMutableArray<TestStep*>* tasks = [[NSMutableArray alloc] init]; + AsyncTestStep* root_task = [[AsyncTestStep alloc] init]; + root_task.providedFeature = @"feature_a"; + [tasks addObject:root_task]; + + for (int i = 0; i < 5; i++) { + AsyncTestStep* task = [[AsyncTestStep alloc] init]; + task.providedFeature = [NSString stringWithFormat:@"feature_%d_", i]; + [root_task.dependencies addObject:task]; + for (int j = 0; j < 5; j++) { + TestStep* task2 = [[TestStep alloc] init]; + task2.providedFeature = + [NSString stringWithFormat:@"feature_%d_%d", i, j]; + [task.dependencies addObject:task2]; + for (int k = 0; k < 5; k++) { + AsyncTestStep* task3 = [[AsyncTestStep alloc] init]; + task3.providedFeature = + [NSString stringWithFormat:@"feature_%d_%d_%d", i, j, k]; + [task2.dependencies addObject:task3]; + [tasks addObject:task3]; + } + [tasks addObject:task2]; + } + [tasks addObject:task]; + } + + for (int i = 0; i < 20; i++) { + NSUInteger index1 = arc4random() % [tasks count]; + NSUInteger index2 = arc4random() % [tasks count]; + AsyncTestStep* atask = [[AsyncTestStep alloc] init]; + atask.providedFeature = [NSString stringWithFormat:@"feature_x_%d", i]; + [[tasks objectAtIndex:index1].dependencies addObject:atask]; + [[tasks objectAtIndex:index2].dependencies addObject:atask]; + [tasks addObject:atask]; + + index1 = arc4random() % [tasks count]; + index2 = arc4random() % [tasks count]; + TestStep* stask = [[TestStep alloc] init]; + stask.providedFeature = [NSString stringWithFormat:@"feature_y_%d", i]; + [[tasks objectAtIndex:index1].dependencies addObject:stask]; + [[tasks objectAtIndex:index2].dependencies addObject:stask]; + [tasks addObject:stask]; + } + + NSUInteger testPhase = 1; + + PhasedStepRunner* runner = [[PhasedStepRunner alloc] init]; + [runner addSteps:tasks]; + EXPECT_NE(testPhase, runner.phase); + [runner requireFeature:@"feature_a" forPhase:testPhase]; + for (TestStep* task in tasks) { + EXPECT_FALSE(task.hasRun); + } + runner.phase = testPhase; + + EXPECT_EQ(runner.phase, testPhase); + for (TestStep* task in tasks) { + EXPECT_TRUE(task.hasRun) + << "Expected task " << task.providedFeature.UTF8String + << " to have run"; + } +} + +// Simple phase change +TEST(PhasedStepRunnerTest, TestPhaseChangeInStep) { + TestStep* step1 = [[TestStep alloc] init]; + step1.providedFeature = @"feature_a"; + + PhaseChangeTestStep* task2 = [[PhaseChangeTestStep alloc] init]; + task2.providedFeature = @"feature_b"; + + TestStep* task3 = [[TestStep alloc] init]; + task3.providedFeature = @"feature_c"; + + TestStep* task4 = [[TestStep alloc] init]; + task4.providedFeature = @"feature_d"; + + [step1.dependencies addObject:task2]; + [task2.dependencies addObject:task3]; + + NSUInteger testPhase = 1; + NSUInteger otherPhase = 2; + task2.newPhase = otherPhase; + + PhasedStepRunner* runner = [[PhasedStepRunner alloc] init]; + [runner addSteps:@[ step1, task2, task3, task4 ]]; + EXPECT_NE(testPhase, runner.phase); + [runner requireFeature:@"feature_a" forPhase:testPhase]; + [runner requireFeature:@"feature_d" forPhase:otherPhase]; + + EXPECT_FALSE(step1.hasRun); + EXPECT_FALSE(task2.hasRun); + EXPECT_FALSE(task3.hasRun); + EXPECT_FALSE(task4.hasRun); + + runner.phase = testPhase; + + EXPECT_EQ(runner.phase, otherPhase); + EXPECT_FALSE(step1.hasRun); + EXPECT_TRUE(task2.hasRun); + EXPECT_TRUE(task3.hasRun); + EXPECT_TRUE(task4.hasRun); +} + +// Sync phase change with async tasks +TEST(PhasedStepRunnerTest, TestPhaseChangeInStepWithAsync) { + TestStep* step1 = [[AsyncTestStep alloc] init]; + step1.providedFeature = @"feature_a"; + + TestStep* task2 = [[AsyncTestStep alloc] init]; + task2.providedFeature = @"feature_b"; + + PhaseChangeTestStep* task3 = [[PhaseChangeTestStep alloc] init]; + task3.providedFeature = @"feature_c"; + + TestStep* task4 = [[AsyncTestStep alloc] init]; + task4.providedFeature = @"feature_d"; + + TestStep* task5 = [[AsyncTestStep alloc] init]; + task5.providedFeature = @"feature_e"; + + TestStep* task6 = [[TestStep alloc] init]; + task6.providedFeature = @"feature_f"; + + [task3.dependencies addObject:step1]; + [task3.dependencies addObject:task2]; + [task4.dependencies addObject:task3]; + [task5.dependencies addObject:task3]; + + NSUInteger testPhase = 1; + NSUInteger otherPhase = 2; + task3.newPhase = otherPhase; + + PhasedStepRunner* runner = [[PhasedStepRunner alloc] init]; + [runner addSteps:@[ step1, task2, task3, task4, task5, task6 ]]; + EXPECT_NE(testPhase, runner.phase); + [runner requireFeature:@"feature_d" forPhase:testPhase]; + [runner requireFeature:@"feature_e" forPhase:testPhase]; + [runner requireFeature:@"feature_f" forPhase:otherPhase]; + + EXPECT_FALSE(step1.hasRun); + EXPECT_FALSE(task2.hasRun); + EXPECT_FALSE(task3.hasRun); + EXPECT_FALSE(task4.hasRun); + EXPECT_FALSE(task5.hasRun); + EXPECT_FALSE(task6.hasRun); + + runner.phase = testPhase; + + EXPECT_EQ(runner.phase, otherPhase); + EXPECT_TRUE(step1.hasRun); + EXPECT_TRUE(task2.hasRun); + EXPECT_TRUE(task3.hasRun); + EXPECT_FALSE(task4.hasRun); + EXPECT_FALSE(task5.hasRun); + EXPECT_TRUE(task6.hasRun); +}
diff --git a/ios/clean/chrome/app/steps/provider_initializer.h b/ios/clean/chrome/app/steps/provider_initializer.h new file mode 100644 index 0000000..259f1355 --- /dev/null +++ b/ios/clean/chrome/app/steps/provider_initializer.h
@@ -0,0 +1,16 @@ +// 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 IOS_CLEAN_CHROME_APP_STEPS_PROVIDER_INITIALIZER_H_ +#define IOS_CLEAN_CHROME_APP_STEPS_PROVIDER_INITIALIZER_H_ + +#import <Foundation/Foundation.h> + +#import "ios/clean/chrome/app/steps/application_step.h" +#import "ios/clean/chrome/app/steps/simple_application_step.h" + +@interface ProviderInitializer : SimpleApplicationStep<ApplicationStep> +@end + +#endif // IOS_CLEAN_CHROME_APP_STEPS_PROVIDER_INITIALIZER_H_
diff --git a/ios/clean/chrome/app/steps/provider_initializer.mm b/ios/clean/chrome/app/steps/provider_initializer.mm new file mode 100644 index 0000000..724d280 --- /dev/null +++ b/ios/clean/chrome/app/steps/provider_initializer.mm
@@ -0,0 +1,27 @@ +// 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. + +#import "ios/clean/chrome/app/steps/provider_initializer.h" + +#import "ios/chrome/app/startup/provider_registration.h" +#include "ios/chrome/browser/application_context.h" +#import "ios/clean/chrome/app/steps/step_features.h" + +@protocol StepContext; +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif +@implementation ProviderInitializer +- (instancetype)init { + if ((self = [super init])) { + self.providedFeature = step_features::kProviders; + } + return self; +} + +- (void)runFeature:(NSString*)feature withContext:(id<StepContext>)context { + [ProviderRegistration registerProviders]; +} + +@end
diff --git a/ios/clean/chrome/app/steps/root_coordinator+application_step.h b/ios/clean/chrome/app/steps/root_coordinator+application_step.h deleted file mode 100644 index 4558831..0000000 --- a/ios/clean/chrome/app/steps/root_coordinator+application_step.h +++ /dev/null
@@ -1,21 +0,0 @@ -// 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 IOS_CLEAN_CHROME_APP_STEPS_ROOT_COORDINATOR_APPLICATION_STEP_H_ -#define IOS_CLEAN_CHROME_APP_STEPS_ROOT_COORDINATOR_APPLICATION_STEP_H_ - -#import "ios/clean/chrome/app/application_step.h" -#import "ios/clean/chrome/browser/ui/root/root_coordinator.h" - -// Category on RootCoordinator to allow it to act as an application -// step and control the root UI for the application when is starts. -// Creates the main window and makes it key, but doesn't make it visible yet. -// Pre: Application phase is APPLICATION_FOREGROUNDED and the main window is -// key. -// Post: Application phase is (still) APPLICATION_FOREGROUNDED, the main window -// is visible and has a root view controller set. -@interface RootCoordinator (ApplicationStep)<ApplicationStep> -@end - -#endif // IOS_CLEAN_CHROME_APP_STEPS_ROOT_COORDINATOR_APPLICATION_STEP_H_
diff --git a/ios/clean/chrome/app/steps/root_coordinator+application_step_unittest.mm b/ios/clean/chrome/app/steps/root_coordinator+application_step_unittest.mm deleted file mode 100644 index 00a84ca..0000000 --- a/ios/clean/chrome/app/steps/root_coordinator+application_step_unittest.mm +++ /dev/null
@@ -1,61 +0,0 @@ -// 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. - -#import "ios/clean/chrome/app/steps/root_coordinator+application_step.h" - -#include "base/macros.h" -#import "ios/clean/chrome/app/application_state.h" -#include "testing/gtest/include/gtest/gtest.h" -#include "testing/platform_test.h" -#import "third_party/ocmock/OCMock/OCMock.h" - -#if !defined(__has_feature) || !__has_feature(objc_arc) -#error "This file requires ARC support." -#endif - -namespace { - -class RootCoordinatorApplicationStepTest : public PlatformTest { - public: - RootCoordinatorApplicationStepTest() { - coordinator_ = [[RootCoordinator alloc] init]; - state_ = [[ApplicationState alloc] init]; - window_ = OCMClassMock([UIWindow class]); - } - - protected: - RootCoordinator* coordinator_; - ApplicationState* state_; - id window_; - - private: - DISALLOW_COPY_AND_ASSIGN(RootCoordinatorApplicationStepTest); -}; - -// Tests that the coordinator can run if the window is key and application is -// foregrounded. -TEST_F(RootCoordinatorApplicationStepTest, TestRunsInKeyWindow) { - state_.phase = APPLICATION_FOREGROUNDED; - state_.window = window_; - OCMStub([window_ isKeyWindow]).andReturn(YES); - EXPECT_TRUE([coordinator_ canRunInState:state_]); -} - -// Tests that the coordinator cannot run if the window is not key. -TEST_F(RootCoordinatorApplicationStepTest, TestCannotRunWithoutKeyWindow) { - state_.phase = APPLICATION_FOREGROUNDED; - state_.window = window_; - OCMStub([window_ isKeyWindow]).andReturn(NO); - EXPECT_FALSE([coordinator_ canRunInState:state_]); -} - -// Tests that the coordinator cannot run if application is not foregrounded. -TEST_F(RootCoordinatorApplicationStepTest, TestCannotRunIfNotForegrounded) { - state_.phase = APPLICATION_COLD; - state_.window = window_; - OCMStub([window_ isKeyWindow]).andReturn(YES); - EXPECT_FALSE([coordinator_ canRunInState:state_]); -} - -} // namespace
diff --git a/ios/clean/chrome/app/steps/root_coordinator_initializer.h b/ios/clean/chrome/app/steps/root_coordinator_initializer.h new file mode 100644 index 0000000..6a3c7bf6 --- /dev/null +++ b/ios/clean/chrome/app/steps/root_coordinator_initializer.h
@@ -0,0 +1,16 @@ +// 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 IOS_CLEAN_CHROME_APP_STEPS_ROOT_COORDINATOR_INITIALIZER_H_ +#define IOS_CLEAN_CHROME_APP_STEPS_ROOT_COORDINATOR_INITIALIZER_H_ + +#import <Foundation/Foundation.h> + +#import "ios/clean/chrome/app/steps/application_step.h" +#import "ios/clean/chrome/app/steps/simple_application_step.h" + +@interface RootCoordinatorInitializer : SimpleApplicationStep<ApplicationStep> +@end + +#endif // IOS_CLEAN_CHROME_APP_STEPS_ROOT_COORDINATOR_INITIALIZER_H_
diff --git a/ios/clean/chrome/app/steps/root_coordinator_initializer.mm b/ios/clean/chrome/app/steps/root_coordinator_initializer.mm new file mode 100644 index 0000000..d098a546 --- /dev/null +++ b/ios/clean/chrome/app/steps/root_coordinator_initializer.mm
@@ -0,0 +1,67 @@ +// 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. + +#import "ios/clean/chrome/app/steps/root_coordinator_initializer.h" + +#import "ios/chrome/app/startup/provider_registration.h" +#include "ios/chrome/browser/application_context.h" +#include "ios/chrome/browser/browser_state/chrome_browser_state.h" +#import "ios/chrome/browser/web_state_list/web_state_list.h" +#import "ios/chrome/browser/web_state_list/web_state_opener.h" +#import "ios/clean/chrome/app/steps/step_context.h" +#import "ios/clean/chrome/app/steps/step_features.h" +#import "ios/clean/chrome/browser/ui/root/root_coordinator.h" +#import "ios/shared/chrome/browser/ui/browser_list/browser_list.h" +#import "ios/shared/chrome/browser/ui/browser_list/browser_list_session_service.h" +#import "ios/shared/chrome/browser/ui/browser_list/browser_list_session_service_factory.h" +#import "ios/shared/chrome/browser/ui/coordinators/browser_coordinator+internal.h" +#include "ios/web/public/web_state/web_state.h" + +@protocol StepContext; + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +@implementation RootCoordinatorInitializer { + RootCoordinator* _rootCoordinator; +} +- (instancetype)init { + if ((self = [super init])) { + self.providedFeature = step_features::kRootCoordinatorStarted; + self.requiredFeatures = @[ step_features::kMainWindow ]; + } + return self; +} + +- (void)runFeature:(NSString*)feature withContext:(id<StepContext>)context { + _rootCoordinator = [[RootCoordinator alloc] init]; + [_rootCoordinator + setBrowser:BrowserList::FromBrowserState(context.browserState) + ->CreateNewBrowser()]; + + BrowserListSessionService* service = + BrowserListSessionServiceFactory::GetForBrowserState( + context.browserState); + + if (!service || !service->RestoreSession()) { + WebStateList& webStateList = _rootCoordinator.browser->web_state_list(); + web::WebState::CreateParams webStateCreateParams( + _rootCoordinator.browser->browser_state()); + webStateList.InsertWebState(0, web::WebState::Create(webStateCreateParams), + WebStateList::INSERT_NO_FLAGS, + WebStateOpener()); + webStateList.ActivateWebStateAt(0); + } + + [_rootCoordinator start]; + context.URLOpener = _rootCoordinator; + context.window.rootViewController = _rootCoordinator.viewController; + + // Size the main view controller to fit the whole screen. + [_rootCoordinator.viewController.view setFrame:context.window.bounds]; + context.window.hidden = NO; +} + +@end
diff --git a/ios/clean/chrome/app/steps/simple_application_step.h b/ios/clean/chrome/app/steps/simple_application_step.h new file mode 100644 index 0000000..6e37da2 --- /dev/null +++ b/ios/clean/chrome/app/steps/simple_application_step.h
@@ -0,0 +1,21 @@ +// 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 IOS_CLEAN_CHROME_APP_STEPS_SIMPLE_APPLICATION_STEP_H_ +#define IOS_CLEAN_CHROME_APP_STEPS_SIMPLE_APPLICATION_STEP_H_ + +#import <Foundation/Foundation.h> + +// Note that SimpleApplicationStep doesn't conform to ApplicationStep; it's +// an abstract base class that many steps will derive from, but they will +// need to (at least) implement -runFeature:withContext. +@interface SimpleApplicationStep : NSObject +@property(nonatomic, copy) NSString* providedFeature; +// ApplicationStep properties. +@property(nonatomic) NSArray<NSString*>* providedFeatures; +@property(nonatomic) NSArray<NSString*>* requiredFeatures; +@property(nonatomic) BOOL synchronous; +@end + +#endif // IOS_CLEAN_CHROME_APP_STEPS_SIMPLE_APPLICATION_STEP_H_
diff --git a/ios/clean/chrome/app/steps/simple_application_step.mm b/ios/clean/chrome/app/steps/simple_application_step.mm new file mode 100644 index 0000000..ca716a4 --- /dev/null +++ b/ios/clean/chrome/app/steps/simple_application_step.mm
@@ -0,0 +1,28 @@ +// 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. + +#import "ios/clean/chrome/app/steps/simple_application_step.h" + +@implementation SimpleApplicationStep + +@synthesize providedFeature = _providedFeature; +@synthesize providedFeatures = _providedFeatures; +@synthesize requiredFeatures = _requiredFeatures; +@synthesize synchronous = _synchronous; + +- (instancetype)init { + if ((self = [super init])) { + _synchronous = YES; + _providedFeatures = @[]; + _requiredFeatures = @[]; + } + return self; +} + +- (void)setProvidedFeature:(NSString*)providedFeature { + _providedFeature = providedFeature; + self.providedFeatures = @[ self.providedFeature ]; +} + +@end
diff --git a/ios/clean/chrome/app/steps/step_collections.h b/ios/clean/chrome/app/steps/step_collections.h new file mode 100644 index 0000000..0e355e82 --- /dev/null +++ b/ios/clean/chrome/app/steps/step_collections.h
@@ -0,0 +1,21 @@ +// 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 IOS_CLEAN_CHROME_APP_STEPS_STEP_COLLECTIONS_H_ +#define IOS_CLEAN_CHROME_APP_STEPS_STEP_COLLECTIONS_H_ + +#import <Foundation/Foundation.h> + +@protocol ApplicationStep; + +typedef NSArray<id<ApplicationStep>> ApplicationSteps; + +@interface StepCollections : NSObject + +// Returns an array containing an instance of every application step. ++ (ApplicationSteps*)allApplicationSteps; + +@end + +#endif // IOS_CLEAN_CHROME_APP_STEPS_STEP_COLLECTIONS_H_
diff --git a/ios/clean/chrome/app/steps/step_collections.mm b/ios/clean/chrome/app/steps/step_collections.mm new file mode 100644 index 0000000..3eddb73 --- /dev/null +++ b/ios/clean/chrome/app/steps/step_collections.mm
@@ -0,0 +1,26 @@ +// 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. + +#import "ios/clean/chrome/app/steps/step_collections.h" + +#import "ios/clean/chrome/app/steps/browser_state_setter.h" +#import "ios/clean/chrome/app/steps/bundle_and_defaults_configurator.h" +#import "ios/clean/chrome/app/steps/chrome_main.h" +#import "ios/clean/chrome/app/steps/foregrounder.h" +#import "ios/clean/chrome/app/steps/provider_initializer.h" +#import "ios/clean/chrome/app/steps/root_coordinator_initializer.h" +#import "ios/clean/chrome/app/steps/ui_initializer.h" + +@implementation StepCollections + ++ (ApplicationSteps*)allApplicationSteps { + return @[ + [[BrowserStateSetter alloc] init], + [[BundleAndDefaultsConfigurator alloc] init], [[ChromeMain alloc] init], + [[Foregrounder alloc] init], [[ProviderInitializer alloc] init], + [[UIInitializer alloc] init], [[RootCoordinatorInitializer alloc] init] + ]; +} + +@end
diff --git a/ios/clean/chrome/app/steps/step_context.h b/ios/clean/chrome/app/steps/step_context.h new file mode 100644 index 0000000..6b7cf70b --- /dev/null +++ b/ios/clean/chrome/app/steps/step_context.h
@@ -0,0 +1,45 @@ +// 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 IOS_CLEAN_CHROME_APP_STEPS_STEP_CONTEXT_H_ +#define IOS_CLEAN_CHROME_APP_STEPS_STEP_CONTEXT_H_ + +#import <UIKit/UIKit.h> + +namespace ios { +class ChromeBrowserState; +} + +@class BrowserCoordinator; + +@protocol URLOpening; + +// Objects that provide a context for ApplicationSteps to run in should conform +// to this protocol. +@protocol StepContext<NSObject> + +// The current phase that the object running the step is in. Generally +// speaking, the phase value determines which steps will be run. +// This property is writable, so steps can change the current phase. +@property(nonatomic) NSUInteger phase; + +@property(nonatomic, weak) id<URLOpening> URLOpener; + +@optional +// Properties with contextual information for the execution of application +// steps. + +// The main browserState object for the application +@property(nonatomic) ios::ChromeBrowserState* browserState; + +// A dictionary with keys and values as provided by the UIApplicationDelegate +// -application:(did|will)FinishLaunchingWithOptions: methods. +@property(nonatomic, readonly) NSDictionary* launchOptions; + +// The main UIWindow for the application. +@property(nonatomic, strong) UIWindow* window; + +@end + +#endif // IOS_CLEAN_CHROME_APP_STEPS_STEP_CONTEXT_H_
diff --git a/ios/clean/chrome/app/steps/step_features.h b/ios/clean/chrome/app/steps/step_features.h new file mode 100644 index 0000000..b61fd6e7a --- /dev/null +++ b/ios/clean/chrome/app/steps/step_features.h
@@ -0,0 +1,24 @@ +// 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 IOS_CLEAN_CHROME_APP_STEPS_STEP_FEATURES_H_ +#define IOS_CLEAN_CHROME_APP_STEPS_STEP_FEATURES_H_ + +#import <Foundation/Foundation.h> + +namespace step_features { + +extern NSString* const kProviders; +extern NSString* const kBundleAndDefaults; +extern NSString* const kChromeMainStarted; +extern NSString* const kChromeMainStopped; +extern NSString* const kForeground; +extern NSString* const kBrowserState; +extern NSString* const kBrowserStateInitialized; +extern NSString* const kMainWindow; +extern NSString* const kRootCoordinatorStarted; + +} // namespace step_features + +#endif // IOS_CLEAN_CHROME_APP_STEPS_STEP_FEATURES_H_
diff --git a/ios/clean/chrome/app/steps/step_features.mm b/ios/clean/chrome/app/steps/step_features.mm new file mode 100644 index 0000000..2f8dbc4 --- /dev/null +++ b/ios/clean/chrome/app/steps/step_features.mm
@@ -0,0 +1,19 @@ +// 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. + +#import "ios/clean/chrome/app/steps/step_features.h" + +namespace step_features { + +NSString* const kProviders = @"providers"; +NSString* const kBundleAndDefaults = @"bundleAndDefaults"; +NSString* const kChromeMainStarted = @"chromeMainStarted"; +NSString* const kChromeMainStopped = @"chromeMainStopped"; +NSString* const kForeground = @"foreground"; +NSString* const kBrowserState = @"browserState"; +NSString* const kBrowserStateInitialized = @"browserStateInit"; +NSString* const kMainWindow = @"mainWindow"; +NSString* const kRootCoordinatorStarted = @"rootCoordinatorStarted"; + +} // namespace step_features
diff --git a/ios/clean/chrome/app/steps/ui_initializer.h b/ios/clean/chrome/app/steps/ui_initializer.h new file mode 100644 index 0000000..fa68f01 --- /dev/null +++ b/ios/clean/chrome/app/steps/ui_initializer.h
@@ -0,0 +1,16 @@ +// 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 IOS_CLEAN_CHROME_APP_STEPS_UI_INITIALIZER_H_ +#define IOS_CLEAN_CHROME_APP_STEPS_UI_INITIALIZER_H_ + +#import <Foundation/Foundation.h> + +#import "ios/clean/chrome/app/steps/application_step.h" +#import "ios/clean/chrome/app/steps/simple_application_step.h" + +@interface UIInitializer : SimpleApplicationStep<ApplicationStep> +@end + +#endif // IOS_CLEAN_CHROME_APP_STEPS_UI_INITIALIZER_H_
diff --git a/ios/clean/chrome/app/steps/ui_initializer.mm b/ios/clean/chrome/app/steps/ui_initializer.mm new file mode 100644 index 0000000..0affe06d --- /dev/null +++ b/ios/clean/chrome/app/steps/ui_initializer.mm
@@ -0,0 +1,33 @@ +// 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. + +#import "ios/clean/chrome/app/steps/ui_initializer.h" + +#import "base/logging.h" +#import "ios/clean/chrome/app/steps/step_context.h" +#import "ios/clean/chrome/app/steps/step_features.h" + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +@implementation UIInitializer + +- (instancetype)init { + if ((self = [super init])) { + self.providedFeature = step_features::kMainWindow; + self.requiredFeatures = @[ step_features::kForeground ]; + } + return self; +} + +- (void)runFeature:(NSString*)feature withContext:(id<StepContext>)context { + DCHECK([context respondsToSelector:@selector(setWindow:)]); + DCHECK([context respondsToSelector:@selector(window)]); + context.window = + [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + [context.window makeKeyWindow]; +} + +@end
diff --git a/ios/clean/chrome/browser/ui/ntp/ntp_home_header_coordinator.mm b/ios/clean/chrome/browser/ui/ntp/ntp_home_header_coordinator.mm index 7b92bc3..91a7b2c 100644 --- a/ios/clean/chrome/browser/ui/ntp/ntp_home_header_coordinator.mm +++ b/ios/clean/chrome/browser/ui/ntp/ntp_home_header_coordinator.mm
@@ -11,7 +11,7 @@ #error "This file requires ARC support." #endif -@interface NTPHomeHeaderCoordinator () +@interface NTPHomeHeaderCoordinator ()<NTPHomeHeaderMediatorAlerter> @property(nonatomic, strong) NTPHomeHeaderMediator* mediator; @property(nonatomic, strong) NTPHomeHeaderViewController* viewController; @@ -54,6 +54,7 @@ self.mediator.commandHandler = self.commandHandler; self.mediator.collectionSynchronizer = self.collectionSynchronizer; self.mediator.headerViewController = self.viewController; + self.mediator.alerter = self; [super start]; } @@ -64,4 +65,21 @@ self.viewController = nil; } +#pragma mark - NTPHomeHeaderMediatorAlerter + +- (void)showAlert:(NSString*)title { + UIAlertController* alertController = + [UIAlertController alertControllerWithTitle:title + message:nil + preferredStyle:UIAlertControllerStyleAlert]; + UIAlertAction* action = + [UIAlertAction actionWithTitle:@"Done" + style:UIAlertActionStyleCancel + handler:nil]; + [alertController addAction:action]; + [self.viewController presentViewController:alertController + animated:YES + completion:nil]; +} + @end
diff --git a/ios/clean/chrome/browser/ui/ntp/ntp_home_header_mediator.h b/ios/clean/chrome/browser/ui/ntp/ntp_home_header_mediator.h index 9403b4ba..1788310 100644 --- a/ios/clean/chrome/browser/ui/ntp/ntp_home_header_mediator.h +++ b/ios/clean/chrome/browser/ui/ntp/ntp_home_header_mediator.h
@@ -17,6 +17,11 @@ @protocol ContentSuggestionsHeaderViewControllerDelegate; @class NTPHomeHeaderViewController; +// TODO(crbug.com/740793): Remove this protocol once no item is using it. +@protocol NTPHomeHeaderMediatorAlerter +- (void)showAlert:(NSString*)title; +@end + @interface NTPHomeHeaderMediator : NSObject<ContentSuggestionsHeaderControlling, ContentSuggestionsHeaderProvider, @@ -30,6 +35,7 @@ commandHandler; @property(nonatomic, weak) id<ContentSuggestionsCollectionSynchronizing> collectionSynchronizer; +@property(nonatomic, weak) id<NTPHomeHeaderMediatorAlerter> alerter; @property(nonatomic, weak) NTPHomeHeaderViewController* headerViewController;
diff --git a/ios/clean/chrome/browser/ui/ntp/ntp_home_header_mediator.mm b/ios/clean/chrome/browser/ui/ntp/ntp_home_header_mediator.mm index e829da4a..148692a 100644 --- a/ios/clean/chrome/browser/ui/ntp/ntp_home_header_mediator.mm +++ b/ios/clean/chrome/browser/ui/ntp/ntp_home_header_mediator.mm
@@ -31,6 +31,7 @@ @synthesize commandHandler = _commandHandler; @synthesize collectionSynchronizer = _collectionSynchronizer; @synthesize headerViewController = _headerViewController; +@synthesize alerter = _alerter; @synthesize isShowing = _isShowing; @synthesize omniboxFocused = _omniboxFocused; @@ -55,7 +56,7 @@ - (void)unfocusOmnibox { if (self.omniboxFocused) { // TODO(crbug.com/740793): Remove alert once VoiceSearch is implemented. - [self showAlert:@"Cancel omnibox edit"]; + [self.alerter showAlert:@"Cancel omnibox edit"]; } else { [self locationBarResignsFirstResponder]; } @@ -159,7 +160,7 @@ if (!IsIPadIdiom()) { [self.headerViewController collectionWillShiftDown]; // TODO(crbug.com/740793): Remove alert once VoiceSearch is implemented. - [self showAlert:@"Omnibox unfocused"]; + [self.alerter showAlert:@"Omnibox unfocused"]; } [self.collectionSynchronizer shiftTilesDown]; @@ -171,28 +172,11 @@ void (^completionBlock)() = ^{ if (!IsIPadIdiom()) { // TODO(crbug.com/740793): Remove alert once VoiceSearch is implemented. - [self showAlert:@"Omnibox animation completed"]; + [self.alerter showAlert:@"Omnibox animation completed"]; [self.headerViewController collectionDidShiftUp]; } }; [self.collectionSynchronizer shiftTilesUpWithCompletionBlock:completionBlock]; } -// TODO(crbug.com/740793): Remove this method once no item is using it. -- (void)showAlert:(NSString*)title { - UIAlertController* alertController = - [UIAlertController alertControllerWithTitle:title - message:nil - preferredStyle:UIAlertControllerStyleAlert]; - UIAlertAction* action = - [UIAlertAction actionWithTitle:@"Done" - style:UIAlertActionStyleCancel - handler:nil]; - [alertController addAction:action]; - [self.headerViewController.parentViewController - presentViewController:alertController - animated:YES - completion:nil]; -} - @end
diff --git a/third_party/WebKit/LayoutTests/TestExpectations b/third_party/WebKit/LayoutTests/TestExpectations index 38a269d..44fd6622 100644 --- a/third_party/WebKit/LayoutTests/TestExpectations +++ b/third_party/WebKit/LayoutTests/TestExpectations
@@ -1740,7 +1740,7 @@ crbug.com/626703 virtual/threaded/transitions/transition-end-event-multiple-03.html [ Pass Failure ] # ====== New tests from wpt-importer added here ====== -crbug.com/626703 external/wpt/beacon/headers/header-content-type.html [ Pass Failure Timeout ] +crbug.com/626703 external/wpt/beacon/headers/header-content-type.html [ Pass Timeout ] crbug.com/626703 [ Win ] external/wpt/css/css-ui-3/outline-004.html [ Failure ] crbug.com/626703 [ Win ] external/wpt/css/css-ui-3/text-overflow-001.html [ Pass Failure ] crbug.com/626703 [ Win ] external/wpt/css/css-ui-3/text-overflow-002.html [ Pass Failure ]
diff --git a/third_party/WebKit/LayoutTests/external/wpt/beacon/headers/header-content-type-expected.txt b/third_party/WebKit/LayoutTests/external/wpt/beacon/headers/header-content-type-expected.txt index 3282c62..9fe202e 100644 --- a/third_party/WebKit/LayoutTests/external/wpt/beacon/headers/header-content-type-expected.txt +++ b/third_party/WebKit/LayoutTests/external/wpt/beacon/headers/header-content-type-expected.txt
@@ -1,6 +1,9 @@ This is a testharness.js-based test. PASS Test content-type header for a body string -FAIL Test content-type header for a body ArrayBuffer assert_equals: Correct Content-Type header result expected "" but got "application/octet-stream" +FAIL Test content-type header for a body ArrayBufferView assert_equals: Correct Content-Type header result expected "" but got "application/octet-stream" +FAIL Test content-type header for a body ArrayBuffer assert_equals: Correct Content-Type header result expected "" but got "text/plain;charset=UTF-8" +PASS Test content-type header for a body Blob PASS Test content-type header for a body FormData +FAIL Test content-type header for a body URLSearchParams assert_equals: Correct Content-Type header result expected "application/x-www-form-urlencoded;charset=UTF-8" but got "text/plain;charset=UTF-8" Harness: the test ran to completion.
diff --git a/third_party/WebKit/Source/core/loader/DocumentThreadableLoader.cpp b/third_party/WebKit/Source/core/loader/DocumentThreadableLoader.cpp index db0c4da..2482d91 100644 --- a/third_party/WebKit/Source/core/loader/DocumentThreadableLoader.cpp +++ b/third_party/WebKit/Source/core/loader/DocumentThreadableLoader.cpp
@@ -51,7 +51,6 @@ #include "core/probe/CoreProbes.h" #include "platform/SharedBuffer.h" #include "platform/exported/WrappedResourceRequest.h" -#include "platform/exported/WrappedResourceResponse.h" #include "platform/loader/fetch/FetchParameters.h" #include "platform/loader/fetch/FetchUtils.h" #include "platform/loader/fetch/Resource.h" @@ -648,10 +647,11 @@ if (cors_flag_) { // The redirect response must pass the access control check if the CORS // flag is set. - WebCORS::AccessStatus cors_status = - WebCORS::CheckAccess(WrappedResourceResponse(redirect_response), - new_request.GetFetchCredentialsMode(), - WebSecurityOrigin(GetSecurityOrigin())); + WebCORS::AccessStatus cors_status = WebCORS::CheckAccess( + redirect_response.Url(), redirect_response.HttpStatusCode(), + redirect_response.HttpHeaderFields(), + new_request.GetFetchCredentialsMode(), + WebSecurityOrigin(GetSecurityOrigin())); if (cors_status != WebCORS::AccessStatus::kAccessAllowed) { StringBuilder builder; builder.Append("Redirect from '"); @@ -660,7 +660,8 @@ builder.Append(new_url.GetString()); builder.Append("' has been blocked by CORS policy: "); builder.Append(WebCORS::AccessControlErrorString( - cors_status, WrappedResourceResponse(redirect_response), + cors_status, redirect_response.HttpStatusCode(), + redirect_response.HttpHeaderFields(), WebSecurityOrigin(GetSecurityOrigin()), request_context_)); DispatchDidFailAccessControlCheck( ResourceError::CancelledDueToAccessCheckError( @@ -780,41 +781,41 @@ const ResourceResponse& response) { String access_control_error_description; - WebCORS::AccessStatus cors_status = - WebCORS::CheckAccess(WrappedResourceResponse(response), - actual_request_.GetFetchCredentialsMode(), - WebSecurityOrigin(GetSecurityOrigin())); + WebCORS::AccessStatus cors_status = WebCORS::CheckAccess( + response.Url(), response.HttpStatusCode(), response.HttpHeaderFields(), + actual_request_.GetFetchCredentialsMode(), + WebSecurityOrigin(GetSecurityOrigin())); if (cors_status != WebCORS::AccessStatus::kAccessAllowed) { StringBuilder builder; builder.Append( "Response to preflight request doesn't pass access " "control check: "); builder.Append(WebCORS::AccessControlErrorString( - cors_status, WrappedResourceResponse(response), + cors_status, response.HttpStatusCode(), response.HttpHeaderFields(), WebSecurityOrigin(GetSecurityOrigin()), request_context_)); HandlePreflightFailure(response.Url(), builder.ToString()); return; } WebCORS::PreflightStatus preflight_status = - WebCORS::CheckPreflight(WrappedResourceResponse(response)); + WebCORS::CheckPreflight(response.HttpStatusCode()); if (preflight_status != WebCORS::PreflightStatus::kPreflightSuccess) { - HandlePreflightFailure( - response.Url(), - WebCORS::PreflightErrorString(preflight_status, - WrappedResourceResponse(response))); + HandlePreflightFailure(response.Url(), + WebCORS::PreflightErrorString( + preflight_status, response.HttpHeaderFields(), + response.HttpStatusCode())); return; } if (actual_request_.IsExternalRequest()) { WebCORS::PreflightStatus external_preflight_status = - WebCORS::CheckExternalPreflight(WrappedResourceResponse(response)); + WebCORS::CheckExternalPreflight(response.HttpHeaderFields()); if (external_preflight_status != WebCORS::PreflightStatus::kPreflightSuccess) { - HandlePreflightFailure( - response.Url(), - WebCORS::PreflightErrorString(external_preflight_status, - WrappedResourceResponse(response))); + HandlePreflightFailure(response.Url(), WebCORS::PreflightErrorString( + external_preflight_status, + response.HttpHeaderFields(), + response.HttpStatusCode())); return; } } @@ -893,9 +894,9 @@ network::mojom::FetchResponseType::kOpaque) { StringBuilder builder; builder.Append(WebCORS::AccessControlErrorString( - WebCORS::AccessStatus::kInvalidResponse, - WrappedResourceResponse(response), - WebSecurityOrigin(GetSecurityOrigin()), request_context_)); + WebCORS::AccessStatus::kInvalidResponse, response.HttpStatusCode(), + response.HttpHeaderFields(), WebSecurityOrigin(GetSecurityOrigin()), + request_context_)); DispatchDidFailAccessControlCheck( ResourceError::CancelledDueToAccessCheckError( response.Url(), ResourceRequestBlockedReason::kOther, @@ -924,15 +925,16 @@ if (IsCORSEnabledRequestMode(request_mode) && cors_flag_) { WebCORS::AccessStatus cors_status = WebCORS::CheckAccess( - WrappedResourceResponse(response), credentials_mode, - WebSecurityOrigin(GetSecurityOrigin())); + response.Url(), response.HttpStatusCode(), response.HttpHeaderFields(), + credentials_mode, WebSecurityOrigin(GetSecurityOrigin())); if (cors_status != WebCORS::AccessStatus::kAccessAllowed) { ReportResponseReceived(identifier, response); DispatchDidFailAccessControlCheck( ResourceError::CancelledDueToAccessCheckError( response.Url(), ResourceRequestBlockedReason::kOther, WebCORS::AccessControlErrorString( - cors_status, WrappedResourceResponse(response), + cors_status, response.HttpStatusCode(), + response.HttpHeaderFields(), WebSecurityOrigin(GetSecurityOrigin()), request_context_))); return; }
diff --git a/third_party/WebKit/Source/platform/exported/WebCORS.cpp b/third_party/WebKit/Source/platform/exported/WebCORS.cpp index d7c0cf1a..d2213487 100644 --- a/third_party/WebKit/Source/platform/exported/WebCORS.cpp +++ b/third_party/WebKit/Source/platform/exported/WebCORS.cpp
@@ -40,6 +40,7 @@ #include "public/platform/WebSecurityOrigin.h" #include "public/platform/WebURLResponse.h" #include "url/gurl.h" +#include "url/url_util.h" namespace blink { @@ -171,22 +172,23 @@ } // namespace AccessStatus CheckAccess( - const WebURLResponse& response, + const WebURL response_url, + const int response_status_code, + const HTTPHeaderMap& response_header, const WebURLRequest::FetchCredentialsMode credentials_mode, const WebSecurityOrigin& security_origin) { - int status_code = response.HttpStatusCode(); - if (!status_code) + if (!response_status_code) return AccessStatus::kInvalidResponse; const WebString& allow_origin_header_value = - response.HttpHeaderField(HTTPNames::Access_Control_Allow_Origin); + response_header.Get(HTTPNames::Access_Control_Allow_Origin); // Check Suborigins, unless the Access-Control-Allow-Origin is '*', which // implies that all Suborigins are okay as well. if (!security_origin.Suborigin().IsEmpty() && allow_origin_header_value != WebString(g_star_atom)) { const WebString& allow_suborigin_header_value = - response.HttpHeaderField(HTTPNames::Access_Control_Allow_Suborigin); + response_header.Get(HTTPNames::Access_Control_Allow_Suborigin); if (allow_suborigin_header_value != WebString(g_star_atom) && allow_suborigin_header_value != security_origin.Suborigin()) { return AccessStatus::kSubOriginMismatch; @@ -200,7 +202,7 @@ return AccessStatus::kAccessAllowed; // TODO(hintzed): Is the following a sound substitute for // blink::ResourceResponse::IsHTTP()? - if (GURL(response.Url().GetString().Utf16()).SchemeIsHTTPOrHTTPS()) { + if (GURL(response_url.GetString().Utf16()).SchemeIsHTTPOrHTTPS()) { return AccessStatus::kWildcardOriginNotAllowed; } } else if (allow_origin_header_value != security_origin.ToString()) { @@ -219,7 +221,7 @@ if (FetchUtils::ShouldTreatCredentialsModeAsInclude(credentials_mode)) { const WebString& allow_credentials_header_value = - response.HttpHeaderField(HTTPNames::Access_Control_Allow_Credentials); + response_header.Get(HTTPNames::Access_Control_Allow_Credentials); if (allow_credentials_header_value != "true") { return AccessStatus::kDisallowCredentialsNotSetToTrue; } @@ -229,11 +231,13 @@ bool HandleRedirect(WebSecurityOrigin& current_security_origin, WebURLRequest& new_request, - const WebURLResponse& redirect_response, + const WebURL redirect_response_url, + const int redirect_response_status_code, + const HTTPHeaderMap& redirect_response_header, WebURLRequest::FetchCredentialsMode credentials_mode, ResourceLoaderOptions& options, WebString& error_message) { - const KURL& last_url = redirect_response.Url(); + const KURL& last_url = redirect_response_url; const KURL& new_url = new_request.Url(); WebSecurityOrigin& new_security_origin = current_security_origin; @@ -252,15 +256,16 @@ return false; } - AccessStatus cors_status = CheckAccess(redirect_response, credentials_mode, - current_security_origin); + AccessStatus cors_status = CheckAccess( + redirect_response_url, redirect_response_status_code, + redirect_response_header, credentials_mode, current_security_origin); if (cors_status != AccessStatus::kAccessAllowed) { StringBuilder builder; builder.Append("Redirect from '"); builder.Append(last_url.GetString()); builder.Append("' has been blocked by CORS policy: "); builder.Append(AccessControlErrorString( - cors_status, redirect_response, + cors_status, redirect_response_status_code, redirect_response_header, WebSecurityOrigin(current_security_origin.Get()), new_request.GetRequestContext())); error_message = builder.ToString(); @@ -314,25 +319,23 @@ return RedirectStatus::kRedirectSuccess; } -PreflightStatus CheckPreflight(const WebURLResponse& response) { +PreflightStatus CheckPreflight(const int preflight_response_status_code) { // CORS preflight with 3XX is considered network error in // Fetch API Spec: https://fetch.spec.whatwg.org/#cors-preflight-fetch // CORS Spec: http://www.w3.org/TR/cors/#cross-origin-request-with-preflight-0 // https://crbug.com/452394 - int status_code = response.HttpStatusCode(); - if (!FetchUtils::IsOkStatus(status_code)) + if (!FetchUtils::IsOkStatus(preflight_response_status_code)) return PreflightStatus::kPreflightInvalidStatus; return PreflightStatus::kPreflightSuccess; } -PreflightStatus CheckExternalPreflight(const WebURLResponse& response) { +PreflightStatus CheckExternalPreflight(const HTTPHeaderMap& response_header) { WebString result = - response.HttpHeaderField(HTTPNames::Access_Control_Allow_External); + response_header.Get(HTTPNames::Access_Control_Allow_External); if (result.IsNull()) return PreflightStatus::kPreflightMissingAllowExternal; - // TODO(hintzed) replace with EqualIgnoringASCIICase() - if (!DeprecatedEqualIgnoringCase(result, "true")) + if (!EqualIgnoringASCIICase(result, "true")) return PreflightStatus::kPreflightInvalidAllowExternal; return PreflightStatus::kPreflightSuccess; } @@ -372,7 +375,8 @@ WebString AccessControlErrorString( const AccessStatus status, - const WebURLResponse& response, + const int response_status_code, + const HTTPHeaderMap& response_header, const WebSecurityOrigin& origin, const WebURLRequest::RequestContext context) { String origin_denied = @@ -395,7 +399,7 @@ return String::Format( "The 'Access-Control-Allow-Suborigin' header has a value '%s' that " "is not equal to the supplied suborigin. %s", - response.HttpHeaderField(HTTPNames::Access_Control_Allow_Suborigin) + response_header.Get(HTTPNames::Access_Control_Allow_Suborigin) .Utf8() .data(), origin_denied.Utf8().data()); @@ -413,9 +417,9 @@ } case AccessStatus::kMissingAllowOriginHeader: { String status_code_msg = - IsInterestingStatusCode(response.HttpStatusCode()) + IsInterestingStatusCode(response_status_code) ? String::Format(" The response had HTTP status code %d.", - response.HttpStatusCode()) + response_status_code) : ""; return String::Format( @@ -431,7 +435,7 @@ return String::Format( "The 'Access-Control-Allow-Origin' header contains multiple values " "'%s', but only one is allowed. %s%s", - response.HttpHeaderField(HTTPNames::Access_Control_Allow_Origin) + response_header.Get(HTTPNames::Access_Control_Allow_Origin) .Utf8() .data(), origin_denied.Utf8().data(), no_cors_information.Utf8().data()); @@ -440,7 +444,7 @@ return String::Format( "The 'Access-Control-Allow-Origin' header contains the invalid value " "'%s'. %s%s", - response.HttpHeaderField(HTTPNames::Access_Control_Allow_Origin) + response_header.Get(HTTPNames::Access_Control_Allow_Origin) .Utf8() .data(), origin_denied.Utf8().data(), no_cors_information.Utf8().data()); @@ -449,7 +453,7 @@ return String::Format( "The 'Access-Control-Allow-Origin' header has a value '%s' that is " "not equal to the supplied origin. %s%s", - response.HttpHeaderField(HTTPNames::Access_Control_Allow_Origin) + response_header.Get(HTTPNames::Access_Control_Allow_Origin) .Utf8() .data(), origin_denied.Utf8().data(), no_cors_information.Utf8().data()); @@ -459,7 +463,7 @@ "The value of the 'Access-Control-Allow-Credentials' header in " "the response is '%s' which must be 'true' when the request's " "credentials mode is 'include'. %s%s", - response.HttpHeaderField(HTTPNames::Access_Control_Allow_Credentials) + response_header.Get(HTTPNames::Access_Control_Allow_Credentials) .Utf8() .data(), origin_denied.Utf8().data(), @@ -475,13 +479,14 @@ } } -WebString PreflightErrorString(PreflightStatus status, - const WebURLResponse& response) { +WebString PreflightErrorString(const PreflightStatus status, + const HTTPHeaderMap& response_header, + const int preflight_response_status_code) { switch (status) { case PreflightStatus::kPreflightInvalidStatus: { return String::Format( "Response for preflight has invalid HTTP status code %d", - response.HttpStatusCode()); + preflight_response_status_code); } case PreflightStatus::kPreflightMissingAllowExternal: { return String::Format( @@ -496,7 +501,7 @@ "response for this external request had a value of '%s', not 'true' " "(This is an experimental header which is defined in " "'https://wicg.github.io/cors-rfc1918/').", - response.HttpHeaderField("access-control-allow-external") + response_header.Get(HTTPNames::Access_Control_Allow_External) .Utf8() .data()); }
diff --git a/third_party/WebKit/Source/platform/loader/fetch/ResourceLoader.cpp b/third_party/WebKit/Source/platform/loader/fetch/ResourceLoader.cpp index 3448e240..c5e7658a 100644 --- a/third_party/WebKit/Source/platform/loader/fetch/ResourceLoader.cpp +++ b/third_party/WebKit/Source/platform/loader/fetch/ResourceLoader.cpp
@@ -280,11 +280,11 @@ WebSecurityOrigin source_web_origin(source_origin.Get()); WrappedResourceRequest new_request_wrapper(new_request); WebString cors_error_msg; - if (!WebCORS::HandleRedirect(source_web_origin, new_request_wrapper, - WrappedResourceResponse(redirect_response), - fetch_credentials_mode, - resource_->MutableOptions(), - cors_error_msg)) { + if (!WebCORS::HandleRedirect( + source_web_origin, new_request_wrapper, redirect_response.Url(), + redirect_response.HttpStatusCode(), + redirect_response.HttpHeaderFields(), fetch_credentials_mode, + resource_->MutableOptions(), cors_error_msg)) { resource_->SetCORSStatus(CORSStatus::kFailed); if (!unused_preload) { @@ -432,7 +432,9 @@ : response; WebCORS::AccessStatus cors_status = - WebCORS::CheckAccess(WrappedResourceResponse(response_for_access_control), + WebCORS::CheckAccess(response_for_access_control.Url(), + response_for_access_control.HttpStatusCode(), + response_for_access_control.HttpHeaderFields(), initial_request.GetFetchCredentialsMode(), WebSecurityOrigin(source_origin)); @@ -449,7 +451,8 @@ error_msg.Append(source_origin->ToString()); error_msg.Append("' has been blocked by CORS policy: "); error_msg.Append(WebCORS::AccessControlErrorString( - cors_status, WrappedResourceResponse(response_for_access_control), + cors_status, response_for_access_control.HttpStatusCode(), + response_for_access_control.HttpHeaderFields(), WebSecurityOrigin(source_origin), initial_request.GetRequestContext())); return CORSStatus::kFailed;
diff --git a/third_party/WebKit/public/platform/WebCORS.h b/third_party/WebKit/public/platform/WebCORS.h index 5c528329..20ba7ac 100644 --- a/third_party/WebKit/public/platform/WebCORS.h +++ b/third_party/WebKit/public/platform/WebCORS.h
@@ -28,6 +28,7 @@ #define WebCORS_h #include "platform/loader/fetch/ResourceLoaderOptions.h" +#include "platform/network/HTTPHeaderMap.h" #include "platform/wtf/HashSet.h" #include "platform/wtf/text/StringHash.h" #include "public/platform/WebString.h" @@ -86,11 +87,14 @@ kRedirectContainsCredentials, }; -// Perform a CORS access check on the response. Returns |kAccessAllowed| if -// access is allowed. Use |AccessControlErrorString()| to construct a -// user-friendly error message for any of the other (error) conditions. +// Perform a CORS access check on the response parameters. Returns +// |kAccessAllowed| if access is allowed. Use |AccessControlErrorString()| to +// construct a user-friendly error message for any of the other (error) +// conditions. BLINK_PLATFORM_EXPORT AccessStatus -CheckAccess(const WebURLResponse&, +CheckAccess(const WebURL, + const int response_status_code, + const HTTPHeaderMap&, WebURLRequest::FetchCredentialsMode, const WebSecurityOrigin&); @@ -108,13 +112,14 @@ // Returns |kPreflightSuccess| if preflight response was successful. // Use |PreflightErrorString()| to construct a user-friendly error message // for any of the other (error) conditions. -BLINK_PLATFORM_EXPORT PreflightStatus CheckPreflight(const WebURLResponse&); +BLINK_PLATFORM_EXPORT PreflightStatus +CheckPreflight(const int preflight_response_status_code); // Error checking for the currently experimental // "Access-Control-Allow-External:" header. Shares error conditions with // standard preflight checking. BLINK_PLATFORM_EXPORT PreflightStatus -CheckExternalPreflight(const WebURLResponse&); +CheckExternalPreflight(const HTTPHeaderMap&); BLINK_PLATFORM_EXPORT WebURLRequest CreateAccessControlPreflightRequest(const WebURLRequest&); @@ -122,22 +127,28 @@ // TODO(tyoshino): Using platform/loader/fetch/ResourceLoaderOptions violates // the DEPS rule. This will be fixed soon by making HandleRedirect() not // depending on ResourceLoaderOptions. -BLINK_PLATFORM_EXPORT bool HandleRedirect(WebSecurityOrigin&, - WebURLRequest&, - const WebURLResponse&, - WebURLRequest::FetchCredentialsMode, - ResourceLoaderOptions&, - WebString&); +BLINK_PLATFORM_EXPORT bool HandleRedirect( + WebSecurityOrigin&, + WebURLRequest&, + const WebURL, + const int redirect_response_status_code, + const HTTPHeaderMap&, + WebURLRequest::FetchCredentialsMode, + ResourceLoaderOptions&, + WebString&); // Stringify errors from CORS access checks, preflight or redirect checks. BLINK_PLATFORM_EXPORT WebString AccessControlErrorString(const AccessStatus, - const WebURLResponse&, + const int response_status_code, + const HTTPHeaderMap&, const WebSecurityOrigin&, const WebURLRequest::RequestContext); -BLINK_PLATFORM_EXPORT WebString PreflightErrorString(const PreflightStatus, - const WebURLResponse&); +BLINK_PLATFORM_EXPORT WebString +PreflightErrorString(const PreflightStatus, + const HTTPHeaderMap&, + const int preflight_response_status_code); BLINK_PLATFORM_EXPORT WebString RedirectErrorString(const RedirectStatus, const WebURL&);
diff --git a/ui/login/account_picker/md_screen_account_picker.js b/ui/login/account_picker/md_screen_account_picker.js index a65e465..d392cc3 100644 --- a/ui/login/account_picker/md_screen_account_picker.js +++ b/ui/login/account_picker/md_screen_account_picker.js
@@ -427,10 +427,15 @@ // pods. Main goal is to clear any credentials the user might have input. if (state === LOCK_SCREEN_APPS_STATE.FOREGROUND) { $('pod-row').clearFocusedPod(); - } else if (wasForeground) { - // If the app window was moved to background, ensure the active pod is - // focused. - $('pod-row').refocusCurrentPod(); + $('pod-row').disabled = true; + } else { + $('pod-row').disabled = false; + if (wasForeground) { + // If the app window was moved to background, ensure the active pod is + // focused. + $('pod-row').maybePreselectPod(); + $('pod-row').refocusCurrentPod(); + } } },
diff --git a/ui/login/account_picker/md_user_pod_row.js b/ui/login/account_picker/md_user_pod_row.js index f0f0ab9..dae3ddd1c 100644 --- a/ui/login/account_picker/md_user_pod_row.js +++ b/ui/login/account_picker/md_user_pod_row.js
@@ -895,6 +895,10 @@ element.disabled = value }); + this.tabIndex = value ? -1 : UserPodTabOrder.POD_INPUT; + this.actionBoxAreaElement.tabIndex = + value ? -1 : UserPodTabOrder.POD_INPUT; + // Special handling for submit button - the submit button should be // enabled only if there is the password value set. var submitButton = this.submitButton;