| // 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. |
| |
| #include "base/barrier_closure.h" |
| #include "base/bind.h" |
| #include "base/callback_forward.h" |
| #include "base/command_line.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/run_loop.h" |
| #include "build/build_config.h" |
| #include "build/chromeos_buildflags.h" |
| #include "chrome/browser/extensions/api/desktop_capture/desktop_capture_api.h" |
| #include "chrome/browser/media/webrtc/fake_desktop_media_picker_factory.h" |
| #include "chrome/browser/media/webrtc/webrtc_browsertest_base.h" |
| #include "chrome/browser/media/webrtc/webrtc_browsertest_common.h" |
| #include "chrome/browser/sessions/tab_restore_service_factory.h" |
| #include "chrome/browser/sessions/tab_restore_service_load_waiter.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_commands.h" |
| #include "chrome/browser/ui/browser_tabstrip.h" |
| #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chrome/test/base/in_process_browser_test.h" |
| #include "chrome/test/base/ui_test_utils.h" |
| #include "components/infobars/content/content_infobar_manager.h" |
| #include "components/infobars/core/confirm_infobar_delegate.h" |
| #include "components/infobars/core/infobar.h" |
| #include "components/infobars/core/infobar_manager.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/common/content_switches.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "media/base/media_switches.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| |
| namespace { |
| static const char kMainWebrtcTestHtmlPage[] = "/webrtc/webrtc_jsep01_test.html"; |
| |
| content::WebContents* GetWebContents(Browser* browser, int tab) { |
| return browser->tab_strip_model()->GetWebContentsAt(tab); |
| } |
| |
| content::DesktopMediaID GetDesktopMediaIDForScreen() { |
| return content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN, |
| content::DesktopMediaID::kNullId); |
| } |
| |
| content::DesktopMediaID GetDesktopMediaIDForTab(Browser* browser, int tab) { |
| content::RenderFrameHost* main_frame = |
| GetWebContents(browser, tab)->GetMainFrame(); |
| return content::DesktopMediaID( |
| content::DesktopMediaID::TYPE_WEB_CONTENTS, |
| content::DesktopMediaID::kNullId, |
| content::WebContentsMediaCaptureId(main_frame->GetProcess()->GetID(), |
| main_frame->GetRoutingID())); |
| } |
| |
| infobars::ContentInfoBarManager* GetInfoBarManager(Browser* browser, int tab) { |
| return infobars::ContentInfoBarManager::FromWebContents( |
| GetWebContents(browser, tab)); |
| } |
| |
| infobars::ContentInfoBarManager* GetInfoBarManager( |
| content::WebContents* contents) { |
| return infobars::ContentInfoBarManager::FromWebContents(contents); |
| } |
| |
| ConfirmInfoBarDelegate* GetDelegate(Browser* browser, int tab) { |
| return static_cast<ConfirmInfoBarDelegate*>( |
| GetInfoBarManager(browser, tab)->infobar_at(0)->delegate()); |
| } |
| |
| class InfobarUIChangeObserver : public TabStripModelObserver { |
| public: |
| explicit InfobarUIChangeObserver(Browser* browser) : browser_{browser} { |
| for (int tab = 0; tab < browser_->tab_strip_model()->count(); ++tab) { |
| auto* contents = browser_->tab_strip_model()->GetWebContentsAt(tab); |
| observers_[contents] = |
| std::make_unique<InfoBarChangeObserver>(base::BindOnce( |
| &InfobarUIChangeObserver::EraseObserver, base::Unretained(this))); |
| GetInfoBarManager(contents)->AddObserver(observers_[contents].get()); |
| } |
| browser_->tab_strip_model()->AddObserver(this); |
| } |
| |
| ~InfobarUIChangeObserver() override { |
| for (auto& observer_iter : observers_) { |
| auto* contents = observer_iter.first; |
| auto* observer = observer_iter.second.get(); |
| |
| GetInfoBarManager(contents)->RemoveObserver(observer); |
| } |
| browser_->tab_strip_model()->RemoveObserver(this); |
| observers_.clear(); |
| } |
| |
| void ExpectCalls(size_t expected_changes) { |
| run_loop_ = std::make_unique<base::RunLoop>(); |
| barrier_closure_ = |
| base::BarrierClosure(expected_changes, run_loop_->QuitClosure()); |
| for (auto& observer : observers_) { |
| observer.second->SetCallback(barrier_closure_); |
| } |
| } |
| |
| void Wait() { run_loop_->Run(); } |
| |
| // TabStripModelObserver |
| void OnTabStripModelChanged( |
| TabStripModel* tab_strip_model, |
| const TabStripModelChange& change, |
| const TabStripSelectionChange& selection) override { |
| if (change.type() == TabStripModelChange::kInserted) { |
| for (const auto& contents_with_index : change.GetInsert()->contents) { |
| auto* contents = contents_with_index.contents; |
| if (observers_.find(contents) == observers_.end()) { |
| observers_[contents] = std::make_unique<InfoBarChangeObserver>( |
| base::BindOnce(&InfobarUIChangeObserver::EraseObserver, |
| base::Unretained(this))); |
| GetInfoBarManager(contents)->AddObserver(observers_[contents].get()); |
| if (!barrier_closure_.is_null()) { |
| observers_[contents]->SetCallback(barrier_closure_); |
| } |
| } |
| } |
| } |
| } |
| void TabChangedAt(content::WebContents* contents, |
| int index, |
| TabChangeType change_type) override { |
| if (observers_.find(contents) == observers_.end()) { |
| observers_[contents] = |
| std::make_unique<InfoBarChangeObserver>(base::BindOnce( |
| &InfobarUIChangeObserver::EraseObserver, base::Unretained(this))); |
| GetInfoBarManager(contents)->AddObserver(observers_[contents].get()); |
| if (!barrier_closure_.is_null()) { |
| observers_[contents]->SetCallback(barrier_closure_); |
| } |
| } |
| } |
| |
| private: |
| class InfoBarChangeObserver; |
| |
| public: |
| void EraseObserver(InfoBarChangeObserver* observer) { |
| auto iter = std::find_if(observers_.begin(), observers_.end(), |
| [observer](const auto& observer_iter) { |
| return observer_iter.second.get() == observer; |
| }); |
| observers_.erase(iter); |
| } |
| |
| private: |
| class InfoBarChangeObserver : public infobars::InfoBarManager::Observer { |
| public: |
| using ShutdownCallback = base::OnceCallback<void(InfoBarChangeObserver*)>; |
| |
| explicit InfoBarChangeObserver(ShutdownCallback shutdown_callback) |
| : shutdown_callback_{std::move(shutdown_callback)} {} |
| |
| ~InfoBarChangeObserver() override = default; |
| |
| void SetCallback(base::RepeatingClosure change_closure) { |
| DCHECK(!change_closure.is_null()); |
| change_closure_ = change_closure; |
| } |
| |
| void OnInfoBarAdded(infobars::InfoBar* infobar) override { |
| DCHECK(!change_closure_.is_null()); |
| change_closure_.Run(); |
| } |
| |
| void OnInfoBarRemoved(infobars::InfoBar* infobar, bool animate) override { |
| DCHECK(!change_closure_.is_null()); |
| change_closure_.Run(); |
| } |
| |
| void OnInfoBarReplaced(infobars::InfoBar* old_infobar, |
| infobars::InfoBar* new_infobar) override { |
| NOTREACHED(); |
| } |
| |
| void OnManagerShuttingDown(infobars::InfoBarManager* manager) override { |
| manager->RemoveObserver(this); |
| DCHECK(!shutdown_callback_.is_null()); |
| std::move(shutdown_callback_).Run(this); |
| } |
| |
| private: |
| base::RepeatingClosure change_closure_; |
| ShutdownCallback shutdown_callback_; |
| }; |
| |
| std::unique_ptr<base::RunLoop> run_loop_; |
| std::map<content::WebContents*, std::unique_ptr<InfoBarChangeObserver>> |
| observers_; |
| Browser* browser_; |
| base::RepeatingClosure barrier_closure_; |
| }; |
| |
| } // namespace |
| |
| // Top-level integration test for WebRTC. Uses an actual desktop capture |
| // extension to capture the whole screen or a tab. |
| class WebRtcDesktopCaptureBrowserTest : public WebRtcTestBase { |
| public: |
| using MediaIDCallback = base::OnceCallback<content::DesktopMediaID()>; |
| |
| WebRtcDesktopCaptureBrowserTest() { |
| extensions::DesktopCaptureChooseDesktopMediaFunction:: |
| SetPickerFactoryForTests(&picker_factory_); |
| } |
| |
| void SetUpInProcessBrowserTestFixture() override { |
| DetectErrorsInJavaScript(); // Look for errors in our rather complex js. |
| } |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| // Ensure the infobar is enabled, since we expect that in this test. |
| EXPECT_FALSE(command_line->HasSwitch(switches::kUseFakeUIForMediaStream)); |
| |
| // Flags use to automatically select the right dekstop source and get |
| // around security restrictions. |
| command_line->AppendSwitchASCII(switches::kAutoSelectDesktopCaptureSource, |
| "Entire screen"); |
| command_line->AppendSwitch(switches::kEnableUserMediaScreenCapturing); |
| } |
| |
| protected: |
| void InitializeTabSharingForFirstTab(MediaIDCallback media_id_callback, |
| InfobarUIChangeObserver* observer) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| LoadDesktopCaptureExtension(); |
| auto* first_tab = OpenTestPageInNewTab(kMainWebrtcTestHtmlPage); |
| OpenTestPageInNewTab(kMainWebrtcTestHtmlPage); |
| |
| FakeDesktopMediaPickerFactory::TestFlags test_flags{ |
| .expect_screens = true, |
| .expect_windows = true, |
| .expect_tabs = true, |
| .selected_source = std::move(media_id_callback).Run(), |
| }; |
| picker_factory_.SetTestFlags(&test_flags, /*tests_count=*/1); |
| |
| std::string stream_id = GetDesktopMediaStream(first_tab); |
| EXPECT_NE(stream_id, ""); |
| |
| LOG(INFO) << "Opened desktop media stream, got id " << stream_id; |
| |
| const std::string constraints = |
| "{audio: false, video: {mandatory: {chromeMediaSource: 'desktop'," |
| "chromeMediaSourceId: '" + |
| stream_id + "'}}}"; |
| |
| // Should create 3 infobars if a tab (webcontents) is shared! |
| if (observer) |
| observer->ExpectCalls(3); |
| |
| EXPECT_TRUE(GetUserMediaWithSpecificConstraintsAndAcceptIfPrompted( |
| first_tab, constraints)); |
| |
| if (observer) |
| observer->Wait(); |
| } |
| |
| void DetectVideoAndHangUp(content::WebContents* first_tab, |
| content::WebContents* second_tab) { |
| StartDetectingVideo(first_tab, "remote-view"); |
| StartDetectingVideo(second_tab, "remote-view"); |
| #if !defined(OS_MAC) |
| // Video is choppy on Mac OS X. http://crbug.com/443542. |
| WaitForVideoToPlay(first_tab); |
| WaitForVideoToPlay(second_tab); |
| #endif |
| HangUp(first_tab); |
| HangUp(second_tab); |
| } |
| |
| void RunP2PScreenshareWhileSharing(MediaIDCallback media_id_callback) { |
| InitializeTabSharingForFirstTab(std::move(media_id_callback), nullptr); |
| auto* first_tab = browser()->tab_strip_model()->GetWebContentsAt(1); |
| auto* second_tab = browser()->tab_strip_model()->GetWebContentsAt(2); |
| GetUserMediaAndAccept(second_tab); |
| |
| SetupPeerconnectionWithLocalStream(first_tab); |
| SetupPeerconnectionWithLocalStream(second_tab); |
| NegotiateCall(first_tab, second_tab); |
| VerifyStatsGeneratedCallback(second_tab); |
| DetectVideoAndHangUp(first_tab, second_tab); |
| } |
| |
| FakeDesktopMediaPickerFactory picker_factory_; |
| }; |
| |
| // TODO(crbug.com/796889): Enable on Mac when thread check crash is fixed. |
| // TODO(sprang): Figure out why test times out on Win 10 and ChromeOS. |
| // TODO(crbug.com/1052397): Revisit the macro expression once build flag switch |
| // of lacros-chrome is complete. |
| // TODO(crbug.com/1225911): Test is flaky on Linux. |
| IN_PROC_BROWSER_TEST_F(WebRtcDesktopCaptureBrowserTest, |
| DISABLED_RunP2PScreenshareWhileSharingScreen) { |
| RunP2PScreenshareWhileSharing(base::BindOnce(GetDesktopMediaIDForScreen)); |
| } |
| |
| // TODO(crbug.com/1282292): Test is flaky on Linux and ChromeOS. |
| #if defined(OS_LINUX) || defined(OS_CHROMEOS) |
| #define MAYBE_RunP2PScreenshareWhileSharingTab \ |
| DISABLED_RunP2PScreenshareWhileSharingTab |
| #else |
| #define MAYBE_RunP2PScreenshareWhileSharingTab RunP2PScreenshareWhileSharingTab |
| #endif |
| IN_PROC_BROWSER_TEST_F(WebRtcDesktopCaptureBrowserTest, |
| MAYBE_RunP2PScreenshareWhileSharingTab) { |
| RunP2PScreenshareWhileSharing( |
| base::BindOnce(GetDesktopMediaIDForTab, base::Unretained(browser()), 2)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebRtcDesktopCaptureBrowserTest, |
| SwitchSharedTabBackAndForth) { |
| InfobarUIChangeObserver observer(browser()); |
| |
| InitializeTabSharingForFirstTab( |
| base::BindOnce(GetDesktopMediaIDForTab, base::Unretained(browser()), 2), |
| &observer); |
| |
| // Should delete 3 infobars and create 3 new! |
| observer.ExpectCalls(6); |
| // Switch shared tab from 2 to 0. |
| GetDelegate(browser(), 0)->Cancel(); |
| observer.Wait(); |
| |
| // Should delete 3 infobars and create 3 new! |
| observer.ExpectCalls(6); |
| // Switch shared tab from 0 to 2. |
| GetDelegate(browser(), 2)->Cancel(); |
| observer.Wait(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebRtcDesktopCaptureBrowserTest, |
| CloseAndReopenNonSharedTab) { |
| InfobarUIChangeObserver observer(browser()); |
| |
| InitializeTabSharingForFirstTab( |
| base::BindOnce(GetDesktopMediaIDForTab, base::Unretained(browser()), 2), |
| &observer); |
| |
| // Should delete 1 infobar. |
| observer.ExpectCalls(1); |
| // Close non-shared and non-sharing, i.e., unrelated tab |
| browser()->tab_strip_model()->CloseWebContentsAt( |
| 0, TabStripModel::CloseTypes::CLOSE_CREATE_HISTORICAL_TAB); |
| observer.Wait(); |
| |
| // Should create 1 infobar. |
| observer.ExpectCalls(1); |
| // Restore tab |
| chrome::RestoreTab(browser()); |
| observer.Wait(); |
| } |