Commit 56992081 authored by Scott Violet's avatar Scott Violet Committed by Commit Bot

favicons: fix manifest crash

This crash happens under the following circumstances:
1. on android.
2. request manifest from WebContents.
3. ~WebContentsImpl

As part of WebContentsImpl deletion any outstanding requests for
manifests are run. The callback scheduled at 2 may trigger running
code in ContentFaviconDriver that attempts to use web_contents().
Problem is at this time WebContentsImpl has already set the
web_contents() of ContentFaviconDriver to null, and we crash.

Fix is to route the callback to ContentFaviconDriver and have
ContentFaviconDriver do nothing if web_contents() is null.

BUG=1114237
TEST=ContentFaviconDriverTest.WebContentsDeletedWithInProgressManifestRequest

Change-Id: I0e88884c1ea7a8ffacb443b65ba09786cf4f54c2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2343718Reviewed-by: default avatarPeter Kotwicz <pkotwicz@chromium.org>
Commit-Queue: Scott Violet <sky@chromium.org>
Cr-Commit-Position: refs/heads/master@{#796889}
parent 3d0a851d
...@@ -19,21 +19,6 @@ ...@@ -19,21 +19,6 @@
#include "ui/gfx/image/image.h" #include "ui/gfx/image/image.h"
namespace favicon { namespace favicon {
namespace {
void ExtractManifestIcons(
ContentFaviconDriver::ManifestDownloadCallback callback,
const GURL& manifest_url,
const blink::Manifest& manifest) {
std::vector<FaviconURL> candidates;
for (const auto& icon : manifest.icons) {
candidates.emplace_back(icon.src, favicon_base::IconType::kWebManifestIcon,
icon.sizes);
}
std::move(callback).Run(candidates);
}
} // namespace
// static // static
void ContentFaviconDriver::CreateForWebContents( void ContentFaviconDriver::CreateForWebContents(
...@@ -80,7 +65,25 @@ ContentFaviconDriver::ContentFaviconDriver(content::WebContents* web_contents, ...@@ -80,7 +65,25 @@ ContentFaviconDriver::ContentFaviconDriver(content::WebContents* web_contents,
FaviconDriverImpl(favicon_service), FaviconDriverImpl(favicon_service),
document_on_load_completed_(false) {} document_on_load_completed_(false) {}
ContentFaviconDriver::~ContentFaviconDriver() { ContentFaviconDriver::~ContentFaviconDriver() = default;
void ContentFaviconDriver::OnDidDownloadManifest(
ManifestDownloadCallback callback,
const GURL& manifest_url,
const blink::Manifest& manifest) {
// ~WebContentsImpl triggers running any pending callbacks for manifests.
// As we're about to be destroyed ignore the request. To do otherwise may
// result in calling back to this and attempting to use the WebContents, which
// will crash.
if (!web_contents())
return;
std::vector<FaviconURL> candidates;
for (const auto& icon : manifest.icons) {
candidates.emplace_back(icon.src, favicon_base::IconType::kWebManifestIcon,
icon.sizes);
}
std::move(callback).Run(candidates);
} }
int ContentFaviconDriver::DownloadImage(const GURL& url, int ContentFaviconDriver::DownloadImage(const GURL& url,
...@@ -97,7 +100,8 @@ int ContentFaviconDriver::DownloadImage(const GURL& url, ...@@ -97,7 +100,8 @@ int ContentFaviconDriver::DownloadImage(const GURL& url,
void ContentFaviconDriver::DownloadManifest(const GURL& url, void ContentFaviconDriver::DownloadManifest(const GURL& url,
ManifestDownloadCallback callback) { ManifestDownloadCallback callback) {
web_contents()->GetManifest( web_contents()->GetManifest(
base::BindOnce(&ExtractManifestIcons, std::move(callback))); base::BindOnce(&ContentFaviconDriver::OnDidDownloadManifest,
base::Unretained(this), std::move(callback)));
} }
bool ContentFaviconDriver::IsOffTheRecord() { bool ContentFaviconDriver::IsOffTheRecord() {
......
...@@ -51,6 +51,11 @@ class ContentFaviconDriver ...@@ -51,6 +51,11 @@ class ContentFaviconDriver
private: private:
friend class content::WebContentsUserData<ContentFaviconDriver>; friend class content::WebContentsUserData<ContentFaviconDriver>;
// Callback when a manifest is downloaded.
void OnDidDownloadManifest(ManifestDownloadCallback callback,
const GURL& manifest_url,
const blink::Manifest& manifest);
// FaviconHandler::Delegate implementation. // FaviconHandler::Delegate implementation.
int DownloadImage(const GURL& url, int DownloadImage(const GURL& url,
int max_image_size, int max_image_size,
......
...@@ -9,9 +9,11 @@ ...@@ -9,9 +9,11 @@
#include "base/macros.h" #include "base/macros.h"
#include "base/run_loop.h" #include "base/run_loop.h"
#include "base/test/bind_test_util.h"
#include "base/test/metrics/histogram_tester.h" #include "base/test/metrics/histogram_tester.h"
#include "components/favicon/core/favicon_client.h" #include "components/favicon/core/favicon_client.h"
#include "components/favicon/core/favicon_handler.h" #include "components/favicon/core/favicon_handler.h"
#include "components/favicon/core/test/favicon_driver_impl_test_helper.h"
#include "components/favicon/core/test/mock_favicon_service.h" #include "components/favicon/core/test/mock_favicon_service.h"
#include "content/public/browser/web_contents_observer.h" #include "content/public/browser/web_contents_observer.h"
#include "content/public/test/navigation_simulator.h" #include "content/public/test/navigation_simulator.h"
...@@ -38,15 +40,16 @@ class ContentFaviconDriverTest : public content::RenderViewHostTestHarness { ...@@ -38,15 +40,16 @@ class ContentFaviconDriverTest : public content::RenderViewHostTestHarness {
const GURL kIconURL = GURL("http://www.google.com/favicon.ico"); const GURL kIconURL = GURL("http://www.google.com/favicon.ico");
ContentFaviconDriverTest() { ContentFaviconDriverTest() {
ContentFaviconDriverTest* t = this;
ON_CALL(favicon_service_, UpdateFaviconMappingsAndFetch(_, _, _, _, _, _)) ON_CALL(favicon_service_, UpdateFaviconMappingsAndFetch(_, _, _, _, _, _))
.WillByDefault([](auto, auto, auto, auto, .WillByDefault([t](auto, auto, auto, auto,
favicon_base::FaviconResultsCallback callback, favicon_base::FaviconResultsCallback callback,
base::CancelableTaskTracker* tracker) { base::CancelableTaskTracker* tracker) {
return tracker->PostTask( return tracker->PostTask(
base::ThreadTaskRunnerHandle::Get().get(), FROM_HERE, base::ThreadTaskRunnerHandle::Get().get(), FROM_HERE,
base::BindOnce( base::BindOnce(&ContentFaviconDriverTest::
std::move(callback), OnCallUpdateFaviconMappingsAndFetch,
std::vector<favicon_base::FaviconRawBitmapResult>())); base::Unretained(t), std::move(callback)));
}); });
ON_CALL(favicon_service_, GetFaviconForPageURL(_, _, _, _, _)) ON_CALL(favicon_service_, GetFaviconForPageURL(_, _, _, _, _))
.WillByDefault([](auto, auto, auto, .WillByDefault([](auto, auto, auto,
...@@ -60,7 +63,7 @@ class ContentFaviconDriverTest : public content::RenderViewHostTestHarness { ...@@ -60,7 +63,7 @@ class ContentFaviconDriverTest : public content::RenderViewHostTestHarness {
}); });
} }
~ContentFaviconDriverTest() override {} ~ContentFaviconDriverTest() override = default;
// content::RenderViewHostTestHarness: // content::RenderViewHostTestHarness:
void SetUp() override { void SetUp() override {
...@@ -85,7 +88,21 @@ class ContentFaviconDriverTest : public content::RenderViewHostTestHarness { ...@@ -85,7 +88,21 @@ class ContentFaviconDriverTest : public content::RenderViewHostTestHarness {
base::RunLoop().RunUntilIdle(); base::RunLoop().RunUntilIdle();
} }
// This is run *after* the callback posted by way of
// UpdateFaviconMappingsAndFetch() is run. It allows hooking processing
// immediately after it.
base::OnceClosure on_did_update_favicon_mappings_and_fetch_;
testing::NiceMock<MockFaviconService> favicon_service_; testing::NiceMock<MockFaviconService> favicon_service_;
private:
// Callback from UpdateFaviconMappingsAndFetch().
void OnCallUpdateFaviconMappingsAndFetch(
favicon_base::FaviconResultsCallback callback) {
std::move(callback).Run({});
if (on_did_update_favicon_mappings_and_fetch_)
std::move(on_did_update_favicon_mappings_and_fetch_).Run();
}
}; };
// Test that a download is initiated when there isn't a favicon in the database // Test that a download is initiated when there isn't a favicon in the database
...@@ -171,5 +188,39 @@ TEST_F(ContentFaviconDriverTest, FaviconUpdateNoLastCommittedEntry) { ...@@ -171,5 +188,39 @@ TEST_F(ContentFaviconDriverTest, FaviconUpdateNoLastCommittedEntry) {
EXPECT_TRUE(driver->favicon_urls().empty()); EXPECT_TRUE(driver->favicon_urls().empty());
} }
// This test verifies a crash doesn't happen during deletion of the
// WebContents. The crash occurred because ~WebContentsImpl would trigger
// running callbacks for manifests. This mean FaviconHandler would be called
// while ContentFaviconDriver::web_contents() was null, which is unexpected and
// crashed. See https://crbug.com/1114237 for more.
TEST_F(ContentFaviconDriverTest,
WebContentsDeletedWithInProgressManifestRequest) {
// Manifests are only downloaded with TOUCH_LARGEST. Force creating this
// handler so code path is exercised on all platforms.
favicon::ContentFaviconDriver* driver =
favicon::ContentFaviconDriver::FromWebContents(web_contents());
FaviconDriverImplTestHelper::RecreateHandlerForType(
driver, FaviconDriverObserver::TOUCH_LARGEST);
// Mimic a page load.
std::vector<blink::mojom::FaviconURLPtr> favicon_urls;
favicon_urls.push_back(blink::mojom::FaviconURL::New(
kIconURL, blink::mojom::FaviconIconType::kTouchIcon, kEmptyIconSizes));
TestFetchFaviconForPage(kPageURL, favicon_urls);
// Trigger downloading a manifest.
base::RunLoop run_loop;
on_did_update_favicon_mappings_and_fetch_ =
base::BindLambdaForTesting([&] { run_loop.Quit(); });
static_cast<content::WebContentsObserver*>(driver)->DidUpdateWebManifestURL(
web_contents()->GetMainFrame(), GURL("http://bad.manifest.com"));
run_loop.Run();
// The request for the manifest is still pending, delete the WebContents,
// which should trigger notifying the callback for the manifest and *not*
// crash.
DeleteContents();
}
} // namespace } // namespace
} // namespace favicon } // namespace favicon
...@@ -6,7 +6,6 @@ ...@@ -6,7 +6,6 @@
#include <memory> #include <memory>
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_util.h" #include "base/strings/string_util.h"
#include "build/build_config.h" #include "build/build_config.h"
#include "components/favicon/core/core_favicon_service.h" #include "components/favicon/core/core_favicon_service.h"
...@@ -38,8 +37,7 @@ FaviconDriverImpl::FaviconDriverImpl(CoreFaviconService* favicon_service) ...@@ -38,8 +37,7 @@ FaviconDriverImpl::FaviconDriverImpl(CoreFaviconService* favicon_service)
} }
} }
FaviconDriverImpl::~FaviconDriverImpl() { FaviconDriverImpl::~FaviconDriverImpl() = default;
}
void FaviconDriverImpl::FetchFavicon(const GURL& page_url, void FaviconDriverImpl::FetchFavicon(const GURL& page_url,
bool is_same_document) { bool is_same_document) {
......
...@@ -51,12 +51,13 @@ class FaviconDriverImpl : public FaviconDriver, ...@@ -51,12 +51,13 @@ class FaviconDriverImpl : public FaviconDriver,
const std::vector<FaviconURL>& candidates, const std::vector<FaviconURL>& candidates,
const GURL& manifest_url); const GURL& manifest_url);
protected:
CoreFaviconService* favicon_service() { return favicon_service_; } CoreFaviconService* favicon_service() { return favicon_service_; }
private: private:
// KeyedService used by FaviconDriverImpl. It may be null, if non-null, it friend class FaviconDriverImplTestHelper;
// must outlive FaviconDriverImpl.
// KeyedService used by FaviconDriverImpl. It may be null during testing,
// but if it is defined, it must outlive the FaviconDriverImpl.
CoreFaviconService* favicon_service_; CoreFaviconService* favicon_service_;
// FaviconHandlers used to download the different kind of favicons. // FaviconHandlers used to download the different kind of favicons.
......
...@@ -6,6 +6,8 @@ static_library("test_support") { ...@@ -6,6 +6,8 @@ static_library("test_support") {
testonly = true testonly = true
sources = [ sources = [
"favicon_driver_impl_test_helper.cc",
"favicon_driver_impl_test_helper.h",
"mock_favicon_service.cc", "mock_favicon_service.cc",
"mock_favicon_service.h", "mock_favicon_service.h",
] ]
......
// 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 "components/favicon/core/test/favicon_driver_impl_test_helper.h"
#include <memory>
#include "components/favicon/core/favicon_driver_impl.h"
namespace favicon {
// static
void FaviconDriverImplTestHelper::RecreateHandlerForType(
FaviconDriverImpl* driver,
FaviconDriverObserver::NotificationIconType type) {
driver->handlers_.clear();
driver->handlers_.push_back(
std::make_unique<FaviconHandler>(driver->favicon_service_, driver, type));
}
} // namespace favicon
// Copyright 2020 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.
#ifndef COMPONENTS_FAVICON_CORE_TEST_FAVICON_DRIVER_IMPL_TEST_HELPER_H_
#define COMPONENTS_FAVICON_CORE_TEST_FAVICON_DRIVER_IMPL_TEST_HELPER_H_
#include "components/favicon/core/favicon_driver_observer.h"
namespace favicon {
class FaviconDriverImpl;
// Test helper for reaching into the internals of FaviconDriverImpl.
class FaviconDriverImplTestHelper {
public:
FaviconDriverImplTestHelper(const FaviconDriverImplTestHelper&) = delete;
FaviconDriverImplTestHelper& operator=(const FaviconDriverImplTestHelper&) =
delete;
FaviconDriverImplTestHelper() = delete;
// Resets |driver->handler_| to a FaviconHandler of type |type|. This should
// be called at a time when there are no outstanding requests.
static void RecreateHandlerForType(
FaviconDriverImpl* driver,
FaviconDriverObserver::NotificationIconType type);
};
} // namespace favicon
#endif // COMPONENTS_FAVICON_CORE_TEST_FAVICON_DRIVER_IMPL_TEST_HELPER_H_
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