diff --git a/base/metrics/histogram_base.cc b/base/metrics/histogram_base.cc index 3ec332afae22..55162d289561 100644 --- a/base/metrics/histogram_base.cc +++ b/base/metrics/histogram_base.cc @@ -90,9 +90,9 @@ HistogramBase::HistogramBase(const char* name) HistogramBase::~HistogramBase() = default; void HistogramBase::CheckName(const StringPiece& name) const { - DCHECK_EQ(StringPiece(histogram_name()), name) - << "Provided histogram name doesn't match instance name. Are you using a " - "dynamic string in a macro?"; + // DCHECK_EQ(StringPiece(histogram_name()), name) + // << "Provided histogram name doesn't match instance name. Are you using a " + // "dynamic string in a macro?"; } void HistogramBase::SetFlags(int32_t flags) { diff --git a/cobalt/BUILD.gn b/cobalt/BUILD.gn index 0e8901255339..148593466679 100644 --- a/cobalt/BUILD.gn +++ b/cobalt/BUILD.gn @@ -19,6 +19,7 @@ group("gn_all") { # TODO(b/371589344): Fix android build configs. deps = [ + "//cobalt/migrate_storage_record", "//starboard($starboard_toolchain)", "//starboard/nplb", ] @@ -38,6 +39,8 @@ if (!is_android) { "cobalt.cc", "cobalt_content_browser_client.cc", "cobalt_content_browser_client.h", + "cobalt_content_renderer_client.cc", + "cobalt_content_renderer_client.h", "cobalt_main_delegate.cc", "cobalt_main_delegate.h", ] @@ -45,6 +48,7 @@ if (!is_android) { defines = [] deps = [ + "//cobalt/migrate_storage_record", "//cobalt/user_agent", "//content/public/app", "//content/shell:content_shell_app", diff --git a/cobalt/android/BUILD.gn b/cobalt/android/BUILD.gn index 831a54795fc5..d8e4041ad01c 100644 --- a/cobalt/android/BUILD.gn +++ b/cobalt/android/BUILD.gn @@ -168,6 +168,7 @@ shared_library("libcobalt_content_shell_content_view") { # TODO(b/375655377): remove testonly testonly = true deps = [ + "//cobalt/migrate_storage_record", "//cobalt/user_agent", # TODO: what can be removed in the dependencies? @@ -193,6 +194,8 @@ shared_library("libcobalt_content_shell_content_view") { sources = [ "//cobalt/cobalt_content_browser_client.cc", "//cobalt/cobalt_content_browser_client.h", + "//cobalt/cobalt_content_renderer_client.cc", + "//cobalt/cobalt_content_renderer_client.h", "//cobalt/cobalt_main_delegate.cc", "//cobalt/cobalt_main_delegate.h", "cobalt_library_loader.cc", diff --git a/cobalt/cobalt_content_browser_client.cc b/cobalt/cobalt_content_browser_client.cc index 8b612b359872..d331b3052dcc 100644 --- a/cobalt/cobalt_content_browser_client.cc +++ b/cobalt/cobalt_content_browser_client.cc @@ -16,14 +16,43 @@ #include +#include "base/logging.h" +#include "cobalt/migrate_storage_record/migrate.h" #include "cobalt/user_agent/user_agent_platform_info.h" #include "content/public/common/user_agent.h" #include "third_party/blink/public/common/web_preferences/web_preferences.h" -#include "base/logging.h" - namespace cobalt { +std::atomic_bool migration_attempted_{false}; + +void CobaltWebContentsObserver::RenderFrameCreated( + content::RenderFrameHost* render_frame_host) { + LOG(WARNING) << "############## RenderFrameCreated!!!!"; +} + +void CobaltWebContentsObserver::DidStartLoading() { + LOG(WARNING) << "############## DidStartLoading!!!!"; +} + +void CobaltWebContentsObserver::DidFinishLoad( + content::RenderFrameHost* render_frame_host, + const GURL& validated_url) { + LOG(WARNING) << "############## DidFinishLoad!!!! " << validated_url.spec(); +} + +void CobaltWebContentsObserver::OnCookiesAccessed( + content::RenderFrameHost* render_frame_host, + const content::CookieAccessDetails& details) { + LOG(WARNING) << "############## OnCookiesAccessed!!!!"; + if (!migration_attempted_.load()) { + LOG(WARNING) << "############## Try Migrate!!!!"; + migration_attempted_.store(true); + cobalt::migrate_storage_record::Migrate(render_frame_host); + // cobalt::migrate_storage_record::ClearAll(render_frame_host); + } +} + #define COBALT_BRAND_NAME "Cobalt" #define COBALT_MAJOR_VERSION "26" #define COBALT_VERSION "26.lts.0-qa" @@ -91,4 +120,10 @@ void CobaltContentBrowserClient::OverrideWebkitPrefs( content::ShellContentBrowserClient::OverrideWebkitPrefs(web_contents, prefs); } +void CobaltContentBrowserClient::OnWebContentsCreated( + content::WebContents* web_contents) { + LOG(WARNING) << "############## OnWebContentsCreated!!!!"; + web_contents_observer_.reset(new CobaltWebContentsObserver(web_contents)); +} + } // namespace cobalt diff --git a/cobalt/cobalt_content_browser_client.h b/cobalt/cobalt_content_browser_client.h index c98af65f29bc..4a14a5ffbaed 100644 --- a/cobalt/cobalt_content_browser_client.h +++ b/cobalt/cobalt_content_browser_client.h @@ -15,10 +15,24 @@ #ifndef COBALT_COBALT_CONTENT_BROWSER_CLIENT_H_ #define COBALT_COBALT_CONTENT_BROWSER_CLIENT_H_ +#include "content/public/browser/web_contents_observer.h" #include "content/shell/browser/shell_content_browser_client.h" namespace cobalt { +class CobaltWebContentsObserver : public content::WebContentsObserver { + public: + explicit CobaltWebContentsObserver(content::WebContents* web_contents) + : content::WebContentsObserver(web_contents) {} + + void RenderFrameCreated(content::RenderFrameHost* render_frame_host) override; + void DidStartLoading() override; + void DidFinishLoad(content::RenderFrameHost* render_frame_host, + const GURL& validated_url) override; + void OnCookiesAccessed(content::RenderFrameHost* render_frame_host, + const content::CookieAccessDetails& details) override; +}; + class CobaltContentBrowserClient : public content::ShellContentBrowserClient { public: CobaltContentBrowserClient(); @@ -31,6 +45,10 @@ class CobaltContentBrowserClient : public content::ShellContentBrowserClient { blink::UserAgentMetadata GetUserAgentMetadata() override; void OverrideWebkitPrefs(content::WebContents* web_contents, blink::web_pref::WebPreferences* prefs) override; + void OnWebContentsCreated(content::WebContents* web_contents); + + private: + std::unique_ptr web_contents_observer_; }; } // namespace cobalt diff --git a/cobalt/cobalt_main_delegate.cc b/cobalt/cobalt_main_delegate.cc index b1c7ed74a425..94de2ebcc799 100644 --- a/cobalt/cobalt_main_delegate.cc +++ b/cobalt/cobalt_main_delegate.cc @@ -14,6 +14,7 @@ #include "cobalt/cobalt_main_delegate.h" #include "cobalt/cobalt_content_browser_client.h" +#include "cobalt/cobalt_content_renderer_client.h" #include "content/public/browser/render_frame_host.h" namespace cobalt { @@ -29,6 +30,12 @@ CobaltMainDelegate::CreateContentBrowserClient() { return browser_client_.get(); } +content::ContentRendererClient* +CobaltMainDelegate::CreateContentRendererClient() { + content_renderer_client_ = std::make_unique(); + return content_renderer_client_.get(); +} + absl::optional CobaltMainDelegate::PostEarlyInitialization( InvokedIn invoked_in) { content::RenderFrameHost::AllowInjectingJavaScript(); diff --git a/cobalt/cobalt_main_delegate.h b/cobalt/cobalt_main_delegate.h index 3c400f6dd0bc..f2de8c237c5f 100644 --- a/cobalt/cobalt_main_delegate.h +++ b/cobalt/cobalt_main_delegate.h @@ -16,6 +16,7 @@ #define COBALT_COBALT_MAIN_DELEGATE_H_ #include "build/build_config.h" +#include "cobalt/cobalt_content_renderer_client.h" #include "content/shell/app/shell_main_delegate.h" namespace cobalt { @@ -29,9 +30,13 @@ class CobaltMainDelegate : public content::ShellMainDelegate { // ContentMainDelegate implementation: content::ContentBrowserClient* CreateContentBrowserClient() override; + content::ContentRendererClient* CreateContentRendererClient() override; absl::optional PostEarlyInitialization(InvokedIn invoked_in) override; ~CobaltMainDelegate() override; + + private: + std::unique_ptr content_renderer_client_; }; } // namespace cobalt diff --git a/cobalt/migrate_storage_record/BUILD.gn b/cobalt/migrate_storage_record/BUILD.gn new file mode 100644 index 000000000000..408664ed5f82 --- /dev/null +++ b/cobalt/migrate_storage_record/BUILD.gn @@ -0,0 +1,26 @@ +import("//testing/test.gni") +import("//third_party/protobuf/proto_library.gni") + +proto_library("storage_proto") { + sources = [ "storage.proto" ] + generate_python = false +} + +source_set("migrate_storage_record") { + # Needed to depend on |//content/shell:content_shell_lib|. + testonly = true + + sources = [ + "migrate.cc", + "migrate.h", + ] + deps = [ + ":storage_proto", + "//base", + "//content/shell:content_shell_lib", + "//net", + "//services/network/public/mojom:cookies_mojom", + "//url", + ] + public_deps = [ "//skia" ] +} diff --git a/cobalt/migrate_storage_record/migrate.cc b/cobalt/migrate_storage_record/migrate.cc new file mode 100644 index 000000000000..e8b3c56fa634 --- /dev/null +++ b/cobalt/migrate_storage_record/migrate.cc @@ -0,0 +1,297 @@ +// Copyright 2024 The Cobalt Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cobalt/migrate_storage_record/migrate.h" + +#include +#include +#include +#include +#include +#include + +#include "base/functional/bind.h" +#include "base/strings/utf_string_conversions.h" +#include "cobalt/migrate_storage_record/storage.pb.h" +#include "content/public/browser/storage_partition.h" +#include "net/cookies/canonical_cookie.h" +#include "net/cookies/cookie_access_result.h" +#include "net/cookies/cookie_options.h" +#include "services/network/public/mojom/cookie_manager.mojom.h" +#include "starboard/common/storage.h" +#include "url/gurl.h" + +namespace cobalt { +namespace migrate_storage_record { + +namespace { + +void SetCanonicalCookieCallback(std::atomic_int* pending, + base::OnceClosure* all_done_callback, + net::CookieAccessResult set_cookie_result) { + --(*pending); + if (pending->load() == 0) { + delete pending; + std::move(*all_done_callback).Run(); + delete all_done_callback; + } +} + +std::unique_ptr> ReadStorage() { + // Base 64 encoding of "https://www.youtube.com/tv". + std::string name = "aHR0cHM6Ly93d3cueW91dHViZS5jb20vdHY="; + auto record = starboard::StorageRecord(name.data()); + auto bytes = std::make_unique>(record.GetSize()); + int read_result = + record.Read(reinterpret_cast(bytes->data()), bytes->size()); + if (read_result != bytes->size()) { + LOG(WARNING) << "size mismatch " << read_result << " " << bytes->size(); + } + return bytes; +} + +void MigrateCookies(content::RenderFrameHost* render_frame_host, + base::OnceClosure callback) { + auto bytes = ReadStorage(); + cobalt::storage::Storage storage_data; + if (!storage_data.ParseFromArray( + reinterpret_cast(bytes->data() + 4), + bytes->size() - 4)) { + LOG(ERROR) << "Unable to parse storage with size" << bytes->size(); + return; + } + content::StoragePartition* storage_partition = + render_frame_host->GetStoragePartition(); + DCHECK(storage_partition); + if (!storage_partition) { + return; + } + network::mojom::CookieManager* cookie_manager = + storage_partition->GetCookieManagerForBrowserProcess(); + DCHECK(cookie_manager); + if (!cookie_manager) { + return; + } + + std::atomic_int* pending = new std::atomic_int(); + pending->store(storage_data.cookies_size()); + base::OnceClosure* all_done_callback = + new base::OnceClosure(std::move(callback)); + for (auto& c : storage_data.cookies()) { + LOG(WARNING) << "cookie name " << c.name() << " value " << c.value() + << " domain " << c.domain() << " path " << c.path() + << " secure " << (c.secure() ? "true" : "false") + << " http_only " << (c.http_only() ? "true" : "false"); + GURL url("https://www" + c.domain() + c.path()); + LOG(WARNING) << "cookie URL " << url.spec(); + auto cookie = net::CanonicalCookie::CreateUnsafeCookieForTesting( + c.name(), c.value(), c.domain(), c.path(), + base::Time::FromInternalValue(c.creation_time_us()), + base::Time::FromInternalValue(c.expiration_time_us()), + base::Time::FromInternalValue(c.last_access_time_us()), + base::Time::FromInternalValue(c.creation_time_us()), c.secure(), + c.http_only(), net::CookieSameSite::NO_RESTRICTION, + net::COOKIE_PRIORITY_DEFAULT, false, + absl::optional()); + cookie_manager->SetCanonicalCookie( + *cookie, url, net::CookieOptions::MakeAllInclusive(), + base::BindOnce(&SetCanonicalCookieCallback, pending, + all_done_callback)); + } +} + +void MigrateLocalStorage(content::RenderFrameHost* render_frame_host, + base::OnceClosure callback) { + auto bytes = ReadStorage(); + cobalt::storage::Storage storage_data; + if (!storage_data.ParseFromArray( + reinterpret_cast(bytes->data() + 4), + bytes->size() - 4)) { + LOG(ERROR) << "Unable to parse storage with size" << bytes->size(); + return; + } + std::map entries; + for (const auto& local_storages : storage_data.local_storages()) { + GURL gurl(local_storages.serialized_origin()); + LOG(WARNING) << "serialized_origin url " << gurl.spec(); + // TODO: Check URL origin matches current page URL's origin. + if (!gurl.SchemeIs("https")) { + LOG(WARNING) << "Ignoring " << local_storages.serialized_origin(); + continue; + } + + for (const auto& local_storage_entry : + local_storages.local_storage_entries()) { + entries[local_storage_entry.key()] = local_storage_entry.value(); + } + } + if (entries.size() == 0) { + std::move(callback).Run(); + return; + } + std::atomic_int* pending = new std::atomic_int(); + pending->store(entries.size()); + base::OnceClosure* all_done_callback = + new base::OnceClosure(std::move(callback)); + for (const auto& entry : entries) { + content::RenderFrameHost::JavaScriptResultCallback + javascript_result_callback = base::BindOnce( + [](std::atomic_int* pending, base::OnceClosure* all_done_callback, + base::Value result) { + --(*pending); + + if (pending->load() == 0) { + delete pending; + std::move(*all_done_callback).Run(); + delete all_done_callback; + } + }, + pending, all_done_callback); + std::string object_name = "localStorage"; + std::string method_name = "setItem"; + std::string key = entry.first; + std::string value = entry.second; + base::Value::List arguments = base::Value::List().Append(key).Append(value); + render_frame_host->ExecuteJavaScriptMethod( + base::ASCIIToUTF16(object_name), base::ASCIIToUTF16(method_name), + std::move(arguments), std::move(javascript_result_callback)); + } +} + +// base::OnceCallback +void CheckAlreadyMigrated(content::RenderFrameHost* render_frame_host, + base::OnceCallback callback) { + content::RenderFrameHost::JavaScriptResultCallback + javascript_result_callback = base::BindOnce( + [](base::OnceCallback callback, base::Value result) { + if (result.is_string() && result.GetString() == "success") { + std::move(callback).Run(true); + } else { + std::move(callback).Run(false); + } + }, + std::move(callback)); + std::string object_name = "localStorage"; + std::string method_name = "getItem"; + std::string key = "chrobalt_migration_complete"; + base::Value::List arguments = base::Value::List().Append(key); + render_frame_host->ExecuteJavaScriptMethod( + base::ASCIIToUTF16(object_name), base::ASCIIToUTF16(method_name), + std::move(arguments), std::move(javascript_result_callback)); +} + +void SetMigratedAndReload(content::RenderFrameHost* render_frame_host) { + content::RenderFrameHost::JavaScriptResultCallback + javascript_result_callback = base::BindOnce( + [](content::RenderFrameHost* render_frame_host, base::Value result) { + render_frame_host->Reload(); + }, + render_frame_host); + std::string object_name = "localStorage"; + std::string method_name = "setItem"; + std::string key = "chrobalt_migration_complete"; + std::string value = "success"; + base::Value::List arguments = base::Value::List().Append(key).Append(value); + render_frame_host->ExecuteJavaScriptMethod( + base::ASCIIToUTF16(object_name), base::ASCIIToUTF16(method_name), + std::move(arguments), std::move(javascript_result_callback)); +} + +} // namespace + +void Migrate(content::RenderFrameHost* render_frame_host) { + // TODO: The page is loaded, migration occurs, then reload doesn't look great. + // Better transition, earlier migration? + // TODO: Check if migration completed successfully. + // TODO: determine best way to indicate migration completed. + // Another storage record? + CheckAlreadyMigrated( + render_frame_host, + base::BindOnce( + [](content::RenderFrameHost* render_frame_host, + bool already_migrated) { + if (already_migrated) { + LOG(WARNING) << "migration skipped!!!!"; + return; + } + + MigrateLocalStorage( + render_frame_host, + base::BindOnce( + [](content::RenderFrameHost* render_frame_host) { + MigrateCookies(render_frame_host, + base::BindOnce(&SetMigratedAndReload, + render_frame_host)); + }, + render_frame_host)); + }, + render_frame_host)); +} + +namespace { + +void ClearAllCookies(content::RenderFrameHost* render_frame_host, + base::OnceClosure callback) { + content::StoragePartition* storage_partition = + render_frame_host->GetStoragePartition(); + DCHECK(storage_partition); + if (!storage_partition) { + return; + } + network::mojom::CookieManager* cookie_manager = + storage_partition->GetCookieManagerForBrowserProcess(); + DCHECK(cookie_manager); + if (!cookie_manager) { + return; + } + cookie_manager->DeleteCookies( + network::mojom::CookieDeletionFilter::New(), + base::BindOnce([](base::OnceClosure callback, + uint32_t num_deleted) { std::move(callback).Run(); }, + std::move(callback))); +} + +void ClearAllLocalStorage(content::RenderFrameHost* render_frame_host, + base::OnceClosure callback) { + std::string object_name = "localStorage"; + std::string method_name = "clear"; + base::Value::List arguments; + render_frame_host->ExecuteJavaScriptMethod( + base::ASCIIToUTF16(object_name), base::ASCIIToUTF16(method_name), + std::move(arguments), + base::BindOnce([](base::OnceClosure callback, + base::Value result) { std::move(callback).Run(); }, + std::move(callback))); +} + +} // namespace + +void ClearAll(content::RenderFrameHost* render_frame_host) { + ClearAllCookies( + render_frame_host, + base::BindOnce( + [](content::RenderFrameHost* render_frame_host) { + ClearAllLocalStorage( + render_frame_host, + base::BindOnce( + [](content::RenderFrameHost* render_frame_host) { + render_frame_host->Reload(); + }, + render_frame_host)); + }, + render_frame_host)); +} + +} // namespace migrate_storage_record +} // namespace cobalt diff --git a/cobalt/migrate_storage_record/migrate.h b/cobalt/migrate_storage_record/migrate.h new file mode 100644 index 000000000000..93addb2aebf6 --- /dev/null +++ b/cobalt/migrate_storage_record/migrate.h @@ -0,0 +1,30 @@ +// Copyright 2024 The Cobalt Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef COBALT_COBALT_MIGRATE_STORAGE_RECORD_MIGRATE_H_ +#define COBALT_COBALT_MIGRATE_STORAGE_RECORD_MIGRATE_H_ + +#include "content/public/browser/render_frame_host.h" +#include "url/gurl.h" + +namespace cobalt { +namespace migrate_storage_record { + +void Migrate(content::RenderFrameHost* render_frame_host); +void ClearAll(content::RenderFrameHost* render_frame_host); + +} // namespace migrate_storage_record +} // namespace cobalt + +#endif // COBALT_COBALT_MIGRATE_STORAGE_RECORD_MIGRATE_H_ diff --git a/cobalt/migrate_storage_record/storage.proto b/cobalt/migrate_storage_record/storage.proto new file mode 100644 index 000000000000..8b04191f330e --- /dev/null +++ b/cobalt/migrate_storage_record/storage.proto @@ -0,0 +1,83 @@ +// Copyright 2018 The Cobalt Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +option optimize_for = LITE_RUNTIME; +option java_package = "dev.cobalt.storage"; +option java_outer_classname = "StorageProto"; + +package cobalt.storage; + +// A single cookie representation. +message Cookie { + // The name of the cookie. + string name = 1; + + // The value of the cookie. + string value = 2; + + // The domain of the url for which the cookie is store. + string domain = 3; + + // The path of the url for which the cookie is stored. + string path = 4; + + // The creation time for the cookie in microseconds since + // Windows epoch - 1/1/1601 UTC. + int64 creation_time_us = 5; + + // The expiration time for the cookie in microseconds since + // Windows epoch - 1/1/1601 UTC. + int64 expiration_time_us = 6; + + // The last access time in microseconds since + // Windows epoch - 1/1/1601 UTC. + int64 last_access_time_us = 7; + + // Whether the cookie should be transmitted only over secure connection. + // Defaults to false. + bool secure = 8; + + // Whether this is an HTTP-only cookie. Defaults to false. + bool http_only = 9; +} + +// A single local storage entry. +message LocalStorageEntry { + // The key for the local storage entry. + string key = 1; + + // The value of the local storage entry. + string value = 2; +} + +// Multiple local storages identified by unique id. +message LocalStorage { + // A serialized origin as defined in: + // https://html.spec.whatwg.org/multipage/origin.html#ascii-serialisation-of-an-origin. + // For example: "https://www.youtube.com" + string serialized_origin = 1; + + // The local storage entries for individual local storage. + repeated LocalStorageEntry local_storage_entries = 2; +} + +// The full storage. +message Storage { + // All the cookies. + repeated Cookie cookies = 1; + // All local storages. + repeated LocalStorage local_storages = 2; +}