Fix browser crash when tabs that call chrome.webstore.install get closed before the call completes
This manifested itself as WebstoreInlineInstallTest.ArgumentValidation test
crashes because the test would make several chrome.webstore.install() calls
and not wait for responses before the test completed.
The fix was to make WebstoreInlineInstaller subclass TabContentsObserver and
check if the tab is still alive before trying to send responses back to it.
BUG=95713
Review URL: http://codereview.chromium.org/7861008
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@100295 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/extensions/extension_tab_helper.h b/chrome/browser/extensions/extension_tab_helper.h
index 1b4900d..7dda6ff 100644
--- a/chrome/browser/extensions/extension_tab_helper.h
+++ b/chrome/browser/extensions/extension_tab_helper.h
@@ -75,7 +75,7 @@
}
TabContents* tab_contents() const {
- return TabContentsObserver::tab_contents();
+ return TabContentsObserver::tab_contents();
}
// Sets a non-extension app icon associated with TabContents and fires an
diff --git a/chrome/browser/extensions/webstore_inline_install_browsertest.cc b/chrome/browser/extensions/webstore_inline_install_browsertest.cc
index a5e14df..3fce5a1 100644
--- a/chrome/browser/extensions/webstore_inline_install_browsertest.cc
+++ b/chrome/browser/extensions/webstore_inline_install_browsertest.cc
@@ -137,14 +137,7 @@
RunInlineInstallTest();
}
-// Flakily fails on Linux and Mac. http://crbug.com/95246
-#if defined(OS_LINUX) || defined(OS_MACOSX)
-#define MAYBE_ArgumentValidation DISABLED_ArgumentValidation
-#else
-#define MAYBE_ArgumentValidation ArgumentValidation
-#endif
-
-IN_PROC_BROWSER_TEST_F(WebstoreInlineInstallTest, MAYBE_ArgumentValidation) {
+IN_PROC_BROWSER_TEST_F(WebstoreInlineInstallTest, ArgumentValidation) {
SetExtensionInstallDialogForManifestAutoConfirmForTests(false);
ui_test_utils::NavigateToURL(
browser(), GenerateTestServerUrl(kAppDomain, "argument_validation.html"));
diff --git a/chrome/browser/extensions/webstore_inline_installer.cc b/chrome/browser/extensions/webstore_inline_installer.cc
index 7db39fb..8219ab30 100644
--- a/chrome/browser/extensions/webstore_inline_installer.cc
+++ b/chrome/browser/extensions/webstore_inline_installer.cc
@@ -136,7 +136,7 @@
std::string webstore_item_id,
GURL requestor_url,
Delegate* delegate)
- : tab_contents_(tab_contents),
+ : TabContentsObserver(tab_contents),
install_id_(install_id),
id_(webstore_item_id),
requestor_url_(requestor_url),
@@ -146,7 +146,7 @@
}
void WebstoreInlineInstaller::BeginInstall() {
- AddRef(); // Balanced in CompleteInstall.
+ AddRef(); // Balanced in CompleteInstall or TabContentsDestroyed.
if (!Extension::IdIsValid(id_)) {
CompleteInstall(kInvalidWebstoreItemId);
@@ -158,7 +158,7 @@
webstore_data_url_fetcher_.reset(
new URLFetcher(webstore_data_url, URLFetcher::GET, this));
Profile* profile = Profile::FromBrowserContext(
- tab_contents_->browser_context());
+ tab_contents()->browser_context());
webstore_data_url_fetcher_->set_request_context(
profile->GetRequestContext());
webstore_data_url_fetcher_->Start();
@@ -166,6 +166,9 @@
void WebstoreInlineInstaller::OnURLFetchComplete(const URLFetcher* source) {
CHECK_EQ(webstore_data_url_fetcher_.get(), source);
+ // We shouldn't be getting UrlFetcher callbacks if the TabContents has gone
+ // away; we stop any in in-progress fetches in TabContentsDestroyed.
+ CHECK(tab_contents());
if (!webstore_data_url_fetcher_->status().is_success() ||
webstore_data_url_fetcher_->response_code() != 200) {
@@ -259,7 +262,7 @@
manifest,
"", // We don't have any icon data.
icon_url,
- Profile::FromBrowserContext(tab_contents_->browser_context())->
+ Profile::FromBrowserContext(tab_contents()->browser_context())->
GetRequestContext());
// The helper will call us back via OnWebstoreParseSucces or
// OnWebstoreParseFailure.
@@ -274,11 +277,17 @@
void WebstoreInlineInstaller::OnWebstoreParseSuccess(
const SkBitmap& icon,
base::DictionaryValue* manifest) {
+ // Check if the tab has gone away in the meantime.
+ if (!tab_contents()) {
+ CompleteInstall("");
+ return;
+ }
+
manifest_.reset(manifest);
icon_ = icon;
Profile* profile = Profile::FromBrowserContext(
- tab_contents_->browser_context());
+ tab_contents()->browser_context());
ExtensionInstallUI::Prompt prompt(ExtensionInstallUI::INLINE_INSTALL_PROMPT);
prompt.SetInlineInstallWebstoreData(localized_user_count_,
@@ -310,6 +319,12 @@
}
void WebstoreInlineInstaller::InstallUIProceed() {
+ // Check if the tab has gone away in the meantime.
+ if (!tab_contents()) {
+ CompleteInstall("");
+ return;
+ }
+
CrxInstaller::WhitelistEntry* entry = new CrxInstaller::WhitelistEntry;
entry->parsed_manifest.reset(manifest_.get()->DeepCopy());
@@ -320,7 +335,7 @@
GURL install_url(extension_urls::GetWebstoreInstallUrl(
id_, g_browser_process->GetApplicationLocale()));
- NavigationController& controller = tab_contents_->controller();
+ NavigationController& controller = tab_contents()->controller();
// TODO(mihaip): we pretend like the referrer is the gallery in order to pass
// the checks in ExtensionService::IsDownloadFromGallery. We should instead
// pass the real referrer, track that this is an inline install in the
@@ -339,11 +354,24 @@
CompleteInstall(kUserCancelledError);
}
-void WebstoreInlineInstaller::CompleteInstall(const std::string& error) {
- if (error.empty()) {
- delegate_->OnInlineInstallSuccess(install_id_);
- } else {
- delegate_->OnInlineInstallFailure(install_id_, error);
+void WebstoreInlineInstaller::TabContentsDestroyed(TabContents* tab_contents) {
+ // Abort any in-progress fetches.
+ if (webstore_data_url_fetcher_.get()) {
+ webstore_data_url_fetcher_.reset();
+ Release(); // Matches the AddRef in BeginInstall.
}
+}
+
+void WebstoreInlineInstaller::CompleteInstall(const std::string& error) {
+ // Only bother responding if there's still a tab contents to send back the
+ // response to.
+ if (tab_contents()) {
+ if (error.empty()) {
+ delegate_->OnInlineInstallSuccess(install_id_);
+ } else {
+ delegate_->OnInlineInstallFailure(install_id_, error);
+ }
+ }
+
Release(); // Matches the AddRef in BeginInstall.
}
diff --git a/chrome/browser/extensions/webstore_inline_installer.h b/chrome/browser/extensions/webstore_inline_installer.h
index df35496..72ea056 100644
--- a/chrome/browser/extensions/webstore_inline_installer.h
+++ b/chrome/browser/extensions/webstore_inline_installer.h
@@ -13,6 +13,7 @@
#include "base/values.h"
#include "chrome/browser/extensions/extension_install_ui.h"
#include "chrome/browser/extensions/webstore_install_helper.h"
+#include "content/browser/tab_contents/tab_contents_observer.h"
#include "content/common/url_fetcher.h"
#include "googleurl/src/gurl.h"
#include "third_party/skia/include/core/SkBitmap.h"
@@ -24,10 +25,12 @@
// from the webstore, shows the install UI, starts the download once the user
// confirms). Clients must implement the WebstoreInlineInstaller::Delegate
// interface to be notified when the inline install completes (successfully or
-// not).
+// not). The client will not be notified if the TabContents that this install
+// request is attached to goes away.
class WebstoreInlineInstaller
: public base::RefCountedThreadSafe<WebstoreInlineInstaller>,
public ExtensionInstallUI::Delegate,
+ public TabContentsObserver,
public URLFetcher::Delegate,
public WebstoreInstallHelper::Delegate {
public:
@@ -83,9 +86,11 @@
virtual void InstallUIProceed() OVERRIDE;
virtual void InstallUIAbort(bool user_initiated) OVERRIDE;
+ // TabContentsObserver interface implementation.
+ virtual void TabContentsDestroyed(TabContents* tab_contents) OVERRIDE;
+
void CompleteInstall(const std::string& error);
- TabContents* tab_contents_;
int install_id_;
std::string id_;
GURL requestor_url_;