chromium/ios/chrome/browser/web/model/image_fetch/image_fetch_tab_helper.h

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef IOS_CHROME_BROWSER_WEB_MODEL_IMAGE_FETCH_IMAGE_FETCH_TAB_HELPER_H_
#define IOS_CHROME_BROWSER_WEB_MODEL_IMAGE_FETCH_IMAGE_FETCH_TAB_HELPER_H_

#include <string>
#include <unordered_map>

#import "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "ios/chrome/browser/web/model/image_fetch/image_fetch_java_script_feature.h"
#include "ios/web/public/web_state_observer.h"
#import "ios/web/public/web_state_user_data.h"

// Key of the UMA ContextMenu.iOS.GetImageDataByJsResult histogram.
extern const char kUmaGetImageDataByJsResult[];

// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
// Enum for the ContextMenu.iOS.GetImageDataByJsResult UMA histogram to report
// the results of GetImageDataByJs.
enum class ContextMenuGetImageDataByJsResult {
  kCanvasSucceed = 0,
  kXMLHttpRequestSucceed = 1,
  kFail = 2,
  kTimeout = 3,
  kMaxValue = kTimeout,
};

// Gets the image data by JavaScript or
// image_fetcher::FetchImageData. Always use this class by
// ImageFetchTabHelper::FromWebState on UI thread. All callbacks will also be
// invoked on UI thread.
class ImageFetchTabHelper : public ImageFetchJavaScriptFeature::Handler,
                            public web::WebStateObserver,
                            public web::WebStateUserData<ImageFetchTabHelper> {
 public:
  ImageFetchTabHelper(const ImageFetchTabHelper&) = delete;
  ImageFetchTabHelper& operator=(const ImageFetchTabHelper&) = delete;

  ~ImageFetchTabHelper() override;

  // Callback for GetImageData. `data` will be in binary format, or nil if
  // GetImageData failed.
  typedef void (^ImageDataCallback)(NSData* data);

  // Gets image data in binary format by following steps:
  //   1. Call injected JavaScript to get the image data from web page;
  //   2. If JavaScript fails or does not send a message back in 300ms, try
  //   downloading the image by image_fetcher::FetchImageData.
  virtual void GetImageData(const GURL& url,
                            const web::Referrer& referrer,
                            ImageDataCallback callback);

  // ImageFetchJavaScriptFeature::Handler.
  void HandleJsSuccess(int call_id,
                       std::string& decoded_data,
                       std::string& from) override;
  void HandleJsFailure(int call_id) override;

 protected:
  friend class web::WebStateUserData<ImageFetchTabHelper>;

  explicit ImageFetchTabHelper(web::WebState* web_state);

  // web::WebStateObserver overrides:
  void DidStartNavigation(web::WebState* web_state,
                          web::NavigationContext* navigation_context) override;
  void WebStateDestroyed(web::WebState* web_state) override;

  // Callback for GetImageDataByJs. `data` will be in binary format, or nullptr
  // if GetImageDataByJs failed.
  typedef base::OnceCallback<void(const std::string* data)> JsCallback;

  // Gets image data in binary format via ImageFetchJavaScriptFeature.
  // `url` should be equal to the resolved "src" attribute of <img>, otherwise
  // the method 1 would fail. If the JavaScript does not respond after
  // `timeout`, the `callback` will be invoked with nullptr.
  void GetImageDataByJs(const GURL& url,
                        base::TimeDelta timeout,
                        JsCallback&& callback);

  // Records ContextMenu.iOS.GetImageDataByJsResult UMA histogram.
  void RecordGetImageDataByJsResult(ContextMenuGetImageDataByJsResult result);

  // Handler for timeout on GetImageDataByJs.
  void OnJsTimeout(int call_id);

  // Handler for calling GetImageDataByJs inside GetImageData.
  void JsCallbackOfGetImageData(const GURL& url,
                                const web::Referrer& referrer,
                                ImageDataCallback callback,
                                const std::string* data);

  // WebState this tab helper is attached to.
  raw_ptr<web::WebState> web_state_ = nullptr;

  // Store callbacks for GetImageData, with url as key.
  std::unordered_map<int, JsCallback> js_callbacks_;

  // `GetImageData` uses this counter as ID to match calls with callbacks. Each
  // call on `GetImageData` will increment `call_id_` by 1 and pass it as ID
  // when calling JavaScript. The ID will be regained in the message received in
  // `OnImageDataReceived` and used to invoke the corresponding callback.
  int call_id_ = 0;

  base::WeakPtrFactory<ImageFetchTabHelper> weak_ptr_factory_;

  WEB_STATE_USER_DATA_KEY_DECL();
};

#endif  // IOS_CHROME_BROWSER_WEB_MODEL_IMAGE_FETCH_IMAGE_FETCH_TAB_HELPER_H_