| // 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. |
| |
| #import "ios/web/annotations/annotations_java_script_feature.h" |
| |
| #import <vector> |
| |
| #import "base/logging.h" |
| #import "base/metrics/histogram_macros.h" |
| #import "base/no_destructor.h" |
| #import "base/strings/sys_string_conversions.h" |
| #import "components/shared_highlighting/ios/parsing_utils.h" |
| #import "ios/web/annotations/annotations_text_manager_impl.h" |
| #import "ios/web/common/features.h" |
| #import "ios/web/public/js_messaging/script_message.h" |
| #import "ios/web/public/js_messaging/web_frame.h" |
| #import "ios/web/public/js_messaging/web_frames_manager.h" |
| |
| namespace { |
| const char kScriptName[] = "text_main"; |
| const char kScriptHandlerName[] = "annotations"; |
| } // namespace |
| |
| namespace web { |
| |
| AnnotationsJavaScriptFeature::AnnotationsJavaScriptFeature() |
| : JavaScriptFeature( |
| ContentWorld::kIsolatedWorld, |
| {FeatureScript::CreateWithFilename( |
| kScriptName, |
| FeatureScript::InjectionTime::kDocumentStart, |
| FeatureScript::TargetFrames::kMainFrame, |
| FeatureScript::ReinjectionBehavior::kInjectOncePerWindow)}) {} |
| |
| AnnotationsJavaScriptFeature::~AnnotationsJavaScriptFeature() = default; |
| |
| // static |
| AnnotationsJavaScriptFeature* AnnotationsJavaScriptFeature::GetInstance() { |
| static base::NoDestructor<AnnotationsJavaScriptFeature> instance; |
| return instance.get(); |
| } |
| |
| void AnnotationsJavaScriptFeature::ExtractText(WebState* web_state, |
| int maximum_text_length, |
| int seq_id) { |
| DCHECK(web_state); |
| WebFrame* frame = GetWebFramesManager(web_state)->GetMainWebFrame(); |
| if (!frame) { |
| return; |
| } |
| |
| base::Value::List parameters; |
| CallJavaScriptFunction(frame, "annotations.start", parameters); |
| } |
| |
| void AnnotationsJavaScriptFeature::DecorateAnnotations(WebState* web_state, |
| base::Value& annotations, |
| int seq_id) { |
| DCHECK(web_state); |
| WebFrame* frame = GetWebFramesManager(web_state)->GetMainWebFrame(); |
| if (!frame) { |
| return; |
| } |
| |
| base::Value::List parameters; |
| parameters.Append(std::move(annotations)); |
| parameters.Append(seq_id); |
| CallJavaScriptFunction(frame, "annotations.decorateAnnotations", parameters); |
| } |
| |
| void AnnotationsJavaScriptFeature::RemoveDecorations(WebState* web_state) { |
| DCHECK(web_state); |
| WebFrame* frame = GetWebFramesManager(web_state)->GetMainWebFrame(); |
| if (!frame) { |
| return; |
| } |
| |
| CallJavaScriptFunction(frame, "annotations.removeDecorations", {}); |
| } |
| |
| void AnnotationsJavaScriptFeature::RemoveDecorationsWithType( |
| WebState* web_state, |
| const std::string& type) { |
| DCHECK(web_state); |
| WebFrame* frame = GetWebFramesManager(web_state)->GetMainWebFrame(); |
| if (!frame) { |
| return; |
| } |
| |
| base::Value::List parameters; |
| parameters.Append(std::move(type)); |
| |
| CallJavaScriptFunction(frame, "annotations.removeDecorationsWithType", |
| parameters); |
| } |
| |
| void AnnotationsJavaScriptFeature::RemoveHighlight(WebState* web_state) { |
| DCHECK(web_state); |
| WebFrame* frame = GetWebFramesManager(web_state)->GetMainWebFrame(); |
| if (!frame) { |
| return; |
| } |
| |
| CallJavaScriptFunction(frame, "annotations.removeHighlight", {}); |
| } |
| |
| void AnnotationsJavaScriptFeature::ScriptMessageReceived( |
| WebState* web_state, |
| const ScriptMessage& script_message) { |
| if (!script_message.is_main_frame()) { |
| return; |
| } |
| |
| AnnotationsTextManagerImpl* manager = |
| static_cast<AnnotationsTextManagerImpl*>( |
| AnnotationsTextManager::FromWebState(web_state)); |
| if (!manager) { |
| return; |
| } |
| |
| base::Value* response = script_message.body(); |
| if (!response || !response->is_dict()) { |
| return; |
| } |
| |
| const base::Value::Dict& dict = response->GetDict(); |
| |
| const std::string* command = dict.FindString("command"); |
| if (!command) { |
| return; |
| } |
| |
| // Discard messages if we've navigated away. |
| auto sender_url = script_message.request_url(); |
| GURL current_url = web_state->GetLastCommittedURL(); |
| if (!sender_url || !(*sender_url).EqualsIgnoringRef(current_url)) { |
| return; |
| } |
| |
| if (*command == "annotations.extractedText") { |
| const std::string* text = dict.FindString("text"); |
| std::optional<double> seq_id = dict.FindDouble("seqId"); |
| const base::Value::Dict* metadata = dict.FindDict("metadata"); |
| if (!text || !seq_id || !metadata) { |
| return; |
| } |
| manager->OnTextExtracted(web_state, *text, static_cast<int>(seq_id.value()), |
| *metadata); |
| } else if (*command == "annotations.decoratingComplete") { |
| std::optional<double> optional_annotations = dict.FindDouble("annotations"); |
| std::optional<double> optional_successes = dict.FindDouble("successes"); |
| std::optional<double> optional_failures = dict.FindDouble("failures"); |
| const base::Value::List* cancelled = dict.FindList("cancelled"); |
| if (!optional_annotations || !optional_successes || !optional_failures || |
| !cancelled) { |
| return; |
| } |
| int annotations = static_cast<int>(optional_annotations.value()); |
| int successes = static_cast<int>(optional_successes.value()); |
| int failures = static_cast<int>(optional_failures.value()); |
| manager->OnDecorated(web_state, annotations, successes, failures, |
| *cancelled); |
| } else if (*command == "annotations.onClick") { |
| const std::string* data = dict.FindString("data"); |
| std::optional<CGRect> rect = |
| shared_highlighting::ParseRect(dict.FindDict("rect")); |
| const std::string* text = dict.FindString("text"); |
| std::optional<bool> cancel = dict.FindBool("cancel"); |
| if (!data || !rect || !text || !cancel) { |
| return; |
| } |
| UMA_HISTOGRAM_BOOLEAN("IOS.Annotations.UserTap.Cancelled", *cancel); |
| if (!*cancel) { |
| manager->OnClick( |
| web_state, *text, |
| shared_highlighting::ConvertToBrowserRect(*rect, web_state), *data); |
| } |
| } |
| } |
| |
| std::optional<std::string> |
| AnnotationsJavaScriptFeature::GetScriptMessageHandlerName() const { |
| return kScriptHandlerName; |
| } |
| |
| } // namespace web |