| // Copyright 2020 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/web_state/ui/crw_context_menu_element_fetcher.h" |
| |
| #import "base/strings/sys_string_conversions.h" |
| #import "base/unguessable_token.h" |
| #import "ios/web/js_features/context_menu/context_menu_java_script_feature.h" |
| #import "ios/web/public/js_messaging/web_frames_manager.h" |
| #import "ios/web/public/ui/context_menu_params.h" |
| #import "ios/web/public/web_state.h" |
| #import "ios/web/public/web_state_observer_bridge.h" |
| #import "ios/web/web_state/ui/crw_html_element_fetch_request.h" |
| |
| @interface CRWContextMenuElementFetcher () <CRWWebStateObserver> { |
| std::unique_ptr<web::WebStateObserverBridge> _observer; |
| } |
| |
| @property(nonatomic, readonly, weak) WKWebView* webView; |
| |
| @property(nonatomic, assign) web::WebState* webState; |
| |
| // Details for currently in progress element fetches. The objects are |
| // instances of CRWHTMLElementFetchRequest and are keyed by a unique requestId |
| // string. |
| @property(nonatomic, strong) NSMutableDictionary* pendingElementFetchRequests; |
| |
| @end |
| |
| @implementation CRWContextMenuElementFetcher |
| |
| - (instancetype)initWithWebView:(WKWebView*)webView |
| webState:(web::WebState*)webState { |
| self = [super init]; |
| if (self) { |
| _pendingElementFetchRequests = [[NSMutableDictionary alloc] init]; |
| |
| _webView = webView; |
| |
| _webState = webState; |
| _observer = std::make_unique<web::WebStateObserverBridge>(self); |
| webState->AddObserver(_observer.get()); |
| |
| } |
| return self; |
| } |
| |
| - (void)dealloc { |
| if (self.webState) |
| self.webState->RemoveObserver(_observer.get()); |
| } |
| |
| - (void)fetchDOMElementAtPoint:(CGPoint)point |
| completionHandler: |
| (void (^)(const web::ContextMenuParams&))handler { |
| if (!self.webState) { |
| return; |
| } |
| |
| web::ContextMenuJavaScriptFeature* context_menu_feature = |
| web::ContextMenuJavaScriptFeature::FromBrowserState( |
| self.webState->GetBrowserState()); |
| if (!context_menu_feature->GetWebFramesManager(self.webState) |
| ->GetMainWebFrame()) { |
| // A WebFrame may not exist for certain types of content, like PDFs. |
| return; |
| } |
| DCHECK(handler); |
| |
| std::string requestID = base::UnguessableToken::Create().ToString(); |
| CRWHTMLElementFetchRequest* fetchRequest = |
| [[CRWHTMLElementFetchRequest alloc] initWithFoundElementHandler:handler]; |
| _pendingElementFetchRequests[base::SysUTF8ToNSString(requestID)] = |
| fetchRequest; |
| |
| __weak __typeof(self) weakSelf = self; |
| context_menu_feature->GetElementAtPoint( |
| self.webState, requestID, point, |
| base::BindOnce(^(const std::string& innerRequestID, |
| const web::ContextMenuParams& params) { |
| web::ContextMenuParams context_menu_params(params); |
| [weakSelf |
| elementDetailsReceived:context_menu_params |
| forRequestID:base::SysUTF8ToNSString(innerRequestID)]; |
| })); |
| } |
| |
| - (void)cancelFetches { |
| for (CRWHTMLElementFetchRequest* fetchRequest in _pendingElementFetchRequests |
| .allValues) { |
| [fetchRequest invalidate]; |
| } |
| } |
| |
| #pragma mark - Private |
| |
| - (void)elementDetailsReceived:(web::ContextMenuParams&)params |
| forRequestID:(NSString*)requestID { |
| CRWHTMLElementFetchRequest* fetchRequest = |
| _pendingElementFetchRequests[requestID]; |
| if (!fetchRequest) { |
| // Do not process the message if a fetch request with a matching `requestID` |
| // was not found. This ensures that the response matches a request made by |
| // this instance. |
| return; |
| } |
| |
| [_pendingElementFetchRequests removeObjectForKey:requestID]; |
| |
| params.view = self.webView; |
| [fetchRequest runHandlerWithResponse:params]; |
| } |
| |
| #pragma mark - CRWWebStateObserver |
| |
| - (void)webStateDestroyed:(web::WebState*)webState { |
| if (self.webState) |
| self.webState->RemoveObserver(_observer.get()); |
| self.webState = nullptr; |
| } |
| |
| @end |