blob: 40e62a30def5e8497b8f8afc88394e73c4627df6 [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_CONTENT_SETTINGS_BROWSER_PAGE_SPECIFIC_CONTENT_SETTINGS_H_
#define COMPONENTS_CONTENT_SETTINGS_BROWSER_PAGE_SPECIFIC_CONTENT_SETTINGS_H_
#include <stdint.h>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <variant>
#include <vector>
#include "base/containers/enum_set.h"
#include "base/containers/flat_set.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/scoped_observation.h"
#include "base/timer/timer.h"
#include "build/build_config.h"
#include "components/browsing_data/content/browsing_data_model.h"
#include "components/browsing_data/content/cookie_helper.h"
#include "components/content_settings/common/content_settings_manager.mojom.h"
#include "components/content_settings/core/browser/content_settings_observer.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/content_settings/core/common/content_settings.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "components/privacy_sandbox/canonical_topic.h"
#include "content/public/browser/allow_service_worker_result.h"
#include "content/public/browser/page_user_data.h"
#include "content/public/browser/render_frame_host.h"
#include "net/base/schemeful_site.h"
#include "url/gurl.h"
namespace blink {
class StorageKey;
} // namespace blink
namespace content {
class WebContents;
class WebContentsObserver;
} // namespace content
namespace url {
class Origin;
} // namespace url
namespace content_settings {
enum class SiteDataType {
kUnknown,
kStorage,
kCookies,
kServiceWorker,
kSharedWorker,
kInterestGroup,
kTopic,
kTrustToken,
};
enum class AccessType {
// This value represents situations where by the PSCS doesn't have enough
// information to assess the accurate nature of site data access.
kUnknown,
kRead,
kWrite,
};
// Holds extra details about the site data access deemed useful by PSCS
// observers.
struct AccessDetails {
AccessDetails();
AccessDetails(SiteDataType site_data_type,
AccessType access_type,
GURL url,
bool blocked_by_policy,
bool is_from_primary_page);
~AccessDetails();
bool operator<(const AccessDetails& other) const;
SiteDataType site_data_type = SiteDataType::kUnknown;
AccessType access_type = AccessType::kUnknown;
GURL url;
bool blocked_by_policy = false;
// Specifies whether the access occurred in the primary page.
bool is_from_primary_page = false;
};
// TODO(msramek): Media is storing their state in PageSpecificContentSettings:
// |microphone_camera_state_| without being tied to a single content setting.
// This state is not ideal, potential solution is to save this information via
// content::WebContentsUserData
// This class manages state about permissions, content settings, cookies and
// site data for a specific page (main document and all of its child frames). It
// tracks which content was accessed and which content was blocked. Based on
// this it provides information about which types of content were accessed and
// blocked.
//
// Tracking is done per main document so instances of this class will be deleted
// when the main document is deleted. This can happen after the tab navigates
// away to a new document or when the tab itself is deleted, so you should not
// keep references to objects of this class.
//
// When a page enters the back-forward cache its associated
// PageSpecificContentSettings are not cleared and will be restored along with
// the document when navigating back. These stored instances still listen to
// content settings updates and keep their internal state up to date.
//
// Events tied to a main frame navigation will be associated with the newly
// loaded page once the navigation commits or discarded if it does not.
class PageSpecificContentSettings
: public content_settings::Observer,
public content::PageUserData<PageSpecificContentSettings> {
public:
// Fields describing the current mic/camera state. If a page has attempted to
// access a device, the kXxxAccessed bit will be set. If access was blocked,
// kXxxBlocked will be set.
enum MicrophoneCameraStateFlags {
kMicrophoneAccessed,
kMicrophoneBlocked,
kCameraAccessed,
kCameraBlocked,
kMinValue = kMicrophoneAccessed,
kMaxValue = kCameraBlocked,
};
using MicrophoneCameraState =
base::EnumSet<MicrophoneCameraStateFlags,
MicrophoneCameraStateFlags::kMinValue,
MicrophoneCameraStateFlags::kMaxValue>;
class Delegate {
public:
virtual ~Delegate() = default;
// Called when content settings state changes that might require updating
// the location bar.
virtual void UpdateLocationBar() = 0;
// Gets the pref service for the current web contents.
virtual PrefService* GetPrefs() = 0;
// Gets the settings map for the current web contents.
virtual HostContentSettingsMap* GetSettingsMap() = 0;
// Gets the `BrowsingDataModel::Delegate` for the current profile.
virtual std::unique_ptr<BrowsingDataModel::Delegate>
CreateBrowsingDataModelDelegate() = 0;
// Allows delegate to override content setting rules that will be sent to
// the renderer.
virtual void SetDefaultRendererContentSettingRules(
content::RenderFrameHost* rfh,
RendererContentSettingRules* rules) = 0;
// Allows the delegate to provide additional logic for getting microphone
// and camera state on top of the microphone and camera state at the last
// media stream request.
virtual MicrophoneCameraState GetMicrophoneCameraState() = 0;
// If there is a document picture-in-picture window open, check if the given
// web contents is the opener web contents or child web contents, and return
// the other web contents to be synced.
virtual content::WebContents* MaybeGetSyncedWebContentsForPictureInPicture(
content::WebContents* web_contents) = 0;
// Returns `true` if `web_contents` represents a PiP window. Returns `false`
// otherwise.
virtual bool IsPiPWindow(content::WebContents* web_contents) = 0;
// Notifies the delegate a particular content settings type was allowed for
// the first time on this page.
virtual void OnContentAllowed(ContentSettingsType type) = 0;
// Notifies the delegate a particular content settings type was blocked.
virtual void OnContentBlocked(ContentSettingsType type) = 0;
// Returns `true` if ContentSettingsType is blocked on the system level.
virtual bool IsBlockedOnSystemLevel(ContentSettingsType type) = 0;
// Returns true if `render_frame_host` should be allowlisted for using
// JavaScript.
virtual bool IsFrameAllowlistedForJavaScript(
content::RenderFrameHost* render_frame_host) = 0;
};
// Classes that want to be notified about site data events must implement
// this abstract class and add themselves as observer to the
// |PageSpecificContentSettings|.
class SiteDataObserver {
public:
explicit SiteDataObserver(content::WebContents* web_contents);
SiteDataObserver(const SiteDataObserver&) = delete;
SiteDataObserver& operator=(const SiteDataObserver&) = delete;
virtual ~SiteDataObserver();
// Called whenever site data is accessed.
virtual void OnSiteDataAccessed(const AccessDetails& access_details) = 0;
// Called whenever this page is loaded via a redirect with stateful bounces.
virtual void OnStatefulBounceDetected() = 0;
content::WebContents* web_contents() { return web_contents_; }
// Called when the WebContents is destroyed; nulls out
// the local reference.
void WebContentsDestroyed();
private:
raw_ptr<content::WebContents, DanglingUntriaged> web_contents_;
};
class PermissionUsageObserver : public base::CheckedObserver {
public:
virtual void OnPermissionUsageChange() = 0;
};
PageSpecificContentSettings(const PageSpecificContentSettings&) = delete;
PageSpecificContentSettings& operator=(const PageSpecificContentSettings&) =
delete;
~PageSpecificContentSettings() override;
static void CreateForWebContents(content::WebContents* web_contents,
std::unique_ptr<Delegate> delegate);
static void DeleteForWebContentsForTest(content::WebContents* web_contents);
// Returns the object given a RenderFrameHost ids. Returns nullptr if the
// frame no longer exist or there are no PageSpecificContentSettings attached
// to the document.
static PageSpecificContentSettings* GetForFrame(
const content::GlobalRenderFrameHostId& id);
// Returns the object given a RenderFrameHost. Returns nullptr if the frame
// is nullptr or there are no PageSpecificContentSettings attached to the
// document.
static PageSpecificContentSettings* GetForFrame(
content::RenderFrameHost* rfh);
// Returns the Delegate that was associated to |web_contents| in
// CreateForWebContents. Null if CreateForWebContents was not called for
// |web_contents|.
static PageSpecificContentSettings::Delegate* GetDelegateForWebContents(
content::WebContents* web_contents);
static void StorageAccessed(
mojom::ContentSettingsManager::StorageType storage_type,
std::variant<content::GlobalRenderFrameHostToken,
content::GlobalRenderFrameHostId> frame_id,
const blink::StorageKey& storage_key,
bool blocked_by_policy);
static void BrowsingDataAccessed(content::RenderFrameHost* rfh,
BrowsingDataModel::DataKey data_key,
BrowsingDataModel::StorageType storage_type,
bool blocked);
// Called when content access is blocked in the renderer process.
static void ContentBlocked(
const content::GlobalRenderFrameHostToken& frame_token,
ContentSettingsType type);
// Called when a specific Shared Worker was accessed.
static void SharedWorkerAccessed(
int render_process_id,
int render_frame_id,
const GURL& worker_url,
const std::string& name,
const blink::StorageKey& storage_key,
const blink::mojom::SharedWorkerSameSiteCookies same_site_cookies,
bool blocked_by_policy);
// Called when |api_origin| attempts to join an interest group via the
// Interest Group API.
static void InterestGroupJoined(content::RenderFrameHost* rfh,
const url::Origin& api_origin,
bool blocked_by_policy);
// Called when |api_origin| attempts to access browsing topics.
static void TopicAccessed(content::RenderFrameHost* rfh,
const url::Origin& api_origin,
bool blocked_by_policy,
privacy_sandbox::CanonicalTopic topic);
// Called when notifications are accessed on `rfh`.
static void NotificationsAccessed(content::RenderFrameHost* rfh,
bool blocked);
static content::WebContentsObserver* GetWebContentsObserverForTest(
content::WebContents* web_contents);
// Returns a WeakPtr to this instance. Given that PageSpecificContentSettings
// instances are tied to a page it is generally unsafe to store these
// references, instead a WeakPtr should be used instead.
base::WeakPtr<PageSpecificContentSettings> AsWeakPtr() {
return weak_factory_.GetWeakPtr();
}
// Add/remove observer.
void AddPermissionUsageObserver(PermissionUsageObserver* observer);
void RemovePermissionUsageObserver(PermissionUsageObserver* observer);
// Changes the |content_blocked_| entry for popups.
void ClearPopupsBlocked();
// Called when audio has been blocked on the page.
void OnAudioBlocked();
// Records one additional stateful bounce during the navigation that led to
// this page.
void IncrementStatefulBounceCount();
// Returns whether a particular kind of content has been blocked for this
// page.
bool IsContentBlocked(ContentSettingsType content_type) const;
// Returns whether a particular kind of content has been allowed. Currently
// only tracks cookies.
bool IsContentAllowed(ContentSettingsType content_type) const;
// Returns a map from sites that requested |content_setting| to whether the
// permission was granted. This method is only supported for permissions that
// are scoped to sites and apply to embedded content, e.g. StorageAccess.
std::map<net::SchemefulSite, /*is_allowed*/ bool> GetTwoSiteRequests(
ContentSettingsType content_type);
const GURL& media_stream_access_origin() const {
return media_stream_access_origin_;
}
bool camera_was_just_granted_on_site_level() {
return camera_was_just_granted_on_site_level_;
}
bool mic_was_just_granted_on_site_level() {
return mic_was_just_granted_on_site_level_;
}
bool geolocation_was_just_granted_on_site_level() {
return geolocation_was_just_granted_on_site_level_;
}
// Called when notifications permission was auto-denied because the system
// level permission for a PWA was previously denied. This is currently only
// possible/used on macOS.
void SetNotificationsWasDeniedBecauseOfSystemPermission();
bool notifications_was_denied_because_of_system_permission() {
return notifications_was_denied_because_of_system_permission_;
}
// Returns the state of the camera and microphone usage.
// The return value always includes all active media capture devices, on top
// of the devices from the last request.
MicrophoneCameraState GetMicrophoneCameraState() const;
// Returns whether the camera or microphone permission or media device setting
// has changed since the last permission request.
bool IsMicrophoneCameraStateChanged() const;
int stateful_bounce_count() const { return stateful_bounce_count_; }
BrowsingDataModel* allowed_browsing_data_model() const {
return allowed_browsing_data_model_.get();
}
BrowsingDataModel* blocked_browsing_data_model() const {
return blocked_browsing_data_model_.get();
}
void OnContentBlocked(ContentSettingsType type);
void OnContentAllowed(ContentSettingsType type);
// Call when a two-site permission was prompted or modified in order to
// display a ContentSettingsImageModel icon.
void OnTwoSitePermissionChanged(ContentSettingsType type,
net::SchemefulSite requesting_site,
ContentSetting content_setting);
void OnInterestGroupJoined(const url::Origin& api_origin,
bool blocked_by_policy);
void OnTopicAccessed(const url::Origin& api_origin,
bool blocked_by_policy,
privacy_sandbox::CanonicalTopic topic);
void OnTrustTokenAccessed(const url::Origin& api_origin, bool blocked);
void OnBrowsingDataAccessed(BrowsingDataModel::DataKey data_key,
BrowsingDataModel::StorageType storage_type,
bool blocked,
content::Page* originating_page = nullptr);
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_WIN)
void OnProtectedMediaIdentifierPermissionSet(const GURL& requesting_frame,
bool allowed);
#endif
// This method is called to update the status about the microphone and
// camera stream access.
void OnMediaStreamPermissionSet(
const GURL& request_origin,
MicrophoneCameraState new_microphone_camera_state);
// |originating_page| is non-null when it differs from page(), which happens
// when an embedding page's PSCS is notified of an access that happens in an
// embedded page (through |MaybeUpdateParent|).
void OnCookiesAccessed(const content::CookieAccessDetails& details,
content::Page* originating_page = nullptr);
void OnServiceWorkerAccessed(const GURL& scope,
const blink::StorageKey& storage_key,
content::AllowServiceWorkerResult allowed,
content::Page* originating_page = nullptr);
// Block all content. Used for testing content setting bubbles.
void BlockAllContentForTesting();
// Stores content settings changed by the user via PageInfo.
void ContentSettingChangedViaPageInfo(ContentSettingsType type);
// Returns true if the user changed the given ContentSettingsType via PageInfo
// since the last navigation.
bool HasContentSettingChangedViaPageInfo(ContentSettingsType type) const;
// Returns true if the user was joined to an interest group and if the page
// is the joining origin.
bool HasJoinedUserToInterestGroup() const;
// Returns true if the page has accessed the Topics API.
bool HasAccessedTopics() const;
// Returns the topics that were accessed by this page.
std::vector<privacy_sandbox::CanonicalTopic> GetAccessedTopics() const;
// Runs any queued updates in |updates_queued_during_prerender_|, should be
// called after the page activates.
void OnPrerenderingPageActivation();
// This method is called when audio or video capturing is started or finished.
void OnCapturingStateChanged(ContentSettingsType type, bool is_capturing);
#if BUILDFLAG(IS_CHROMEOS)
// This method is called every time when device capabilities (currently, only
// Smart Cards) are used to communicate with a device. Because device
// connections can be long-lived, this method may be called multiple times
// during a connection.
void OnDeviceUsed(ContentSettingsType type);
// This is called when the last connection from this page to devices of `type`
// is closed.
void OnLastDeviceConnectionLost(ContentSettingsType type);
#endif
// Returns true if a page is currently using a feature gated behind `type`
// permission. Returns false otherwise.
bool IsInUse(ContentSettingsType type) const;
#if BUILDFLAG(IS_CHROMEOS)
// This is specific to the object based content settings (for now, just smart
// cards) - they should show for 15 more seconds after `IsInUse` turns false.
bool ShouldShowDeviceInUseIndicator(ContentSettingsType type) const;
#endif
// Returns a time of last usage of a feature gated behind `type` permission.
// Returns base::Time() if `type` was not used in the last 24 hours.
const base::Time GetLastUsedTime(ContentSettingsType type) const;
// This method is called when audio or video activity indicator is opened.
void OnActivityIndicatorBubbleOpened(ContentSettingsType type);
// This method is called when audio or video activity indicator is closed.
void OnActivityIndicatorBubbleClosed(ContentSettingsType type);
// Returns `true` if an activity indicator is displaying for
// `ContentSettingsType`. Returns `false` otherwise.
bool IsIndicatorVisible(ContentSettingsType type) const;
// Save `ContentSettingsType` to a set of currently displaying activity
// indicators.
void OnPermissionIndicatorShown(ContentSettingsType type);
// Remove `ContentSettingsType` from a set of currently displaying activity
// indicators.
void OnPermissionIndicatorHidden(ContentSettingsType type);
// If permission requests need to be cleaned up due to a page refresh, PSCS
// should be temporarily frozen to prevent unnecessary update of activity
// indicators.
void OnPermissionRequestCleanupStart() { freeze_indicators_ = true; }
void OnPermissionRequestCleanupEnd() { freeze_indicators_ = false; }
// This method resets a media blocked state for `type`. If `update_indicators`
// is true, then it will try to update activity indicators in the location
// bar.
void ResetMediaBlockedState(ContentSettingsType type, bool update_indicators);
// Called to update the location bar when a site first registers for automatic
// picture-in-picture.
void OnRegisteredForAutoPictureInPictureChanged();
void set_media_stream_access_origin_for_testing(const GURL& url) {
media_stream_access_origin_ = url;
}
void set_last_used_time_for_testing(ContentSettingsType type,
base::Time time) {
last_used_time_[type] = time;
}
std::map<ContentSettingsType, base::OneShotTimer>&
get_media_blocked_indicator_timer_for_testing() {
return media_blocked_indicator_timer_;
}
std::map<ContentSettingsType, base::OneShotTimer>&
get_indicators_hiding_delay_timer_for_testing() {
return indicators_hiding_delay_timer_;
}
private:
friend class content::PageUserData<PageSpecificContentSettings>;
struct PendingUpdates {
PendingUpdates();
~PendingUpdates();
std::vector<base::OnceClosure> delegate_updates;
bool site_data_accessed = false;
AccessDetails access_details;
};
explicit PageSpecificContentSettings(content::Page& page, Delegate* delegate);
// Updates `microphone_camera_state_` after audio/video is started/finished.
void OnCapturingStateChangedInternal(ContentSettingsType type,
bool is_capturing);
// This method is called when a camera and/or mic blocked indicator is
// displayed.
void StartBlockedIndicatorTimer(ContentSettingsType type);
// content_settings::Observer implementation.
void OnContentSettingChanged(const ContentSettingsPattern& primary_pattern,
const ContentSettingsPattern& secondary_pattern,
ContentSettingsType content_type) override;
// Clears settings changed by the user via PageInfo since the last navigation.
void ClearContentSettingsChangedViaPageInfo();
bool IsPagePrerendering() const;
bool IsEmbeddedPage() const;
// Delays the call of the delegate method if the page is currently
// prerendering until the page is activated; directly calls the method
// otherwise.
template <typename DelegateMethod, typename... Args>
void NotifyDelegate(DelegateMethod method, Args... args) {
if (IsEmbeddedPage()) {
return;
}
if (IsPagePrerendering()) {
DCHECK(updates_queued_during_prerender_);
updates_queued_during_prerender_->delegate_updates.emplace_back(
base::BindOnce(method, base::Unretained(delegate_), args...));
return;
}
(*delegate_.*method)(args...);
}
// Used to notify the parent page's PSCS of a content access.
template <typename PSCSMethod, typename... Args>
void MaybeUpdateParent(PSCSMethod method, Args... args) {
if (IsEmbeddedPage()) {
PageSpecificContentSettings* pscs =
PageSpecificContentSettings::GetForFrame(
page().GetMainDocument().GetParentOrOuterDocument());
DCHECK(pscs);
(*pscs.*method)(args...);
}
}
// Notifies observers. Like |NotifyDelegate|, the notification is delayed for
// prerendering pages until the page is activated. Embedded pages will not
// notify observers directly and rely on the outermost page to do so.
void MaybeNotifySiteDataObservers(const AccessDetails& access_details);
// Tells the delegate to update the location bar. This method is a no-op if
// the page is currently prerendering or is embedded.
void MaybeUpdateLocationBar();
content::WebContents* GetWebContents() const;
// Document picture-in-picture allows changing content settings in both the
// browser window and the PiP window. When the settings is changed in one
// place, return the settings in another place to be synced as well. We should
// update settings in either place at most once, so we will avoid getting into
// deadlock by using |is_updating_synced_pscs_|.
PageSpecificContentSettings* MaybeGetSyncedSettingsForPictureInPicture();
// An auto reset variable to make sure we do not get into deadlock when
// updating synced PageSpecificContentSettings for the document
// picture-in-picture case.
bool is_updating_synced_pscs_ = false;
// If `false` PSCS is allowed to save a new blocked state for an activity
// indicator. If `true` PSCS is frozen and a new blocked state will be
// ignored. This variable is controlled by PermissionRequestManager to prevent
// showing indicators after requests were cleaned up due to a new navigation.
bool freeze_indicators_ = false;
raw_ptr<Delegate> delegate_;
struct ContentSettingsStatus {
bool blocked;
bool allowed;
};
// Stores which content setting types actually have blocked content.
std::map<ContentSettingsType, ContentSettingsStatus> content_settings_status_;
// Stores embedded sites that requested a permission. Only applies to
// permissions that are scoped to two sites, e.g. StorageAccess.
std::map<ContentSettingsType, std::map<net::SchemefulSite, bool>>
content_settings_two_site_requests_;
// Profile-bound, this will outlive this class (which is WebContents bound).
raw_ptr<HostContentSettingsMap> map_;
// Stores the count of stateful bounces during the navigation that led to this
// page.
int stateful_bounce_count_ = 0u;
std::unique_ptr<BrowsingDataModel> allowed_browsing_data_model_;
std::unique_ptr<BrowsingDataModel> blocked_browsing_data_model_;
// The origin of the media stream request. Note that we only support handling
// settings for one request per tab. The latest request's origin will be
// stored here. http://crbug.com/259794
// TODO(crbug.com/40276922): Remove `media_stream_access_origin_` and
// calculate a proper origin internaly.
GURL media_stream_access_origin_;
// The microphone and camera state at the last media stream request.
MicrophoneCameraState microphone_camera_state_;
// Contains URLs which attempted to join interest groups. Note: The UI will
// only currently show the top frame as having attempted to join.
std::vector<url::Origin> allowed_interest_group_api_;
std::vector<url::Origin> blocked_interest_group_api_;
// Contains topics that were accessed by this page.
base::flat_set<privacy_sandbox::CanonicalTopic> accessed_topics_;
// The Geolocation, camera, and/or microphone permission was granted to this
// origin from a permission prompt that was triggered by the currently active
// document.
bool camera_was_just_granted_on_site_level_ = false;
bool mic_was_just_granted_on_site_level_ = false;
bool geolocation_was_just_granted_on_site_level_ = false;
bool notifications_was_denied_because_of_system_permission_ = false;
// The time when the media indicator was displayed.
base::TimeTicks media_indicator_time_;
// Stores timers for delaying hiding an activity indicators.
std::map<ContentSettingsType, base::OneShotTimer>
indicators_hiding_delay_timer_;
// Stores last used time when a permission-gate feature is no longer in use.
std::map<ContentSettingsType, base::Time> last_used_time_;
// Stores `ContentSettingsType` that is currently used by a page.
std::set<ContentSettingsType> in_use_;
// A timer to removed a blocked media indicator.
std::map<ContentSettingsType, base::OneShotTimer>
media_blocked_indicator_timer_;
// Stores `ContentSettingsType` that is currently displaying. It is used only
// for the Left-Hand Side indicators.
std::set<ContentSettingsType> visible_indicators_;
// Observer to watch for content settings changed.
base::ScopedObservation<HostContentSettingsMap, content_settings::Observer>
observation_{this};
// Stores content settings changed by the user via page info since the last
// navigation. Used to determine whether to display the settings in page info.
std::set<ContentSettingsType> content_settings_changed_via_page_info_;
// Calls to |delegate_| and SiteDataObservers that have been queued up while
// the page is prerendering. These calls are run when the page is activated.
std::unique_ptr<PendingUpdates> updates_queued_during_prerender_;
base::ObserverList<PermissionUsageObserver> permission_usage_observers_;
PAGE_USER_DATA_KEY_DECL();
base::WeakPtrFactory<PageSpecificContentSettings> weak_factory_{this};
};
} // namespace content_settings
#endif // COMPONENTS_CONTENT_SETTINGS_BROWSER_PAGE_SPECIFIC_CONTENT_SETTINGS_H_