Index: ios/chrome/browser/find_in_page/js_findinpage_manager.mm |
diff --git a/ios/chrome/browser/find_in_page/js_findinpage_manager.mm b/ios/chrome/browser/find_in_page/js_findinpage_manager.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..e718aa6635a238638e9f0f644a7bbca4d7d66f7e |
--- /dev/null |
+++ b/ios/chrome/browser/find_in_page/js_findinpage_manager.mm |
@@ -0,0 +1,278 @@ |
+// Copyright 2012 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#import "ios/chrome/browser/find_in_page/js_findinpage_manager.h" |
+ |
+#include <string> |
+ |
+#import "base/ios/weak_nsobject.h" |
+#include "base/json/json_reader.h" |
+#include "base/json/string_escape.h" |
+#include "base/logging.h" |
+#include "base/memory/scoped_ptr.h" |
+#include "base/strings/sys_string_conversions.h" |
+#include "base/values.h" |
+#import "ios/chrome/browser/find_in_page/find_in_page_model.h" |
+#import "ios/web/public/web_state/js/crw_js_early_script_manager.h" |
+ |
+namespace { |
+ |
+// Global variable defined in find_in_page.js that can be used for testing |
+// whether JavaScript bas heen loaded. |
+NSString* const kFindInPageBeacon = @"window.__gCrWeb.findInPage"; |
+ |
+// Initializes Find In Page JavaScript with the width and height of the window. |
+NSString* const kFindInPageInit = @"window.__gCrWeb.findInPage && " |
+ "window.__gCrWeb.findInPage.init(%.f, %.f);"; |
+ |
+// This will only do verbatim matches. |
+// The timeout of 100ms is hardcoded into this string so we don't have |
+// to spend any time at runtime to format this constant into another constant. |
+NSString* const kFindInPageVerbatim = |
+ @"window.__gCrWeb.findInPage && " |
+ "window.__gCrWeb.findInPage.highlightWord(%@, false, 100.0);"; |
+ |
+// The timeout of 100ms is hardcoded into this string so we don't have |
+// to spend any time at runtime to format this constant into another constant. |
+NSString* const kFindInPagePump = |
+ @"window.__gCrWeb.findInPage && " |
+ "window.__gCrWeb.findInPage.pumpSearch(100.0);"; |
+ |
+NSString* const kFindInPagePrev = @"window.__gCrWeb.findInPage && " |
+ "window.__gCrWeb.findInPage.goPrev();"; |
+ |
+NSString* const kFindInPageNext = @"window.__gCrWeb.findInPage && " |
+ "window.__gCrWeb.findInPage.goNext();"; |
+ |
+NSString* const kFindInPageDisable = @"window.__gCrWeb.findInPage && " |
+ "window.__gCrWeb.findInPage.disable();"; |
+ |
+NSString* const kFindInPagePending = @"[false]"; |
+ |
+const FindInPageEntry kFindInPageEntryZero = {{0.0, 0.0}, 0}; |
+ |
+} // namespace |
+ |
+@interface JsFindinpageManager () |
+// Update find in page model with results, return true if fip completes or |
+// false if still pending and requires pumping. If |point| is not nil, it will |
+// contain the scroll position upon return. |
+- (BOOL)processFindInPageResult:(NSString*)result |
+ scrollPosition:(CGPoint*)point; |
+// Updates find in page model with results. Calls |completionHandler| with the |
+// the result of the processing and the new scroll position if successfull. If |
+// |completionHandler| is called with NO, further pumping is required. |
+// |completionHandler| cannot be nil. |
+- (void)processFindInPagePumpResult:(NSString*)result |
+ completionHandler:(void (^)(BOOL, CGPoint))completionHandler; |
+// Helper functions to extract FindInPageEntry from JSON. |
+- (FindInPageEntry)findInPageEntryForJson:(NSString*)jsonStr; |
+- (FindInPageEntry)entryForListValue:(base::ListValue*)position; |
+// Executes |script| which is a piece of JavaScript to move to the next or |
+// previous element in the page and executes |completionHandler| after moving |
+// with the new scroll position passed in. |
+- (void)moveHighlightByEvaluatingJavaScript:(NSString*)script |
+ completionHandler: |
+ (void (^)(CGPoint))completionHandler; |
+// Updates the current match index and its found position in the model. |
+- (void)updateIndex:(NSInteger)index atPoint:(CGPoint)point; |
+@end |
+ |
+@implementation JsFindinpageManager |
+ |
+- (FindInPageModel*)findInPageModel { |
+ if (!findInPageModel_) |
+ findInPageModel_.reset([[FindInPageModel alloc] init]); |
+ return findInPageModel_.get(); |
+} |
+ |
+- (void)setWidth:(CGFloat)width height:(CGFloat)height { |
+ NSString* javaScript = |
+ [NSString stringWithFormat:kFindInPageInit, width, height]; |
+ [self evaluate:javaScript stringResultHandler:nil]; |
+} |
+ |
+- (void)findString:(NSString*)query |
+ completionHandler:(void (^)(BOOL, CGPoint))completionHandler { |
+ DCHECK(completionHandler); |
+ // Save the query in the model before searching. |
+ [findInPageModel_ updateQuery:query matches:0]; |
+ |
+ // Escape |query| before passing to js. |
+ std::string escapedJson; |
+ base::EscapeJSONString(base::SysNSStringToUTF16(query), true, &escapedJson); |
+ NSString* jsonQuery = |
+ [NSString stringWithFormat:kFindInPageVerbatim, |
+ base::SysUTF8ToNSString(escapedJson.c_str())]; |
+ base::WeakNSObject<JsFindinpageManager> weakSelf(self); |
+ [self evaluate:jsonQuery |
+ stringResultHandler:^(NSString* result, NSError* error) { |
+ [weakSelf processFindInPagePumpResult:result |
+ completionHandler:completionHandler]; |
+ }]; |
+} |
+ |
+- (void)pumpWithCompletionHandler:(void (^)(BOOL, CGPoint))completionHandler { |
+ DCHECK(completionHandler); |
+ base::WeakNSObject<JsFindinpageManager> weakSelf(self); |
+ [self evaluate:kFindInPagePump |
+ stringResultHandler:^(NSString* result, NSError* error) { |
+ // TODO(shreyasv): What to do here if this returns an NSError in the |
+ // WKWebView version. |
+ [weakSelf processFindInPagePumpResult:result |
+ completionHandler:completionHandler]; |
+ }]; |
+} |
+ |
+- (void)nextMatchWithCompletionHandler:(void (^)(CGPoint))completionHandler { |
+ [self moveHighlightByEvaluatingJavaScript:kFindInPageNext |
+ completionHandler:completionHandler]; |
+} |
+ |
+- (void)previousMatchWithCompletionHandler: |
+ (void (^)(CGPoint))completionHandler { |
+ [self moveHighlightByEvaluatingJavaScript:kFindInPagePrev |
+ completionHandler:completionHandler]; |
+} |
+ |
+- (void)moveHighlightByEvaluatingJavaScript:(NSString*)script |
+ completionHandler: |
+ (void (^)(CGPoint))completionHandler { |
+ base::WeakNSObject<JsFindinpageManager> weakSelf(self); |
+ [self evaluate:script |
+ stringResultHandler:^(NSString* result, NSError* error) { |
+ base::WeakNSObject<JsFindinpageManager> strongSelf([weakSelf retain]); |
+ if (!strongSelf) |
+ return; |
+ DCHECK(!error); |
+ FindInPageEntry entry = kFindInPageEntryZero; |
+ if (![result isEqualToString:kFindInPagePending]) |
+ entry = [strongSelf findInPageEntryForJson:result]; |
+ CGPoint newPoint = entry.point; |
+ [strongSelf updateIndex:entry.index atPoint:newPoint]; |
+ if (completionHandler) |
+ completionHandler(newPoint); |
+ }]; |
+} |
+ |
+- (void)disableWithCompletionHandler:(ProceduralBlock)completionHandler { |
+ DCHECK(completionHandler); |
+ base::WeakNSObject<FindInPageModel> weakFindInPageModel(findInPageModel_); |
+ [self evaluate:kFindInPageDisable |
+ stringResultHandler:^(NSString* result, NSError* error) { |
+ [weakFindInPageModel setEnabled:NO]; |
+ completionHandler(); |
+ }]; |
+} |
+ |
+#pragma mark - |
+#pragma mark FindInPageEntry |
+ |
+- (BOOL)processFindInPageResult:(NSString*)result |
+ scrollPosition:(CGPoint*)point { |
+ if (!result) |
+ return NO; |
+ |
+ // Parse JSONs. |
+ std::string json([result UTF8String]); |
+ scoped_ptr<base::Value> root(base::JSONReader::Read(json, false)); |
+ if (!root.get()) |
+ return YES; |
+ if (!root->IsType(base::Value::TYPE_LIST)) |
+ return YES; |
+ |
+ base::ListValue* resultList = static_cast<base::ListValue*>(root.get()); |
+ DCHECK(resultList); |
+ if (resultList) { |
+ if (resultList->GetSize() == 2) { |
+ int numHighlighted = 0; |
+ if (resultList->GetInteger(0, &numHighlighted)) { |
+ if (numHighlighted > 0) { |
+ base::ListValue* position; |
+ if (resultList->GetList(1, &position)) { |
+ [findInPageModel_ updateQuery:nil matches:numHighlighted]; |
+ // Scroll to first match. |
+ FindInPageEntry entry = [self entryForListValue:position]; |
+ [findInPageModel_ updateIndex:entry.index atPoint:entry.point]; |
+ if (point) |
+ *point = entry.point; |
+ } |
+ } |
+ } |
+ } |
+ } |
+ return YES; |
+} |
+ |
+- (void)processFindInPagePumpResult:(NSString*)result |
+ completionHandler:(void (^)(BOOL, CGPoint))completionHandler { |
+ CGPoint point = CGPointZero; |
+ if ([result isEqualToString:kFindInPagePending]) { |
+ completionHandler(NO, point); |
+ } |
+ // TODO(shreyasv): Inline this call from the logic from the above function |
+ // and remove the above function. |
+ BOOL processFIPResult = |
+ [self processFindInPageResult:result scrollPosition:&point]; |
+ completionHandler(processFIPResult, point); |
+} |
+ |
+- (void)updateIndex:(NSInteger)index atPoint:(CGPoint)point { |
+ [findInPageModel_ updateIndex:index atPoint:point]; |
+} |
+ |
+- (FindInPageEntry)findInPageEntryForJson:(NSString*)jsonStr { |
+ std::string json([jsonStr UTF8String]); |
+ scoped_ptr<base::Value> root(base::JSONReader::Read(json, false)); |
+ if (!root.get()) |
+ return kFindInPageEntryZero; |
+ |
+ if (!root->IsType(base::Value::TYPE_LIST)) |
+ return kFindInPageEntryZero; |
+ |
+ base::ListValue* position = static_cast<base::ListValue*>(root.get()); |
+ return [self entryForListValue:position]; |
+} |
+ |
+- (FindInPageEntry)entryForListValue:(base::ListValue*)position { |
+ if (!position) |
+ return kFindInPageEntryZero; |
+ |
+ // Position should always be of length 3, from [index,x,y]. |
+ DCHECK(position->GetSize() == 3); |
+ if (position->GetSize() != 3) |
+ return kFindInPageEntryZero; |
+ |
+ // The array position comes from the JSON string [index, x, y], which |
+ // represents the index of the currently found string, and the x and y |
+ // position necessary to center that string. Pull out that data into a |
+ // FindInPageEntry struct. |
+ int index; |
+ double x = 0, y = 0; |
+ position->GetInteger(0, &index); |
+ position->GetDouble(1, &x); |
+ position->GetDouble(2, &y); |
+ FindInPageEntry entry; |
+ entry.index = index; |
+ entry.point.x = x; |
+ entry.point.y = y; |
+ return entry; |
+} |
+ |
+#pragma mark - |
+#pragma mark ProtectedMethods |
+ |
+- (NSString*)scriptPath { |
+ return @"find_in_page"; |
+} |
+ |
+- (NSString*)presenceBeacon { |
+ return kFindInPageBeacon; |
+} |
+ |
+- (NSArray*)directDependencies { |
+ return @[ [CRWJSEarlyScriptManager class] ]; |
+} |
+ |
+@end |