| // Copyright 2014 The Chromium Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "chrome/browser/devtools/devtools_window_testing.h" | |
| #include <algorithm> | |
| #include "base/functional/bind.h" | |
| #include "base/functional/callback_helpers.h" | |
| #include "base/no_destructor.h" | |
| #include "base/strings/utf_string_conversions.h" | |
| #include "base/values.h" | |
| #include "chrome/browser/devtools/chrome_devtools_manager_delegate.h" | |
| #include "chrome/browser/devtools/devtools_window.h" | |
| #include "components/policy/core/browser/browser_policy_connector.h" | |
| #include "components/policy/core/common/policy_map.h" | |
| #include "components/policy/policy_constants.h" | |
| #include "content/public/browser/devtools_agent_host.h" | |
| #include "content/public/browser/render_frame_host.h" | |
| #include "content/public/browser/web_contents.h" | |
| #include "content/public/test/test_utils.h" | |
| #if !BUILDFLAG(IS_ANDROID) | |
| #include "chrome/browser/ui/browser_window.h" | |
| #include "chrome/browser/ui/browser_window/public/browser_window_interface.h" // nogncheck crbug.com/40147906 | |
| #include "chrome/browser/ui/tabs/tab_strip_model.h" | |
| #endif // !BUILDFLAG(IS_ANDROID) | |
| namespace { | |
| const char kHarnessScript[] = "Tests.js"; | |
| typedef std::vector<DevToolsWindowTesting*> DevToolsWindowTestings; | |
| DevToolsWindowTestings& GetDevToolsWindowTestingInstances() { | |
| static base::NoDestructor<DevToolsWindowTestings> instances; | |
| return *instances; | |
| } | |
| } | |
| DevToolsWindowTesting::DevToolsWindowTesting(DevToolsWindow* window) | |
| : devtools_window_(window) { | |
| DCHECK(window); | |
| window->close_callback_ = | |
| base::BindOnce(&DevToolsWindowTesting::WindowClosed, window); | |
| GetDevToolsWindowTestingInstances().push_back(this); | |
| } | |
| DevToolsWindowTesting::~DevToolsWindowTesting() { | |
| DevToolsWindowTestings& instances = GetDevToolsWindowTestingInstances(); | |
| auto it = std::ranges::find(instances, this); | |
| CHECK(it != instances.end()); | |
| instances.erase(it); | |
| if (!close_callback_.is_null()) | |
| std::move(close_callback_).Run(); | |
| #if !BUILDFLAG(IS_ANDROID) | |
| // Needed for Chrome_DevToolsADBThread to shut down gracefully in tests. | |
| ChromeDevToolsManagerDelegate::GetInstance() | |
| ->ResetAndroidDeviceManagerForTesting(); | |
| #endif // !BUILDFLAG(IS_ANDROID) | |
| } | |
| // static | |
| DevToolsWindowTesting* DevToolsWindowTesting::Get(DevToolsWindow* window) { | |
| DevToolsWindowTesting* testing = DevToolsWindowTesting::Find(window); | |
| if (!testing) | |
| testing = new DevToolsWindowTesting(window); | |
| return testing; | |
| } | |
| // static | |
| DevToolsWindowTesting* DevToolsWindowTesting::Find(DevToolsWindow* window) { | |
| for (auto& instance : GetDevToolsWindowTestingInstances()) { | |
| if (instance->devtools_window_ == window) { | |
| return instance; | |
| } | |
| } | |
| return nullptr; | |
| } | |
| BrowserWindowInterface* DevToolsWindowTesting::browser() { | |
| return devtools_window_->browser_; | |
| } | |
| content::WebContents* DevToolsWindowTesting::main_web_contents() { | |
| return devtools_window_->main_web_contents_; | |
| } | |
| content::WebContents* DevToolsWindowTesting::toolbox_web_contents() { | |
| return devtools_window_->toolbox_web_contents_; | |
| } | |
| void DevToolsWindowTesting::SetInspectedPageBounds(const gfx::Rect& bounds) { | |
| devtools_window_->SetInspectedPageBounds(bounds); | |
| } | |
| void DevToolsWindowTesting::SetCloseCallback(base::OnceClosure closure) { | |
| close_callback_ = std::move(closure); | |
| } | |
| void DevToolsWindowTesting::SetOpenNewWindowForPopups(bool value) { | |
| devtools_window_->SetOpenNewWindowForPopups(value); | |
| } | |
| // static | |
| void DevToolsWindowTesting::WindowClosed(DevToolsWindow* window) { | |
| DevToolsWindowTesting* testing = DevToolsWindowTesting::Find(window); | |
| if (testing) | |
| delete testing; | |
| } | |
| // static | |
| void DevToolsWindowTesting::WaitForDevToolsWindowLoad(DevToolsWindow* window) { | |
| if (!window) { | |
| return; | |
| } | |
| auto* main_web_contents = window->main_web_contents_.get(); | |
| if (!window->ready_for_test_) { | |
| scoped_refptr<content::MessageLoopRunner> runner = | |
| new content::MessageLoopRunner; | |
| window->ready_for_test_callback_ = runner->QuitClosure(); | |
| runner->Run(); | |
| } | |
| // The window might be removed upon creation. | |
| // E.g. devtools windows may not be allowed in kiosk mode and killed upon | |
| // creation.. | |
| if (!DevToolsWindow::IsDevToolsWindow(main_web_contents)) { | |
| return; | |
| } | |
| std::u16string harness = base::UTF8ToUTF16( | |
| content::DevToolsFrontendHost::GetFrontendResource(kHarnessScript)); | |
| window->main_web_contents_->GetPrimaryMainFrame()->ExecuteJavaScript( | |
| harness, base::NullCallback()); | |
| } | |
| // static | |
| DevToolsWindow* DevToolsWindowTesting::OpenDevToolsWindowSync( | |
| content::WebContents* inspected_web_contents, | |
| Profile* profile, | |
| bool is_docked) { | |
| std::string settings = is_docked ? | |
| "{\"isUnderTest\": true, \"currentDockState\":\"\\\"bottom\\\"\"}" : | |
| "{\"isUnderTest\": true, \"currentDockState\":\"\\\"undocked\\\"\"}"; | |
| scoped_refptr<content::DevToolsAgentHost> agent( | |
| content::DevToolsAgentHost::GetOrCreateForTab(inspected_web_contents)); | |
| DevToolsWindow::ToggleDevToolsWindow(inspected_web_contents, profile, true, | |
| DevToolsToggleAction::Show(), settings); | |
| DevToolsWindow* window = DevToolsWindow::FindDevToolsWindow(agent.get()); | |
| WaitForDevToolsWindowLoad(window); | |
| return window; | |
| } | |
| // static | |
| DevToolsWindow* DevToolsWindowTesting::OpenDevToolsWindowSync( | |
| content::WebContents* inspected_web_contents, | |
| bool is_docked) { | |
| return OpenDevToolsWindowSync( | |
| inspected_web_contents, | |
| DevToolsWindow::GetProfileForDevToolsWindow(inspected_web_contents), | |
| is_docked); | |
| } | |
| #if !BUILDFLAG(IS_ANDROID) | |
| // static | |
| DevToolsWindow* DevToolsWindowTesting::OpenDevToolsWindowSync( | |
| BrowserWindowInterface* browser, | |
| bool is_docked) { | |
| return OpenDevToolsWindowSync( | |
| browser->GetTabStripModel()->GetActiveWebContents(), is_docked); | |
| } | |
| #endif // !BUILDFLAG(IS_ANDROID) | |
| // static | |
| DevToolsWindow* DevToolsWindowTesting::OpenDevToolsWindowSync( | |
| Profile* profile, | |
| scoped_refptr<content::DevToolsAgentHost> agent_host) { | |
| DevToolsWindow::OpenDevToolsWindow(agent_host, profile, | |
| DevToolsOpenedByAction::kUnknown); | |
| DevToolsWindow* window = DevToolsWindow::FindDevToolsWindow(agent_host.get()); | |
| WaitForDevToolsWindowLoad(window); | |
| return window; | |
| } | |
| // static | |
| DevToolsWindow* DevToolsWindowTesting::OpenDiscoveryDevToolsWindowSync( | |
| Profile* profile) { | |
| DevToolsWindow* window = DevToolsWindow::OpenNodeFrontendWindow( | |
| profile, DevToolsOpenedByAction::kUnknown); | |
| WaitForDevToolsWindowLoad(window); | |
| return window; | |
| } | |
| // static | |
| void DevToolsWindowTesting::CloseDevToolsWindow( | |
| DevToolsWindow* window) { | |
| if (window->is_docked_) { | |
| window->CloseWindow(); | |
| } else { | |
| #if BUILDFLAG(IS_ANDROID) | |
| window->main_web_contents_->Close(); | |
| #else | |
| window->browser_->GetWindow()->Close(); | |
| #endif // BUILDFLAG(IS_ANDROID) | |
| } | |
| } | |
| // static | |
| void DevToolsWindowTesting::CloseDevToolsWindowSync( | |
| DevToolsWindow* window) { | |
| scoped_refptr<content::MessageLoopRunner> runner = | |
| new content::MessageLoopRunner; | |
| DevToolsWindowTesting::Get(window)->SetCloseCallback(runner->QuitClosure()); | |
| CloseDevToolsWindow(window); | |
| runner->Run(); | |
| } | |
| // DevToolsDisabler() --------------------------------------------------------- | |
| DevToolsDisabler::DevToolsDisabler() = default; | |
| DevToolsDisabler::~DevToolsDisabler() = default; | |
| void DevToolsDisabler::SetUp() { | |
| provider_.SetDefaultReturns( | |
| /*is_initialization_complete_return=*/true, | |
| /*is_first_policy_load_complete_return=*/true); | |
| policy::BrowserPolicyConnector::SetPolicyProviderForTesting(&provider_); | |
| } | |
| void DevToolsDisabler::DisableDevTools() { | |
| base::ListValue blocklist; | |
| blocklist.Append("devtools://*"); | |
| policy::PolicyMap policies; | |
| policies.Set(policy::key::kURLBlocklist, policy::POLICY_LEVEL_MANDATORY, | |
| policy::POLICY_SCOPE_USER, policy::POLICY_SOURCE_CLOUD, | |
| base::Value(std::move(blocklist)), nullptr); | |
| provider_.UpdateChromePolicy(policies); | |
| content::RunAllTasksUntilIdle(); | |
| } | |
| // DevToolsWindowCreationObserver --------------------------------------------- | |
| DevToolsWindowCreationObserver::DevToolsWindowCreationObserver() | |
| : DevToolsWindowCreationObserver(base::NullCallback()) {} | |
| DevToolsWindowCreationObserver::DevToolsWindowCreationObserver( | |
| base::RepeatingCallback<void(DevToolsWindow*)> on_creation_callback) | |
| : creation_callback_(base::BindRepeating( | |
| &DevToolsWindowCreationObserver::DevToolsWindowCreated, | |
| base::Unretained(this))), | |
| on_creation_callback_(std::move(on_creation_callback)) { | |
| DevToolsWindow::AddCreationCallbackForTest(creation_callback_); | |
| } | |
| DevToolsWindowCreationObserver::~DevToolsWindowCreationObserver() { | |
| DevToolsWindow::RemoveCreationCallbackForTest(creation_callback_); | |
| } | |
| void DevToolsWindowCreationObserver::Wait() { | |
| if (!devtools_windows_.empty()) | |
| return; | |
| runner_ = new content::MessageLoopRunner(); | |
| runner_->Run(); | |
| runner_ = nullptr; | |
| } | |
| void DevToolsWindowCreationObserver::WaitForLoad() { | |
| Wait(); | |
| if (devtools_window()) | |
| DevToolsWindowTesting::WaitForDevToolsWindowLoad(devtools_window()); | |
| } | |
| void DevToolsWindowCreationObserver::DevToolsWindowCreated( | |
| DevToolsWindow* devtools_window) { | |
| devtools_windows_.push_back(devtools_window); | |
| if (on_creation_callback_) { | |
| on_creation_callback_.Run(devtools_window); | |
| } | |
| if (runner_.get()) | |
| runner_->QuitClosure().Run(); | |
| } | |
| DevToolsWindow* DevToolsWindowCreationObserver::devtools_window() { | |
| return !devtools_windows_.empty() ? devtools_windows_.back() : nullptr; | |
| } | |
| void DevToolsWindowCreationObserver::CloseAllSync() { | |
| for (DevToolsWindow* window : devtools_windows_) | |
| DevToolsWindowTesting::CloseDevToolsWindowSync(window); | |
| devtools_windows_.clear(); | |
| } |