| // Copyright 2015 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "third_party/blink/renderer/core/inspector/inspector_emulation_agent.h" |
| |
| #include "third_party/blink/public/common/input/web_touch_event.h" |
| #include "third_party/blink/public/common/loader/network_utils.h" |
| #include "third_party/blink/public/platform/platform.h" |
| #include "third_party/blink/public/platform/web_theme_engine.h" |
| #include "third_party/blink/public/web/web_render_theme.h" |
| #include "third_party/blink/renderer/core/css/vision_deficiency.h" |
| #include "third_party/blink/renderer/core/exported/web_view_impl.h" |
| #include "third_party/blink/renderer/core/frame/local_frame_view.h" |
| #include "third_party/blink/renderer/core/frame/settings.h" |
| #include "third_party/blink/renderer/core/frame/web_local_frame_impl.h" |
| #include "third_party/blink/renderer/core/inspector/dev_tools_emulator.h" |
| #include "third_party/blink/renderer/core/inspector/locale_controller.h" |
| #include "third_party/blink/renderer/core/inspector/protocol/dom.h" |
| #include "third_party/blink/renderer/core/loader/document_loader.h" |
| #include "third_party/blink/renderer/core/page/focus_controller.h" |
| #include "third_party/blink/renderer/core/page/page.h" |
| #include "third_party/blink/renderer/platform/graphics/color.h" |
| #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/loader_freeze_mode.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/resource.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/resource_request.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/url_loader/request_conversion.h" |
| #include "third_party/blink/renderer/platform/network/network_utils.h" |
| #include "third_party/blink/renderer/platform/scheduler/public/thread_cpu_throttler.h" |
| #include "third_party/blink/renderer/platform/scheduler/public/virtual_time_controller.h" |
| #include "third_party/blink/renderer/platform/theme/web_theme_engine_helper.h" |
| |
| namespace blink { |
| using protocol::Maybe; |
| |
| InspectorEmulationAgent::InspectorEmulationAgent( |
| WebLocalFrameImpl* web_local_frame_impl, |
| VirtualTimeController& virtual_time_controller) |
| : web_local_frame_(web_local_frame_impl), |
| virtual_time_controller_(virtual_time_controller), |
| default_background_color_override_rgba_(&agent_state_, |
| /*default_value=*/{}), |
| script_execution_disabled_(&agent_state_, /*default_value=*/false), |
| scrollbars_hidden_(&agent_state_, /*default_value=*/false), |
| document_cookie_disabled_(&agent_state_, /*default_value=*/false), |
| touch_event_emulation_enabled_(&agent_state_, /*default_value=*/false), |
| max_touch_points_(&agent_state_, /*default_value=*/1), |
| emulated_media_(&agent_state_, /*default_value=*/WTF::String()), |
| emulated_media_features_(&agent_state_, /*default_value=*/WTF::String()), |
| emulated_vision_deficiency_(&agent_state_, |
| /*default_value=*/WTF::String()), |
| navigator_platform_override_(&agent_state_, |
| /*default_value=*/WTF::String()), |
| hardware_concurrency_override_(&agent_state_, /*default_value=*/0), |
| user_agent_override_(&agent_state_, /*default_value=*/WTF::String()), |
| serialized_ua_metadata_override_( |
| &agent_state_, |
| /*default_value=*/std::vector<uint8_t>()), |
| accept_language_override_(&agent_state_, |
| /*default_value=*/WTF::String()), |
| locale_override_(&agent_state_, /*default_value=*/WTF::String()), |
| virtual_time_budget_(&agent_state_, /*default_value*/ 0.0), |
| initial_virtual_time_(&agent_state_, /*default_value=*/0.0), |
| virtual_time_policy_(&agent_state_, /*default_value=*/WTF::String()), |
| virtual_time_task_starvation_count_(&agent_state_, /*default_value=*/0), |
| emulate_focus_(&agent_state_, /*default_value=*/false), |
| emulate_auto_dark_mode_(&agent_state_, /*default_value=*/false), |
| auto_dark_mode_override_(&agent_state_, /*default_value=*/false), |
| timezone_id_override_(&agent_state_, /*default_value=*/WTF::String()), |
| disabled_image_types_(&agent_state_, /*default_value=*/false), |
| cpu_throttling_rate_(&agent_state_, /*default_value=*/1), |
| automation_override_(&agent_state_, /*default_value=*/false) {} |
| |
| InspectorEmulationAgent::~InspectorEmulationAgent() = default; |
| |
| WebViewImpl* InspectorEmulationAgent::GetWebViewImpl() { |
| return web_local_frame_ ? web_local_frame_->ViewImpl() : nullptr; |
| } |
| |
| void InspectorEmulationAgent::Restore() { |
| // Since serialized_ua_metadata_override_ can't directly be converted back |
| // to appropriate protocol message, we initially pass null and decode it |
| // directly. |
| std::vector<uint8_t> save_serialized_ua_metadata_override = |
| serialized_ua_metadata_override_.Get(); |
| setUserAgentOverride( |
| user_agent_override_.Get(), accept_language_override_.Get(), |
| navigator_platform_override_.Get(), |
| protocol::Maybe<protocol::Emulation::UserAgentMetadata>()); |
| ua_metadata_override_ = blink::UserAgentMetadata::Demarshal(std::string( |
| reinterpret_cast<char*>(save_serialized_ua_metadata_override.data()), |
| save_serialized_ua_metadata_override.size())); |
| serialized_ua_metadata_override_.Set(save_serialized_ua_metadata_override); |
| setCPUThrottlingRate(cpu_throttling_rate_.Get()); |
| |
| if (int concurrency = hardware_concurrency_override_.Get()) |
| setHardwareConcurrencyOverride(concurrency); |
| |
| if (!locale_override_.Get().empty()) |
| setLocaleOverride(locale_override_.Get()); |
| if (!web_local_frame_) |
| return; |
| |
| // Following code only runs for pages. |
| if (script_execution_disabled_.Get()) |
| GetWebViewImpl()->GetDevToolsEmulator()->SetScriptExecutionDisabled(true); |
| if (scrollbars_hidden_.Get()) |
| GetWebViewImpl()->GetDevToolsEmulator()->SetScrollbarsHidden(true); |
| if (document_cookie_disabled_.Get()) |
| GetWebViewImpl()->GetDevToolsEmulator()->SetDocumentCookieDisabled(true); |
| setTouchEmulationEnabled(touch_event_emulation_enabled_.Get(), |
| max_touch_points_.Get()); |
| auto features = |
| std::make_unique<protocol::Array<protocol::Emulation::MediaFeature>>(); |
| for (auto const& name : emulated_media_features_.Keys()) { |
| auto const& value = emulated_media_features_.Get(name); |
| features->push_back(protocol::Emulation::MediaFeature::create() |
| .setName(name) |
| .setValue(value) |
| .build()); |
| } |
| setEmulatedMedia(emulated_media_.Get(), std::move(features)); |
| if (!emulated_vision_deficiency_.Get().IsNull()) |
| setEmulatedVisionDeficiency(emulated_vision_deficiency_.Get()); |
| auto status_or_rgba = protocol::DOM::RGBA::ReadFrom( |
| default_background_color_override_rgba_.Get()); |
| if (status_or_rgba.ok()) |
| setDefaultBackgroundColorOverride(std::move(status_or_rgba).value()); |
| setFocusEmulationEnabled(emulate_focus_.Get()); |
| if (emulate_auto_dark_mode_.Get()) |
| setAutoDarkModeOverride(auto_dark_mode_override_.Get()); |
| if (!timezone_id_override_.Get().IsNull()) |
| setTimezoneOverride(timezone_id_override_.Get()); |
| |
| if (virtual_time_policy_.Get().IsNull()) |
| return; |
| |
| // Reinstate the stored policy. |
| double virtual_time_ticks_base_ms; |
| |
| // For Pause, do not pass budget or starvation count. |
| if (virtual_time_policy_.Get() == |
| protocol::Emulation::VirtualTimePolicyEnum::Pause) { |
| setVirtualTimePolicy( |
| protocol::Emulation::VirtualTimePolicyEnum::Pause, Maybe<double>(), |
| Maybe<int>(), initial_virtual_time_.Get(), &virtual_time_ticks_base_ms); |
| return; |
| } |
| |
| // Calculate remaining budget for the advancing modes. |
| double budget_remaining = virtual_time_budget_.Get(); |
| DCHECK_GE(budget_remaining, 0); |
| |
| setVirtualTimePolicy(virtual_time_policy_.Get(), budget_remaining, |
| virtual_time_task_starvation_count_.Get(), |
| initial_virtual_time_.Get(), |
| &virtual_time_ticks_base_ms); |
| } |
| |
| protocol::Response InspectorEmulationAgent::disable() { |
| if (enabled_) { |
| instrumenting_agents_->RemoveInspectorEmulationAgent(this); |
| enabled_ = false; |
| } |
| |
| hardware_concurrency_override_.Clear(); |
| setUserAgentOverride( |
| String(), protocol::Maybe<String>(), protocol::Maybe<String>(), |
| protocol::Maybe<protocol::Emulation::UserAgentMetadata>()); |
| if (!locale_override_.Get().empty()) |
| setLocaleOverride(String()); |
| if (!web_local_frame_) |
| return protocol::Response::Success(); |
| setScriptExecutionDisabled(false); |
| setScrollbarsHidden(false); |
| setDocumentCookieDisabled(false); |
| setTouchEmulationEnabled(false, Maybe<int>()); |
| setAutomationOverride(false); |
| // Clear emulated media features. Note that the current approach |
| // doesn't work well in cases where two clients have the same set of |
| // features overridden to the same value by two different clients |
| // (e.g. if we allowed two different front-ends with the same |
| // settings to attach to the same page). TODO: support this use case. |
| setEmulatedMedia( |
| String(), |
| std::make_unique<protocol::Array<protocol::Emulation::MediaFeature>>()); |
| if (!emulated_vision_deficiency_.Get().IsNull()) |
| setEmulatedVisionDeficiency(String("none")); |
| setCPUThrottlingRate(1); |
| setFocusEmulationEnabled(false); |
| if (emulate_auto_dark_mode_.Get()) { |
| setAutoDarkModeOverride(Maybe<bool>()); |
| } |
| timezone_override_.reset(); |
| setDefaultBackgroundColorOverride(Maybe<protocol::DOM::RGBA>()); |
| disabled_image_types_.Clear(); |
| return protocol::Response::Success(); |
| } |
| |
| protocol::Response InspectorEmulationAgent::resetPageScaleFactor() { |
| protocol::Response response = AssertPage(); |
| if (!response.IsSuccess()) |
| return response; |
| GetWebViewImpl()->ResetScaleStateImmediately(); |
| return response; |
| } |
| |
| protocol::Response InspectorEmulationAgent::setPageScaleFactor( |
| double page_scale_factor) { |
| protocol::Response response = AssertPage(); |
| if (!response.IsSuccess()) |
| return response; |
| GetWebViewImpl()->SetPageScaleFactor(static_cast<float>(page_scale_factor)); |
| return response; |
| } |
| |
| protocol::Response InspectorEmulationAgent::setScriptExecutionDisabled( |
| bool value) { |
| protocol::Response response = AssertPage(); |
| if (!response.IsSuccess()) |
| return response; |
| if (script_execution_disabled_.Get() == value) |
| return response; |
| script_execution_disabled_.Set(value); |
| GetWebViewImpl()->GetDevToolsEmulator()->SetScriptExecutionDisabled(value); |
| return response; |
| } |
| |
| protocol::Response InspectorEmulationAgent::setScrollbarsHidden(bool hidden) { |
| protocol::Response response = AssertPage(); |
| if (!response.IsSuccess()) |
| return response; |
| if (scrollbars_hidden_.Get() == hidden) |
| return response; |
| scrollbars_hidden_.Set(hidden); |
| GetWebViewImpl()->GetDevToolsEmulator()->SetScrollbarsHidden(hidden); |
| return response; |
| } |
| |
| protocol::Response InspectorEmulationAgent::setDocumentCookieDisabled( |
| bool disabled) { |
| protocol::Response response = AssertPage(); |
| if (!response.IsSuccess()) |
| return response; |
| if (document_cookie_disabled_.Get() == disabled) |
| return response; |
| document_cookie_disabled_.Set(disabled); |
| GetWebViewImpl()->GetDevToolsEmulator()->SetDocumentCookieDisabled(disabled); |
| return response; |
| } |
| |
| protocol::Response InspectorEmulationAgent::setTouchEmulationEnabled( |
| bool enabled, |
| protocol::Maybe<int> max_touch_points) { |
| protocol::Response response = AssertPage(); |
| if (!response.IsSuccess()) |
| return response; |
| int max_points = max_touch_points.value_or(1); |
| if (max_points < 1 || max_points > WebTouchEvent::kTouchesLengthCap) { |
| String msg = |
| "Touch points must be between 1 and " + |
| String::Number(static_cast<uint16_t>(WebTouchEvent::kTouchesLengthCap)); |
| return protocol::Response::InvalidParams(msg.Utf8()); |
| } |
| touch_event_emulation_enabled_.Set(enabled); |
| max_touch_points_.Set(max_points); |
| GetWebViewImpl()->GetDevToolsEmulator()->SetTouchEventEmulationEnabled( |
| enabled, max_points); |
| return response; |
| } |
| |
| protocol::Response InspectorEmulationAgent::setEmulatedMedia( |
| Maybe<String> media, |
| Maybe<protocol::Array<protocol::Emulation::MediaFeature>> features) { |
| protocol::Response response = AssertPage(); |
| if (!response.IsSuccess()) |
| return response; |
| String media_value = media.value_or(""); |
| emulated_media_.Set(media_value); |
| GetWebViewImpl()->GetPage()->GetSettings().SetMediaTypeOverride(media_value); |
| |
| auto const old_emulated_media_features_keys = emulated_media_features_.Keys(); |
| emulated_media_features_.Clear(); |
| |
| if (features.has_value()) { |
| for (const auto& media_feature : features.value()) { |
| String name = media_feature->getName(); |
| String value = media_feature->getValue(); |
| emulated_media_features_.Set(name, value); |
| } |
| |
| auto const& forced_colors_value = |
| emulated_media_features_.Get("forced-colors"); |
| auto const& prefers_color_scheme_value = |
| emulated_media_features_.Get("prefers-color-scheme"); |
| |
| if (forced_colors_value == "active") { |
| if (!forced_colors_override_) { |
| initial_system_forced_colors_state_ = |
| GetWebViewImpl()->GetPage()->GetSettings().GetInForcedColors(); |
| } |
| forced_colors_override_ = true; |
| bool is_dark_mode = false; |
| if (prefers_color_scheme_value.empty()) { |
| is_dark_mode = GetWebViewImpl() |
| ->GetPage() |
| ->GetSettings() |
| .GetPreferredColorScheme() == |
| mojom::blink::PreferredColorScheme::kDark; |
| } else { |
| is_dark_mode = prefers_color_scheme_value == "dark"; |
| } |
| GetWebViewImpl()->GetPage()->EmulateForcedColors(is_dark_mode); |
| GetWebViewImpl()->GetPage()->GetSettings().SetInForcedColors(true); |
| } else if (forced_colors_value == "none") { |
| if (!forced_colors_override_) { |
| initial_system_forced_colors_state_ = |
| GetWebViewImpl()->GetPage()->GetSettings().GetInForcedColors(); |
| } |
| forced_colors_override_ = true; |
| GetWebViewImpl()->GetPage()->DisableEmulatedForcedColors(); |
| GetWebViewImpl()->GetPage()->GetSettings().SetInForcedColors(false); |
| } else if (forced_colors_override_) { |
| GetWebViewImpl()->GetPage()->DisableEmulatedForcedColors(); |
| GetWebViewImpl()->GetPage()->GetSettings().SetInForcedColors( |
| initial_system_forced_colors_state_); |
| } |
| |
| for (const WTF::String& feature : emulated_media_features_.Keys()) { |
| auto const& value = emulated_media_features_.Get(feature); |
| GetWebViewImpl()->GetPage()->SetMediaFeatureOverride( |
| AtomicString(feature), value); |
| } |
| |
| if (forced_colors_override_) { |
| blink::SystemColorsChanged(); |
| |
| if (forced_colors_value != "none" && forced_colors_value != "active") { |
| forced_colors_override_ = false; |
| } |
| } |
| } |
| |
| for (const WTF::String& feature : old_emulated_media_features_keys) { |
| auto const& value = emulated_media_features_.Get(feature); |
| if (!value) { |
| GetWebViewImpl()->GetPage()->SetMediaFeatureOverride( |
| AtomicString(feature), ""); |
| } |
| } |
| |
| return response; |
| } |
| |
| protocol::Response InspectorEmulationAgent::setEmulatedVisionDeficiency( |
| const String& type) { |
| protocol::Response response = AssertPage(); |
| if (!response.IsSuccess()) |
| return response; |
| |
| VisionDeficiency vision_deficiency; |
| namespace TypeEnum = |
| protocol::Emulation::SetEmulatedVisionDeficiency::TypeEnum; |
| if (type == TypeEnum::None) |
| vision_deficiency = VisionDeficiency::kNoVisionDeficiency; |
| else if (type == TypeEnum::BlurredVision) |
| vision_deficiency = VisionDeficiency::kBlurredVision; |
| else if (type == TypeEnum::ReducedContrast) |
| vision_deficiency = VisionDeficiency::kReducedContrast; |
| else if (type == TypeEnum::Achromatopsia) |
| vision_deficiency = VisionDeficiency::kAchromatopsia; |
| else if (type == TypeEnum::Deuteranopia) |
| vision_deficiency = VisionDeficiency::kDeuteranopia; |
| else if (type == TypeEnum::Protanopia) |
| vision_deficiency = VisionDeficiency::kProtanopia; |
| else if (type == TypeEnum::Tritanopia) |
| vision_deficiency = VisionDeficiency::kTritanopia; |
| else |
| return protocol::Response::InvalidParams("Unknown vision deficiency type"); |
| |
| emulated_vision_deficiency_.Set(type); |
| GetWebViewImpl()->GetPage()->SetVisionDeficiency(vision_deficiency); |
| return response; |
| } |
| |
| protocol::Response InspectorEmulationAgent::setCPUThrottlingRate(double rate) { |
| protocol::Response response = AssertPage(); |
| if (!response.IsSuccess()) |
| return response; |
| cpu_throttling_rate_.Set(rate); |
| scheduler::ThreadCPUThrottler::GetInstance()->SetThrottlingRate(rate); |
| return response; |
| } |
| |
| protocol::Response InspectorEmulationAgent::setFocusEmulationEnabled( |
| bool enabled) { |
| protocol::Response response = AssertPage(); |
| if (!response.IsSuccess()) |
| return response; |
| if (enabled == emulate_focus_.Get()) { |
| return response; |
| } |
| emulate_focus_.Set(enabled); |
| GetWebViewImpl()->GetPage()->GetFocusController().SetFocusEmulationEnabled( |
| enabled); |
| return response; |
| } |
| |
| protocol::Response InspectorEmulationAgent::setAutoDarkModeOverride( |
| Maybe<bool> enabled) { |
| protocol::Response response = AssertPage(); |
| if (!response.IsSuccess()) |
| return response; |
| if (enabled.has_value()) { |
| emulate_auto_dark_mode_.Set(true); |
| auto_dark_mode_override_.Set(enabled.value()); |
| GetWebViewImpl()->GetDevToolsEmulator()->SetAutoDarkModeOverride( |
| enabled.value()); |
| } else { |
| emulate_auto_dark_mode_.Set(false); |
| GetWebViewImpl()->GetDevToolsEmulator()->ResetAutoDarkModeOverride(); |
| } |
| return response; |
| } |
| |
| protocol::Response InspectorEmulationAgent::setVirtualTimePolicy( |
| const String& policy, |
| Maybe<double> virtual_time_budget_ms, |
| protocol::Maybe<int> max_virtual_time_task_starvation_count, |
| protocol::Maybe<double> initial_virtual_time, |
| double* virtual_time_ticks_base_ms) { |
| VirtualTimeController::VirtualTimePolicy scheduler_policy = |
| VirtualTimeController::VirtualTimePolicy::kPause; |
| if (protocol::Emulation::VirtualTimePolicyEnum::Advance == policy) { |
| scheduler_policy = VirtualTimeController::VirtualTimePolicy::kAdvance; |
| } else if (protocol::Emulation::VirtualTimePolicyEnum:: |
| PauseIfNetworkFetchesPending == policy) { |
| scheduler_policy = |
| VirtualTimeController::VirtualTimePolicy::kDeterministicLoading; |
| } else { |
| DCHECK_EQ(scheduler_policy, |
| VirtualTimeController::VirtualTimePolicy::kPause); |
| if (virtual_time_budget_ms.has_value()) { |
| return protocol::Response::InvalidParams( |
| "Can only specify budget for non-Pause policy"); |
| } |
| if (max_virtual_time_task_starvation_count.has_value()) { |
| return protocol::Response::InvalidParams( |
| "Can only specify starvation count for non-Pause policy"); |
| } |
| } |
| |
| virtual_time_policy_.Set(policy); |
| virtual_time_budget_.Set(virtual_time_budget_ms.value_or(0)); |
| initial_virtual_time_.Set(initial_virtual_time.value_or(0)); |
| virtual_time_task_starvation_count_.Set( |
| max_virtual_time_task_starvation_count.value_or(0)); |
| |
| InnerEnable(); |
| |
| // This needs to happen before we apply virtual time. |
| base::Time initial_time = |
| initial_virtual_time.has_value() |
| ? base::Time::FromSecondsSinceUnixEpoch(initial_virtual_time.value()) |
| : base::Time(); |
| virtual_time_base_ticks_ = |
| virtual_time_controller_.EnableVirtualTime(initial_time); |
| virtual_time_controller_.SetVirtualTimePolicy(scheduler_policy); |
| if (virtual_time_budget_ms.value_or(0) > 0) { |
| TRACE_EVENT_NESTABLE_ASYNC_BEGIN1("renderer.scheduler", "VirtualTimeBudget", |
| TRACE_ID_LOCAL(this), "budget", |
| virtual_time_budget_ms.value()); |
| const base::TimeDelta budget_amount = |
| base::Milliseconds(virtual_time_budget_ms.value()); |
| virtual_time_controller_.GrantVirtualTimeBudget( |
| budget_amount, |
| WTF::BindOnce(&InspectorEmulationAgent::VirtualTimeBudgetExpired, |
| WrapWeakPersistent(this))); |
| for (DocumentLoader* loader : pending_document_loaders_) |
| loader->SetDefersLoading(LoaderFreezeMode::kNone); |
| pending_document_loaders_.clear(); |
| } |
| |
| if (max_virtual_time_task_starvation_count.value_or(0)) { |
| virtual_time_controller_.SetMaxVirtualTimeTaskStarvationCount( |
| max_virtual_time_task_starvation_count.value()); |
| } |
| |
| *virtual_time_ticks_base_ms = |
| virtual_time_base_ticks_.is_null() |
| ? 0 |
| : (virtual_time_base_ticks_ - base::TimeTicks()).InMillisecondsF(); |
| |
| return protocol::Response::Success(); |
| } |
| |
| AtomicString InspectorEmulationAgent::OverrideAcceptImageHeader( |
| const HashSet<String>* disabled_image_types) { |
| String header(network_utils::ImageAcceptHeader()); |
| for (String type : *disabled_image_types) { |
| // The header string is expected to be like |
| // `image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8` |
| // and is expected to be always ending with `image/*,*/*;q=xxx`, therefore, |
| // to remove a type we replace `image/x,` with empty string. Only webp and |
| // avif types can be disabled. |
| header.Replace(String(type + ","), ""); |
| } |
| return AtomicString(header); |
| } |
| |
| void InspectorEmulationAgent::PrepareRequest(DocumentLoader* loader, |
| ResourceRequest& request, |
| ResourceLoaderOptions& options, |
| ResourceType resource_type) { |
| if (!accept_language_override_.Get().empty() && |
| request.HttpHeaderField(http_names::kAcceptLanguage).empty()) { |
| request.SetHttpHeaderField( |
| http_names::kAcceptLanguage, |
| AtomicString(network_utils::GenerateAcceptLanguageHeader( |
| accept_language_override_.Get()))); |
| } |
| |
| if (resource_type != ResourceType::kImage || disabled_image_types_.IsEmpty()) |
| return; |
| |
| if (!options.unsupported_image_mime_types) { |
| options.unsupported_image_mime_types = |
| base::MakeRefCounted<base::RefCountedData<HashSet<String>>>(); |
| } |
| |
| for (String type : disabled_image_types_.Keys()) { |
| options.unsupported_image_mime_types->data.insert(type); |
| } |
| |
| request.SetHTTPAccept( |
| OverrideAcceptImageHeader(&options.unsupported_image_mime_types->data)); |
| // Bypassing caching to prevent the use of the previously loaded and cached |
| // images. |
| request.SetCacheMode(mojom::blink::FetchCacheMode::kBypassCache); |
| } |
| |
| protocol::Response InspectorEmulationAgent::setNavigatorOverrides( |
| const String& platform) { |
| protocol::Response response = AssertPage(); |
| if (!response.IsSuccess()) |
| return response; |
| navigator_platform_override_.Set(platform); |
| GetWebViewImpl()->GetPage()->GetSettings().SetNavigatorPlatformOverride( |
| platform); |
| return response; |
| } |
| |
| void InspectorEmulationAgent::VirtualTimeBudgetExpired() { |
| TRACE_EVENT_NESTABLE_ASYNC_END0("renderer.scheduler", "VirtualTimeBudget", |
| TRACE_ID_LOCAL(this)); |
| // Disregard the event if the agent is disabled. Another agent may take care |
| // of pausing the time in case of an in-process frame swap. |
| if (!enabled_) { |
| return; |
| } |
| virtual_time_controller_.SetVirtualTimePolicy( |
| VirtualTimeController::VirtualTimePolicy::kPause); |
| virtual_time_policy_.Set(protocol::Emulation::VirtualTimePolicyEnum::Pause); |
| // We could have been detached while VT was still running. |
| // TODO(caseq): should we rather force-pause the time upon Disable()? |
| if (auto* frontend = GetFrontend()) |
| frontend->virtualTimeBudgetExpired(); |
| } |
| |
| protocol::Response InspectorEmulationAgent::setDefaultBackgroundColorOverride( |
| Maybe<protocol::DOM::RGBA> color) { |
| protocol::Response response = AssertPage(); |
| if (!response.IsSuccess()) |
| return response; |
| if (!color.has_value()) { |
| // Clear the override and state. |
| GetWebViewImpl()->SetBaseBackgroundColorOverrideForInspector(std::nullopt); |
| default_background_color_override_rgba_.Clear(); |
| return protocol::Response::Success(); |
| } |
| |
| blink::protocol::DOM::RGBA* rgba = &color.value(); |
| default_background_color_override_rgba_.Set(rgba->Serialize()); |
| // Clamping of values is done by Color() constructor. |
| int alpha = static_cast<int>(lroundf(255.0f * rgba->getA(1.0f))); |
| GetWebViewImpl()->SetBaseBackgroundColorOverrideForInspector( |
| Color(rgba->getR(), rgba->getG(), rgba->getB(), alpha).Rgb()); |
| return protocol::Response::Success(); |
| } |
| |
| protocol::Response InspectorEmulationAgent::setDeviceMetricsOverride( |
| int width, |
| int height, |
| double device_scale_factor, |
| bool mobile, |
| Maybe<double> scale, |
| Maybe<int> screen_width, |
| Maybe<int> screen_height, |
| Maybe<int> position_x, |
| Maybe<int> position_y, |
| Maybe<bool> dont_set_visible_size, |
| Maybe<protocol::Emulation::ScreenOrientation>, |
| Maybe<protocol::Page::Viewport>, |
| Maybe<protocol::Emulation::DisplayFeature>, |
| Maybe<protocol::Emulation::DevicePosture>) { |
| // We don't have to do anything other than reply to the client, as the |
| // emulation parameters should have already been updated by the handling of |
| // blink::mojom::FrameWidget::EnableDeviceEmulation. |
| return AssertPage(); |
| } |
| |
| protocol::Response InspectorEmulationAgent::clearDeviceMetricsOverride() { |
| // We don't have to do anything other than reply to the client, as the |
| // emulation parameters should have already been cleared by the handling of |
| // blink::mojom::FrameWidget::DisableDeviceEmulation. |
| return AssertPage(); |
| } |
| |
| protocol::Response InspectorEmulationAgent::setHardwareConcurrencyOverride( |
| int hardware_concurrency) { |
| if (hardware_concurrency <= 0) { |
| return protocol::Response::InvalidParams( |
| "HardwareConcurrency must be a positive number"); |
| } |
| InnerEnable(); |
| hardware_concurrency_override_.Set(hardware_concurrency); |
| |
| return protocol::Response::Success(); |
| } |
| |
| protocol::Response InspectorEmulationAgent::setUserAgentOverride( |
| const String& user_agent, |
| protocol::Maybe<String> accept_language, |
| protocol::Maybe<String> platform, |
| protocol::Maybe<protocol::Emulation::UserAgentMetadata> |
| ua_metadata_override) { |
| if (!user_agent.empty() || accept_language.has_value() || |
| platform.has_value()) { |
| InnerEnable(); |
| } |
| user_agent_override_.Set(user_agent); |
| accept_language_override_.Set(accept_language.value_or(String())); |
| navigator_platform_override_.Set(platform.value_or(String())); |
| if (web_local_frame_) { |
| GetWebViewImpl()->GetPage()->GetSettings().SetNavigatorPlatformOverride( |
| navigator_platform_override_.Get()); |
| } |
| |
| if (ua_metadata_override.has_value()) { |
| blink::UserAgentMetadata default_ua_metadata = |
| Platform::Current()->UserAgentMetadata(); |
| |
| if (user_agent.empty()) { |
| ua_metadata_override_ = std::nullopt; |
| serialized_ua_metadata_override_.Set(std::vector<uint8_t>()); |
| return protocol::Response::InvalidParams( |
| "Can't specify UserAgentMetadata but no UA string"); |
| } |
| protocol::Emulation::UserAgentMetadata& ua_metadata = |
| ua_metadata_override.value(); |
| ua_metadata_override_.emplace(); |
| if (ua_metadata.hasBrands()) { |
| for (const auto& bv : *ua_metadata.getBrands(nullptr)) { |
| blink::UserAgentBrandVersion out_bv; |
| out_bv.brand = bv->getBrand().Ascii(); |
| out_bv.version = bv->getVersion().Ascii(); |
| ua_metadata_override_->brand_version_list.push_back(std::move(out_bv)); |
| } |
| } else { |
| ua_metadata_override_->brand_version_list = |
| std::move(default_ua_metadata.brand_version_list); |
| } |
| |
| if (ua_metadata.hasFullVersionList()) { |
| for (const auto& bv : *ua_metadata.getFullVersionList(nullptr)) { |
| blink::UserAgentBrandVersion out_bv; |
| out_bv.brand = bv->getBrand().Ascii(); |
| out_bv.version = bv->getVersion().Ascii(); |
| ua_metadata_override_->brand_full_version_list.push_back( |
| std::move(out_bv)); |
| } |
| } else { |
| ua_metadata_override_->brand_full_version_list = |
| std::move(default_ua_metadata.brand_full_version_list); |
| } |
| |
| if (ua_metadata.hasFullVersion()) { |
| ua_metadata_override_->full_version = |
| ua_metadata.getFullVersion("").Ascii(); |
| } else { |
| ua_metadata_override_->full_version = |
| std::move(default_ua_metadata.full_version); |
| } |
| ua_metadata_override_->platform = ua_metadata.getPlatform().Ascii(); |
| ua_metadata_override_->platform_version = |
| ua_metadata.getPlatformVersion().Ascii(); |
| ua_metadata_override_->architecture = ua_metadata.getArchitecture().Ascii(); |
| ua_metadata_override_->model = ua_metadata.getModel().Ascii(); |
| ua_metadata_override_->mobile = ua_metadata.getMobile(); |
| |
| if (ua_metadata.hasBitness()) { |
| ua_metadata_override_->bitness = ua_metadata.getBitness("").Ascii(); |
| } else { |
| ua_metadata_override_->bitness = std::move(default_ua_metadata.bitness); |
| } |
| if (ua_metadata.hasWow64()) { |
| ua_metadata_override_->wow64 = ua_metadata.getWow64(false); |
| } else { |
| ua_metadata_override_->wow64 = default_ua_metadata.wow64; |
| } |
| |
| } else { |
| ua_metadata_override_ = std::nullopt; |
| } |
| |
| std::string marshalled = |
| blink::UserAgentMetadata::Marshal(ua_metadata_override_) |
| .value_or(std::string()); |
| std::vector<uint8_t> marshalled_as_bytes; |
| marshalled_as_bytes.insert(marshalled_as_bytes.end(), marshalled.begin(), |
| marshalled.end()); |
| serialized_ua_metadata_override_.Set(std::move(marshalled_as_bytes)); |
| |
| return protocol::Response::Success(); |
| } |
| |
| protocol::Response InspectorEmulationAgent::setLocaleOverride( |
| protocol::Maybe<String> maybe_locale) { |
| // Only allow resetting overrides set by the same agent. |
| if (locale_override_.Get().empty() && |
| LocaleController::instance().has_locale_override()) { |
| return protocol::Response::ServerError( |
| "Another locale override is already in effect"); |
| } |
| String locale = maybe_locale.value_or(String()); |
| String error = LocaleController::instance().SetLocaleOverride(locale); |
| if (!error.empty()) |
| return protocol::Response::ServerError(error.Utf8()); |
| locale_override_.Set(locale); |
| return protocol::Response::Success(); |
| } |
| |
| protocol::Response InspectorEmulationAgent::setTimezoneOverride( |
| const String& timezone_id) { |
| if (timezone_id == TimeZoneController::TimeZoneIdOverride()) { |
| // Do nothing. |
| } else if (timezone_id.empty()) { |
| timezone_override_.reset(); |
| } else { |
| if (timezone_override_) { |
| timezone_override_->change(timezone_id); |
| } else { |
| timezone_override_ = TimeZoneController::SetTimeZoneOverride(timezone_id); |
| } |
| if (!timezone_override_) { |
| return TimeZoneController::HasTimeZoneOverride() |
| ? protocol::Response::ServerError( |
| "Timezone override is already in effect") |
| : protocol::Response::InvalidParams("Invalid timezone id"); |
| } |
| } |
| |
| timezone_id_override_.Set(timezone_id); |
| |
| return protocol::Response::Success(); |
| } |
| |
| void InspectorEmulationAgent::GetDisabledImageTypes(HashSet<String>* result) { |
| if (disabled_image_types_.IsEmpty()) |
| return; |
| |
| for (String type : disabled_image_types_.Keys()) |
| result->insert(type); |
| } |
| |
| void InspectorEmulationAgent::WillCommitLoad(LocalFrame*, |
| DocumentLoader* loader) { |
| if (virtual_time_policy_.Get() != |
| protocol::Emulation::VirtualTimePolicyEnum::Pause) { |
| return; |
| } |
| loader->SetDefersLoading(LoaderFreezeMode::kStrict); |
| pending_document_loaders_.push_back(loader); |
| } |
| |
| void InspectorEmulationAgent::WillCreateDocumentParser( |
| bool& force_sync_parsing) { |
| if (virtual_time_policy_.Get().IsNull()) |
| return; |
| force_sync_parsing = true; |
| } |
| |
| void InspectorEmulationAgent::ApplyAcceptLanguageOverride(String* accept_lang) { |
| if (!accept_language_override_.Get().empty()) |
| *accept_lang = accept_language_override_.Get(); |
| } |
| |
| void InspectorEmulationAgent::ApplyHardwareConcurrencyOverride( |
| unsigned int& hardware_concurrency) { |
| if (int concurrency = hardware_concurrency_override_.Get()) |
| hardware_concurrency = concurrency; |
| } |
| |
| void InspectorEmulationAgent::ApplyUserAgentOverride(String* user_agent) { |
| if (!user_agent_override_.Get().empty()) |
| *user_agent = user_agent_override_.Get(); |
| } |
| |
| void InspectorEmulationAgent::ApplyUserAgentMetadataOverride( |
| std::optional<blink::UserAgentMetadata>* ua_metadata) { |
| // This applies when UA override is set. |
| if (!user_agent_override_.Get().empty()) { |
| *ua_metadata = ua_metadata_override_; |
| } |
| } |
| |
| void InspectorEmulationAgent::InnerEnable() { |
| if (enabled_) |
| return; |
| enabled_ = true; |
| instrumenting_agents_->AddInspectorEmulationAgent(this); |
| } |
| |
| void InspectorEmulationAgent::SetSystemThemeState() {} |
| |
| protocol::Response InspectorEmulationAgent::AssertPage() { |
| if (!web_local_frame_) { |
| return protocol::Response::ServerError( |
| "Operation is only supported for pages, not workers"); |
| } |
| return protocol::Response::Success(); |
| } |
| |
| void InspectorEmulationAgent::Trace(Visitor* visitor) const { |
| visitor->Trace(web_local_frame_); |
| visitor->Trace(pending_document_loaders_); |
| InspectorBaseAgent::Trace(visitor); |
| } |
| |
| protocol::Response InspectorEmulationAgent::setDisabledImageTypes( |
| std::unique_ptr<protocol::Array<protocol::Emulation::DisabledImageType>> |
| disabled_types) { |
| if (disabled_types->size() > 0 && !enabled_) |
| InnerEnable(); |
| disabled_image_types_.Clear(); |
| String prefix = "image/"; |
| namespace DisabledImageTypeEnum = protocol::Emulation::DisabledImageTypeEnum; |
| for (protocol::Emulation::DisabledImageType type : *disabled_types) { |
| if (DisabledImageTypeEnum::Avif == type || |
| DisabledImageTypeEnum::Webp == type) { |
| disabled_image_types_.Set(prefix + type, true); |
| continue; |
| } |
| disabled_image_types_.Clear(); |
| return protocol::Response::InvalidParams("Invalid image type"); |
| } |
| return protocol::Response::Success(); |
| } |
| |
| protocol::Response InspectorEmulationAgent::setAutomationOverride( |
| bool enabled) { |
| if (enabled) |
| InnerEnable(); |
| automation_override_.Set(enabled); |
| return protocol::Response::Success(); |
| } |
| |
| void InspectorEmulationAgent::ApplyAutomationOverride(bool& enabled) const { |
| enabled |= automation_override_.Get(); |
| } |
| |
| } // namespace blink |