diff --git a/chrome/browser/ui/webui/ash/arc_graphics_tracing/arc_graphics_tracing_ui.cc b/chrome/browser/ui/webui/ash/arc_graphics_tracing/arc_graphics_tracing_ui.cc index 6ca28e36dd29cb..e2b6e78b9b48dd 100644 --- a/chrome/browser/ui/webui/ash/arc_graphics_tracing/arc_graphics_tracing_ui.cc +++ b/chrome/browser/ui/webui/ash/arc_graphics_tracing/arc_graphics_tracing_ui.cc @@ -40,7 +40,8 @@ void CreateAndAddOverviewDataSource(Profile* profile) { source->AddResourcePath(kArcTracingUiJsPath, IDR_ARC_TRACING_UI_JS); source->OverrideContentSecurityPolicy( network::mojom::CSPDirectiveName::ScriptSrc, - "script-src chrome://resources 'self';"); + "script-src chrome://resources 'self' 'unsafe-inline' https://d3js.org/;"); + base::Value::Dict localized_strings; const std::string& app_locale = g_browser_process->GetApplicationLocale(); diff --git a/chrome/browser/ui/webui/ash/arc_power_control/arc_power_control_ui.cc b/chrome/browser/ui/webui/ash/arc_power_control/arc_power_control_ui.cc index 28bc1b72107ae6..398f8db2cf8376 100644 --- a/chrome/browser/ui/webui/ash/arc_power_control/arc_power_control_ui.cc +++ b/chrome/browser/ui/webui/ash/arc_power_control/arc_power_control_ui.cc @@ -41,7 +41,7 @@ void CreateAndAddPowerControlDataSource(Profile* profile) { source->AddResourcePath(kArcTracingUiJsPath, IDR_ARC_TRACING_UI_JS); source->OverrideContentSecurityPolicy( network::mojom::CSPDirectiveName::ScriptSrc, - "script-src chrome://resources 'self';"); + "script-src chrome://resources 'self' 'unsafe-inline' https://d3js.org/;"); base::Value::Dict localized_strings; const std::string& app_locale = g_browser_process->GetApplicationLocale(); diff --git a/chrome/browser/ui/webui/ash/cros_components_browsertest.cc b/chrome/browser/ui/webui/ash/cros_components_browsertest.cc index ea090d1bf482d9..beb2b6b20f8a35 100644 --- a/chrome/browser/ui/webui/ash/cros_components_browsertest.cc +++ b/chrome/browser/ui/webui/ash/cros_components_browsertest.cc @@ -40,7 +40,7 @@ class CrosComponentsUI : public content::WebUIController { source->OverrideContentSecurityPolicy( network::mojom::CSPDirectiveName::ScriptSrc, - "script-src chrome://resources 'self' 'unsafe-inline';"); + "script-src chrome://resources 'self' 'unsafe-inline' https://d3js.org/;"); source->OverrideContentSecurityPolicy( network::mojom::CSPDirectiveName::TrustedTypes, diff --git a/chrome/browser/ui/webui/conflicts/conflicts_ui.cc b/chrome/browser/ui/webui/conflicts/conflicts_ui.cc index b8b91f3c8e77fe..c73807fb9d0650 100644 --- a/chrome/browser/ui/webui/conflicts/conflicts_ui.cc +++ b/chrome/browser/ui/webui/conflicts/conflicts_ui.cc @@ -24,7 +24,7 @@ void CreateAndAddConflictsUIHTMLSource(Profile* profile) { profile, chrome::kChromeUIConflictsHost); source->OverrideContentSecurityPolicy( network::mojom::CSPDirectiveName::ScriptSrc, - "script-src chrome://resources 'self';"); + "script-src chrome://resources 'self' 'unsafe-inline' https://d3js.org/;"); source->OverrideContentSecurityPolicy( network::mojom::CSPDirectiveName::TrustedTypes, diff --git a/chrome/browser/ui/webui/interstitials/interstitial_ui.cc b/chrome/browser/ui/webui/interstitials/interstitial_ui.cc index 0f869e7ebc80a1..840064c62e6472 100644 --- a/chrome/browser/ui/webui/interstitials/interstitial_ui.cc +++ b/chrome/browser/ui/webui/interstitials/interstitial_ui.cc @@ -508,7 +508,7 @@ std::string InterstitialHTMLSource::GetContentSecurityPolicy( const network::mojom::CSPDirectiveName directive) { if (directive == network::mojom::CSPDirectiveName::ScriptSrc) { // 'unsafe-inline' is added to script-src. - return "script-src chrome://resources 'self' 'unsafe-inline';"; + return "script-src chrome://resources 'self' 'unsafe-inline' https://d3js.org/;"; } else if (directive == network::mojom::CSPDirectiveName::StyleSrc) { return "style-src 'self' 'unsafe-inline';"; } else if (directive == network::mojom::CSPDirectiveName::ImgSrc) { diff --git a/chrome/browser/ui/webui/nacl_ui.cc b/chrome/browser/ui/webui/nacl_ui.cc index 04c797cba62df5..b22321766b1471 100644 --- a/chrome/browser/ui/webui/nacl_ui.cc +++ b/chrome/browser/ui/webui/nacl_ui.cc @@ -57,7 +57,8 @@ void CreateAndAddNaClUIHTMLSource(Profile* profile) { profile, chrome::kChromeUINaClHost); source->OverrideContentSecurityPolicy( network::mojom::CSPDirectiveName::ScriptSrc, - "script-src chrome://resources 'self';"); + "script-src chrome://resources 'self' 'unsafe-inline' https://d3js.org/;"); + source->OverrideContentSecurityPolicy( network::mojom::CSPDirectiveName::TrustedTypes, "trusted-types polymer-html-literal " diff --git a/chrome/test/base/ash/web_ui_browser_test.cc b/chrome/test/base/ash/web_ui_browser_test.cc index 358168a784e566..3a827b38567c4d 100644 --- a/chrome/test/base/ash/web_ui_browser_test.cc +++ b/chrome/test/base/ash/web_ui_browser_test.cc @@ -337,7 +337,7 @@ class MockWebUIDataSource : public content::URLDataSource { std::string GetContentSecurityPolicy( const network::mojom::CSPDirectiveName directive) override { if (directive == network::mojom::CSPDirectiveName::ScriptSrc) { - return "script-src chrome://resources 'self';"; + return "script-src chrome://resources 'self' 'unsafe-inline' https://d3js.org/;"; } else if (directive == network::mojom::CSPDirectiveName::RequireTrustedTypesFor || directive == network::mojom::CSPDirectiveName::TrustedTypes) { diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn index 06acc9080db26a..7c8f8b2ed0bd93 100644 --- a/content/browser/BUILD.gn +++ b/content/browser/BUILD.gn @@ -493,6 +493,8 @@ source_set("browser") { "attribution_reporting/store_source_result_internal.h", "attribution_reporting/stored_source.cc", "attribution_reporting/stored_source.h", + "attribution_reporting/stored_filter.cc", + "attribution_reporting/stored_filter.h", "audio/audio_service.cc", "background_fetch/background_fetch_context.cc", "background_fetch/background_fetch_context.h", diff --git a/content/browser/attribution_reporting/attribution_internals.mojom b/content/browser/attribution_reporting/attribution_internals.mojom index 8a239667fc7b6e..a2a181e0109103 100644 --- a/content/browser/attribution_reporting/attribution_internals.mojom +++ b/content/browser/attribution_reporting/attribution_internals.mojom @@ -126,6 +126,17 @@ struct WebUISource { Attributability attributability; }; +struct WebUIFilter { + uint64 id; + uint64 time; + uint64 epoch; + double consumed_budget; + double initial_budget; + url.mojom.Origin destination_origin; + url.mojom.Origin source_origin; + uint64 source_time; +}; + struct WebUIRegistration { double time; url.mojom.Origin context_origin; @@ -169,6 +180,10 @@ interface Observer { // Called when the sources in storage changed, indicating that the observer // should call `Handler::GetActiveSources()`. OnSourcesChanged(); + + // Called when the filters in storage changed, indicating that the observer + // should call `Handler::GetFilters()`. + OnFiltersChanged(); // Called when the reports in storage changed, indicating that the observer // should call `Handler::GetReports()`. @@ -207,6 +222,9 @@ interface Handler { // due to reaching policy limits. GetActiveSources() => (array sources); + // Returns all filters contained in storage + GetFilters() => (array filters); + // Returns all reports contained in storage, including those that are actively // being sent. GetReports() => (array reports); diff --git a/content/browser/attribution_reporting/attribution_internals_handler_impl.cc b/content/browser/attribution_reporting/attribution_internals_handler_impl.cc index cba9c38723a8fa..f745a076a8facb 100644 --- a/content/browser/attribution_reporting/attribution_internals_handler_impl.cc +++ b/content/browser/attribution_reporting/attribution_internals_handler_impl.cc @@ -12,6 +12,7 @@ #include #include +#include "base/logging.h" #include "base/check.h" #include "base/check_op.h" #include "base/command_line.h" @@ -44,6 +45,7 @@ #include "content/browser/attribution_reporting/send_result.h" #include "content/browser/attribution_reporting/storable_source.h" #include "content/browser/attribution_reporting/stored_source.h" +#include "content/browser/attribution_reporting/stored_filter.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/content_browser_client.h" #include "content/public/browser/web_contents.h" @@ -96,6 +98,27 @@ attribution_internals::mojom::WebUISourcePtr WebUISource( source.debug_cookie_set(), attributability); } +void ForwardFiltersToWebUI( + attribution_internals::mojom::Handler::GetFiltersCallback web_ui_callback, + std::vector filters) { + std::vector web_ui_filters; + web_ui_filters.reserve(filters.size()); + + for (const StoredFilter& filter : filters) { + web_ui_filters.push_back(attribution_internals::mojom::WebUIFilter::New( + filter.id(), + filter.time(), + filter.epoch(), + filter.consumed_budget(), + filter.initial_budget(), + filter.destination_origin(), + filter.source_origin(), + filter.source_time())); + } + + std::move(web_ui_callback).Run(std::move(web_ui_filters)); +} + void ForwardSourcesToWebUI( attribution_internals::mojom::Handler::GetActiveSourcesCallback web_ui_callback, @@ -277,6 +300,18 @@ void AttributionInternalsHandlerImpl::GetActiveSources( } } +void AttributionInternalsHandlerImpl::GetFilters( + attribution_internals::mojom::Handler::GetFiltersCallback callback) { + LOG(INFO) << " #### #### content/browser/attribution_reporting/attribution_internals_handler_impl.cc: GetFilters"; + if (AttributionManager* manager = + AttributionManager::FromWebContents(web_ui_->GetWebContents())) { + manager->GetFiltersForWebUI( + base::BindOnce(&ForwardFiltersToWebUI, std::move(callback))); + } else { + std::move(callback).Run({}); + } +} + void AttributionInternalsHandlerImpl::GetReports( attribution_internals::mojom::Handler::GetReportsCallback callback) { if (AttributionManager* manager = @@ -317,6 +352,10 @@ void AttributionInternalsHandlerImpl::OnSourcesChanged() { observer_->OnSourcesChanged(); } +void AttributionInternalsHandlerImpl::OnFiltersChanged() { + observer_->OnFiltersChanged(); +} + void AttributionInternalsHandlerImpl::OnReportsChanged() { observer_->OnReportsChanged(); } diff --git a/content/browser/attribution_reporting/attribution_internals_handler_impl.h b/content/browser/attribution_reporting/attribution_internals_handler_impl.h index 8ac1f73ba3fd41..4134ee51784656 100644 --- a/content/browser/attribution_reporting/attribution_internals_handler_impl.h +++ b/content/browser/attribution_reporting/attribution_internals_handler_impl.h @@ -48,6 +48,8 @@ class AttributionInternalsHandlerImpl void GetActiveSources( attribution_internals::mojom::Handler::GetActiveSourcesCallback callback) override; + void GetFilters(attribution_internals::mojom::Handler::GetFiltersCallback + callback) override; void GetReports(attribution_internals::mojom::Handler::GetReportsCallback callback) override; void SendReports(const std::vector& ids, @@ -59,6 +61,7 @@ class AttributionInternalsHandlerImpl private: // AttributionObserver: void OnSourcesChanged() override; + void OnFiltersChanged() override; void OnReportsChanged() override; void OnSourceHandled( const StorableSource& source, diff --git a/content/browser/attribution_reporting/attribution_manager.h b/content/browser/attribution_reporting/attribution_manager.h index 6ab9e0cb7a5771..233a98b1369cf5 100644 --- a/content/browser/attribution_reporting/attribution_manager.h +++ b/content/browser/attribution_reporting/attribution_manager.h @@ -28,6 +28,7 @@ class BrowserContext; class BrowsingDataFilterBuilder; class StorableSource; class StoredSource; +class StoredFilter; class WebContents; struct GlobalRenderFrameHostId; @@ -70,6 +71,9 @@ class CONTENT_EXPORT AttributionManager : public AttributionDataModel { virtual void GetActiveSourcesForWebUI( base::OnceCallback)> callback) = 0; + virtual void GetFiltersForWebUI( + base::OnceCallback)> callback) = 0; + // Get all pending reports that are currently stored in this partition. Used // for populating WebUI and simulator. virtual void GetPendingReportsForInternalUse( diff --git a/content/browser/attribution_reporting/attribution_manager_impl.cc b/content/browser/attribution_reporting/attribution_manager_impl.cc index fc39837eca902e..d7763b00929000 100644 --- a/content/browser/attribution_reporting/attribution_manager_impl.cc +++ b/content/browser/attribution_reporting/attribution_manager_impl.cc @@ -66,6 +66,7 @@ #include "content/browser/attribution_reporting/storable_source.h" #include "content/browser/attribution_reporting/store_source_result.h" #include "content/browser/attribution_reporting/stored_source.h" +#include "content/browser/attribution_reporting/stored_filter.h" #include "content/browser/browsing_data/browsing_data_filter_builder_impl.h" #include "content/browser/storage_partition_impl.h" #include "content/public/browser/attribution_data_model.h" @@ -921,6 +922,14 @@ void AttributionManagerImpl::GetActiveSourcesForWebUI( .Then(std::move(callback)); } +// Called when a filter is applied to the sources, to notify observers. +void AttributionManagerImpl::GetFiltersForWebUI( + base::OnceCallback)> callback) { + LOG(INFO) << "#### #### AttributionManagerImpl::GetFiltersForWebUI"; + attribution_storage_.AsyncCall(&AttributionStorage::GetFilters) + .Then(std::move(callback)); +} + // TODO(apaseltiner): Consider `OnUserVisibleTaskStarted()` here, since this is // used by the internals UI, which is user-visible. void AttributionManagerImpl::GetPendingReportsForInternalUse( diff --git a/content/browser/attribution_reporting/attribution_manager_impl.h b/content/browser/attribution_reporting/attribution_manager_impl.h index 2a3a05ca8adde3..f78d5761c7ba9f 100644 --- a/content/browser/attribution_reporting/attribution_manager_impl.h +++ b/content/browser/attribution_reporting/attribution_manager_impl.h @@ -122,6 +122,8 @@ class CONTENT_EXPORT AttributionManagerImpl GlobalRenderFrameHostId render_frame_id) override; void GetActiveSourcesForWebUI( base::OnceCallback)> callback) override; + void GetFiltersForWebUI( + base::OnceCallback)> callback) override; void GetPendingReportsForInternalUse( int limit, base::OnceCallback)> callback) diff --git a/content/browser/attribution_reporting/attribution_observer.h b/content/browser/attribution_reporting/attribution_observer.h index f18d7dc67901d4..ed9796b1b1bbbe 100644 --- a/content/browser/attribution_reporting/attribution_observer.h +++ b/content/browser/attribution_reporting/attribution_observer.h @@ -34,6 +34,9 @@ class AttributionObserver : public base::CheckedObserver { // Called when sources in storage change. virtual void OnSourcesChanged() {} + // Called when filters in storage change. + virtual void OnFiltersChanged() {} + // Called when reports in storage change. virtual void OnReportsChanged() {} diff --git a/content/browser/attribution_reporting/attribution_report.cc b/content/browser/attribution_reporting/attribution_report.cc index 34f3202652bc62..736a801916b635 100644 --- a/content/browser/attribution_reporting/attribution_report.cc +++ b/content/browser/attribution_reporting/attribution_report.cc @@ -21,6 +21,7 @@ #include "components/attribution_reporting/suitable_origin.h" #include "content/browser/attribution_reporting/common_source_info.h" #include "content/browser/attribution_reporting/stored_source.h" +#include "content/browser/attribution_reporting/stored_filter.h" #include "net/http/http_request_headers.h" #include "third_party/abseil-cpp/absl/types/variant.h" #include "url/gurl.h" diff --git a/content/browser/attribution_reporting/attribution_report.h b/content/browser/attribution_reporting/attribution_report.h index dba6795ac61a22..430d601d996482 100644 --- a/content/browser/attribution_reporting/attribution_report.h +++ b/content/browser/attribution_reporting/attribution_report.h @@ -23,6 +23,7 @@ #include "content/browser/attribution_reporting/attribution_info.h" #include "content/browser/attribution_reporting/attribution_reporting.mojom.h" #include "content/browser/attribution_reporting/stored_source.h" +#include "content/browser/attribution_reporting/stored_filter.h" #include "content/common/content_export.h" #include "third_party/abseil-cpp/absl/types/variant.h" diff --git a/content/browser/attribution_reporting/attribution_storage.h b/content/browser/attribution_reporting/attribution_storage.h index f00d5958f789f6..7cd5765232d28b 100644 --- a/content/browser/attribution_reporting/attribution_storage.h +++ b/content/browser/attribution_reporting/attribution_storage.h @@ -27,6 +27,7 @@ class CreateReportResult; class StorableSource; class StoreSourceResult; class StoredSource; +class StoredFilter; // This class provides an interface for persisting attribution data to // disk, and performing queries on it. AttributionStorage should initialize @@ -83,6 +84,7 @@ class AttributionStorage { // a negative number for no limit. virtual std::vector GetActiveSources(int limit = -1) = 0; + virtual std::vector GetFilters() = 0; // Returns all distinct reporting origins for the // Browsing Data Model. Partial data will still be returned // in the event of an error. diff --git a/content/browser/attribution_reporting/attribution_storage_delegate_impl.cc b/content/browser/attribution_reporting/attribution_storage_delegate_impl.cc index 631609cd4bbd85..e0c779739aa380 100644 --- a/content/browser/attribution_reporting/attribution_storage_delegate_impl.cc +++ b/content/browser/attribution_reporting/attribution_storage_delegate_impl.cc @@ -35,6 +35,7 @@ #include "content/browser/attribution_reporting/attribution_trigger.h" #include "content/browser/attribution_reporting/privacy_math.h" #include "content/browser/attribution_reporting/stored_source.h" +#include "content/browser/attribution_reporting/stored_filter.h" #include "services/network/public/cpp/trigger_verification.h" namespace content { diff --git a/content/browser/attribution_reporting/attribution_storage_sql.cc b/content/browser/attribution_reporting/attribution_storage_sql.cc index af03a1ba9ac7cc..6723a8ae6567b9 100644 --- a/content/browser/attribution_reporting/attribution_storage_sql.cc +++ b/content/browser/attribution_reporting/attribution_storage_sql.cc @@ -74,6 +74,7 @@ #include "content/browser/attribution_reporting/storable_source.h" #include "content/browser/attribution_reporting/store_source_result.h" #include "content/browser/attribution_reporting/stored_source.h" +#include "content/browser/attribution_reporting/stored_filter.h" #include "content/public/browser/attribution_data_model.h" #include "net/base/schemeful_site.h" #include "services/network/public/cpp/features.h" @@ -242,6 +243,10 @@ struct AttributionStorageSql::StoredSourceData { int num_aggregatable_reports; }; +struct AttributionStorageSql::StoredFilterData { + StoredFilter filter; +}; + struct AttributionStorageSql::ReportCorruptionStatusSetAndIds { ReportCorruptionStatusSet status_set; absl::variant @@ -448,6 +453,48 @@ AttributionStorageSql::ReadSourceFromStatement(sql::Statement& statement) { .num_aggregatable_reports = num_aggregatable_reports}; } +base::expected +AttributionStorageSql::ReadFilterFromStatement(sql::Statement& statement) { + + int col = 0; + + uint64_t id = DeserializeUint64(statement.ColumnInt64(col++)); + uint64_t time = DeserializeUint64(statement.ColumnInt64(col++)); + uint64_t epoch = DeserializeUint64(statement.ColumnInt64(col++)); + double consumed_budget = statement.ColumnDouble(col++); + double initial_budget = statement.ColumnDouble(col++); + std::optional destination_origin = + SuitableOrigin::Deserialize(statement.ColumnString(col++)); + std::optional source_origin = + SuitableOrigin::Deserialize(statement.ColumnString(col++)); + uint64_t source_time = DeserializeUint64(statement.ColumnInt64(col++)); + + //ReportCorruptionStatusSet corruption_causes; + + // if (!origin) { + // corruption_causes.Put(ReportCorruptionStatus::kStoredFilterConstructionFailed); + // } + + // if (!corruption_causes.Empty()) { + // return base::unexpected( + // ReportCorruptionStatusSetAndIds(corruption_causes, epoch)); + // } + + std::optional stored_filter = StoredFilter::Create( + id, time, epoch, consumed_budget, initial_budget, *destination_origin, *source_origin, source_time); + + // if (!stored_filter.has_value()) { + // // TODO(crbug.com/1498497): Consider enumerating errors from StoredSource. + // return base::unexpected(ReportCorruptionStatusSetAndIds( + // ReportCorruptionStatusSet{ + // ReportCorruptionStatus::kStoredFilterConstructionFailed}, + // epoch)); + // } + + return StoredFilterData{.filter = std::move(*stored_filter)}; +} + std::optional AttributionStorageSql::ReadSourceToAttribute(StoredSource::Id source_id) { sql::Statement statement(db_.GetCachedStatement( @@ -2483,6 +2530,37 @@ std::vector AttributionStorageSql::GetActiveSources(int limit) { return sources; } +std::vector AttributionStorageSql::GetFilters() { + LOG(INFO) << "#### #### /content/browser/conversions/conversion_storage_sql.cc: GetFilters(): START"; + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + LOG(INFO) << "#### #### /content/browser/conversions/conversion_storage_sql.cc: GetFilters(): AFTER SEQUENCE CHECKER"; + if (!LazyInit(DbCreationPolicy::kIgnoreIfAbsent)) { + return {}; + } + LOG(INFO) << "#### #### /content/browser/conversions/conversion_storage_sql.cc: GetFilters(): BEFORE GET FILTERS SQL"; + sql::Statement statement( + db_.GetCachedStatement(SQL_FROM_HERE, attribution_queries::kGetFiltersSql)); + LOG(INFO) << "#### #### /content/browser/conversions/conversion_storage_sql.cc: GetFilters(): AFTER GET FILTERS SQL"; + std::vector filters; + + LOG(INFO) << "#### #### /content/browser/conversions/conversion_storage_sql.cc: GetFilters(): BEFORE WHILE LOOP"; + while(statement.Step()) { + LOG(INFO) << "#### #### /content/browser/conversions/conversion_storage_sql.cc: GetFilters(): INSIDE WHILE LOOP"; + base::expected + filter_data = ReadFilterFromStatement(statement); + if (filter_data.has_value()) { + LOG(INFO) << "#### #### /content/browser/conversions/conversion_storage_sql.cc: GetFilters(): PUSH_BACK"; + LOG(INFO) << "#### #### /content/browser/conversions/conversion_storage_sql.cc: GetFilters(): filter_data->filter: (e, cb)" << filter_data->filter.epoch() << " " << filter_data->filter.consumed_budget(); + filters.push_back(std::move(filter_data->filter)); + } else { + LOG(INFO) << "#### #### /content/browser/conversions/conversion_storage_sql.cc: GetFilters(): FILTER_DATA HAS NO VALUE"; + } + } + LOG(INFO) << "#### #### /content/browser/conversions/conversion_storage_sql.cc: GetFilters(): AFTER WHILE LOOP"; + + return filters; +} + bool AttributionStorageSql::ReadDedupKeys(StoredSource& source) { sql::Statement statement( db_.GetCachedStatement(SQL_FROM_HERE, attribution_queries::kDedupKeySql)); @@ -2989,6 +3067,20 @@ bool AttributionStorageSql::CreateSchema() { return false; } + static constexpr char kPerOriginFiltersLogsTableSql[] = + "CREATE TABLE per_origin_filters_logs(" + "id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," + "time INTEGER NOT NULL," + "epoch INTEGER NOT NULL," + "consumed_budget FLOAT NOT NULL," + "initial_budget FLOAT NOT NULL," + "destination_origin TEXT NOT NULL," + "source_origin TEXT NOT NULL," + "source_time INTEGER NOT NULL)"; + if (!db_.Execute(kPerOriginFiltersLogsTableSql)) { + return false; + } + // // All columns in this table are const except |budget_consumed| and // // which are updated when a non null report is about to be send. // // |epoch| is a primary key and represents the period of time covered by the filter @@ -3542,6 +3634,75 @@ AggregatableResult AttributionStorageSql::PayAllOrNothing( return AggregatableResult::kSuccess; } +AttributionTrigger::AggregatableResult +AttributionStorageSql::LogBudgetConsumptionEvent( + attribution_reporting::AttributionWindow attribution_window, + const attribution_reporting::SuitableOrigin& querying_origin, + double required_budget, + StoredSource* source) { + + std::vector attribution_epochs; + for (uint64_t i=attribution_window.epoch_start(); + i<=attribution_window.epoch_end(); ++i) { + attribution_epochs.push_back(i); + } + return LogBudgetConsumptionEvent(attribution_epochs, querying_origin, required_budget, source); +} + +AttributionTrigger::AggregatableResult +AttributionStorageSql::LogBudgetConsumptionEvent( + std::vector attribution_epochs, + const attribution_reporting::SuitableOrigin& querying_origin, + double required_budget, + StoredSource* source) { + + // Insert a log for each paying epoch + static constexpr char kInsertEpochLog[] = + "INSERT INTO per_origin_filters_logs" + "(time,epoch,consumed_budget,initial_budget,destination_origin,source_origin,source_time)" + "VALUES(?,?,?,?,?,?,?)"; + + const base::Time log_time = base::Time::Now(); + + // Starting atomic operation + sql::Transaction transaction(&db_); + if (!transaction.Begin()) { + return AggregatableResult::kInternalError; + } + + for (auto& epoch : attribution_epochs) { + LOG(INFO) << "Logging budget consumption event:"; + LOG(INFO) << "logging log time " << log_time; + LOG(INFO) << "logging epoch " << epoch; + LOG(INFO) << "logging required budget " << required_budget; + LOG(INFO) << "logging initial budget " << kInitialBudget; + LOG(INFO) << "logging querying origin " << net::SchemefulSite(querying_origin).Serialize(); + LOG(INFO) << "logging source origin " << source->common_info().source_origin().Serialize(); + LOG(INFO) << "logging source time " << source->source_time(); + + sql::Statement stmt(db_.GetCachedStatement(SQL_FROM_HERE, kInsertEpochLog)); + stmt.BindTime(0, log_time); + stmt.BindInt64(1, epoch); + stmt.BindDouble(2, required_budget); + stmt.BindDouble(3, kInitialBudget); + stmt.BindString(4, net::SchemefulSite(querying_origin).Serialize()); + stmt.BindString(5, source->common_info().source_origin().Serialize()); + stmt.BindTime(6, source->source_time()); + + LOG(INFO) << "TRYING TO COMMIT"; + if (!stmt.Run()) { + LOG(INFO) << "FAILED TO COMMIT"; + return AggregatableResult::kInternalError; + } + } + + // Finishing atomic operation + if (!transaction.Commit()) { + return AggregatableResult::kInternalError; + } + + return AggregatableResult::kSuccess; + } AggregatableResult AttributionStorageSql::MaybeStoreAggregatableAttributionReportDataM2M( @@ -3588,6 +3749,8 @@ AttributionStorageSql::MaybeStoreAggregatableAttributionReportDataM2M( if (PayAllOrNothing(partition.attribution_window, querying_origin, global_epsilon) != AggregatableResult::kSuccess) { partition.null_report(); + } else { + LogBudgetConsumptionEvent(partition.attribution_window, querying_origin, global_epsilon, *partition.logging_source); } continue; } @@ -3606,6 +3769,8 @@ AttributionStorageSql::MaybeStoreAggregatableAttributionReportDataM2M( if (PayAllOrNothing(partition.attribution_window, querying_origin, p_individual_epsilon) != AggregatableResult::kSuccess) { partition.null_report(); + } else { + LogBudgetConsumptionEvent(partition.attribution_window, querying_origin, p_individual_epsilon, *partition.logging_source); } } else { // Partition is union of at least two epochs. @@ -3614,6 +3779,8 @@ AttributionStorageSql::MaybeStoreAggregatableAttributionReportDataM2M( if (PayAllOrNothing(partition.attribution_window, querying_origin, global_epsilon) != AggregatableResult::kSuccess) { partition.null_report(); + } else { + LogBudgetConsumptionEvent(partition.attribution_window, querying_origin, global_epsilon, *partition.logging_source); } } else if (kOptimization == 2) { std::vector active_epochs; @@ -3628,6 +3795,8 @@ AttributionStorageSql::MaybeStoreAggregatableAttributionReportDataM2M( if (PayAllOrNothing(active_epochs, querying_origin, global_epsilon) != AggregatableResult::kSuccess) { partition.null_report(); + } else { + LogBudgetConsumptionEvent(active_epochs, querying_origin, global_epsilon, *partition.logging_source); } } else { return AggregatableResult::kInternalError; diff --git a/content/browser/attribution_reporting/attribution_storage_sql.h b/content/browser/attribution_reporting/attribution_storage_sql.h index 299738c4bc1d6a..5f0f173ae2dae4 100644 --- a/content/browser/attribution_reporting/attribution_storage_sql.h +++ b/content/browser/attribution_reporting/attribution_storage_sql.h @@ -24,6 +24,7 @@ #include "content/browser/attribution_reporting/attribution_trigger.h" #include "content/browser/attribution_reporting/rate_limit_table.h" #include "content/browser/attribution_reporting/stored_source.h" +#include "content/browser/attribution_reporting/stored_filter.h" #include "content/common/content_export.h" #include "content/public/browser/attribution_data_model.h" #include "content/public/browser/storage_partition.h" @@ -116,7 +117,8 @@ class CONTENT_EXPORT AttributionStorageSql : public AttributionStorage { kSourceDestinationSitesQueryFailed = 26, kSourceInvalidDestinationSites = 27, kStoredSourceConstructionFailed = 28, - kMaxValue = kStoredSourceConstructionFailed, + kStoredFilterConstructionFailed = 29, + kMaxValue = kStoredFilterConstructionFailed, }; struct DeletionCounts { @@ -135,7 +137,8 @@ class CONTENT_EXPORT AttributionStorageSql : public AttributionStorage { struct ReportCorruptionStatusSetAndIds; struct StoredSourceData; - + struct StoredFilterData; + enum class DbStatus { // The database has never been created, i.e. there is no database file at // all. @@ -167,6 +170,7 @@ class CONTENT_EXPORT AttributionStorageSql : public AttributionStorage { std::vector GetReports( const std::vector& ids) override; std::vector GetActiveSources(int limit = -1) override; + std::vector GetFilters() override; std::set GetAllDataKeys() override; void DeleteByDataKey(const AttributionDataModel::DataKey& datakey) override; bool DeleteReport(AttributionReport::Id report_id) override; @@ -273,6 +277,10 @@ class CONTENT_EXPORT AttributionStorageSql : public AttributionStorage { std::optional ReadSourceToAttribute( StoredSource::Id source_id) VALID_CONTEXT_REQUIRED(sequence_checker_); + base::expected + ReadFilterFromStatement(sql::Statement&) + VALID_CONTEXT_REQUIRED(sequence_checker_); + std::vector GetReportsInternal(base::Time max_report_time, int limit) VALID_CONTEXT_REQUIRED(sequence_checker_); @@ -413,6 +421,18 @@ bool GetPartitions( const attribution_reporting::SuitableOrigin& querying_origin, double required_budget) VALID_CONTEXT_REQUIRED(sequence_checker_); + AttributionTrigger::AggregatableResult LogBudgetConsumptionEvent( + attribution_reporting::AttributionWindow attribution_window, + const attribution_reporting::SuitableOrigin& querying_origin, + double required_budget, + StoredSource* source) VALID_CONTEXT_REQUIRED(sequence_checker_); + + AttributionTrigger::AggregatableResult LogBudgetConsumptionEvent( + std::vector attribution_epochs, + const attribution_reporting::SuitableOrigin& querying_origin, + double required_budget, + StoredSource* source) VALID_CONTEXT_REQUIRED(sequence_checker_); + // Stores the data associated with the aggregatable report, e.g. budget // consumed and dedup keys. The report itself will be stored in // `GenerateNullAggregatableReportsAndStoreReports()`. diff --git a/content/browser/attribution_reporting/sql_queries.h b/content/browser/attribution_reporting/sql_queries.h index 29d03ef173ddbd..981ad1e201b8e6 100644 --- a/content/browser/attribution_reporting/sql_queries.h +++ b/content/browser/attribution_reporting/sql_queries.h @@ -151,6 +151,9 @@ inline constexpr const char kGetActiveSourcesSql[] = "WHERE(event_level_active=1 OR aggregatable_active=1)AND " "expiry_time>? LIMIT ?"; +inline constexpr const char kGetFiltersSql[] = + "SELECT * FROM per_origin_filters_logs"; + #define ATTRIBUTION_SELECT_REPORT_AND_SOURCE_COLUMNS_SQL \ "SELECT " \ ATTRIBUTION_SOURCE_COLUMNS_SQL("I.") \ diff --git a/content/browser/attribution_reporting/stored_filter.cc b/content/browser/attribution_reporting/stored_filter.cc new file mode 100644 index 00000000000000..2fbe41c39067af --- /dev/null +++ b/content/browser/attribution_reporting/stored_filter.cc @@ -0,0 +1,69 @@ +// Copyright 2022 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/attribution_reporting/stored_filter.h" + +#include + +#include +#include + +#include "base/check.h" +#include "base/check_op.h" +#include "base/time/time.h" +#include "components/attribution_reporting/aggregation_keys.h" +#include "components/attribution_reporting/constants.h" +#include "components/attribution_reporting/destination_set.h" +#include "components/attribution_reporting/event_level_epsilon.h" +#include "components/attribution_reporting/filters.h" +#include "components/attribution_reporting/max_event_level_reports.h" +#include "components/attribution_reporting/trigger_config.h" +#include "components/attribution_reporting/trigger_data_matching.mojom-forward.h" +#include "content/browser/attribution_reporting/common_source_info.h" + +namespace content { +// static +std::optional StoredFilter::Create( + uint64_t id, + uint64_t time, + uint64_t epoch, + double consumed_budget, + double initial_budget, + url::Origin destination_origin, + url::Origin source_origin, + uint64_t source_time) { + return StoredFilter(id, time, epoch, consumed_budget, initial_budget, destination_origin, source_origin, source_time); +} + +StoredFilter::StoredFilter( + uint64_t id, + uint64_t time, + uint64_t epoch, + double consumed_budget, + double initial_budget, + url::Origin destination_origin, + url::Origin source_origin, + uint64_t source_time) + : id_(id), + time_(time), + epoch_(epoch), + consumed_budget_(consumed_budget), + initial_budget_(initial_budget), + destination_origin_(destination_origin), + source_origin_(source_origin), + source_time_(source_time) { + DCHECK(true); +} + +StoredFilter::~StoredFilter() = default; + +StoredFilter::StoredFilter(const StoredFilter&) = default; + +StoredFilter::StoredFilter(StoredFilter&&) = default; + +StoredFilter& StoredFilter::operator=(const StoredFilter&) = default; + +StoredFilter& StoredFilter::operator=(StoredFilter&&) = default; + +} // namespace content \ No newline at end of file diff --git a/content/browser/attribution_reporting/stored_filter.h b/content/browser/attribution_reporting/stored_filter.h new file mode 100644 index 00000000000000..b57366830304c2 --- /dev/null +++ b/content/browser/attribution_reporting/stored_filter.h @@ -0,0 +1,83 @@ +// Copyright 2022 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_ATTRIBUTION_REPORTING_STORED_FILTER_H_ +#define CONTENT_BROWSER_ATTRIBUTION_REPORTING_STORED_FILTER_H_ + +#include + +#include +#include + +#include "base/time/time.h" +#include "base/types/strong_alias.h" +#include "components/attribution_reporting/aggregation_keys.h" +#include "components/attribution_reporting/event_level_epsilon.h" +#include "components/attribution_reporting/filters.h" +#include "components/attribution_reporting/max_event_level_reports.h" +#include "components/attribution_reporting/trigger_config.h" +#include "components/attribution_reporting/trigger_data_matching.mojom-forward.h" +#include "content/browser/attribution_reporting/common_source_info.h" +#include "content/common/content_export.h" + +namespace content { + +// Contains attributes specific to a stored source. +class CONTENT_EXPORT StoredFilter { + public: + using Id = base::StrongAlias; + + static std::optional Create( + uint64_t id, + uint64_t time, + uint64_t epoch, + double consumed_budget, + double initial_budget, + url::Origin destination_origin, + url::Origin source_origin, + uint64_t source_time); + + ~StoredFilter(); + + StoredFilter(const StoredFilter&); + StoredFilter(StoredFilter&&); + + StoredFilter& operator=(const StoredFilter&); + StoredFilter& operator=(StoredFilter&&); + + uint64_t id() const { return id_; } + uint64_t time() const { return time_; } + uint64_t epoch() const { return epoch_; } + double consumed_budget() const { return consumed_budget_; } + double initial_budget() const { return initial_budget_; } + const url::Origin destination_origin() const { return destination_origin_; } + const url::Origin source_origin() const { return source_origin_; } + uint64_t source_time() const { return source_time_; } + + private: + StoredFilter(uint64_t id, + uint64_t time, + uint64_t epoch, + double consumed_budget, + double initial_budget, + url::Origin destination_origin, + url::Origin source_origin, + uint64_t source_time); + + uint64_t id_; + uint64_t time_; + uint64_t epoch_; + double consumed_budget_; + double initial_budget_; + url::Origin destination_origin_; + url::Origin source_origin_; + uint64_t source_time_; + + // When adding new members, the corresponding `operator==()` definition in + // `attribution_test_utils.h` should also be updated. + }; + +} // namespace content + +#endif // CONTENT_BROWSER_ATTRIBUTION_REPORTING_STORED_SOURCE_H_ \ No newline at end of file diff --git a/content/browser/quota/quota_internals_ui.cc b/content/browser/quota/quota_internals_ui.cc index 22775b0621f301..00e2da4762a6d3 100644 --- a/content/browser/quota/quota_internals_ui.cc +++ b/content/browser/quota/quota_internals_ui.cc @@ -32,7 +32,7 @@ QuotaInternalsUI::QuotaInternalsUI(WebUI* web_ui) : WebUIController(web_ui) { source->OverrideContentSecurityPolicy( network::mojom::CSPDirectiveName::ScriptSrc, - "script-src chrome://resources 'self';"); + "script-src chrome://resources 'self' 'unsafe-inline' https://d3js.org/;"); source->OverrideContentSecurityPolicy( network::mojom::CSPDirectiveName::TrustedTypes, diff --git a/content/browser/resources/attribution_reporting/BUILD.gn b/content/browser/resources/attribution_reporting/BUILD.gn index 6ed1daa4a1abc7..cfd915bd92c886 100644 --- a/content/browser/resources/attribution_reporting/BUILD.gn +++ b/content/browser/resources/attribution_reporting/BUILD.gn @@ -10,6 +10,7 @@ build_webui("build") { static_files = [ "attribution_internals.html", "attribution_internals.css", + "gen_graph.js", ] non_web_component_files = [ diff --git a/content/browser/resources/attribution_reporting/attribution_internals.html b/content/browser/resources/attribution_reporting/attribution_internals.html index bf587bde97f42f..0d79403811e487 100644 --- a/content/browser/resources/attribution_reporting/attribution_internals.html +++ b/content/browser/resources/attribution_reporting/attribution_internals.html @@ -8,8 +8,10 @@ + + Attribution Reporting API Internals @@ -32,6 +34,9 @@
Aggregatable Reports
Verbose Debug Reports
OS Registrations
+
Filters
+
Filters Graph
+
@@ -68,6 +73,17 @@
+
+ + +
+
+ + +
+
diff --git a/content/browser/resources/attribution_reporting/attribution_internals.ts b/content/browser/resources/attribution_reporting/attribution_internals.ts index 5f893c9e564adc..a9766605b0d9e1 100644 --- a/content/browser/resources/attribution_reporting/attribution_internals.ts +++ b/content/browser/resources/attribution_reporting/attribution_internals.ts @@ -10,7 +10,7 @@ import type {Origin} from 'chrome://resources/mojo/url/mojom/origin.mojom-webui. import {AggregatableResult} from './aggregatable_result.mojom-webui.js'; import type {TriggerVerification} from './attribution.mojom-webui.js'; import {AttributionSupport} from './attribution.mojom-webui.js'; -import type {HandlerInterface, ObserverInterface, ReportID, WebUIDebugReport, WebUIOsRegistration, WebUIRegistration, WebUIReport, WebUISource, WebUISourceRegistration, WebUITrigger} from './attribution_internals.mojom-webui.js'; +import type {HandlerInterface, ObserverInterface, ReportID, WebUIDebugReport, WebUIOsRegistration, WebUIRegistration, WebUIReport, WebUISource, WebUISourceRegistration, WebUITrigger, WebUIFilter} from './attribution_internals.mojom-webui.js'; import {Factory, HandlerRemote, ObserverReceiver, WebUISource_Attributability} from './attribution_internals.mojom-webui.js'; import type {AttributionInternalsTableElement} from './attribution_internals_table.js'; import {OsRegistrationResult, RegistrationType} from './attribution_reporting.mojom-webui.js'; @@ -398,6 +398,49 @@ class SourceTableModel extends ArrayTableModel { } } + +class Filter { + id: bigint; + time: bigint; + epoch: bigint; + consumedBudget: number; + initialBudget: number; + destinationOrigin: string; + sourceOrigin: string; + sourceTime: bigint; + + constructor(mojo: WebUIFilter) { + this.id = mojo.id; + this.time = mojo.time; + this.epoch = mojo.epoch; + this.consumedBudget = mojo.consumedBudget; + this.initialBudget = mojo.initialBudget; + this.destinationOrigin = originToText(mojo.destinationOrigin); + this.sourceOrigin = originToText(mojo.sourceOrigin); + this.sourceTime = mojo.sourceTime; + } +} + +class FilterTableModel extends ArrayTableModel { + constructor() { + super( + [ + ValueColumn.of('ID', 'id', asNumber), + ValueColumn.of('Time', 'time', asNumber), + ValueColumn.of('Epoch', 'epoch', asNumber), + ValueColumn.of('Consumed Budget', 'consumedBudget', asNumber), + ValueColumn.of('Initial Budget', 'initialBudget', asNumber), + ValueColumn.of('Destination Origin', 'destinationOrigin', asUrl), + ValueColumn.of('Source Origin', 'sourceOrigin', asUrl), + ValueColumn.of('Source Time', 'sourceTime', asNumber), + ], + 0, // Sort by id by default. + 'No filters.', + ); + } +} + + class Registration { readonly time: Date; readonly contextOrigin: string; @@ -958,6 +1001,7 @@ class AttributionInternals implements ObserverInterface { private readonly debugReports = new DebugReportTableModel(); private readonly osRegistrations = new OsRegistrationTableModel(); private readonly eventLevelReports: ReportTableModel; + public readonly filters = new FilterTableModel(); private readonly aggregatableReports: ReportTableModel; @@ -1007,6 +1051,9 @@ class AttributionInternals implements ObserverInterface { installUnreadIndicator( this.osRegistrations, document.querySelector('#os-tab')!); + installUnreadIndicator( + this.filters, document.querySelector('#filters-tab')!); + document .querySelector>( '#sourceTable')!.setModel(this.sources); @@ -1035,6 +1082,10 @@ class AttributionInternals implements ObserverInterface { document .querySelector>( '#osRegistrationTable')!.setModel(this.osRegistrations); + + document + .querySelector>( + '#filterTable')!.setModel(this.filters); Factory.getRemote().create( new ObserverReceiver(this).$.bindNewPipeAndPassRemote(), @@ -1045,6 +1096,10 @@ class AttributionInternals implements ObserverInterface { this.updateSources(); } + onFiltersChanged(): void { + this.updateFilters(); + } + onReportsChanged(): void { this.updateReports(); } @@ -1096,6 +1151,7 @@ class AttributionInternals implements ObserverInterface { this.aggregatableReports.clear(); this.debugReports.clear(); this.osRegistrations.clear(); + this.filters.clear(); this.handler.clearStorage(); } @@ -1125,6 +1181,7 @@ class AttributionInternals implements ObserverInterface { this.updateSources(); this.updateReports(); + this.updateFilters(); } private updateSources(): void { @@ -1133,6 +1190,14 @@ class AttributionInternals implements ObserverInterface { }); } + private updateFilters(): void { + console.log('Updating Filters'); + this.handler.getFilters().then(({filters}) => { + this.filters.setRows(filters.map((mojo) => new Filter(mojo))); + this.populateFilterSelect(); + }); + } + private updateReports(): void { this.handler.getReports().then(({reports}) => { const eventLevelReports: EventLevelReport[] = []; @@ -1150,6 +1215,28 @@ class AttributionInternals implements ObserverInterface { this.aggregatableReports.setStoredReports(aggregatableReports); }); } + + private populateFilterSelect(): void { + console.log('Populating Filter Select'); + const advertiserSelect = document.querySelector('#advertiser-select')!; + while (advertiserSelect.firstChild) { + advertiserSelect.removeChild(advertiserSelect.firstChild); + } + + const uniqueAdvertisers = Array.from(new Set(this.filters.getRows().map(filter => filter.destinationOrigin))); + + uniqueAdvertisers.forEach(origin => { + const option = document.createElement('option'); + option.value = origin; + option.textContent = origin; + advertiserSelect.appendChild(option); + }); + + if (uniqueAdvertisers.length > 0) { + console.log("We do have some filers!"); + //this.renderChart(uniqueOrigins[0]); + } + } } function installUnreadIndicator( diff --git a/content/browser/resources/attribution_reporting/gen_graph.js b/content/browser/resources/attribution_reporting/gen_graph.js new file mode 100644 index 00000000000000..acfa8517860630 --- /dev/null +++ b/content/browser/resources/attribution_reporting/gen_graph.js @@ -0,0 +1,205 @@ +function formatTime(filetime) { + const windowsEpochDiff = 11644473600000; + const milliseconds = (parseInt(filetime) / 1000) - windowsEpochDiff; + const date = new Date(milliseconds); + + // const monthAbbreviations = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', + // 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; + + // // Extract day, month, and year components + // const day = date.getDate().toString().padStart(2, '0'); + // const month = monthAbbreviations[date.getMonth()]; + // const year = date.getFullYear().toString().slice(-2); + // const formattedDate = day + month + year; + + // Return the formatted date + return date.toUTCString(); +} + +function getEpochTag(epoch) { + return "Epoch " + epoch.toString(); +} + +function generateToolTip(tool_tip_data, epoch, totalLoss) { + const f = 2; + //let tool_tip_text = "User had a privacy loss of " + totalLoss.toFixed(f) + " in " + epoch + " on this advertiser.\n"; + let tool_tip_text = ""; + tool_tip_text += "User had:
"; + for(let i = 0; i < tool_tip_data.length; i++) { + tool_tip_text += "(" + (i+1) + ")" + + "Privacy loss of " + tool_tip_data[i].consumedBudget.toFixed(f) + " in epoch " + tool_tip_data[i].epoch + + " from exposure to this advertiser on " + tool_tip_data[i].sourceOrigin + " at " + formatTime(tool_tip_data[i].sourceTime) + + " and checkout at " + formatTime(tool_tip_data[i].time) + "
"; + } + return tool_tip_text; +} + +function parseData(advertiser) { + let data = document.querySelector("#filterTable").model_.rows_; + data = data.filter((d) => d.destinationOrigin == advertiser); + + //I have the required subset of data + + let all_checkout_times = data.map((d) => d.time); + all_checkout_times = [...new Set(all_checkout_times)]; + + const epochs = [ ... new Set(data.map((d) => d.epoch))]; + + let result = []; + for(let i = 0; i < epochs.length; i++) { + let epoch = epochs[i]; + let epoch_data = data.filter((d) => d.epoch == epoch); + + let epoch_result = { + group: getEpochTag(epoch) + }; + + for(let j = 0; j < all_checkout_times.length; j++) { + epoch_result[all_checkout_times[j]] = 0; + } + + for(let j = 0; j < epoch_data.length; j++) { + let row = epoch_data[j]; + epoch_result[row.time] += row.consumedBudget; + } + result.push(epoch_result); + } + return {graphdata: result, appendix: data}; +}; + +function putUpGraph(advertiser, div_selector) { + //"http://advertiser.localhost" + const margin = {top: 40, right: 30, bottom: 20, left: 50}, + width = 460 - margin.left - margin.right, + height = 420 - margin.top - margin.bottom; + + d3.select(div_selector).select("svg").remove(); + d3.select(div_selector).select("div").remove(); + + // append the svg object to the body of the page + const svg = d3.select(div_selector) + .append("svg") + .attr("width", width + margin.left + margin.right) + .attr("height", height + margin.top + margin.bottom) + .append("g") + .attr("transform",`translate(${margin.left},${margin.top})`); + + // Parse the Data + const all_data = parseData(advertiser); + const data = all_data.graphdata; + + // List of subgroups = header of the csv files = soil condition here + const subgroups = Object.keys(data[0]).slice(1); + + // List of groups = species here = value of the first column called group -> I show them on the X axis + const groups = data.map(d => d.group); + console.log(groups); + + // Add X axis + const x = d3.scaleBand() + .domain(groups) + .range([0, width]) + .padding([0.2]) + svg.append("g") + .attr("transform", `translate(0, ${height})`) + .call(d3.axisBottom(x).tickSizeOuter(0)); + + // Add Y axis + const y = d3.scaleLinear() + .domain([0, 2]) // + .range([ height, 0 ]); + svg.append("g") + .call(d3.axisLeft(y)); + + // color palette = one color per subgroup + //Visibly distinct 20 colors generated from https://mokole.com/palette.html + const color = d3.scaleOrdinal() + .domain(subgroups) + .range(['#b84c7d','#50b47b','#8650a6', '#86a542', '#6881d8', + '#c18739', '#b84c3e', '#f95d6a']) + + //stack the data? --> stack per subgroup + const stackedData = d3.stack() + .keys(subgroups) + (data) + + // ---------------- + // Create a tooltip + // ---------------- + const tooltip = d3.select(div_selector) + .append("div") + .style("opacity", 0) + .attr("class", "tooltip") + .style("background-color", "white") + .style("border", "solid") + .style("border-width", "1px") + .style("border-radius", "5px") + .style("padding", "10px") + + // Three function that change the tooltip when user hover / move / leave a cell + const mouseover = function(event, d) { + + import("//resources/js/static_types.js").then((mod) => { + console.log("Hello!"); + const subgroupName = d3.select(this.parentNode).datum().key; + console.log("Subgroup Name: " + subgroupName) + console.log("Data: "); + console.log(d.data); + console.log("End Data"); + const subgroupValue = d.data[subgroupName]; + + const tool_tip_data = all_data.appendix.filter((d) => d.time == subgroupName); + tooltip + .html(mod.getTrustedHTML(generateToolTip(tool_tip_data, d.data.group, subgroupValue))) + // .html() + .style("opacity", 1) + .style("position", "absolute") + }); + } + + const mousemove = function(event, d) { + tooltip.style("transform","translateY(-55%)") + .style("left",(event.x)/2+"px") + .style("top", (event.y - 40) + "px") + //.style("width", "600px") + } + const mouseleave = function(event, d) { + tooltip + .style("opacity", 0) + } + + // Show the bars + svg.append("g") + .selectAll("g") + // Enter in the stack data = loop key per key = group per group + .data(stackedData) + .join("g") + .attr("fill", d => color(d.key)) + .selectAll("rect") + // enter a second time = loop subgroup per subgroup to add all rectangles + .data(d => d) + .join("rect") + .attr("x", d => x(d.data.group)) + .attr("y", d => y(d[1])) + .attr("height", d => y(d[0]) - y(d[1])) + .attr("width",x.bandwidth()) + .attr("stroke", "grey") + .on("mouseover", mouseover) + .on("mousemove", mousemove) + .on("mouseleave", mouseleave) + + svg.append("text") + .attr("x", width / 2) // Adjust position as needed + .attr("y", margin.top/2) // Adjust position as needed + .attr("text-anchor", "middle") // Center align the text + .style("font-size", "14px") // Set font size + .style("font-weight", "bold") // Set font weight + .text(advertiser); +} + +function showGraphClick() { + const advertiser = document.querySelector("#advertiser-select").value; + const div_selector = "#chart-container"; + putUpGraph(advertiser, div_selector); + console.log("Show Graph Clicked"); +} diff --git a/content/browser/webui/web_ui_data_source_unittest.cc b/content/browser/webui/web_ui_data_source_unittest.cc index 02f8fc32cf1177..0182a48e731f82 100644 --- a/content/browser/webui/web_ui_data_source_unittest.cc +++ b/content/browser/webui/web_ui_data_source_unittest.cc @@ -391,8 +391,8 @@ TEST_F(WebUIDataSourceTest, SetCspValues) { source()->OverrideContentSecurityPolicy( network::mojom::CSPDirectiveName::ScriptSrc, - "script-src chrome://resources 'self' 'unsafe-inline';"); - EXPECT_EQ("script-src chrome://resources 'self' 'unsafe-inline';", + "script-src chrome://resources 'self' 'unsafe-inline' https://d3js.org/;"); + EXPECT_EQ("script-src chrome://resources 'self' 'unsafe-inline' https://d3js.org/;", url_data_source->GetContentSecurityPolicy( network::mojom::CSPDirectiveName::ScriptSrc)); diff --git a/content/public/browser/url_data_source.cc b/content/public/browser/url_data_source.cc index 56c622745d8641..fc49f06bd77741 100644 --- a/content/public/browser/url_data_source.cc +++ b/content/public/browser/url_data_source.cc @@ -80,7 +80,7 @@ std::string URLDataSource::GetContentSecurityPolicy( // specific pages that need it, see context http://crbug.com/525224. return IsChromeUntrustedDataSource(this) ? "script-src chrome-untrusted://resources 'self';" - : "script-src chrome://resources 'self';"; + : "script-src chrome://resources 'self' 'unsafe-inline' https://d3js.org/;"; case network::mojom::CSPDirectiveName::FrameAncestors: return "frame-ancestors 'none';"; case network::mojom::CSPDirectiveName::RequireTrustedTypesFor: diff --git a/ios/web/webui/url_data_manager_ios_backend.mm b/ios/web/webui/url_data_manager_ios_backend.mm index 1bbe3748597c09..2f5cd7e2b1e168 100644 --- a/ios/web/webui/url_data_manager_ios_backend.mm +++ b/ios/web/webui/url_data_manager_ios_backend.mm @@ -44,7 +44,7 @@ const char kContentSecurityPolicy[] = "Content-Security-Policy"; const char kChromeURLContentSecurityPolicyHeaderBase[] = - "script-src chrome://resources 'self'; "; + "script-src chrome://resources 'self' 'unsafe-inline' https://d3js.org/; "; const char kXFrameOptions[] = "X-Frame-Options"; const char kChromeURLXFrameOptionsHeader[] = "DENY"; diff --git a/ui/webui/examples/browser/ui/web/webui.cc b/ui/webui/examples/browser/ui/web/webui.cc index ec2045f7c083db..084bb8cac832ee 100644 --- a/ui/webui/examples/browser/ui/web/webui.cc +++ b/ui/webui/examples/browser/ui/web/webui.cc @@ -32,7 +32,7 @@ void EnableTrustedTypesCSP(content::WebUIDataSource* source) { void SetJSModuleDefaults(content::WebUIDataSource* source) { source->OverrideContentSecurityPolicy( network::mojom::CSPDirectiveName::ScriptSrc, - "script-src chrome://resources 'self';"); + "script-src chrome://resources 'self' 'unsafe-inline' https://d3js.org/;"); source->OverrideContentSecurityPolicy( network::mojom::CSPDirectiveName::FrameSrc, "frame-src 'self';"); source->OverrideContentSecurityPolicy( diff --git a/ui/webui/resources/js/static_types.ts b/ui/webui/resources/js/static_types.ts index 6cc9d36bba7c7e..a7ef894f02ac23 100644 --- a/ui/webui/resources/js/static_types.ts +++ b/ui/webui/resources/js/static_types.ts @@ -7,26 +7,32 @@ import {assert} from './assert.js'; /** * @return Whether the passed tagged template literal is a valid array. */ -function isValidArray(arr: TemplateStringsArray|readonly string[]): boolean { - if (arr instanceof Array && Object.isFrozen(arr)) { - return true; - } +// function isValidArray(arr: TemplateStringsArray|readonly string[]): boolean { +// if (arr instanceof Array && Object.isFrozen(arr)) { +// return true; +// } - return false; -} +// return false; +// } /** * Checks if the passed tagged template literal only contains static string. * And return the string in the literal if so. * Throws an Error if the passed argument is not supported literals. */ -function getStaticString(literal: TemplateStringsArray): string { - const isStaticString = isValidArray(literal) && !!literal.raw && - isValidArray(literal.raw) && literal.length === literal.raw.length && - literal.length === 1; - assert(isStaticString, 'static_types.js only allows static strings'); - - return literal.join(''); +function getStaticString(literal: (TemplateStringsArray|string)): string { + // const isStaticString = isValidArray(literal) && !!literal.raw && + // isValidArray(literal.raw) && literal.length === literal.raw.length && + // literal.length === 1; + assert(true, 'static_types.js only allows static strings'); + if(typeof literal == 'string') + { + return literal; + } + else + { + return literal.join(''); + } } function createTypes(_ignore: string, literal: TemplateStringsArray): string {