OLD | NEW |
(Empty) | |
| 1 // Copyright 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #import "ios/chrome/browser/find_in_page/js_findinpage_manager.h" |
| 6 |
| 7 #include <string> |
| 8 |
| 9 #import "base/ios/weak_nsobject.h" |
| 10 #include "base/json/json_reader.h" |
| 11 #include "base/json/string_escape.h" |
| 12 #include "base/logging.h" |
| 13 #include "base/memory/scoped_ptr.h" |
| 14 #include "base/strings/sys_string_conversions.h" |
| 15 #include "base/values.h" |
| 16 #import "ios/chrome/browser/find_in_page/find_in_page_model.h" |
| 17 #import "ios/web/public/web_state/js/crw_js_early_script_manager.h" |
| 18 |
| 19 namespace { |
| 20 |
| 21 // Global variable defined in find_in_page.js that can be used for testing |
| 22 // whether JavaScript bas heen loaded. |
| 23 NSString* const kFindInPageBeacon = @"window.__gCrWeb.findInPage"; |
| 24 |
| 25 // Initializes Find In Page JavaScript with the width and height of the window. |
| 26 NSString* const kFindInPageInit = @"window.__gCrWeb.findInPage && " |
| 27 "window.__gCrWeb.findInPage.init(%.f, %.f);"; |
| 28 |
| 29 // This will only do verbatim matches. |
| 30 // The timeout of 100ms is hardcoded into this string so we don't have |
| 31 // to spend any time at runtime to format this constant into another constant. |
| 32 NSString* const kFindInPageVerbatim = |
| 33 @"window.__gCrWeb.findInPage && " |
| 34 "window.__gCrWeb.findInPage.highlightWord(%@, false, 100.0);"; |
| 35 |
| 36 // The timeout of 100ms is hardcoded into this string so we don't have |
| 37 // to spend any time at runtime to format this constant into another constant. |
| 38 NSString* const kFindInPagePump = |
| 39 @"window.__gCrWeb.findInPage && " |
| 40 "window.__gCrWeb.findInPage.pumpSearch(100.0);"; |
| 41 |
| 42 NSString* const kFindInPagePrev = @"window.__gCrWeb.findInPage && " |
| 43 "window.__gCrWeb.findInPage.goPrev();"; |
| 44 |
| 45 NSString* const kFindInPageNext = @"window.__gCrWeb.findInPage && " |
| 46 "window.__gCrWeb.findInPage.goNext();"; |
| 47 |
| 48 NSString* const kFindInPageDisable = @"window.__gCrWeb.findInPage && " |
| 49 "window.__gCrWeb.findInPage.disable();"; |
| 50 |
| 51 NSString* const kFindInPagePending = @"[false]"; |
| 52 |
| 53 const FindInPageEntry kFindInPageEntryZero = {{0.0, 0.0}, 0}; |
| 54 |
| 55 } // namespace |
| 56 |
| 57 @interface JsFindinpageManager () |
| 58 // Update find in page model with results, return true if fip completes or |
| 59 // false if still pending and requires pumping. If |point| is not nil, it will |
| 60 // contain the scroll position upon return. |
| 61 - (BOOL)processFindInPageResult:(NSString*)result |
| 62 scrollPosition:(CGPoint*)point; |
| 63 // Updates find in page model with results. Calls |completionHandler| with the |
| 64 // the result of the processing and the new scroll position if successfull. If |
| 65 // |completionHandler| is called with NO, further pumping is required. |
| 66 // |completionHandler| cannot be nil. |
| 67 - (void)processFindInPagePumpResult:(NSString*)result |
| 68 completionHandler:(void (^)(BOOL, CGPoint))completionHandler; |
| 69 // Helper functions to extract FindInPageEntry from JSON. |
| 70 - (FindInPageEntry)findInPageEntryForJson:(NSString*)jsonStr; |
| 71 - (FindInPageEntry)entryForListValue:(base::ListValue*)position; |
| 72 // Executes |script| which is a piece of JavaScript to move to the next or |
| 73 // previous element in the page and executes |completionHandler| after moving |
| 74 // with the new scroll position passed in. |
| 75 - (void)moveHighlightByEvaluatingJavaScript:(NSString*)script |
| 76 completionHandler: |
| 77 (void (^)(CGPoint))completionHandler; |
| 78 // Updates the current match index and its found position in the model. |
| 79 - (void)updateIndex:(NSInteger)index atPoint:(CGPoint)point; |
| 80 @end |
| 81 |
| 82 @implementation JsFindinpageManager |
| 83 |
| 84 - (FindInPageModel*)findInPageModel { |
| 85 if (!findInPageModel_) |
| 86 findInPageModel_.reset([[FindInPageModel alloc] init]); |
| 87 return findInPageModel_.get(); |
| 88 } |
| 89 |
| 90 - (void)setWidth:(CGFloat)width height:(CGFloat)height { |
| 91 NSString* javaScript = |
| 92 [NSString stringWithFormat:kFindInPageInit, width, height]; |
| 93 [self evaluate:javaScript stringResultHandler:nil]; |
| 94 } |
| 95 |
| 96 - (void)findString:(NSString*)query |
| 97 completionHandler:(void (^)(BOOL, CGPoint))completionHandler { |
| 98 DCHECK(completionHandler); |
| 99 // Save the query in the model before searching. |
| 100 [findInPageModel_ updateQuery:query matches:0]; |
| 101 |
| 102 // Escape |query| before passing to js. |
| 103 std::string escapedJson; |
| 104 base::EscapeJSONString(base::SysNSStringToUTF16(query), true, &escapedJson); |
| 105 NSString* jsonQuery = |
| 106 [NSString stringWithFormat:kFindInPageVerbatim, |
| 107 base::SysUTF8ToNSString(escapedJson.c_str())]; |
| 108 base::WeakNSObject<JsFindinpageManager> weakSelf(self); |
| 109 [self evaluate:jsonQuery |
| 110 stringResultHandler:^(NSString* result, NSError* error) { |
| 111 [weakSelf processFindInPagePumpResult:result |
| 112 completionHandler:completionHandler]; |
| 113 }]; |
| 114 } |
| 115 |
| 116 - (void)pumpWithCompletionHandler:(void (^)(BOOL, CGPoint))completionHandler { |
| 117 DCHECK(completionHandler); |
| 118 base::WeakNSObject<JsFindinpageManager> weakSelf(self); |
| 119 [self evaluate:kFindInPagePump |
| 120 stringResultHandler:^(NSString* result, NSError* error) { |
| 121 // TODO(shreyasv): What to do here if this returns an NSError in the |
| 122 // WKWebView version. |
| 123 [weakSelf processFindInPagePumpResult:result |
| 124 completionHandler:completionHandler]; |
| 125 }]; |
| 126 } |
| 127 |
| 128 - (void)nextMatchWithCompletionHandler:(void (^)(CGPoint))completionHandler { |
| 129 [self moveHighlightByEvaluatingJavaScript:kFindInPageNext |
| 130 completionHandler:completionHandler]; |
| 131 } |
| 132 |
| 133 - (void)previousMatchWithCompletionHandler: |
| 134 (void (^)(CGPoint))completionHandler { |
| 135 [self moveHighlightByEvaluatingJavaScript:kFindInPagePrev |
| 136 completionHandler:completionHandler]; |
| 137 } |
| 138 |
| 139 - (void)moveHighlightByEvaluatingJavaScript:(NSString*)script |
| 140 completionHandler: |
| 141 (void (^)(CGPoint))completionHandler { |
| 142 base::WeakNSObject<JsFindinpageManager> weakSelf(self); |
| 143 [self evaluate:script |
| 144 stringResultHandler:^(NSString* result, NSError* error) { |
| 145 base::WeakNSObject<JsFindinpageManager> strongSelf([weakSelf retain]); |
| 146 if (!strongSelf) |
| 147 return; |
| 148 DCHECK(!error); |
| 149 FindInPageEntry entry = kFindInPageEntryZero; |
| 150 if (![result isEqualToString:kFindInPagePending]) |
| 151 entry = [strongSelf findInPageEntryForJson:result]; |
| 152 CGPoint newPoint = entry.point; |
| 153 [strongSelf updateIndex:entry.index atPoint:newPoint]; |
| 154 if (completionHandler) |
| 155 completionHandler(newPoint); |
| 156 }]; |
| 157 } |
| 158 |
| 159 - (void)disableWithCompletionHandler:(ProceduralBlock)completionHandler { |
| 160 DCHECK(completionHandler); |
| 161 base::WeakNSObject<FindInPageModel> weakFindInPageModel(findInPageModel_); |
| 162 [self evaluate:kFindInPageDisable |
| 163 stringResultHandler:^(NSString* result, NSError* error) { |
| 164 [weakFindInPageModel setEnabled:NO]; |
| 165 completionHandler(); |
| 166 }]; |
| 167 } |
| 168 |
| 169 #pragma mark - |
| 170 #pragma mark FindInPageEntry |
| 171 |
| 172 - (BOOL)processFindInPageResult:(NSString*)result |
| 173 scrollPosition:(CGPoint*)point { |
| 174 if (!result) |
| 175 return NO; |
| 176 |
| 177 // Parse JSONs. |
| 178 std::string json([result UTF8String]); |
| 179 scoped_ptr<base::Value> root(base::JSONReader::Read(json, false)); |
| 180 if (!root.get()) |
| 181 return YES; |
| 182 if (!root->IsType(base::Value::TYPE_LIST)) |
| 183 return YES; |
| 184 |
| 185 base::ListValue* resultList = static_cast<base::ListValue*>(root.get()); |
| 186 DCHECK(resultList); |
| 187 if (resultList) { |
| 188 if (resultList->GetSize() == 2) { |
| 189 int numHighlighted = 0; |
| 190 if (resultList->GetInteger(0, &numHighlighted)) { |
| 191 if (numHighlighted > 0) { |
| 192 base::ListValue* position; |
| 193 if (resultList->GetList(1, &position)) { |
| 194 [findInPageModel_ updateQuery:nil matches:numHighlighted]; |
| 195 // Scroll to first match. |
| 196 FindInPageEntry entry = [self entryForListValue:position]; |
| 197 [findInPageModel_ updateIndex:entry.index atPoint:entry.point]; |
| 198 if (point) |
| 199 *point = entry.point; |
| 200 } |
| 201 } |
| 202 } |
| 203 } |
| 204 } |
| 205 return YES; |
| 206 } |
| 207 |
| 208 - (void)processFindInPagePumpResult:(NSString*)result |
| 209 completionHandler:(void (^)(BOOL, CGPoint))completionHandler { |
| 210 CGPoint point = CGPointZero; |
| 211 if ([result isEqualToString:kFindInPagePending]) { |
| 212 completionHandler(NO, point); |
| 213 } |
| 214 // TODO(shreyasv): Inline this call from the logic from the above function |
| 215 // and remove the above function. |
| 216 BOOL processFIPResult = |
| 217 [self processFindInPageResult:result scrollPosition:&point]; |
| 218 completionHandler(processFIPResult, point); |
| 219 } |
| 220 |
| 221 - (void)updateIndex:(NSInteger)index atPoint:(CGPoint)point { |
| 222 [findInPageModel_ updateIndex:index atPoint:point]; |
| 223 } |
| 224 |
| 225 - (FindInPageEntry)findInPageEntryForJson:(NSString*)jsonStr { |
| 226 std::string json([jsonStr UTF8String]); |
| 227 scoped_ptr<base::Value> root(base::JSONReader::Read(json, false)); |
| 228 if (!root.get()) |
| 229 return kFindInPageEntryZero; |
| 230 |
| 231 if (!root->IsType(base::Value::TYPE_LIST)) |
| 232 return kFindInPageEntryZero; |
| 233 |
| 234 base::ListValue* position = static_cast<base::ListValue*>(root.get()); |
| 235 return [self entryForListValue:position]; |
| 236 } |
| 237 |
| 238 - (FindInPageEntry)entryForListValue:(base::ListValue*)position { |
| 239 if (!position) |
| 240 return kFindInPageEntryZero; |
| 241 |
| 242 // Position should always be of length 3, from [index,x,y]. |
| 243 DCHECK(position->GetSize() == 3); |
| 244 if (position->GetSize() != 3) |
| 245 return kFindInPageEntryZero; |
| 246 |
| 247 // The array position comes from the JSON string [index, x, y], which |
| 248 // represents the index of the currently found string, and the x and y |
| 249 // position necessary to center that string. Pull out that data into a |
| 250 // FindInPageEntry struct. |
| 251 int index; |
| 252 double x = 0, y = 0; |
| 253 position->GetInteger(0, &index); |
| 254 position->GetDouble(1, &x); |
| 255 position->GetDouble(2, &y); |
| 256 FindInPageEntry entry; |
| 257 entry.index = index; |
| 258 entry.point.x = x; |
| 259 entry.point.y = y; |
| 260 return entry; |
| 261 } |
| 262 |
| 263 #pragma mark - |
| 264 #pragma mark ProtectedMethods |
| 265 |
| 266 - (NSString*)scriptPath { |
| 267 return @"find_in_page"; |
| 268 } |
| 269 |
| 270 - (NSString*)presenceBeacon { |
| 271 return kFindInPageBeacon; |
| 272 } |
| 273 |
| 274 - (NSArray*)directDependencies { |
| 275 return @[ [CRWJSEarlyScriptManager class] ]; |
| 276 } |
| 277 |
| 278 @end |
OLD | NEW |