diff options
author | johnme <johnme@chromium.org> | 2016-01-14 17:13:28 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2016-01-15 01:14:33 +0000 |
commit | 9ed9388c5b6984439ced7abaad2e0900ba526191 (patch) | |
tree | 4f918bee2e2120c61eb2196becd050a05a332258 | |
parent | 03a9d96a42856ae5e733c3086fdde7d8b2ce10e8 (diff) | |
download | chromium_src-9ed9388c5b6984439ced7abaad2e0900ba526191.zip chromium_src-9ed9388c5b6984439ced7abaad2e0900ba526191.tar.gz chromium_src-9ed9388c5b6984439ced7abaad2e0900ba526191.tar.bz2 |
Disable Web Notifications in Incognito
Requests for notifications (and hence push messaging) permissions in
incognito will be auto-denied after a random 1-2 second delay.
This prevents websites from detecting incognito mode, by observing
that notifications are available in incognito but push messaging is not
(until https://crbug.com/401439 is implemented).
Depends on:
- https://codereview.chromium.org/1442083002
Known caveat: Prevents legitimate use of notifications in incognito :(
BUG=479679,542081
Review URL: https://codereview.chromium.org/1575623002
Cr-Commit-Position: refs/heads/master@{#369644}
18 files changed, 418 insertions, 26 deletions
diff --git a/base/test/test_mock_time_task_runner.cc b/base/test/test_mock_time_task_runner.cc index 5a388f2..655a674 100644 --- a/base/test/test_mock_time_task_runner.cc +++ b/base/test/test_mock_time_task_runner.cc @@ -119,6 +119,10 @@ TestMockTimeTaskRunner::TestMockTimeTaskRunner() : now_(Time::UnixEpoch()), next_task_ordinal_(0) { } +TestMockTimeTaskRunner::TestMockTimeTaskRunner(Time start_time, + TimeTicks start_ticks) + : now_(Time::UnixEpoch()), now_ticks_(start_ticks), next_task_ordinal_(0) {} + TestMockTimeTaskRunner::~TestMockTimeTaskRunner() { } diff --git a/base/test/test_mock_time_task_runner.h b/base/test/test_mock_time_task_runner.h index 316e6a6..7a71e01 100644 --- a/base/test/test_mock_time_task_runner.h +++ b/base/test/test_mock_time_task_runner.h @@ -50,6 +50,9 @@ class TestMockTimeTaskRunner : public SingleThreadTaskRunner { // whose time ticks will start at zero. TestMockTimeTaskRunner(); + // Constructs an instance starting at the given virtual time and time ticks. + TestMockTimeTaskRunner(Time start_time, TimeTicks start_ticks); + // Fast-forwards virtual time by |delta|, causing all tasks with a remaining // delay less than or equal to |delta| to be executed. |delta| must be // non-negative. diff --git a/chrome/browser/notifications/notification_browsertest.cc b/chrome/browser/notifications/notification_browsertest.cc index 19ec359..a120e06 100644 --- a/chrome/browser/notifications/notification_browsertest.cc +++ b/chrome/browser/notifications/notification_browsertest.cc @@ -668,18 +668,6 @@ IN_PROC_BROWSER_TEST_F( EXPECT_EQ(0U, settings.size()); } -IN_PROC_BROWSER_TEST_F(NotificationsTest, TestIncognitoNotification) { - ASSERT_TRUE(embedded_test_server()->Start()); - - // Test notifications in incognito window. - Browser* browser = CreateIncognitoBrowser(); - ui_test_utils::NavigateToURL(browser, GetTestPageURL()); - browser->tab_strip_model()->ActivateTabAt(0, true); - ASSERT_TRUE(RequestAndAcceptPermission(browser)); - CreateSimpleNotification(browser, true); - ASSERT_EQ(1, GetNotificationCount()); -} - IN_PROC_BROWSER_TEST_F(NotificationsTest, TestCloseTabWithPermissionRequestUI) { ASSERT_TRUE(embedded_test_server()->Start()); diff --git a/chrome/browser/notifications/notification_permission_context.cc b/chrome/browser/notifications/notification_permission_context.cc index 096b6ed..9e21d0c1 100644 --- a/chrome/browser/notifications/notification_permission_context.cc +++ b/chrome/browser/notifications/notification_permission_context.cc @@ -4,15 +4,131 @@ #include "chrome/browser/notifications/notification_permission_context.h" +#include <queue> + +#include "base/callback.h" +#include "base/location.h" +#include "base/rand_util.h" +#include "base/single_thread_task_runner.h" +#include "base/thread_task_runner_handle.h" +#include "base/timer/timer.h" #include "chrome/browser/notifications/desktop_notification_profile_util.h" +#include "chrome/browser/permissions/permission_request_id.h" +#include "chrome/browser/profiles/profile.h" #include "components/content_settings/core/common/content_settings_pattern.h" +#include "content/public/browser/browser_thread.h" #include "content/public/browser/permission_type.h" +#include "content/public/browser/render_frame_host.h" +#include "content/public/browser/web_contents_observer.h" +#include "content/public/browser/web_contents_user_data.h" #include "url/gurl.h" +namespace { + +// At most one of these is attached to each WebContents. It allows posting +// delayed tasks whose timer only counts down whilst the WebContents is visible +// (and whose timer is reset whenever the WebContents stops being visible). +class VisibilityTimerTabHelper + : public content::WebContentsObserver, + public content::WebContentsUserData<VisibilityTimerTabHelper> { + public: + ~VisibilityTimerTabHelper() override {} + + // Runs |task| after the WebContents has been visible for a consecutive + // duration of at least |visible_delay|. + void PostTaskAfterVisibleDelay(const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta visible_delay); + + // WebContentsObserver: + void WasShown() override; + void WasHidden() override; + void WebContentsDestroyed() override; + + private: + friend class content::WebContentsUserData<VisibilityTimerTabHelper>; + explicit VisibilityTimerTabHelper(content::WebContents* contents); + + void RunTask(const base::Closure& task); + + bool is_visible_; + std::queue<scoped_ptr<base::Timer>> task_queue_; + + DISALLOW_COPY_AND_ASSIGN(VisibilityTimerTabHelper); +}; + +VisibilityTimerTabHelper::VisibilityTimerTabHelper( + content::WebContents* contents) + : content::WebContentsObserver(contents) { + if (!contents->GetMainFrame()) { + is_visible_ = false; + } else { + switch (contents->GetMainFrame()->GetVisibilityState()) { + case blink::WebPageVisibilityStateHidden: + case blink::WebPageVisibilityStatePrerender: + is_visible_ = false; + break; + case blink::WebPageVisibilityStateVisible: + is_visible_ = true; + break; + } + } +} + +void VisibilityTimerTabHelper::PostTaskAfterVisibleDelay( + const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta visible_delay) { + // Safe to use Unretained, as destroying this will destroy task_queue_, hence + // cancelling all timers. + task_queue_.push(make_scoped_ptr(new base::Timer( + from_here, visible_delay, base::Bind(&VisibilityTimerTabHelper::RunTask, + base::Unretained(this), task), + false /* is_repeating */))); + DCHECK(!task_queue_.back()->IsRunning()); + if (is_visible_ && task_queue_.size() == 1) + task_queue_.front()->Reset(); +} + +void VisibilityTimerTabHelper::WasShown() { + if (!is_visible_ && !task_queue_.empty()) + task_queue_.front()->Reset(); + is_visible_ = true; +} + +void VisibilityTimerTabHelper::WasHidden() { + if (is_visible_ && !task_queue_.empty()) + task_queue_.front()->Stop(); + is_visible_ = false; +} + +void VisibilityTimerTabHelper::WebContentsDestroyed() { + // Delete ourselves, to avoid running tasks after WebContents is destroyed. + web_contents()->RemoveUserData(UserDataKey()); + // |this| has been deleted now. +} + +void VisibilityTimerTabHelper::RunTask(const base::Closure& task) { + DCHECK(is_visible_); + task.Run(); + task_queue_.pop(); + if (!task_queue_.empty()) { + task_queue_.front()->Reset(); + return; + } + web_contents()->RemoveUserData(UserDataKey()); + // |this| has been deleted now. +} + +} // namespace + +DEFINE_WEB_CONTENTS_USER_DATA_KEY(VisibilityTimerTabHelper); + NotificationPermissionContext::NotificationPermissionContext(Profile* profile) : PermissionContextBase(profile, content::PermissionType::NOTIFICATIONS, - CONTENT_SETTINGS_TYPE_NOTIFICATIONS) {} + CONTENT_SETTINGS_TYPE_NOTIFICATIONS), + weak_factory_ui_thread_(this) {} NotificationPermissionContext::~NotificationPermissionContext() {} @@ -23,6 +139,42 @@ void NotificationPermissionContext::ResetPermission( profile(), ContentSettingsPattern::FromURLNoWildcard(requesting_origin)); } +void NotificationPermissionContext::DecidePermission( + content::WebContents* web_contents, + const PermissionRequestID& id, + const GURL& requesting_origin, + const GURL& embedding_origin, + bool user_gesture, + const BrowserPermissionCallback& callback) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + // Notifications permission is always denied in incognito. To prevent sites + // from using that to detect whether incognito mode is active, we deny after a + // random time delay, to simulate a user clicking a bubble/infobar. See also + // ContentSettingsRegistry::Init, which marks notifications as + // INHERIT_IN_INCOGNITO_EXCEPT_ALLOW, and + // PermissionMenuModel::PermissionMenuModel which prevents users from manually + // allowing the permission. + if (profile()->IsOffTheRecord()) { + // Random number of seconds in the range [1.0, 2.0). + double delay_seconds = 1.0 + 1.0 * base::RandDouble(); + VisibilityTimerTabHelper::CreateForWebContents(web_contents); + VisibilityTimerTabHelper::FromWebContents(web_contents) + ->PostTaskAfterVisibleDelay( + FROM_HERE, + base::Bind(&NotificationPermissionContext::NotifyPermissionSet, + weak_factory_ui_thread_.GetWeakPtr(), id, + requesting_origin, embedding_origin, callback, + true /* persist */, CONTENT_SETTING_BLOCK), + base::TimeDelta::FromSecondsD(delay_seconds)); + return; + } + + PermissionContextBase::DecidePermission(web_contents, id, requesting_origin, + embedding_origin, user_gesture, + callback); +} + // Unlike other permission types, granting a notification for a given origin // will not take into account the |embedder_origin|, it will only be based // on the requesting iframe origin. diff --git a/chrome/browser/notifications/notification_permission_context.h b/chrome/browser/notifications/notification_permission_context.h index fb30d22..89323a4 100644 --- a/chrome/browser/notifications/notification_permission_context.h +++ b/chrome/browser/notifications/notification_permission_context.h @@ -28,10 +28,18 @@ class NotificationPermissionContext : public PermissionContextBase { NoSecureOriginRequirement); // PermissionContextBase implementation. + void DecidePermission(content::WebContents* web_contents, + const PermissionRequestID& id, + const GURL& requesting_origin, + const GURL& embedding_origin, + bool user_gesture, + const BrowserPermissionCallback& callback) override; void UpdateContentSetting(const GURL& requesting_origin, const GURL& embedder_origin, ContentSetting content_setting) override; bool IsRestrictedToSecureOrigins() const override; + + base::WeakPtrFactory<NotificationPermissionContext> weak_factory_ui_thread_; }; #endif // CHROME_BROWSER_NOTIFICATIONS_NOTIFICATION_PERMISSION_CONTEXT_H_ diff --git a/chrome/browser/notifications/notification_permission_context_unittest.cc b/chrome/browser/notifications/notification_permission_context_unittest.cc index 778b6f1..ead50bf 100644 --- a/chrome/browser/notifications/notification_permission_context_unittest.cc +++ b/chrome/browser/notifications/notification_permission_context_unittest.cc @@ -5,25 +5,83 @@ #include "chrome/browser/notifications/notification_permission_context.h" #include "base/bind.h" +#include "base/message_loop/message_loop.h" +#include "base/test/test_mock_time_task_runner.h" +#include "base/time/time.h" +#include "chrome/browser/content_settings/host_content_settings_map_factory.h" #include "chrome/browser/notifications/desktop_notification_profile_util.h" +#include "chrome/browser/permissions/permission_request_id.h" #include "chrome/browser/profiles/profile.h" +#include "chrome/test/base/chrome_render_view_host_test_harness.h" #include "chrome/test/base/testing_profile.h" #include "components/content_settings/core/browser/host_content_settings_map.h" +#include "content/public/browser/render_process_host.h" +#include "content/public/browser/web_contents.h" #include "content/public/test/test_browser_thread_bundle.h" #include "testing/gtest/include/gtest/gtest.h" #include "url/gurl.h" +namespace { + +void DoNothing(ContentSetting content_setting) {} + +class TestNotificationPermissionContext : public NotificationPermissionContext { + public: + explicit TestNotificationPermissionContext(Profile* profile) + : NotificationPermissionContext(profile), + permission_set_count_(0), + last_permission_set_persisted_(false), + last_permission_set_setting_(CONTENT_SETTING_DEFAULT) {} + + int permission_set_count() const { return permission_set_count_; } + bool last_permission_set_persisted() const { + return last_permission_set_persisted_; + } + ContentSetting last_permission_set_setting() const { + return last_permission_set_setting_; + } + + ContentSetting GetContentSettingFromMap(const GURL& url_a, + const GURL& url_b) { + return HostContentSettingsMapFactory::GetForProfile(profile()) + ->GetContentSetting(url_a.GetOrigin(), url_b.GetOrigin(), + content_settings_type(), std::string()); + } + + private: + // NotificationPermissionContext: + void NotifyPermissionSet(const PermissionRequestID& id, + const GURL& requesting_origin, + const GURL& embedder_origin, + const BrowserPermissionCallback& callback, + bool persist, + ContentSetting content_setting) override { + permission_set_count_++; + last_permission_set_persisted_ = persist; + last_permission_set_setting_ = content_setting; + NotificationPermissionContext::NotifyPermissionSet( + id, requesting_origin, embedder_origin, callback, persist, + content_setting); + } + + int permission_set_count_; + bool last_permission_set_persisted_; + ContentSetting last_permission_set_setting_; +}; + +class NotificationPermissionContextTest + : public ChromeRenderViewHostTestHarness {}; + +} // namespace + // Web Notification permission requests will completely ignore the embedder // origin. See https://crbug.com/416894. -TEST(NotificationPermissionContextTest, IgnoresEmbedderOrigin) { - content::TestBrowserThreadBundle thread_bundle; - TestingProfile profile; - +TEST_F(NotificationPermissionContextTest, IgnoresEmbedderOrigin) { GURL requesting_origin("https://example.com"); GURL embedding_origin("https://chrome.com"); GURL different_origin("https://foobar.com"); - NotificationPermissionContext context(&profile); + NotificationPermissionContext context(profile()); context.UpdateContentSetting(requesting_origin, embedding_origin, CONTENT_SETTING_ALLOW); @@ -45,13 +103,10 @@ TEST(NotificationPermissionContextTest, IgnoresEmbedderOrigin) { // Web Notifications do not require a secure origin when requesting permission. // See https://crbug.com/404095. -TEST(NotificationPermissionContextTest, NoSecureOriginRequirement) { - content::TestBrowserThreadBundle thread_bundle; - TestingProfile profile; - +TEST_F(NotificationPermissionContextTest, NoSecureOriginRequirement) { GURL origin("http://example.com"); - NotificationPermissionContext context(&profile); + NotificationPermissionContext context(profile()); EXPECT_EQ(CONTENT_SETTING_ASK, context.GetPermissionStatus(origin, origin)); @@ -60,3 +115,143 @@ TEST(NotificationPermissionContextTest, NoSecureOriginRequirement) { EXPECT_EQ(CONTENT_SETTING_ALLOW, context.GetPermissionStatus(origin, origin)); } + +// Tests auto-denial after a time delay in incognito. +TEST_F(NotificationPermissionContextTest, TestDenyInIncognitoAfterDelay) { + TestNotificationPermissionContext permission_context( + profile()->GetOffTheRecordProfile()); + GURL url("https://www.example.com"); + NavigateAndCommit(url); + + const PermissionRequestID id(web_contents()->GetRenderProcessHost()->GetID(), + web_contents()->GetMainFrame()->GetRoutingID(), + -1); + + scoped_refptr<base::SingleThreadTaskRunner> old_task_runner( + base::MessageLoop::current()->task_runner()); + scoped_refptr<base::TestMockTimeTaskRunner> task_runner( + new base::TestMockTimeTaskRunner(base::Time::Now(), + base::TimeTicks::Now())); + base::MessageLoop::current()->SetTaskRunner(task_runner); + + ASSERT_EQ(0, permission_context.permission_set_count()); + ASSERT_FALSE(permission_context.last_permission_set_persisted()); + ASSERT_EQ(CONTENT_SETTING_DEFAULT, + permission_context.last_permission_set_setting()); + + permission_context.RequestPermission( + web_contents(), id, url, true /* user_gesture */, base::Bind(&DoNothing)); + + // Should be blocked after 1-2 seconds, but the timer is reset whenever the + // tab is not visible, so these 500ms never add up to >= 1 second. + for (int n = 0; n < 10; n++) { + web_contents()->WasShown(); + task_runner->FastForwardBy(base::TimeDelta::FromMilliseconds(500)); + web_contents()->WasHidden(); + } + + EXPECT_EQ(0, permission_context.permission_set_count()); + EXPECT_EQ(CONTENT_SETTING_ASK, + permission_context.GetContentSettingFromMap(url, url)); + + // Time elapsed whilst hidden is not counted. + // n.b. This line also clears out any old scheduled timer tasks. This is + // important, because otherwise Timer::Reset (triggered by + // VisibilityTimerTabHelper::WasShown) may choose to re-use an existing + // scheduled task, and when it fires Timer::RunScheduledTask will call + // TimeTicks::Now() (which unlike task_runner->NowTicks(), we can't fake), + // and miscalculate the remaining delay at which to fire the timer. + task_runner->FastForwardBy(base::TimeDelta::FromDays(1)); + + EXPECT_EQ(0, permission_context.permission_set_count()); + EXPECT_EQ(CONTENT_SETTING_ASK, + permission_context.GetContentSettingFromMap(url, url)); + + // Should be blocked after 1-2 seconds. So 500ms is not enough. + web_contents()->WasShown(); + task_runner->FastForwardBy(base::TimeDelta::FromMilliseconds(500)); + + EXPECT_EQ(0, permission_context.permission_set_count()); + EXPECT_EQ(CONTENT_SETTING_ASK, + permission_context.GetContentSettingFromMap(url, url)); + + // But 5*500ms > 2 seconds, so it should now be blocked. + for (int n = 0; n < 4; n++) + task_runner->FastForwardBy(base::TimeDelta::FromMilliseconds(500)); + + EXPECT_EQ(1, permission_context.permission_set_count()); + EXPECT_TRUE(permission_context.last_permission_set_persisted()); + EXPECT_EQ(CONTENT_SETTING_BLOCK, + permission_context.last_permission_set_setting()); + EXPECT_EQ(CONTENT_SETTING_BLOCK, + permission_context.GetContentSettingFromMap(url, url)); + + base::MessageLoop::current()->SetTaskRunner(old_task_runner); +} + +// Tests how multiple parallel permission requests get auto-denied in incognito. +TEST_F(NotificationPermissionContextTest, TestParallelDenyInIncognito) { + TestNotificationPermissionContext permission_context( + profile()->GetOffTheRecordProfile()); + GURL url("https://www.example.com"); + NavigateAndCommit(url); + web_contents()->WasShown(); + + const PermissionRequestID id0(web_contents()->GetRenderProcessHost()->GetID(), + web_contents()->GetMainFrame()->GetRoutingID(), + 0); + const PermissionRequestID id1(web_contents()->GetRenderProcessHost()->GetID(), + web_contents()->GetMainFrame()->GetRoutingID(), + 1); + + scoped_refptr<base::SingleThreadTaskRunner> old_task_runner( + base::MessageLoop::current()->task_runner()); + scoped_refptr<base::TestMockTimeTaskRunner> task_runner( + new base::TestMockTimeTaskRunner(base::Time::Now(), + base::TimeTicks::Now())); + base::MessageLoop::current()->SetTaskRunner(task_runner); + + ASSERT_EQ(0, permission_context.permission_set_count()); + ASSERT_FALSE(permission_context.last_permission_set_persisted()); + ASSERT_EQ(CONTENT_SETTING_DEFAULT, + permission_context.last_permission_set_setting()); + + permission_context.RequestPermission(web_contents(), id0, url, + true /* user_gesture */, + base::Bind(&DoNothing)); + permission_context.RequestPermission(web_contents(), id1, url, + true /* user_gesture */, + base::Bind(&DoNothing)); + + EXPECT_EQ(0, permission_context.permission_set_count()); + EXPECT_EQ(CONTENT_SETTING_ASK, + permission_context.GetContentSettingFromMap(url, url)); + + // Fast forward up to 2.5 seconds. Stop as soon as the first permission + // request is auto-denied. + for (int n = 0; n < 5; n++) { + task_runner->FastForwardBy(base::TimeDelta::FromMilliseconds(500)); + if (permission_context.permission_set_count()) + break; + } + + // Only the first permission request receives a response (crbug.com/577336). + EXPECT_EQ(1, permission_context.permission_set_count()); + EXPECT_TRUE(permission_context.last_permission_set_persisted()); + EXPECT_EQ(CONTENT_SETTING_BLOCK, + permission_context.last_permission_set_setting()); + EXPECT_EQ(CONTENT_SETTING_BLOCK, + permission_context.GetContentSettingFromMap(url, url)); + + // After another 2.5 seconds, the second permission request should also have + // received a response. + task_runner->FastForwardBy(base::TimeDelta::FromMilliseconds(2500)); + EXPECT_EQ(2, permission_context.permission_set_count()); + EXPECT_TRUE(permission_context.last_permission_set_persisted()); + EXPECT_EQ(CONTENT_SETTING_BLOCK, + permission_context.last_permission_set_setting()); + EXPECT_EQ(CONTENT_SETTING_BLOCK, + permission_context.GetContentSettingFromMap(url, url)); + + base::MessageLoop::current()->SetTaskRunner(old_task_runner); +} diff --git a/chrome/browser/ui/cocoa/website_settings/permission_selector_button_unittest.mm b/chrome/browser/ui/cocoa/website_settings/permission_selector_button_unittest.mm index 10690b2..bb3f8d2 100644 --- a/chrome/browser/ui/cocoa/website_settings/permission_selector_button_unittest.mm +++ b/chrome/browser/ui/cocoa/website_settings/permission_selector_button_unittest.mm @@ -25,6 +25,7 @@ class PermissionSelectorButtonTest : public CocoaTest { test_info.type = kTestPermissionType; test_info.setting = CONTENT_SETTING_BLOCK; test_info.source = content_settings::SETTING_SOURCE_USER; + test_info.is_incognito = false; GURL test_url("http://www.google.com"); PermissionMenuModel::ChangeCallback callback = base::Bind( &PermissionSelectorButtonTest::Callback, base::Unretained(this)); diff --git a/chrome/browser/ui/cocoa/website_settings/website_settings_bubble_controller.mm b/chrome/browser/ui/cocoa/website_settings/website_settings_bubble_controller.mm index 7f32a38..a19897b4 100644 --- a/chrome/browser/ui/cocoa/website_settings/website_settings_bubble_controller.mm +++ b/chrome/browser/ui/cocoa/website_settings/website_settings_bubble_controller.mm @@ -1355,6 +1355,8 @@ NSPoint AnchorPointForWindow(NSWindow* parent) { WebsiteSettingsUI::PermissionInfo info; info.type = CONTENT_SETTINGS_TYPE_COOKIES; info.setting = CONTENT_SETTING_ALLOW; + // info.default_setting, info.source, and info.is_incognito have not been set, + // but GetPermissionIcon doesn't use any of those. NSImage* image = WebsiteSettingsUI::GetPermissionIcon(info).ToNSImage(); NSImageView* imageView = [self addImageWithSize:[image size] toView:cookiesView_ diff --git a/chrome/browser/ui/cocoa/website_settings/website_settings_bubble_controller_unittest.mm b/chrome/browser/ui/cocoa/website_settings/website_settings_bubble_controller_unittest.mm index e8a5140..b602bc6 100644 --- a/chrome/browser/ui/cocoa/website_settings/website_settings_bubble_controller_unittest.mm +++ b/chrome/browser/ui/cocoa/website_settings/website_settings_bubble_controller_unittest.mm @@ -224,6 +224,7 @@ class WebsiteSettingsBubbleControllerTest : public CocoaTest { if (info.setting == CONTENT_SETTING_DEFAULT) info.default_setting = kTestDefaultSettings[i]; info.source = kTestSettingSources[i]; + info.is_incognito = false; permission_info_list.push_back(info); } ChosenObjectInfoList chosen_object_info_list; diff --git a/chrome/browser/ui/views/website_settings/website_settings_popup_view.cc b/chrome/browser/ui/views/website_settings/website_settings_popup_view.cc index fd8a371..65fd94f 100644 --- a/chrome/browser/ui/views/website_settings/website_settings_popup_view.cc +++ b/chrome/browser/ui/views/website_settings/website_settings_popup_view.cc @@ -590,6 +590,9 @@ void WebsiteSettingsPopupView::SetCookieInfo( WebsiteSettingsUI::PermissionInfo info; info.type = CONTENT_SETTINGS_TYPE_COOKIES; info.setting = CONTENT_SETTING_ALLOW; + info.is_incognito = + Profile::FromBrowserContext(web_contents_->GetBrowserContext()) + ->IsOffTheRecord(); views::ImageView* icon = new views::ImageView(); const gfx::Image& image = WebsiteSettingsUI::GetPermissionIcon(info); icon->SetImage(image.ToImageSkia()); diff --git a/chrome/browser/ui/views/website_settings/website_settings_popup_view_unittest.cc b/chrome/browser/ui/views/website_settings/website_settings_popup_view_unittest.cc index 33b3939..2dbffee 100644 --- a/chrome/browser/ui/views/website_settings/website_settings_popup_view_unittest.cc +++ b/chrome/browser/ui/views/website_settings/website_settings_popup_view_unittest.cc @@ -161,6 +161,7 @@ TEST_F(WebsiteSettingsPopupViewTest, SetPermissionInfo) { PermissionInfoList list(1); list.back().type = CONTENT_SETTINGS_TYPE_GEOLOCATION; list.back().source = content_settings::SETTING_SOURCE_USER; + list.back().is_incognito = false; EXPECT_EQ(0, api_->permissions_content()->child_count()); diff --git a/chrome/browser/ui/website_settings/permission_bubble_manager.cc b/chrome/browser/ui/website_settings/permission_bubble_manager.cc index 0c657f2..50f2a4f 100644 --- a/chrome/browser/ui/website_settings/permission_bubble_manager.cc +++ b/chrome/browser/ui/website_settings/permission_bubble_manager.cc @@ -110,6 +110,9 @@ void PermissionBubbleManager::AddRequest(PermissionBubbleRequest* request) { .IsSameOriginWith(url::Origin(request->GetRequestingHostname())); // Don't re-add an existing request or one with a duplicate text request. + // TODO(johnme): Instead of dropping duplicate requests, we should queue them + // and eventually run their PermissionGranted/PermissionDenied/Cancelled + // callback (crbug.com/577313). bool same_object = false; if (ExistingRequest(request, requests_, &same_object) || ExistingRequest(request, queued_requests_, &same_object) || diff --git a/chrome/browser/ui/website_settings/permission_menu_model.cc b/chrome/browser/ui/website_settings/permission_menu_model.cc index 9f63ee5..7a6ff0d 100644 --- a/chrome/browser/ui/website_settings/permission_menu_model.cc +++ b/chrome/browser/ui/website_settings/permission_menu_model.cc @@ -58,11 +58,16 @@ PermissionMenuModel::PermissionMenuModel( permission_.type == CONTENT_SETTINGS_TYPE_MOUSELOCK) && url.SchemeIsFile(); + // Notifications does not support CONTENT_SETTING_ALLOW in incognito. + bool allow_disabled_for_notifications = + permission_.is_incognito && + permission_.type == CONTENT_SETTINGS_TYPE_NOTIFICATIONS; // Media only supports CONTENT_SETTTING_ALLOW for secure origins. bool is_media_permission = permission_.type == CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC || permission_.type == CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA; - if ((!is_media_permission || content::IsOriginSecure(url)) && + if (!allow_disabled_for_notifications && + (!is_media_permission || content::IsOriginSecure(url)) && !is_exclusive_access_on_file) { label = l10n_util::GetStringUTF16( IDS_WEBSITE_SETTINGS_MENU_ITEM_ALLOW); diff --git a/chrome/browser/ui/website_settings/permission_menu_model_unittest.cc b/chrome/browser/ui/website_settings/permission_menu_model_unittest.cc index 747e2e3..718f108 100644 --- a/chrome/browser/ui/website_settings/permission_menu_model_unittest.cc +++ b/chrome/browser/ui/website_settings/permission_menu_model_unittest.cc @@ -31,6 +31,7 @@ TEST(PermissionMenuModelTest, TestDefault) { permission.type = CONTENT_SETTINGS_TYPE_COOKIES; permission.setting = CONTENT_SETTING_ALLOW; permission.default_setting = CONTENT_SETTING_ALLOW; + permission.is_incognito = false; PermissionMenuModel model( GURL("http://www.google.com"), permission, callback.callback()); EXPECT_EQ(3, model.GetItemCount()); @@ -45,6 +46,7 @@ TEST(PermissionMenuModelTest, TestDefaultMediaHttp) { permission.type = type; permission.setting = CONTENT_SETTING_ALLOW; permission.default_setting = CONTENT_SETTING_ALLOW; + permission.is_incognito = false; PermissionMenuModel model( GURL("http://www.google.com"), permission, callback.callback()); EXPECT_EQ(2, model.GetItemCount()); @@ -65,6 +67,7 @@ TEST(PermissionMenuModelTest, TestFullscreenMouseLockFileUrl) { permission.type = CONTENT_SETTINGS_TYPE_FULLSCREEN; permission.setting = CONTENT_SETTING_ASK; permission.default_setting = CONTENT_SETTING_ASK; + permission.is_incognito = false; PermissionMenuModel fullscreen_model(GURL("file:///test.html"), permission, callback.callback()); EXPECT_EQ(1, fullscreen_model.GetItemCount()); @@ -80,3 +83,21 @@ TEST(PermissionMenuModelTest, TestFullscreenMouseLockFileUrl) { l10n_util::GetStringUTF16(IDS_WEBSITE_SETTINGS_MENU_ITEM_DEFAULT_ASK), fullscreen_model.GetLabelAt(0)); } + +TEST(PermissionMenuModelTest, TestIncognitoNotifications) { + TestCallback callback; + WebsiteSettingsUI::PermissionInfo permission; + permission.type = CONTENT_SETTINGS_TYPE_NOTIFICATIONS; + permission.setting = CONTENT_SETTING_ASK; + permission.default_setting = CONTENT_SETTING_ASK; + + permission.is_incognito = false; + PermissionMenuModel regular_model(GURL("https://www.google.com"), permission, + callback.callback()); + EXPECT_EQ(3, regular_model.GetItemCount()); + + permission.is_incognito = true; + PermissionMenuModel incognito_model(GURL("https://www.google.com"), + permission, callback.callback()); + EXPECT_EQ(2, incognito_model.GetItemCount()); +} diff --git a/chrome/browser/ui/website_settings/website_settings.cc b/chrome/browser/ui/website_settings/website_settings.cc index dfc92c8..6f817da 100644 --- a/chrome/browser/ui/website_settings/website_settings.cc +++ b/chrome/browser/ui/website_settings/website_settings.cc @@ -699,6 +699,7 @@ void WebsiteSettings::PresentSitePermissions() { } permission_info.source = info.source; + permission_info.is_incognito = profile_->IsOffTheRecord(); if (info.primary_pattern == ContentSettingsPattern::Wildcard() && info.secondary_pattern == ContentSettingsPattern::Wildcard()) { diff --git a/chrome/browser/ui/website_settings/website_settings_ui.cc b/chrome/browser/ui/website_settings/website_settings_ui.cc index 936e5bd..373792e 100644 --- a/chrome/browser/ui/website_settings/website_settings_ui.cc +++ b/chrome/browser/ui/website_settings/website_settings_ui.cc @@ -124,8 +124,8 @@ WebsiteSettingsUI::PermissionInfo::PermissionInfo() : type(CONTENT_SETTINGS_TYPE_DEFAULT), setting(CONTENT_SETTING_DEFAULT), default_setting(CONTENT_SETTING_DEFAULT), - source(content_settings::SETTING_SOURCE_NONE) { -} + source(content_settings::SETTING_SOURCE_NONE), + is_incognito(false) {} WebsiteSettingsUI::ChosenObjectInfo::ChosenObjectInfo( const WebsiteSettings::ChooserUIInfo& ui_info, diff --git a/chrome/browser/ui/website_settings/website_settings_ui.h b/chrome/browser/ui/website_settings/website_settings_ui.h index f284630..0ea8943 100644 --- a/chrome/browser/ui/website_settings/website_settings_ui.h +++ b/chrome/browser/ui/website_settings/website_settings_ui.h @@ -73,6 +73,8 @@ class WebsiteSettingsUI { ContentSetting default_setting; // The settings source e.g. user, extensions, policy, ... . content_settings::SettingSource source; + // Whether the profile is off the record. + bool is_incognito; }; // |ChosenObjectInfo| contains information about a single |object| of a diff --git a/components/content_settings/core/browser/content_settings_registry.cc b/components/content_settings/core/browser/content_settings_registry.cc index c16c783..34510e3 100644 --- a/components/content_settings/core/browser/content_settings_registry.cc +++ b/components/content_settings/core/browser/content_settings_registry.cc @@ -173,6 +173,8 @@ void ContentSettingsRegistry::Init() { ValidSettings(CONTENT_SETTING_ALLOW, CONTENT_SETTING_BLOCK, CONTENT_SETTING_ASK), WebsiteSettingsInfo::REQUESTING_ORIGIN_ONLY_SCOPE, + // See also NotificationPermissionContext::DecidePermission which + // implements additional incognito exceptions. ContentSettingsInfo::INHERIT_IN_INCOGNITO_EXCEPT_ALLOW); Register(CONTENT_SETTINGS_TYPE_FULLSCREEN, "fullscreen", CONTENT_SETTING_ASK, |