| // Copyright 2026 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/actor/actor_util.h" |
| |
| #include <optional> |
| #include <vector> |
| |
| #include "chrome/browser/actor/actor_keyed_service.h" |
| #include "chrome/browser/actor/execution_engine.h" |
| #include "chrome/browser/glic/public/glic_instance.h" |
| #include "chrome/browser/glic/public/glic_keyed_service.h" |
| #include "chrome/browser/ui/browser_window/public/browser_window_interface.h" |
| #include "components/optimization_guide/proto/features/common_quality_data.pb.h" |
| #include "components/tabs/public/tab_interface.h" |
| #include "content/public/browser/web_contents.h" |
| #include "skia/ext/font_utils.h" |
| #include "third_party/skia/include/core/SkBitmap.h" |
| #include "third_party/skia/include/core/SkCanvas.h" |
| #include "third_party/skia/include/core/SkFont.h" |
| #include "third_party/skia/include/core/SkPaint.h" |
| #include "ui/gfx/codec/jpeg_codec.h" |
| #include "ui/gfx/codec/png_codec.h" |
| |
| namespace actor { |
| |
| bool HaveActiveTaskForContents(content::WebContents* source_contents) { |
| if (!source_contents) { |
| return false; |
| } |
| |
| actor::ActorKeyedService* actor_service = |
| actor::ActorKeyedService::Get(source_contents->GetBrowserContext()); |
| if (!actor_service) { |
| return false; |
| } |
| |
| return actor_service->GetActingActorTaskForWebContents(source_contents); |
| } |
| |
| bool IsRunningBackgroundActorTask(content::WebContents& source_contents) { |
| if (!HaveActiveTaskForContents(&source_contents)) { |
| return false; |
| } |
| |
| tabs::TabInterface* task_tab = |
| tabs::TabInterface::GetFromContents(&source_contents); |
| if (task_tab->IsActivated()) { |
| // The tab in which the task is running is active, so the task isn't in the |
| // background. |
| return false; |
| } |
| |
| // Determine whether the active tab is showing the conversation instance of |
| // the actor task. If the conversation is showing, consider the task to be in |
| // the foreground. |
| |
| glic::GlicKeyedService* glic_service = |
| glic::GlicKeyedService::Get(source_contents.GetBrowserContext()); |
| if (!glic_service) { |
| // Without Glic, there'd be no conversation to provide context to the user |
| // while the tab isn't active, so consider the task backgrounded. |
| return true; |
| } |
| |
| const glic::GlicInstance* task_instance = |
| glic_service->GetInstanceForTab(task_tab); |
| if (!task_instance) { |
| // There's no Glic instance for the tab running the task (e.g. something |
| // else is invoking the actor framework). As above, there's no conversation, |
| // so consider the task backgrounded. |
| return true; |
| } |
| BrowserWindowInterface* task_window = task_tab->GetBrowserWindowInterface(); |
| const glic::GlicInstance* active_instance = |
| glic_service->GetInstanceForActiveTab(task_window); |
| if (task_instance != active_instance) { |
| // The active tab has no Glic instance or an unrelated instance. The user |
| // doesn't see the conversation related to the task, so consider the task |
| // backgrounded. |
| return true; |
| } |
| |
| return !task_instance->IsShowing(); |
| } |
| |
| bool HasActorTaskPreventingNewWebContents(content::RenderFrameHost* rfh) { |
| auto* wc = content::WebContents::FromRenderFrameHost(rfh); |
| if (!wc) { |
| return false; |
| } |
| |
| auto* actor_service = ActorKeyedService::Get(wc->GetBrowserContext()); |
| if (!actor_service) { |
| return false; |
| } |
| |
| const auto* tab_interface = tabs::TabInterface::MaybeGetFromContents(wc); |
| if (!tab_interface) { |
| return false; |
| } |
| |
| const ActorTask* task = actor_service->GetTaskFromTab(*tab_interface); |
| if (!task) { |
| return false; |
| } |
| |
| return !task->GetExecutionEngine().TabsCanOpenNewWebContents(); |
| } |
| |
| std::optional<std::vector<uint8_t>> GetScreenshotWithIframeBoundingBoxes( |
| const std::vector<uint8_t>& screenshot_data, |
| std::string_view mime_type, |
| const optimization_guide::proto::ScreenshotInfo& screenshot_info) { |
| if (screenshot_info.iframe_info_size() == 0) { |
| return screenshot_data; |
| } |
| |
| SkBitmap bitmap; |
| if (mime_type == "image/png") { |
| bitmap = gfx::PNGCodec::Decode(screenshot_data); |
| } else { |
| bitmap = gfx::JPEGCodec::Decode(screenshot_data); |
| } |
| |
| if (bitmap.isNull()) { |
| return std::nullopt; |
| } |
| |
| SkCanvas canvas(bitmap); |
| SkPaint paint; |
| paint.setColor(SK_ColorRED); |
| paint.setStyle(SkPaint::kStroke_Style); |
| paint.setStrokeWidth(3.0f); |
| |
| for (const auto& iframe : screenshot_info.iframe_info()) { |
| if (iframe.has_bounding_box()) { |
| const auto& rect = iframe.bounding_box(); |
| canvas.drawRect( |
| SkRect::MakeXYWH(rect.x(), rect.y(), rect.width(), rect.height()), |
| paint); |
| |
| if (iframe.has_security_origin() && |
| !iframe.security_origin().value().empty()) { |
| SkFont font = skia::DefaultFont(); |
| font.setSize(12.0f); |
| std::string origin_str = iframe.security_origin().value(); |
| SkRect text_bounds; |
| font.measureText(origin_str.c_str(), origin_str.size(), |
| SkTextEncoding::kUTF8, &text_bounds); |
| |
| SkRect bg_rect = SkRect::MakeXYWH( |
| rect.x() + 5.0f + text_bounds.left() - 2.0f, |
| rect.y() + 15.0f + text_bounds.top() - 2.0f, |
| text_bounds.width() + 4.0f, text_bounds.height() + 4.0f); |
| |
| SkPaint bg_paint; |
| bg_paint.setColor(SK_ColorWHITE); |
| bg_paint.setStyle(SkPaint::kFill_Style); |
| canvas.drawRect(bg_rect, bg_paint); |
| |
| SkPaint text_paint; |
| text_paint.setColor(SK_ColorRED); |
| canvas.drawString(origin_str.c_str(), rect.x() + 5.0f, rect.y() + 15.0f, |
| font, text_paint); |
| } |
| } |
| } |
| |
| std::optional<std::vector<uint8_t>> encoded; |
| if (mime_type == "image/png") { |
| encoded = gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, |
| /*discard_transparency=*/false); |
| } else { |
| encoded = gfx::JPEGCodec::Encode(bitmap, /*quality=*/100); |
| } |
| |
| if (!encoded) { |
| return std::nullopt; |
| } |
| return *encoded; |
| } |
| |
| } // namespace actor |