Commit bbe84115 authored by asargent's avatar asargent Committed by Commit bot

Don't allow inline install if frame is deleted before user accepts

If the frame that called the chrome.webstore.install method to begin an
inline install gets deleted before the user accepts from the dialog, we
don't want the install to continue because a navigation could make it
look like the install request was coming from some unrelated site.

One downside of this approach is that the dialog stays around even after
the frame is deleted, and hitting either accept or cancel buttons both
just cancel the install. It would be better if the dialog is
automatically cancelled, but doing that would involve a lot more
refactoring. The approach in this CL was easier and is probably worth
getting out, and we can improve on it in the future.

BUG=550047

Review URL: https://codereview.chromium.org/1403293008

Cr-Commit-Position: refs/heads/master@{#361218}
parent 2bdf6e40
...@@ -395,10 +395,7 @@ void TabHelper::OnInlineWebstoreInstall(content::RenderFrameHost* host, ...@@ -395,10 +395,7 @@ void TabHelper::OnInlineWebstoreInstall(content::RenderFrameHost* host,
return_route_id); return_route_id);
scoped_refptr<WebstoreInlineInstaller> installer( scoped_refptr<WebstoreInlineInstaller> installer(
webstore_inline_installer_factory_->CreateInstaller( webstore_inline_installer_factory_->CreateInstaller(
web_contents(), web_contents(), host, webstore_item_id, requestor_url, callback));
webstore_item_id,
requestor_url,
callback));
installer->BeginInstall(); installer->BeginInstall();
} }
} }
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include "base/strings/stringprintf.h" #include "base/strings/stringprintf.h"
#include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser_finder.h" #include "chrome/browser/ui/browser_finder.h"
#include "content/public/browser/navigation_details.h"
#include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents.h"
using content::WebContents; using content::WebContents;
...@@ -29,6 +30,7 @@ const char kInitiatedFromPopupError[] = ...@@ -29,6 +30,7 @@ const char kInitiatedFromPopupError[] =
WebstoreInlineInstaller::WebstoreInlineInstaller( WebstoreInlineInstaller::WebstoreInlineInstaller(
content::WebContents* web_contents, content::WebContents* web_contents,
content::RenderFrameHost* host,
const std::string& webstore_item_id, const std::string& webstore_item_id,
const GURL& requestor_url, const GURL& requestor_url,
const Callback& callback) const Callback& callback)
...@@ -37,8 +39,8 @@ WebstoreInlineInstaller::WebstoreInlineInstaller( ...@@ -37,8 +39,8 @@ WebstoreInlineInstaller::WebstoreInlineInstaller(
Profile::FromBrowserContext(web_contents->GetBrowserContext()), Profile::FromBrowserContext(web_contents->GetBrowserContext()),
callback), callback),
content::WebContentsObserver(web_contents), content::WebContentsObserver(web_contents),
requestor_url_(requestor_url) { host_(host),
} requestor_url_(requestor_url) {}
WebstoreInlineInstaller::~WebstoreInlineInstaller() {} WebstoreInlineInstaller::~WebstoreInlineInstaller() {}
...@@ -91,8 +93,8 @@ bool WebstoreInlineInstaller::IsRequestorPermitted( ...@@ -91,8 +93,8 @@ bool WebstoreInlineInstaller::IsRequestorPermitted(
} }
bool WebstoreInlineInstaller::CheckRequestorAlive() const { bool WebstoreInlineInstaller::CheckRequestorAlive() const {
// The tab may have gone away - cancel installation in that case. // The frame or tab may have gone away - cancel installation in that case.
return web_contents() != NULL; return host_ != nullptr && web_contents() != nullptr;
} }
const GURL& WebstoreInlineInstaller::GetRequestorURL() const { const GURL& WebstoreInlineInstaller::GetRequestorURL() const {
...@@ -176,6 +178,15 @@ bool WebstoreInlineInstaller::CheckRequestorPermitted( ...@@ -176,6 +178,15 @@ bool WebstoreInlineInstaller::CheckRequestorPermitted(
// Private implementation. // Private implementation.
// //
void WebstoreInlineInstaller::DidNavigateAnyFrame(
content::RenderFrameHost* render_frame_host,
const content::LoadCommittedDetails& details,
const content::FrameNavigateParams& params) {
if (!details.is_in_page &&
(render_frame_host == host_ || details.is_main_frame))
host_ = nullptr;
}
void WebstoreInlineInstaller::WebContentsDestroyed() { void WebstoreInlineInstaller::WebContentsDestroyed() {
AbortInstall(); AbortInstall();
} }
......
...@@ -31,6 +31,7 @@ class WebstoreInlineInstaller : public WebstoreStandaloneInstaller, ...@@ -31,6 +31,7 @@ class WebstoreInlineInstaller : public WebstoreStandaloneInstaller,
typedef WebstoreStandaloneInstaller::Callback Callback; typedef WebstoreStandaloneInstaller::Callback Callback;
WebstoreInlineInstaller(content::WebContents* web_contents, WebstoreInlineInstaller(content::WebContents* web_contents,
content::RenderFrameHost* host,
const std::string& webstore_item_id, const std::string& webstore_item_id,
const GURL& requestor_url, const GURL& requestor_url,
const Callback& callback); const Callback& callback);
...@@ -61,6 +62,9 @@ class WebstoreInlineInstaller : public WebstoreStandaloneInstaller, ...@@ -61,6 +62,9 @@ class WebstoreInlineInstaller : public WebstoreStandaloneInstaller,
private: private:
// content::WebContentsObserver interface implementation. // content::WebContentsObserver interface implementation.
void DidNavigateAnyFrame(content::RenderFrameHost* render_frame_host,
const content::LoadCommittedDetails& details,
const content::FrameNavigateParams& params) override;
void WebContentsDestroyed() override; void WebContentsDestroyed() override;
// Checks whether the install is initiated by a page in a verified site // Checks whether the install is initiated by a page in a verified site
...@@ -68,6 +72,8 @@ class WebstoreInlineInstaller : public WebstoreStandaloneInstaller, ...@@ -68,6 +72,8 @@ class WebstoreInlineInstaller : public WebstoreStandaloneInstaller,
static bool IsRequestorURLInVerifiedSite(const GURL& requestor_url, static bool IsRequestorURLInVerifiedSite(const GURL& requestor_url,
const std::string& verified_site); const std::string& verified_site);
// This corresponds to the frame that initiated the install request.
content::RenderFrameHost* host_;
GURL requestor_url_; GURL requestor_url_;
DISALLOW_IMPLICIT_CONSTRUCTORS(WebstoreInlineInstaller); DISALLOW_IMPLICIT_CONSTRUCTORS(WebstoreInlineInstaller);
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
#include "chrome/test/base/ui_test_utils.h" #include "chrome/test/base/ui_test_utils.h"
#include "components/content_settings/core/browser/host_content_settings_map.h" #include "components/content_settings/core/browser/host_content_settings_map.h"
#include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test_utils.h"
#include "extensions/browser/extension_registry.h" #include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h" #include "extensions/browser/extension_system.h"
#include "url/gurl.h" #include "url/gurl.h"
...@@ -35,6 +36,14 @@ const char kTestExtensionId[] = "ecglahbcnmdpdciemllbhojghbkagdje"; ...@@ -35,6 +36,14 @@ const char kTestExtensionId[] = "ecglahbcnmdpdciemllbhojghbkagdje";
const char kTestDataPath[] = "extensions/api_test/webstore_inline_install"; const char kTestDataPath[] = "extensions/api_test/webstore_inline_install";
const char kCrxFilename[] = "extension.crx"; const char kCrxFilename[] = "extension.crx";
// A struct for letting us store the actual parameters that were passed to
// the install callback.
struct InstallResult {
bool success = false;
std::string error;
webstore_install::Result result = webstore_install::RESULT_LAST;
};
} // namespace } // namespace
class WebstoreInlineInstallerTest : public WebstoreInstallerTest { class WebstoreInlineInstallerTest : public WebstoreInstallerTest {
...@@ -87,46 +96,82 @@ ExtensionInstallPrompt::Delegate* ProgrammableInstallPrompt::delegate_; ...@@ -87,46 +96,82 @@ ExtensionInstallPrompt::Delegate* ProgrammableInstallPrompt::delegate_;
class WebstoreInlineInstallerForTest : public WebstoreInlineInstaller { class WebstoreInlineInstallerForTest : public WebstoreInlineInstaller {
public: public:
WebstoreInlineInstallerForTest(WebContents* contents, WebstoreInlineInstallerForTest(WebContents* contents,
content::RenderFrameHost* host,
const std::string& extension_id, const std::string& extension_id,
const GURL& requestor_url, const GURL& requestor_url,
const Callback& callback) const Callback& callback)
: WebstoreInlineInstaller( : WebstoreInlineInstaller(
contents, contents,
host,
kTestExtensionId, kTestExtensionId,
requestor_url, requestor_url,
base::Bind(DummyCallback)), base::Bind(&WebstoreInlineInstallerForTest::InstallCallback,
programmable_prompt_(NULL) { base::Unretained(this))),
} install_result_target_(nullptr),
programmable_prompt_(nullptr) {}
scoped_ptr<ExtensionInstallPrompt> CreateInstallUI() override { scoped_ptr<ExtensionInstallPrompt> CreateInstallUI() override {
programmable_prompt_ = new ProgrammableInstallPrompt(web_contents()); programmable_prompt_ = new ProgrammableInstallPrompt(web_contents());
return make_scoped_ptr(programmable_prompt_); return make_scoped_ptr(programmable_prompt_);
} }
// Added here to make it public so that test cases can call it below.
bool CheckRequestorAlive() const override {
return WebstoreInlineInstaller::CheckRequestorAlive();
}
// Tests that care about the actual arguments to the install callback can use
// this to receive a copy in |install_result_target|.
void set_install_result_target(
scoped_ptr<InstallResult>* install_result_target) {
install_result_target_ = install_result_target;
}
private: private:
~WebstoreInlineInstallerForTest() override {} ~WebstoreInlineInstallerForTest() override {}
friend class base::RefCountedThreadSafe<WebstoreStandaloneInstaller>; friend class base::RefCountedThreadSafe<WebstoreStandaloneInstaller>;
static void DummyCallback(bool success, void InstallCallback(bool success,
const std::string& error, const std::string& error,
webstore_install::Result result) { webstore_install::Result result) {
if (install_result_target_) {
install_result_target_->reset(new InstallResult);
(*install_result_target_)->success = success;
(*install_result_target_)->error = error;
(*install_result_target_)->result = result;
}
} }
// This can be set by tests that want to get the actual install callback
// arguments.
scoped_ptr<InstallResult>* install_result_target_;
ProgrammableInstallPrompt* programmable_prompt_; ProgrammableInstallPrompt* programmable_prompt_;
}; };
class WebstoreInlineInstallerForTestFactory : class WebstoreInlineInstallerForTestFactory :
public WebstoreInlineInstallerFactory { public WebstoreInlineInstallerFactory {
public:
WebstoreInlineInstallerForTestFactory() : last_installer_(nullptr) {}
~WebstoreInlineInstallerForTestFactory() override {} ~WebstoreInlineInstallerForTestFactory() override {}
WebstoreInlineInstallerForTest* last_installer() { return last_installer_; }
WebstoreInlineInstaller* CreateInstaller( WebstoreInlineInstaller* CreateInstaller(
WebContents* contents, WebContents* contents,
content::RenderFrameHost* host,
const std::string& webstore_item_id, const std::string& webstore_item_id,
const GURL& requestor_url, const GURL& requestor_url,
const WebstoreStandaloneInstaller::Callback& callback) override { const WebstoreStandaloneInstaller::Callback& callback) override {
return new WebstoreInlineInstallerForTest( last_installer_ = new WebstoreInlineInstallerForTest(
contents, webstore_item_id, requestor_url, callback); contents, host, webstore_item_id, requestor_url, callback);
return last_installer_;
} }
private:
// The last installer that was created.
WebstoreInlineInstallerForTest* last_installer_;
}; };
IN_PROC_BROWSER_TEST_F(WebstoreInlineInstallerTest, IN_PROC_BROWSER_TEST_F(WebstoreInlineInstallerTest,
...@@ -145,6 +190,42 @@ IN_PROC_BROWSER_TEST_F(WebstoreInlineInstallerTest, ...@@ -145,6 +190,42 @@ IN_PROC_BROWSER_TEST_F(WebstoreInlineInstallerTest,
ProgrammableInstallPrompt::Accept(); ProgrammableInstallPrompt::Accept();
} }
IN_PROC_BROWSER_TEST_F(WebstoreInlineInstallerTest,
NavigateBeforeInstallConfirmation) {
GURL install_url = GenerateTestServerUrl(kAppDomain, "install.html");
ui_test_utils::NavigateToURL(browser(), install_url);
WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
TabHelper* tab_helper = TabHelper::FromWebContents(web_contents);
WebstoreInlineInstallerForTestFactory* factory =
new WebstoreInlineInstallerForTestFactory();
tab_helper->SetWebstoreInlineInstallerFactoryForTests(factory);
RunTestAsync("runTest");
while (!ProgrammableInstallPrompt::Ready())
base::RunLoop().RunUntilIdle();
GURL new_url = GenerateTestServerUrl(kNonAppDomain, "empty.html");
web_contents->GetController().LoadURL(
new_url, content::Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
EXPECT_TRUE(content::WaitForLoadStop(web_contents));
ASSERT_NE(factory->last_installer(), nullptr);
EXPECT_NE(factory->last_installer()->web_contents(), nullptr);
EXPECT_FALSE(factory->last_installer()->CheckRequestorAlive());
// Right now the way we handle navigations away from the frame that began the
// inline install is to just declare the requestor to be dead, but not to
// kill the prompt (that would be a better UX, but more complicated to
// implement). If we ever do change things to kill the prompt in this case,
// the following code can be removed (it verifies that clicking ok on the
// dialog does not result in an install).
scoped_ptr<InstallResult> install_result;
factory->last_installer()->set_install_result_target(&install_result);
ASSERT_TRUE(ProgrammableInstallPrompt::Ready());
ProgrammableInstallPrompt::Accept();
ASSERT_NE(install_result.get(), nullptr);
EXPECT_EQ(install_result->success, false);
EXPECT_EQ(install_result->result, webstore_install::ABORTED);
}
// Flaky: https://crbug.com/537526. // Flaky: https://crbug.com/537526.
IN_PROC_BROWSER_TEST_F(WebstoreInlineInstallerTest, IN_PROC_BROWSER_TEST_F(WebstoreInlineInstallerTest,
DISABLED_ShouldBlockInlineInstallFromPopupWindow) { DISABLED_ShouldBlockInlineInstallFromPopupWindow) {
......
...@@ -10,12 +10,13 @@ ...@@ -10,12 +10,13 @@
namespace extensions { namespace extensions {
WebstoreInlineInstaller* WebstoreInlineInstallerFactory::CreateInstaller( WebstoreInlineInstaller* WebstoreInlineInstallerFactory::CreateInstaller(
content::WebContents* contents, content::WebContents* contents,
const std::string& webstore_item_id, content::RenderFrameHost* host,
const GURL& requestor_url, const std::string& webstore_item_id,
const WebstoreStandaloneInstaller::Callback& callback) { const GURL& requestor_url,
return new WebstoreInlineInstaller( const WebstoreStandaloneInstaller::Callback& callback) {
contents, webstore_item_id, requestor_url, callback); return new WebstoreInlineInstaller(contents, host, webstore_item_id,
requestor_url, callback);
} }
} // namespace extensions } // namespace extensions
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
#include "chrome/browser/extensions/webstore_standalone_installer.h" #include "chrome/browser/extensions/webstore_standalone_installer.h"
namespace content { namespace content {
class WebContents; class WebContents;
} }
class GURL; class GURL;
...@@ -28,6 +28,7 @@ class WebstoreInlineInstallerFactory { ...@@ -28,6 +28,7 @@ class WebstoreInlineInstallerFactory {
// Create a new WebstoreInlineInstallerInstance to be owned by the caller. // Create a new WebstoreInlineInstallerInstance to be owned by the caller.
virtual WebstoreInlineInstaller* CreateInstaller( virtual WebstoreInlineInstaller* CreateInstaller(
content::WebContents* contents, content::WebContents* contents,
content::RenderFrameHost* host,
const std::string& webstore_item_id, const std::string& webstore_item_id,
const GURL& requestor_url, const GURL& requestor_url,
const WebstoreStandaloneInstaller::Callback& callback); const WebstoreStandaloneInstaller::Callback& callback);
......
...@@ -39,6 +39,7 @@ TestWebstoreInlineInstaller::TestWebstoreInlineInstaller( ...@@ -39,6 +39,7 @@ TestWebstoreInlineInstaller::TestWebstoreInlineInstaller(
content::WebContents* contents, content::WebContents* contents,
const std::string& requestor_url) const std::string& requestor_url)
: WebstoreInlineInstaller(contents, : WebstoreInlineInstaller(contents,
contents->GetMainFrame(),
"", "",
GURL(requestor_url), GURL(requestor_url),
base::Bind(&TestInstallerCallback)) { base::Bind(&TestInstallerCallback)) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment