blob: 123f44e5af12c67529815951b86c7e549835aecf [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/intelligence/proto_wrappers/page_context_wrapper.h"
#import <memory>
#import "base/barrier_closure.h"
#import "base/check.h"
#import "base/check_op.h"
#import "base/memory/weak_ptr.h"
#import "base/strings/sys_string_conversions.h"
#import "base/strings/utf_string_conversions.h"
#import "ios/chrome/browser/snapshots/model/snapshot_tab_helper.h"
#import "ios/web/public/web_state.h"
#if BUILDFLAG(BUILD_WITH_INTERNAL_OPTIMIZATION_GUIDE)
#import "components/optimization_guide/proto/features/common_quality_data.pb.h"
#endif // BUILDFLAG(BUILD_WITH_INTERNAL_OPTIMIZATION_GUIDE)
@implementation PageContextWrapper {
base::WeakPtr<web::WebState> _webState;
// The amount of async tasks this specific instance of the PageContext wrapper
// needs to complete before executing the `completionCallback`.
NSInteger _asyncTasksToComplete;
#if BUILDFLAG(BUILD_WITH_INTERNAL_OPTIMIZATION_GUIDE)
// The callback to execute once all async work is complete, whichs
// relinquishes ownership of the PageContext proto to the callback's handler.
base::OnceCallback<void(
std::unique_ptr<optimization_guide::proto::features::PageContext>)>
_completion_callback;
// Unique pointer to the PageContext proto.
std::unique_ptr<optimization_guide::proto::features::PageContext>
_page_context;
#endif // BUILDFLAG(BUILD_WITH_INTERNAL_OPTIMIZATION_GUIDE)
}
#if BUILDFLAG(BUILD_WITH_INTERNAL_OPTIMIZATION_GUIDE)
- (instancetype)initWithWebState:(web::WebState*)webState
completionCallback:
(base::OnceCallback<
void(std::unique_ptr<
optimization_guide::proto::features::PageContext>)>)
completionCallback {
self = [super init];
if (self) {
_asyncTasksToComplete = 0;
_webState = webState->GetWeakPtr();
_completion_callback = std::move(completionCallback);
// Create the PageContext proto/object.
_page_context =
std::make_unique<optimization_guide::proto::features::PageContext>();
_page_context->set_url(_webState->GetVisibleURL().spec());
_page_context->set_title(base::UTF16ToUTF8(_webState->GetTitle()));
}
return self;
}
- (void)populatePageContextFieldsAsync {
CHECK_GE(_asyncTasksToComplete, 0);
if (_asyncTasksToComplete == 0) {
[self asyncWorkCompletedForPageContext];
return;
}
// Use a `BarrierClosure` to ensure all async tasks are completed before
// executing the overall completion callback. The BarrierClosure will wait
// until the `barrier` callback is itself run `_asyncTasksToComplete` times.
__weak PageContextWrapper* weakSelf = self;
base::RepeatingClosure barrier =
base::BarrierClosure(_asyncTasksToComplete, base::BindOnce(^{
[weakSelf asyncWorkCompletedForPageContext];
}));
// Asynchronous work. *IMPORTANT NOTES*:
// When adding async tasks below, an accompanying setter should also be
// created to follow the disabled-by-default pattern (which
// increments/decrements `_asyncTasksToComplete` accordingly). Also, if a
// given task is enabled, every code path for that task should eventually
// execute the `barrier` callback, otherwise the `BarrierClosure` will never
// execute its completion block.
// Retrieve WebState snapshot, if enabled.
if (_shouldGetSnapshot) {
auto callback = ^(UIImage* image) {
if ([weakSelf shouldUpdateSnapshotWithImage:image]) {
[weakSelf updateSnapshotWithBarrier:barrier];
return;
}
[weakSelf encodeImageAndSetTabScreenshot:image];
barrier.Run();
};
// If the WebState is currently visible, update the snapshot in case the
// user was scrolling, otherwise retrieve the latest version in cache or on
// disk.
if (_webState->IsVisible()) {
SnapshotTabHelper::FromWebState(_webState.get())
->UpdateSnapshotWithCallback(callback);
} else {
SnapshotTabHelper::FromWebState(_webState.get())
->RetrieveColorSnapshot(callback);
}
}
// Create WebState full page PDF, if enabled.
if (_shouldGetFullPagePDF) {
_webState->CreateFullPagePdf(base::BindOnce(^(NSData* PDFData) {
[weakSelf encodeAndSetFullPagePDF:PDFData];
barrier.Run();
}));
}
}
#pragma mark - Setters
// Sets the flag to enabled/disabled, and increments/decrements accordingly the
// total amount of async tasks gating the completion callback.
- (void)setShouldGetSnapshot:(BOOL)enabled {
if (_shouldGetSnapshot == enabled) {
return;
}
_asyncTasksToComplete += enabled ? 1 : -1;
_shouldGetSnapshot = enabled;
}
// Sets the flag to enabled/disabled, and increments/decrements accordingly the
// total amount of async tasks gating the completion callback.
- (void)setShouldGetFullPagePDF:(BOOL)enabled {
if (_shouldGetFullPagePDF == enabled) {
return;
}
_asyncTasksToComplete += enabled ? 1 : -1;
_shouldGetFullPagePDF = enabled;
}
#pragma mark - Private
// All async tasks are complete, execute the overall completion callback.
// Relinquish ownership to the callback handler.
- (void)asyncWorkCompletedForPageContext {
std::move(_completion_callback).Run(std::move(_page_context));
}
// Returns YES if the image is nil and forcing the update of missing snapshots
// is enabled.
- (BOOL)shouldUpdateSnapshotWithImage:(UIImage*)image {
return !image && _shouldForceUpdateMissingSnapshots;
}
// Updates the snapshot for the given WebState, and executes the `barrier`
// callback when finished.
- (void)updateSnapshotWithBarrier:(base::RepeatingClosure)barrier {
SnapshotTabHelper::FromWebState(_webState.get())
->UpdateSnapshotWithCallback(^(UIImage* image) {
[self encodeImageAndSetTabScreenshot:image];
barrier.Run();
});
}
// Convert UIImage snapshot to PNG, and then to base64 encoded string. Set the
// tab screenshot on the current PageContext.
- (void)encodeImageAndSetTabScreenshot:(UIImage*)image {
if (!image) {
return;
}
NSData* imageData = UIImagePNGRepresentation(image);
NSString* base64String = [imageData base64EncodedStringWithOptions:0];
_page_context->set_tab_screenshot(base::SysNSStringToUTF8(base64String));
}
// If it exists, convert the PDF data to base64 encoded string and set it in the
// PageContext proto.
- (void)encodeAndSetFullPagePDF:(NSData*)PDFData {
if (!PDFData) {
return;
}
NSString* base64String = [PDFData base64EncodedStringWithOptions:0];
_page_context->set_pdf_data(base::SysNSStringToUTF8(base64String));
}
#endif // BUILDFLAG(BUILD_WITH_INTERNAL_OPTIMIZATION_GUIDE)
@end