Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(87)

Side by Side Diff: ios/chrome/browser/find_in_page/find_in_page_controller.mm

Issue 1023813003: [iOS] Upstream ios/chrome/browser/find_in_page (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@upstreamAutofill
Patch Set: Created 5 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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/find_in_page_controller.h"
6
7 #import <UIKit/UIKit.h>
8
9 #include "base/ios/ios_util.h"
10 #include "base/logging.h"
11 #include "base/mac/foundation_util.h"
12 #include "base/mac/scoped_nsobject.h"
13 #import "ios/chrome/browser/find_in_page/find_in_page_model.h"
14 #import "ios/chrome/browser/find_in_page/js_findinpage_manager.h"
15 #import "ios/chrome/browser/web/dom_altering_lock.h"
16 #import "ios/web/public/web_state/crw_web_view_proxy.h"
17 #import "ios/web/public/web_state/crw_web_view_scroll_view_proxy.h"
18 #import "ios/web/public/web_state/js/crw_js_injection_receiver.h"
19 #import "ios/web/public/web_state/web_state.h"
20 #import "ios/web/public/web_state/web_state_observer_bridge.h"
21
22 NSString* const kFindBarTextFieldWillBecomeFirstResponderNotification =
23 @"kFindBarTextFieldWillBecomeFirstResponderNotification";
24 NSString* const kFindBarTextFieldDidResignFirstResponderNotification =
25 @"kFindBarTextFieldDidResignFirstResponderNotification";
26
27 namespace {
28 // The delay (in secs) after which the find in page string will be pumped again.
29 const NSTimeInterval kRecurringPumpDelay = .01;
30 }
31
32 @interface FindInPageController () <DOMAltering, CRWWebStateObserver>
33 // The find in page controller delegate.
34 @property(nonatomic, readonly) id<FindInPageControllerDelegate> delegate;
35 // The web view's scroll view.
36 @property(nonatomic, readonly) CRWWebViewScrollViewProxy* webViewScrollView;
37
38 // Convenience method to obtain UIPasteboardNameFind from UIPasteBoard.
39 - (UIPasteboard*)findPasteboard;
40 // Find in Page text field listeners.
41 - (void)findBarTextFieldWillBecomeFirstResponder:(NSNotification*)note;
42 - (void)findBarTextFieldDidResignFirstResponder:(NSNotification*)note;
43 // Keyboard listeners.
44 - (void)keyboardDidShow:(NSNotification*)note;
45 - (void)keyboardWillHide:(NSNotification*)note;
46 // Constantly injects the find string in page until
47 // |disableFindInPageWithCompletionHandler:| is called or the find operation is
48 // complete. Calls |completionHandler| if the find operation is complete.
49 // |completionHandler| can be nil.
50 - (void)startPumpingWithCompletionHandler:(ProceduralBlock)completionHandler;
51 // Gives find in page more time to complete. Calls |completionHandler| with
52 // a BOOL indicating if the find operation was successfull. |completionHandler|
53 // can be nil.
54 - (void)pumpFindStringInPageWithCompletionHandler:
55 (void (^)(BOOL))completionHandler;
56 // Processes the result of a single find in page pump. Calls |completionHandler|
57 // if pumping is done. Re-pumps if necessary.
58 - (void)processPumpResult:(BOOL)finished
59 scrollPoint:(CGPoint)scrollPoint
60 completionHandler:(ProceduralBlock)completionHandler;
61 // Returns the associated web state. May be null.
62 - (web::WebState*)webState;
63 @end
64
65 @implementation FindInPageController {
66 @private
67 // Object that manages find_in_page.js injection into the web view.
68 JsFindinpageManager* _findInPageJsManager;
69 id<FindInPageControllerDelegate> _delegate;
70
71 // Access to the web view from the web state.
72 base::scoped_nsprotocol<id<CRWWebViewProxy>> _webViewProxy;
73
74 // True when a find is in progress. Used to avoid running JavaScript during
75 // disable when there is nothing to clear.
76 BOOL _findStringStarted;
77
78 // Bridge to observe the web state from Objective-C.
79 scoped_ptr<web::WebStateObserverBridge> _webStateObserverBridge;
80 }
81
82 @synthesize delegate = _delegate;
83
84 - (id)initWithWebState:(web::WebState*)webState
85 delegate:(id<FindInPageControllerDelegate>)delegate {
86 self = [super init];
87 if (self) {
88 DCHECK(delegate);
89 _findInPageJsManager = base::mac::ObjCCastStrict<JsFindinpageManager>(
90 [webState->GetJSInjectionReceiver()
91 instanceOfClass:[JsFindinpageManager class]]);
92 _delegate = delegate;
93 _webStateObserverBridge.reset(
94 new web::WebStateObserverBridge(webState, self));
95 _webViewProxy.reset([webState->GetWebViewProxy() retain]);
96 [[NSNotificationCenter defaultCenter]
97 addObserver:self
98 selector:@selector(findBarTextFieldWillBecomeFirstResponder:)
99 name:kFindBarTextFieldWillBecomeFirstResponderNotification
100 object:nil];
101 [[NSNotificationCenter defaultCenter]
102 addObserver:self
103 selector:@selector(findBarTextFieldDidResignFirstResponder:)
104 name:kFindBarTextFieldDidResignFirstResponderNotification
105 object:nil];
106 DOMAlteringLock::CreateForWebState(webState);
107 }
108 return self;
109 }
110
111 - (void)dealloc {
112 [[NSNotificationCenter defaultCenter] removeObserver:self];
113 [super dealloc];
114 }
115
116 - (FindInPageModel*)findInPageModel {
117 return [_findInPageJsManager findInPageModel];
118 }
119
120 - (BOOL)canFindInPage {
121 return [_webViewProxy hasSearchableTextContent];
122 }
123
124 - (void)initFindInPage {
125 [_findInPageJsManager inject];
126
127 // Initialize the module with our frame size.
128 CGRect frame = [_webViewProxy bounds];
129 [_findInPageJsManager setWidth:frame.size.width height:frame.size.height];
130 }
131
132 - (CRWWebViewScrollViewProxy*)webViewScrollView {
133 return [_webViewProxy scrollViewProxy];
134 }
135
136 - (void)processPumpResult:(BOOL)finished
137 scrollPoint:(CGPoint)scrollPoint
138 completionHandler:(ProceduralBlock)completionHandler {
139 if (finished) {
140 [_delegate willAdjustScrollPosition];
141 [[_webViewProxy scrollViewProxy] setContentOffset:scrollPoint animated:YES];
142 if (completionHandler)
143 completionHandler();
144 } else {
145 [self performSelector:@selector(startPumpingWithCompletionHandler:)
146 withObject:completionHandler
147 afterDelay:kRecurringPumpDelay];
148 }
149 }
150
151 - (void)findStringInPage:(NSString*)query
152 completionHandler:(ProceduralBlock)completionHandler {
153 ProceduralBlockWithBool lockAction = ^(BOOL hasLock) {
154 if (!hasLock) {
155 if (completionHandler) {
156 completionHandler();
157 }
158 return;
159 }
160 // Cancel any previous pumping.
161 [NSObject cancelPreviousPerformRequestsWithTarget:self];
162 [self initFindInPage];
163 // Keep track of whether a find is in progress so to avoid running
164 // JavaScript during disable if unnecessary.
165 _findStringStarted = YES;
166 base::WeakNSObject<FindInPageController> weakSelf(self);
167 [_findInPageJsManager findString:query
168 completionHandler:^(BOOL finished, CGPoint point) {
169 [weakSelf processPumpResult:finished
170 scrollPoint:point
171 completionHandler:completionHandler];
172 }];
173 };
174 DOMAlteringLock::FromWebState([self webState])->Acquire(self, lockAction);
175 }
176
177 - (void)startPumpingWithCompletionHandler:(ProceduralBlock)completionHandler {
178 base::WeakNSObject<FindInPageController> weakSelf(self);
179 id completionHandlerBlock = ^void(BOOL findFinished) {
180 if (findFinished) {
181 // Pumping complete. Nothing else to do.
182 if (completionHandler)
183 completionHandler();
184 return;
185 }
186 // Further pumping is required.
187 [weakSelf performSelector:@selector(startPumpingWithCompletionHandler:)
188 withObject:completionHandler
189 afterDelay:kRecurringPumpDelay];
190 };
191 [self pumpFindStringInPageWithCompletionHandler:completionHandlerBlock];
192 }
193
194 - (void)pumpFindStringInPageWithCompletionHandler:
195 (void (^)(BOOL))completionHandler {
196 base::WeakNSObject<FindInPageController> weakSelf(self);
197 [_findInPageJsManager pumpWithCompletionHandler:^(BOOL finished,
198 CGPoint point) {
199 base::scoped_nsobject<FindInPageController> strongSelf([weakSelf retain]);
200 if (finished) {
201 [[strongSelf delegate] willAdjustScrollPosition];
202 [[strongSelf webViewScrollView] setContentOffset:point animated:YES];
203 }
204 completionHandler(finished);
205 }];
206 }
207
208 - (void)findNextStringInPageWithCompletionHandler:
209 (ProceduralBlock)completionHandler {
210 [self initFindInPage];
211 base::WeakNSObject<FindInPageController> weakSelf(self);
212 [_findInPageJsManager nextMatchWithCompletionHandler:^(CGPoint point) {
213 base::scoped_nsobject<FindInPageController> strongSelf([weakSelf retain]);
214 [[strongSelf delegate] willAdjustScrollPosition];
215 CGFloat contentHeight = [strongSelf webViewScrollView].contentSize.height;
216 CGFloat frameHeight = [strongSelf webViewScrollView].frame.size.height;
217 CGFloat maxScroll = fmax(0, contentHeight - frameHeight);
218 if (point.y > maxScroll) {
219 point.y = maxScroll;
220 }
221 [[strongSelf webViewScrollView] setContentOffset:point animated:YES];
222 if (completionHandler)
223 completionHandler();
224 }];
225 }
226
227 // Highlight the previous search match, update model and scroll to match.
228 - (void)findPreviousStringInPageWithCompletionHandler:
229 (ProceduralBlock)completionHandler {
230 [self initFindInPage];
231 base::WeakNSObject<FindInPageController> weakSelf(self);
232 [_findInPageJsManager previousMatchWithCompletionHandler:^(CGPoint point) {
233 base::scoped_nsobject<FindInPageController> strongSelf([weakSelf retain]);
234 [[strongSelf delegate] willAdjustScrollPosition];
235 [[strongSelf webViewScrollView] setContentOffset:point animated:YES];
236 if (completionHandler)
237 completionHandler();
238 }];
239 }
240
241 // Remove highlights from the page and disable the model.
242 - (void)disableFindInPageWithCompletionHandler:
243 (ProceduralBlock)completionHandler {
244 if (![self canFindInPage])
245 return;
246 // Cancel any queued calls to |recurringPumpWithCompletionHandler|.
247 [NSObject cancelPreviousPerformRequestsWithTarget:self];
248 base::WeakNSObject<FindInPageController> weakSelf(self);
249 ProceduralBlock handler = ^{
250 web::WebState* webState = [self webState];
251 if (webState)
252 DOMAlteringLock::FromWebState(webState)->Release(self);
253 if (completionHandler)
254 completionHandler();
255 };
256 // Only run JSFindInPageManager disable if there is a string in progress to
257 // avoid WKWebView crash on deallocation due to outstanding completion
258 // handler.
259 if (_findStringStarted) {
260 [_findInPageJsManager disableWithCompletionHandler:handler];
261 _findStringStarted = NO;
262 } else {
263 handler();
264 }
265 }
266
267 - (void)saveSearchTerm {
268 [self findPasteboard].string = [[self findInPageModel] text];
269 }
270
271 - (void)restoreSearchTerm {
272 NSString* term = [self findPasteboard].string;
273 [[self findInPageModel] updateQuery:(term ? term : @"") matches:0];
274 }
275
276 - (UIPasteboard*)findPasteboard {
277 return [UIPasteboard pasteboardWithName:UIPasteboardNameFind create:NO];
278 }
279
280 - (web::WebState*)webState {
281 return _webStateObserverBridge ? _webStateObserverBridge->web_state()
282 : nullptr;
283 }
284
285 #pragma mark - Notification listeners
286
287 - (void)findBarTextFieldWillBecomeFirstResponder:(NSNotification*)note {
288 // Listen to the keyboard appearance notifications.
289 NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
290 [defaultCenter addObserver:self
291 selector:@selector(keyboardDidShow:)
292 name:UIKeyboardDidShowNotification
293 object:nil];
294 [defaultCenter addObserver:self
295 selector:@selector(keyboardWillHide:)
296 name:UIKeyboardWillHideNotification
297 object:nil];
298 }
299
300 - (void)findBarTextFieldDidResignFirstResponder:(NSNotification*)note {
301 // Resign from the keyboard appearance notifications on the next turn of the
302 // runloop.
303 dispatch_async(dispatch_get_main_queue(), ^{
304 NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
305 [defaultCenter removeObserver:self
306 name:UIKeyboardDidShowNotification
307 object:nil];
308 [defaultCenter removeObserver:self
309 name:UIKeyboardWillHideNotification
310 object:nil];
311 });
312 }
313
314 - (void)keyboardDidShow:(NSNotification*)note {
315 NSDictionary* info = [note userInfo];
316 CGSize kbSize =
317 [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
318 UIInterfaceOrientation orientation =
319 [[UIApplication sharedApplication] statusBarOrientation];
320 CGFloat kbHeight = kbSize.height;
321 // Prior to iOS 8, the keyboard frame was not dependent on interface
322 // orientation, so height and width need to be swapped in landscape mode.
323 if (UIInterfaceOrientationIsLandscape(orientation) &&
324 !base::ios::IsRunningOnIOS8OrLater()) {
325 kbHeight = kbSize.width;
326 }
327
328 UIEdgeInsets insets = UIEdgeInsetsZero;
329 insets.bottom = kbHeight;
330 [_webViewProxy registerInsets:insets forCaller:self];
331 }
332
333 - (void)keyboardWillHide:(NSNotification*)note {
334 [_webViewProxy unregisterInsetsForCaller:self];
335 }
336
337 - (void)detachFromWebState {
338 _webStateObserverBridge.reset();
339 }
340
341 #pragma mark - CRWWebStateObserver Methods
342
343 - (void)webStateDestroyed:(web::WebState*)webState {
344 [self detachFromWebState];
345 }
346
347 #pragma mark - DOMAltering Methods
348
349 - (BOOL)canReleaseDOMLock {
350 return NO;
351 }
352
353 - (void)releaseDOMLockWithCompletionHandler:(ProceduralBlock)completionHandler {
354 NOTREACHED();
355 }
356
357 @end
OLDNEW
« no previous file with comments | « ios/chrome/browser/find_in_page/find_in_page_controller.h ('k') | ios/chrome/browser/find_in_page/find_in_page_model.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698