| // Copyright (c) 2012 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 <map> |
| |
| #include "base/macros.h" |
| #include "base/strings/stringprintf.h" |
| #include "chrome/browser/devtools/devtools_window.h" |
| #include "chrome/browser/devtools/devtools_window_testing.h" |
| #include "chrome/browser/extensions/extension_browsertest.h" |
| #include "chrome/browser/renderer_host/chrome_navigation_ui_data.h" |
| #include "chrome/browser/sessions/session_tab_helper.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| #include "chrome/test/base/ui_test_utils.h" |
| #include "content/public/browser/navigation_handle.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/browser/web_contents_observer.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "content/public/test/test_navigation_observer.h" |
| #include "extensions/browser/extension_api_frame_id_map.h" |
| #include "extensions/browser/extension_navigation_ui_data.h" |
| |
| namespace extensions { |
| namespace { |
| |
| content::WebContents* GetActiveWebContents(const Browser* browser) { |
| return browser->tab_strip_model()->GetActiveWebContents(); |
| } |
| |
| // Saves ExtensionNavigationUIData for each render frame which completes |
| // navigation. |
| class ExtensionNavigationUIDataObserver : public content::WebContentsObserver { |
| public: |
| explicit ExtensionNavigationUIDataObserver(content::WebContents* web_contents) |
| : WebContentsObserver(web_contents) {} |
| |
| const ExtensionNavigationUIData* GetExtensionNavigationUIData( |
| content::RenderFrameHost* rfh) const { |
| auto iter = navigation_ui_data_map_.find(rfh); |
| if (iter == navigation_ui_data_map_.end()) |
| return nullptr; |
| return iter->second.get(); |
| } |
| |
| private: |
| void DidFinishNavigation( |
| content::NavigationHandle* navigation_handle) override { |
| if (!navigation_handle->HasCommitted()) |
| return; |
| |
| content::RenderFrameHost* rfh = navigation_handle->GetRenderFrameHost(); |
| const auto* data = static_cast<const ChromeNavigationUIData*>( |
| navigation_handle->GetNavigationUIData()); |
| navigation_ui_data_map_[rfh] = |
| data->GetExtensionNavigationUIData()->DeepCopy(); |
| } |
| |
| std::map<content::RenderFrameHost*, |
| std::unique_ptr<ExtensionNavigationUIData>> |
| navigation_ui_data_map_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ExtensionNavigationUIDataObserver); |
| }; |
| |
| } // namespace |
| |
| // Tests that we can load extension pages into the tab area and they can call |
| // extension APIs. |
| IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, WebContents) { |
| ASSERT_TRUE(LoadExtension( |
| test_data_dir_.AppendASCII("good").AppendASCII("Extensions") |
| .AppendASCII("behllobkkfkfnphdnhnkndlbkcpglgmj") |
| .AppendASCII("1.0.0.0"))); |
| |
| ui_test_utils::NavigateToURL( |
| browser(), |
| GURL("chrome-extension://behllobkkfkfnphdnhnkndlbkcpglgmj/page.html")); |
| |
| bool result = false; |
| ASSERT_TRUE(content::ExecuteScriptAndExtractBool( |
| GetActiveWebContents(browser()), "testTabsAPI()", &result)); |
| EXPECT_TRUE(result); |
| |
| // There was a bug where we would crash if we navigated to a page in the same |
| // extension because no new render view was getting created, so we would not |
| // do some setup. |
| ui_test_utils::NavigateToURL( |
| browser(), |
| GURL("chrome-extension://behllobkkfkfnphdnhnkndlbkcpglgmj/page.html")); |
| result = false; |
| ASSERT_TRUE(content::ExecuteScriptAndExtractBool( |
| GetActiveWebContents(browser()), "testTabsAPI()", &result)); |
| EXPECT_TRUE(result); |
| } |
| |
| // Test that the frame data for the Devtools main frame is cached. Regression |
| // test for crbug.com/817075. |
| IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, DevToolsMainFrameIsCached) { |
| auto test_devtools_main_frame_cached = [](Browser* browser, bool is_docked) { |
| SCOPED_TRACE(base::StringPrintf("Testing a %s devtools window", |
| is_docked ? "docked" : "undocked")); |
| const auto* api_frame_id_map = ExtensionApiFrameIdMap::Get(); |
| size_t prior_count = api_frame_id_map->GetFrameDataCountForTesting(); |
| |
| // Open a devtools window. |
| DevToolsWindow* devtools_window = |
| DevToolsWindowTesting::OpenDevToolsWindowSync(browser, is_docked); |
| |
| // Ensure that frame data for its main frame is cached. |
| content::WebContents* devtools_web_contents = |
| DevToolsWindow::GetInTabWebContents( |
| devtools_window->GetInspectedWebContents(), nullptr); |
| ASSERT_TRUE(devtools_web_contents); |
| EXPECT_TRUE(api_frame_id_map->HasCachedFrameDataForTesting( |
| devtools_web_contents->GetMainFrame())); |
| EXPECT_GT(api_frame_id_map->GetFrameDataCountForTesting(), prior_count); |
| |
| DevToolsWindowTesting::CloseDevToolsWindowSync(devtools_window); |
| |
| // Ensure that the frame data for the devtools main frame, which might have |
| // been destroyed by now, is deleted. |
| EXPECT_EQ(prior_count, api_frame_id_map->GetFrameDataCountForTesting()); |
| }; |
| |
| test_devtools_main_frame_cached(browser(), true /*is_docked*/); |
| test_devtools_main_frame_cached(browser(), false /*is_docked*/); |
| } |
| |
| // Test that we correctly cache frame data for all frames on creation. |
| // Regression test for crbug.com/810614. |
| IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, FrameDataCached) { |
| // Load an extension with a web accessible resource. |
| const Extension* extension = |
| LoadExtension(test_data_dir_.AppendASCII("web_accessible_resources")); |
| ASSERT_TRUE(extension); |
| |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| // Some utility functions for the test. |
| |
| // Returns whether the frame data for |rfh| is cached. |
| auto has_cached_frame_data = [](content::RenderFrameHost* rfh) { |
| return ExtensionApiFrameIdMap::Get()->HasCachedFrameDataForTesting(rfh); |
| }; |
| |
| // Returns the cached frame data for |rfh|. |
| auto get_frame_data = [](content::RenderFrameHost* rfh) { |
| return ExtensionApiFrameIdMap::Get()->GetFrameData(rfh); |
| }; |
| |
| // Adds an iframe with the given |name| and |src| to the given |web_contents| |
| // and waits till it loads. Returns true if successful. |
| auto add_iframe = [](content::WebContents* web_contents, |
| const std::string& name, const GURL& src) { |
| content::TestNavigationObserver observer(web_contents, |
| 1 /*number_of_navigations*/); |
| const char* code = R"( |
| var iframe = document.createElement('iframe'); |
| iframe.name = '%s'; |
| iframe.src = '%s'; |
| document.body.appendChild(iframe); |
| )"; |
| content::ExecuteScriptAsync( |
| web_contents, |
| base::StringPrintf(code, name.c_str(), src.spec().c_str())); |
| |
| observer.WaitForNavigationFinished(); |
| return observer.last_navigation_succeeded() && |
| observer.last_navigation_url() == src; |
| }; |
| |
| // Returns the frame with the given |name| in |web_contents|. |
| auto get_frame_by_name = [](content::WebContents* web_contents, |
| const std::string& name) { |
| return content::FrameMatchingPredicate( |
| web_contents, base::Bind(&content::FrameMatchesName, name)); |
| }; |
| |
| // Navigates the browser to |url|. Injects a web-frame and an extension frame |
| // into the page and ensures that extension frame data is correctly cached for |
| // each created frame. |
| auto load_page_and_test = [&](const GURL& url) { |
| using FrameData = ExtensionApiFrameIdMap::FrameData; |
| |
| SCOPED_TRACE(base::StringPrintf("Testing %s", url.spec().c_str())); |
| |
| ui_test_utils::NavigateToURL(browser(), url); |
| content::WebContents* web_contents = GetActiveWebContents(browser()); |
| SessionTabHelper* session_tab_helper = |
| SessionTabHelper::FromWebContents(web_contents); |
| ASSERT_TRUE(session_tab_helper); |
| int expected_tab_id = session_tab_helper->session_id().id(); |
| int expected_window_id = session_tab_helper->window_id().id(); |
| |
| // Ensure that the frame data for the main frame is cached. |
| content::RenderFrameHost* rfh = web_contents->GetMainFrame(); |
| EXPECT_TRUE(has_cached_frame_data(rfh)); |
| FrameData main_frame_data = get_frame_data(rfh); |
| EXPECT_EQ(ExtensionApiFrameIdMap::kTopFrameId, main_frame_data.frame_id); |
| EXPECT_EQ(ExtensionApiFrameIdMap::kInvalidFrameId, |
| main_frame_data.parent_frame_id); |
| EXPECT_EQ(expected_tab_id, main_frame_data.tab_id); |
| EXPECT_EQ(expected_window_id, main_frame_data.window_id); |
| EXPECT_EQ(url, main_frame_data.last_committed_main_frame_url); |
| EXPECT_FALSE(main_frame_data.pending_main_frame_url); |
| |
| // Add an extension iframe to the page and ensure its frame data is cached. |
| ASSERT_TRUE( |
| add_iframe(web_contents, "extension_frame", |
| extension->GetResourceURL("web_accessible_page.html"))); |
| rfh = get_frame_by_name(web_contents, "extension_frame"); |
| EXPECT_TRUE(has_cached_frame_data(rfh)); |
| FrameData extension_frame_data = get_frame_data(rfh); |
| EXPECT_NE(ExtensionApiFrameIdMap::kInvalidFrameId, |
| extension_frame_data.frame_id); |
| EXPECT_NE(ExtensionApiFrameIdMap::kTopFrameId, |
| extension_frame_data.frame_id); |
| EXPECT_EQ(ExtensionApiFrameIdMap::kTopFrameId, |
| extension_frame_data.parent_frame_id); |
| EXPECT_EQ(expected_tab_id, extension_frame_data.tab_id); |
| EXPECT_EQ(expected_window_id, extension_frame_data.window_id); |
| EXPECT_EQ(url, extension_frame_data.last_committed_main_frame_url); |
| EXPECT_FALSE(extension_frame_data.pending_main_frame_url); |
| |
| // Add a web frame to the page and ensure its frame data is cached. |
| ASSERT_TRUE(add_iframe(web_contents, "web_frame", |
| embedded_test_server()->GetURL("/empty.html"))); |
| rfh = get_frame_by_name(web_contents, "web_frame"); |
| EXPECT_TRUE(has_cached_frame_data(rfh)); |
| FrameData web_frame_data = get_frame_data(rfh); |
| EXPECT_NE(ExtensionApiFrameIdMap::kInvalidFrameId, web_frame_data.frame_id); |
| EXPECT_NE(ExtensionApiFrameIdMap::kTopFrameId, web_frame_data.frame_id); |
| EXPECT_NE(extension_frame_data.frame_id, web_frame_data.frame_id); |
| EXPECT_EQ(ExtensionApiFrameIdMap::kTopFrameId, |
| web_frame_data.parent_frame_id); |
| EXPECT_EQ(expected_tab_id, web_frame_data.tab_id); |
| EXPECT_EQ(expected_window_id, web_frame_data.window_id); |
| EXPECT_EQ(url, web_frame_data.last_committed_main_frame_url); |
| EXPECT_FALSE(web_frame_data.pending_main_frame_url); |
| }; |
| // End utility functions. |
| |
| // Test an extension page. |
| load_page_and_test(extension->GetResourceURL("extension_page.html")); |
| |
| // Test a non-extension page. |
| load_page_and_test(embedded_test_server()->GetURL("/empty.html")); |
| } |
| |
| // Test that we correctly set up the ExtensionNavigationUIData for each |
| // navigation. |
| IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, ExtensionNavigationUIData) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| content::WebContents* web_contents = GetActiveWebContents(browser()); |
| GURL last_committed_main_frame_url = web_contents->GetLastCommittedURL(); |
| ExtensionNavigationUIDataObserver observer(web_contents); |
| |
| // Load a page with an iframe. |
| const GURL url = embedded_test_server()->GetURL("/iframe.html"); |
| ui_test_utils::NavigateToURL(browser(), url); |
| |
| SessionTabHelper* session_tab_helper = |
| SessionTabHelper::FromWebContents(web_contents); |
| ASSERT_TRUE(session_tab_helper); |
| int expected_tab_id = session_tab_helper->session_id().id(); |
| int expected_window_id = session_tab_helper->window_id().id(); |
| |
| // Test ExtensionNavigationUIData for the main frame. |
| { |
| const auto* extension_navigation_ui_data = |
| observer.GetExtensionNavigationUIData(web_contents->GetMainFrame()); |
| ASSERT_TRUE(extension_navigation_ui_data); |
| EXPECT_FALSE(extension_navigation_ui_data->is_web_view()); |
| |
| ExtensionApiFrameIdMap::FrameData frame_data = |
| extension_navigation_ui_data->frame_data(); |
| EXPECT_EQ(ExtensionApiFrameIdMap::kTopFrameId, frame_data.frame_id); |
| EXPECT_EQ(ExtensionApiFrameIdMap::kInvalidFrameId, |
| frame_data.parent_frame_id); |
| EXPECT_EQ(expected_tab_id, frame_data.tab_id); |
| EXPECT_EQ(expected_window_id, frame_data.window_id); |
| EXPECT_EQ(last_committed_main_frame_url, |
| frame_data.last_committed_main_frame_url); |
| EXPECT_FALSE(frame_data.pending_main_frame_url); |
| } |
| |
| // Test ExtensionNavigationUIData for the sub-frame. |
| { |
| const auto* extension_navigation_ui_data = |
| observer.GetExtensionNavigationUIData( |
| content::ChildFrameAt(web_contents->GetMainFrame(), 0)); |
| ASSERT_TRUE(extension_navigation_ui_data); |
| EXPECT_FALSE(extension_navigation_ui_data->is_web_view()); |
| |
| ExtensionApiFrameIdMap::FrameData frame_data = |
| extension_navigation_ui_data->frame_data(); |
| EXPECT_NE(ExtensionApiFrameIdMap::kInvalidFrameId, frame_data.frame_id); |
| EXPECT_NE(ExtensionApiFrameIdMap::kTopFrameId, frame_data.frame_id); |
| EXPECT_EQ(ExtensionApiFrameIdMap::kTopFrameId, frame_data.parent_frame_id); |
| EXPECT_EQ(expected_tab_id, frame_data.tab_id); |
| EXPECT_EQ(expected_window_id, frame_data.window_id); |
| EXPECT_EQ(url, frame_data.last_committed_main_frame_url); |
| EXPECT_FALSE(frame_data.pending_main_frame_url); |
| } |
| } |
| |
| } // namespace extensions |