普通文本  |  980行  |  33.77 KB

// Copyright (c) 2013 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.

#include "content/browser/devtools/renderer_overrides_handler.h"

#include <map>
#include <string>

#include "base/barrier_closure.h"
#include "base/base64.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/files/file_path.h"
#include "base/strings/string16.h"
#include "base/values.h"
#include "content/browser/child_process_security_policy_impl.h"
#include "content/browser/devtools/devtools_protocol_constants.h"
#include "content/browser/devtools/devtools_tracing_handler.h"
#include "content/browser/renderer_host/dip_util.h"
#include "content/browser/renderer_host/render_view_host_delegate.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/common/view_messages.h"
#include "content/port/browser/render_widget_host_view_port.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/devtools_agent_host.h"
#include "content/public/browser/javascript_dialog_manager.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/common/content_client.h"
#include "content/public/common/page_transition_types.h"
#include "content/public/common/referrer.h"
#include "ipc/ipc_sender.h"
#include "net/base/net_util.h"
#include "third_party/WebKit/public/web/WebInputEvent.h"
#include "ui/gfx/codec/jpeg_codec.h"
#include "ui/gfx/codec/png_codec.h"
#include "ui/gfx/size_conversions.h"
#include "ui/snapshot/snapshot.h"
#include "url/gurl.h"
#include "webkit/browser/quota/quota_manager.h"

using blink::WebGestureEvent;
using blink::WebInputEvent;
using blink::WebMouseEvent;

namespace content {

namespace {

static const char kPng[] = "png";
static const char kJpeg[] = "jpeg";
static int kDefaultScreenshotQuality = 80;
static int kFrameRateThresholdMs = 100;
static int kCaptureRetryLimit = 2;

void ParseGenericInputParams(base::DictionaryValue* params,
                             WebInputEvent* event) {
  int modifiers = 0;
  if (params->GetInteger(devtools::Input::dispatchMouseEvent::kParamModifiers,
                         &modifiers)) {
    if (modifiers & 1)
      event->modifiers |= WebInputEvent::AltKey;
    if (modifiers & 2)
      event->modifiers |= WebInputEvent::ControlKey;
    if (modifiers & 4)
      event->modifiers |= WebInputEvent::MetaKey;
    if (modifiers & 8)
      event->modifiers |= WebInputEvent::ShiftKey;
  }

  params->GetDouble(devtools::Input::dispatchMouseEvent::kParamTimestamp,
                    &event->timeStampSeconds);
}

}  // namespace

RendererOverridesHandler::RendererOverridesHandler(DevToolsAgentHost* agent)
    : agent_(agent),
      capture_retry_count_(0),
      weak_factory_(this) {
  RegisterCommandHandler(
      devtools::DOM::setFileInputFiles::kName,
      base::Bind(
          &RendererOverridesHandler::GrantPermissionsForSetFileInputFiles,
          base::Unretained(this)));
  RegisterCommandHandler(
      devtools::Page::disable::kName,
      base::Bind(
          &RendererOverridesHandler::PageDisable, base::Unretained(this)));
  RegisterCommandHandler(
      devtools::Page::handleJavaScriptDialog::kName,
      base::Bind(
          &RendererOverridesHandler::PageHandleJavaScriptDialog,
          base::Unretained(this)));
  RegisterCommandHandler(
      devtools::Page::navigate::kName,
      base::Bind(
          &RendererOverridesHandler::PageNavigate,
          base::Unretained(this)));
  RegisterCommandHandler(
      devtools::Page::reload::kName,
      base::Bind(
          &RendererOverridesHandler::PageReload,
          base::Unretained(this)));
  RegisterCommandHandler(
      devtools::Page::getNavigationHistory::kName,
      base::Bind(
          &RendererOverridesHandler::PageGetNavigationHistory,
          base::Unretained(this)));
  RegisterCommandHandler(
      devtools::Page::navigateToHistoryEntry::kName,
      base::Bind(
          &RendererOverridesHandler::PageNavigateToHistoryEntry,
          base::Unretained(this)));
  RegisterCommandHandler(
      devtools::Page::captureScreenshot::kName,
      base::Bind(
          &RendererOverridesHandler::PageCaptureScreenshot,
          base::Unretained(this)));
  RegisterCommandHandler(
      devtools::Page::canScreencast::kName,
      base::Bind(
          &RendererOverridesHandler::PageCanScreencast,
          base::Unretained(this)));
  RegisterCommandHandler(
      devtools::Page::startScreencast::kName,
      base::Bind(
          &RendererOverridesHandler::PageStartScreencast,
          base::Unretained(this)));
  RegisterCommandHandler(
      devtools::Page::stopScreencast::kName,
      base::Bind(
          &RendererOverridesHandler::PageStopScreencast,
          base::Unretained(this)));
  RegisterCommandHandler(
      devtools::Page::queryUsageAndQuota::kName,
      base::Bind(
          &RendererOverridesHandler::PageQueryUsageAndQuota,
          base::Unretained(this)));
  RegisterCommandHandler(
      devtools::Input::dispatchMouseEvent::kName,
      base::Bind(
          &RendererOverridesHandler::InputDispatchMouseEvent,
          base::Unretained(this)));
  RegisterCommandHandler(
      devtools::Input::dispatchGestureEvent::kName,
      base::Bind(
          &RendererOverridesHandler::InputDispatchGestureEvent,
          base::Unretained(this)));
}

RendererOverridesHandler::~RendererOverridesHandler() {}

void RendererOverridesHandler::OnClientDetached() {
  screencast_command_ = NULL;
}

void RendererOverridesHandler::OnSwapCompositorFrame(
    const cc::CompositorFrameMetadata& frame_metadata) {
  last_compositor_frame_metadata_ = frame_metadata;

  if (screencast_command_)
    InnerSwapCompositorFrame();
}

void RendererOverridesHandler::OnVisibilityChanged(bool visible) {
  if (!screencast_command_)
    return;
  NotifyScreencastVisibility(visible);
}

void RendererOverridesHandler::InnerSwapCompositorFrame() {
  if ((base::TimeTicks::Now() - last_frame_time_).InMilliseconds() <
          kFrameRateThresholdMs) {
    return;
  }

  RenderViewHost* host = agent_->GetRenderViewHost();
  if (!host->GetView())
    return;

  last_frame_time_ = base::TimeTicks::Now();
  std::string format;
  int quality = kDefaultScreenshotQuality;
  double scale = 1;
  ParseCaptureParameters(screencast_command_.get(), &format, &quality, &scale);

  RenderWidgetHostViewPort* view_port =
      RenderWidgetHostViewPort::FromRWHV(host->GetView());

  gfx::Rect view_bounds = host->GetView()->GetViewBounds();
  gfx::Size snapshot_size = gfx::ToFlooredSize(
      gfx::ScaleSize(view_bounds.size(), scale));

  view_port->CopyFromCompositingSurface(
      view_bounds, snapshot_size,
      base::Bind(&RendererOverridesHandler::ScreenshotCaptured,
                 weak_factory_.GetWeakPtr(),
                 scoped_refptr<DevToolsProtocol::Command>(), format, quality,
                 last_compositor_frame_metadata_));
}

void RendererOverridesHandler::ParseCaptureParameters(
    DevToolsProtocol::Command* command,
    std::string* format,
    int* quality,
    double* scale) {
  *quality = kDefaultScreenshotQuality;
  *scale = 1;
  double max_width = -1;
  double max_height = -1;
  base::DictionaryValue* params = command->params();
  if (params) {
    params->GetString(devtools::Page::captureScreenshot::kParamFormat,
                      format);
    params->GetInteger(devtools::Page::captureScreenshot::kParamQuality,
                       quality);
    params->GetDouble(devtools::Page::captureScreenshot::kParamMaxWidth,
                      &max_width);
    params->GetDouble(devtools::Page::captureScreenshot::kParamMaxHeight,
                      &max_height);
  }

  RenderViewHost* host = agent_->GetRenderViewHost();
  CHECK(host->GetView());
  gfx::Rect view_bounds = host->GetView()->GetViewBounds();
  if (max_width > 0)
    *scale = std::min(*scale, max_width / view_bounds.width());
  if (max_height > 0)
    *scale = std::min(*scale, max_height / view_bounds.height());

  if (format->empty())
    *format = kPng;
  if (*quality < 0 || *quality > 100)
    *quality = kDefaultScreenshotQuality;
  if (*scale <= 0)
    *scale = 0.1;
  if (*scale > 5)
    *scale = 5;
}

// DOM agent handlers  --------------------------------------------------------

scoped_refptr<DevToolsProtocol::Response>
RendererOverridesHandler::GrantPermissionsForSetFileInputFiles(
    scoped_refptr<DevToolsProtocol::Command> command) {
  base::DictionaryValue* params = command->params();
  base::ListValue* file_list = NULL;
  const char* param =
      devtools::DOM::setFileInputFiles::kParamFiles;
  if (!params || !params->GetList(param, &file_list))
    return command->InvalidParamResponse(param);
  RenderViewHost* host = agent_->GetRenderViewHost();
  if (!host)
    return NULL;

  for (size_t i = 0; i < file_list->GetSize(); ++i) {
    base::FilePath::StringType file;
    if (!file_list->GetString(i, &file))
      return command->InvalidParamResponse(param);
    ChildProcessSecurityPolicyImpl::GetInstance()->GrantReadFile(
        host->GetProcess()->GetID(), base::FilePath(file));
  }
  return NULL;
}


// Page agent handlers  -------------------------------------------------------

scoped_refptr<DevToolsProtocol::Response>
RendererOverridesHandler::PageDisable(
    scoped_refptr<DevToolsProtocol::Command> command) {
  screencast_command_ = NULL;
  return NULL;
}

scoped_refptr<DevToolsProtocol::Response>
RendererOverridesHandler::PageHandleJavaScriptDialog(
    scoped_refptr<DevToolsProtocol::Command> command) {
  base::DictionaryValue* params = command->params();
  const char* paramAccept =
      devtools::Page::handleJavaScriptDialog::kParamAccept;
  bool accept;
  if (!params || !params->GetBoolean(paramAccept, &accept))
    return command->InvalidParamResponse(paramAccept);
  base::string16 prompt_override;
  base::string16* prompt_override_ptr = &prompt_override;
  if (!params || !params->GetString(
      devtools::Page::handleJavaScriptDialog::kParamPromptText,
      prompt_override_ptr)) {
    prompt_override_ptr = NULL;
  }

  RenderViewHost* host = agent_->GetRenderViewHost();
  if (host) {
    WebContents* web_contents = host->GetDelegate()->GetAsWebContents();
    if (web_contents) {
      JavaScriptDialogManager* manager =
          web_contents->GetDelegate()->GetJavaScriptDialogManager();
      if (manager && manager->HandleJavaScriptDialog(
              web_contents, accept, prompt_override_ptr)) {
        return NULL;
      }
    }
  }
  return command->InternalErrorResponse("No JavaScript dialog to handle");
}

scoped_refptr<DevToolsProtocol::Response>
RendererOverridesHandler::PageNavigate(
    scoped_refptr<DevToolsProtocol::Command> command) {
  base::DictionaryValue* params = command->params();
  std::string url;
  const char* param = devtools::Page::navigate::kParamUrl;
  if (!params || !params->GetString(param, &url))
    return command->InvalidParamResponse(param);
  GURL gurl(url);
  if (!gurl.is_valid()) {
    return command->InternalErrorResponse("Cannot navigate to invalid URL");
  }
  RenderViewHost* host = agent_->GetRenderViewHost();
  if (host) {
    WebContents* web_contents = host->GetDelegate()->GetAsWebContents();
    if (web_contents) {
      web_contents->GetController()
          .LoadURL(gurl, Referrer(), PAGE_TRANSITION_TYPED, std::string());
      return command->SuccessResponse(new base::DictionaryValue());
    }
  }
  return command->InternalErrorResponse("No WebContents to navigate");
}

scoped_refptr<DevToolsProtocol::Response>
RendererOverridesHandler::PageReload(
    scoped_refptr<DevToolsProtocol::Command> command) {
  RenderViewHost* host = agent_->GetRenderViewHost();
  if (host) {
    WebContents* web_contents = host->GetDelegate()->GetAsWebContents();
    if (web_contents) {
      // Override only if it is crashed.
      if (!web_contents->IsCrashed())
        return NULL;

      web_contents->GetController().Reload(false);
      return command->SuccessResponse(NULL);
    }
  }
  return command->InternalErrorResponse("No WebContents to reload");
}

scoped_refptr<DevToolsProtocol::Response>
RendererOverridesHandler::PageGetNavigationHistory(
    scoped_refptr<DevToolsProtocol::Command> command) {
  RenderViewHost* host = agent_->GetRenderViewHost();
  if (host) {
    WebContents* web_contents = host->GetDelegate()->GetAsWebContents();
    if (web_contents) {
      base::DictionaryValue* result = new base::DictionaryValue();
      NavigationController& controller = web_contents->GetController();
      result->SetInteger(
          devtools::Page::getNavigationHistory::kResponseCurrentIndex,
          controller.GetCurrentEntryIndex());
      ListValue* entries = new ListValue();
      for (int i = 0; i != controller.GetEntryCount(); ++i) {
        const NavigationEntry* entry = controller.GetEntryAtIndex(i);
        base::DictionaryValue* entry_value = new base::DictionaryValue();
        entry_value->SetInteger(
            devtools::Page::NavigationEntry::kParamId,
            entry->GetUniqueID());
        entry_value->SetString(
            devtools::Page::NavigationEntry::kParamUrl,
            entry->GetURL().spec());
        entry_value->SetString(
            devtools::Page::NavigationEntry::kParamTitle,
            entry->GetTitle());
        entries->Append(entry_value);
      }
      result->Set(
          devtools::Page::getNavigationHistory::kResponseEntries,
          entries);
      return command->SuccessResponse(result);
    }
  }
  return command->InternalErrorResponse("No WebContents to navigate");
}

scoped_refptr<DevToolsProtocol::Response>
RendererOverridesHandler::PageNavigateToHistoryEntry(
    scoped_refptr<DevToolsProtocol::Command> command) {
  int entry_id;

  base::DictionaryValue* params = command->params();
  const char* param = devtools::Page::navigateToHistoryEntry::kParamEntryId;
  if (!params || !params->GetInteger(param, &entry_id)) {
    return command->InvalidParamResponse(param);
  }

  RenderViewHost* host = agent_->GetRenderViewHost();
  if (host) {
    WebContents* web_contents = host->GetDelegate()->GetAsWebContents();
    if (web_contents) {
      NavigationController& controller = web_contents->GetController();
      for (int i = 0; i != controller.GetEntryCount(); ++i) {
        if (controller.GetEntryAtIndex(i)->GetUniqueID() == entry_id) {
          controller.GoToIndex(i);
          return command->SuccessResponse(new base::DictionaryValue());
        }
      }
      return command->InvalidParamResponse(param);
    }
  }
  return command->InternalErrorResponse("No WebContents to navigate");
}

scoped_refptr<DevToolsProtocol::Response>
RendererOverridesHandler::PageCaptureScreenshot(
    scoped_refptr<DevToolsProtocol::Command> command) {
  RenderViewHost* host = agent_->GetRenderViewHost();
  if (!host->GetView())
    return command->InternalErrorResponse("Unable to access the view");

  std::string format;
  int quality = kDefaultScreenshotQuality;
  double scale = 1;
  ParseCaptureParameters(command.get(), &format, &quality, &scale);

  gfx::Rect view_bounds = host->GetView()->GetViewBounds();
  gfx::Size snapshot_size = gfx::ToFlooredSize(
      gfx::ScaleSize(view_bounds.size(), scale));

  // Grab screen pixels if available for current platform.
  // TODO(pfeldman): support format, scale and quality in ui::GrabViewSnapshot.
  std::vector<unsigned char> png;
  bool is_unscaled_png = scale == 1 && format == kPng;
  if (is_unscaled_png && ui::GrabViewSnapshot(host->GetView()->GetNativeView(),
                                              &png,
                                              gfx::Rect(snapshot_size))) {
    std::string base64_data;
    base::Base64Encode(
        base::StringPiece(reinterpret_cast<char*>(&*png.begin()), png.size()),
        &base64_data);
    base::DictionaryValue* result = new base::DictionaryValue();
    result->SetString(
        devtools::Page::captureScreenshot::kResponseData, base64_data);
    return command->SuccessResponse(result);
  }

  // Fallback to copying from compositing surface.
  RenderWidgetHostViewPort* view_port =
      RenderWidgetHostViewPort::FromRWHV(host->GetView());

  view_port->CopyFromCompositingSurface(
      view_bounds, snapshot_size,
      base::Bind(&RendererOverridesHandler::ScreenshotCaptured,
                 weak_factory_.GetWeakPtr(), command, format, quality,
                 last_compositor_frame_metadata_));
  return command->AsyncResponsePromise();
}

scoped_refptr<DevToolsProtocol::Response>
RendererOverridesHandler::PageCanScreencast(
    scoped_refptr<DevToolsProtocol::Command> command) {
  base::DictionaryValue* result = new base::DictionaryValue();
#if defined(OS_ANDROID)
  result->SetBoolean(devtools::kResult, true);
#else
  result->SetBoolean(devtools::kResult, false);
#endif  // defined(OS_ANDROID)
  return command->SuccessResponse(result);
}

scoped_refptr<DevToolsProtocol::Response>
RendererOverridesHandler::PageStartScreencast(
    scoped_refptr<DevToolsProtocol::Command> command) {
  screencast_command_ = command;
  RenderViewHostImpl* host = static_cast<RenderViewHostImpl*>(
      agent_->GetRenderViewHost());
  bool visible = !host->is_hidden();
  NotifyScreencastVisibility(visible);
  if (visible)
    InnerSwapCompositorFrame();
  return command->SuccessResponse(NULL);
}

scoped_refptr<DevToolsProtocol::Response>
RendererOverridesHandler::PageStopScreencast(
    scoped_refptr<DevToolsProtocol::Command> command) {
  last_frame_time_ = base::TimeTicks();
  screencast_command_ = NULL;
  return command->SuccessResponse(NULL);
}

void RendererOverridesHandler::ScreenshotCaptured(
    scoped_refptr<DevToolsProtocol::Command> command,
    const std::string& format,
    int quality,
    const cc::CompositorFrameMetadata& metadata,
    bool success,
    const SkBitmap& bitmap) {
  if (!success) {
    if (command) {
      SendAsyncResponse(
          command->InternalErrorResponse("Unable to capture screenshot"));
    } else if (capture_retry_count_) {
      --capture_retry_count_;
      base::MessageLoop::current()->PostDelayedTask(
          FROM_HERE,
          base::Bind(&RendererOverridesHandler::InnerSwapCompositorFrame,
                     weak_factory_.GetWeakPtr()),
          base::TimeDelta::FromMilliseconds(kFrameRateThresholdMs));
    }
    return;
  }

  std::vector<unsigned char> data;
  SkAutoLockPixels lock_image(bitmap);
  bool encoded;
  if (format == kPng) {
    encoded = gfx::PNGCodec::Encode(
        reinterpret_cast<unsigned char*>(bitmap.getAddr32(0, 0)),
        gfx::PNGCodec::FORMAT_SkBitmap,
        gfx::Size(bitmap.width(), bitmap.height()),
        bitmap.width() * bitmap.bytesPerPixel(),
        false, std::vector<gfx::PNGCodec::Comment>(), &data);
  } else if (format == kJpeg) {
    encoded = gfx::JPEGCodec::Encode(
        reinterpret_cast<unsigned char*>(bitmap.getAddr32(0, 0)),
        gfx::JPEGCodec::FORMAT_SkBitmap,
        bitmap.width(),
        bitmap.height(),
        bitmap.width() * bitmap.bytesPerPixel(),
        quality, &data);
  } else {
    encoded = false;
  }

  if (!encoded) {
    if (command) {
      SendAsyncResponse(
          command->InternalErrorResponse("Unable to encode screenshot"));
    }
    return;
  }

  std::string base_64_data;
  base::Base64Encode(
      base::StringPiece(reinterpret_cast<char*>(&data[0]), data.size()),
      &base_64_data);

  base::DictionaryValue* response = new base::DictionaryValue();
  response->SetString(devtools::Page::screencastFrame::kParamData,
                      base_64_data);

  // Consider metadata empty in case it has no device scale factor.
  if (metadata.device_scale_factor != 0) {
    base::DictionaryValue* response_metadata = new base::DictionaryValue();

    response_metadata->SetDouble(
        devtools::Page::ScreencastFrameMetadata::kParamDeviceScaleFactor,
        metadata.device_scale_factor);
    response_metadata->SetDouble(
        devtools::Page::ScreencastFrameMetadata::kParamPageScaleFactor,
        metadata.page_scale_factor);
    response_metadata->SetDouble(
        devtools::Page::ScreencastFrameMetadata::kParamPageScaleFactorMin,
        metadata.min_page_scale_factor);
    response_metadata->SetDouble(
        devtools::Page::ScreencastFrameMetadata::kParamPageScaleFactorMax,
        metadata.max_page_scale_factor);
    response_metadata->SetDouble(
        devtools::Page::ScreencastFrameMetadata::kParamOffsetTop,
        metadata.location_bar_content_translation.y());
    response_metadata->SetDouble(
        devtools::Page::ScreencastFrameMetadata::kParamOffsetBottom,
        metadata.overdraw_bottom_height);

    base::DictionaryValue* viewport = new base::DictionaryValue();
    viewport->SetDouble(devtools::DOM::Rect::kParamX,
                        metadata.root_scroll_offset.x());
    viewport->SetDouble(devtools::DOM::Rect::kParamY,
                        metadata.root_scroll_offset.y());
    viewport->SetDouble(devtools::DOM::Rect::kParamWidth,
                        metadata.viewport_size.width());
    viewport->SetDouble(devtools::DOM::Rect::kParamHeight,
                        metadata.viewport_size.height());
    response_metadata->Set(
        devtools::Page::ScreencastFrameMetadata::kParamViewport, viewport);

    if (command) {
      response->Set(devtools::Page::captureScreenshot::kResponseMetadata,
                    response_metadata);
    } else {
      response->Set(devtools::Page::screencastFrame::kParamMetadata,
                    response_metadata);
    }
  }

  if (command) {
    SendAsyncResponse(command->SuccessResponse(response));
  } else {
    SendNotification(devtools::Page::screencastFrame::kName, response);
  }
}

// Quota and Usage ------------------------------------------

namespace {

typedef base::Callback<void(scoped_ptr<base::DictionaryValue>)>
    ResponseCallback;

void QueryUsageAndQuotaCompletedOnIOThread(
    scoped_ptr<base::DictionaryValue> quota,
    scoped_ptr<base::DictionaryValue> usage,
    ResponseCallback callback) {

  scoped_ptr<base::DictionaryValue> response_data(new base::DictionaryValue);
  response_data->Set(devtools::Page::queryUsageAndQuota::kResponseQuota,
                     quota.release());
  response_data->Set(devtools::Page::queryUsageAndQuota::kResponseUsage,
                     usage.release());

  BrowserThread::PostTask(
      BrowserThread::UI, FROM_HERE,
      base::Bind(callback, base::Passed(&response_data)));
}

void DidGetHostUsage(
    base::ListValue* list,
    const std::string& client_id,
    const base::Closure& barrier,
    int64 value) {
  base::DictionaryValue* usage_item = new base::DictionaryValue;
  usage_item->SetString(devtools::Page::UsageItem::kParamId, client_id);
  usage_item->SetDouble(devtools::Page::UsageItem::kParamValue, value);
  list->Append(usage_item);
  barrier.Run();
}

void DidGetQuotaValue(
    base::DictionaryValue* dictionary,
    const std::string& item_name,
    const base::Closure& barrier,
    quota::QuotaStatusCode status,
    int64 value) {
  if (status == quota::kQuotaStatusOk)
    dictionary->SetDouble(item_name, value);
  barrier.Run();
}

void DidGetUsageAndQuotaForWebApps(
    base::DictionaryValue* quota,
    const std::string& item_name,
    const base::Closure& barrier,
    quota::QuotaStatusCode status,
    int64 used_bytes,
    int64 quota_in_bytes) {
  if (status == quota::kQuotaStatusOk)
    quota->SetDouble(item_name, quota_in_bytes);
  barrier.Run();
}

std::string GetStorageTypeName(quota::StorageType type) {
  switch (type) {
    case quota::kStorageTypeTemporary:
      return devtools::Page::Usage::kParamTemporary;
    case quota::kStorageTypePersistent:
      return devtools::Page::Usage::kParamPersistent;
    case quota::kStorageTypeSyncable:
      return devtools::Page::Usage::kParamSyncable;
    case quota::kStorageTypeQuotaNotManaged:
    case quota::kStorageTypeUnknown:
      NOTREACHED();
  }
  return "";
}

std::string GetQuotaClientName(quota::QuotaClient::ID id) {
  switch (id) {
    case quota::QuotaClient::kFileSystem:
      return devtools::Page::UsageItem::Id::kEnumFilesystem;
    case quota::QuotaClient::kDatabase:
      return devtools::Page::UsageItem::Id::kEnumDatabase;
    case quota::QuotaClient::kAppcache:
      return devtools::Page::UsageItem::Id::kEnumAppcache;
    case quota::QuotaClient::kIndexedDatabase:
      return devtools::Page::UsageItem::Id::kEnumIndexeddatabase;
    default:
      NOTREACHED();
      return "";
  }
}

void QueryUsageAndQuotaOnIOThread(
    scoped_refptr<quota::QuotaManager> quota_manager,
    const GURL& security_origin,
    const ResponseCallback& callback) {
  scoped_ptr<base::DictionaryValue> quota(new base::DictionaryValue);
  scoped_ptr<base::DictionaryValue> usage(new base::DictionaryValue);

  static quota::QuotaClient::ID kQuotaClients[] = {
      quota::QuotaClient::kFileSystem,
      quota::QuotaClient::kDatabase,
      quota::QuotaClient::kAppcache,
      quota::QuotaClient::kIndexedDatabase
  };

  static const size_t kStorageTypeCount = quota::kStorageTypeUnknown;
  std::map<quota::StorageType, base::ListValue*> storage_type_lists;

  for (size_t i = 0; i != kStorageTypeCount; i++) {
    const quota::StorageType type = static_cast<quota::StorageType>(i);
    if (type == quota::kStorageTypeQuotaNotManaged)
      continue;
    storage_type_lists[type] = new base::ListValue;
    usage->Set(GetStorageTypeName(type), storage_type_lists[type]);
  }

  const int kExpectedResults =
      2 + arraysize(kQuotaClients) * storage_type_lists.size();
  base::DictionaryValue* quota_raw_ptr = quota.get();

  // Takes ownership on usage and quota.
  base::Closure barrier = BarrierClosure(
      kExpectedResults,
      base::Bind(&QueryUsageAndQuotaCompletedOnIOThread,
                 base::Passed(&quota),
                 base::Passed(&usage),
                 callback));
  std::string host = net::GetHostOrSpecFromURL(security_origin);

  quota_manager->GetUsageAndQuotaForWebApps(
      security_origin,
      quota::kStorageTypeTemporary,
      base::Bind(&DidGetUsageAndQuotaForWebApps, quota_raw_ptr,
                 std::string(devtools::Page::Quota::kParamTemporary), barrier));

  quota_manager->GetPersistentHostQuota(
      host,
      base::Bind(&DidGetQuotaValue, quota_raw_ptr,
                 std::string(devtools::Page::Quota::kParamPersistent),
                 barrier));

  for (size_t i = 0; i != arraysize(kQuotaClients); i++) {
    std::map<quota::StorageType, base::ListValue*>::const_iterator iter;
    for (iter = storage_type_lists.begin();
         iter != storage_type_lists.end(); ++iter) {
      const quota::StorageType type = (*iter).first;
      if (!quota_manager->IsTrackingHostUsage(type, kQuotaClients[i])) {
        barrier.Run();
        continue;
      }
      quota_manager->GetHostUsage(
          host, type, kQuotaClients[i],
          base::Bind(&DidGetHostUsage, (*iter).second,
                     GetQuotaClientName(kQuotaClients[i]),
                     barrier));
    }
  }
}

} // namespace

scoped_refptr<DevToolsProtocol::Response>
RendererOverridesHandler::PageQueryUsageAndQuota(
    scoped_refptr<DevToolsProtocol::Command> command) {
  base::DictionaryValue* params = command->params();
  std::string security_origin;
  if (!params || !params->GetString(
      devtools::Page::queryUsageAndQuota::kParamSecurityOrigin,
      &security_origin)) {
    return command->InvalidParamResponse(
        devtools::Page::queryUsageAndQuota::kParamSecurityOrigin);
  }

  ResponseCallback callback = base::Bind(
      &RendererOverridesHandler::PageQueryUsageAndQuotaCompleted,
      weak_factory_.GetWeakPtr(),
      command);

  scoped_refptr<quota::QuotaManager> quota_manager =
      agent_->GetRenderViewHost()->GetProcess()->
          GetStoragePartition()->GetQuotaManager();

  BrowserThread::PostTask(
      BrowserThread::IO, FROM_HERE,
      base::Bind(
          &QueryUsageAndQuotaOnIOThread,
          quota_manager,
          GURL(security_origin),
          callback));

  return command->AsyncResponsePromise();
}

void RendererOverridesHandler::PageQueryUsageAndQuotaCompleted(
    scoped_refptr<DevToolsProtocol::Command> command,
    scoped_ptr<base::DictionaryValue> response_data) {
  SendAsyncResponse(command->SuccessResponse(response_data.release()));
}

void RendererOverridesHandler::NotifyScreencastVisibility(bool visible) {
  if (visible)
    capture_retry_count_ = kCaptureRetryLimit;
  base::DictionaryValue* params = new base::DictionaryValue();
  params->SetBoolean(
      devtools::Page::screencastVisibilityChanged::kParamVisible, visible);
  SendNotification(
      devtools::Page::screencastVisibilityChanged::kName, params);
}

// Input agent handlers  ------------------------------------------------------

scoped_refptr<DevToolsProtocol::Response>
RendererOverridesHandler::InputDispatchMouseEvent(
    scoped_refptr<DevToolsProtocol::Command> command) {
  base::DictionaryValue* params = command->params();
  if (!params)
    return NULL;

  bool device_space = false;
  if (!params->GetBoolean(
          devtools::Input::dispatchMouseEvent::kParamDeviceSpace,
          &device_space) ||
      !device_space) {
    return NULL;
  }

  RenderViewHost* host = agent_->GetRenderViewHost();
  blink::WebMouseEvent mouse_event;
  ParseGenericInputParams(params, &mouse_event);

  std::string type;
  if (params->GetString(devtools::Input::dispatchMouseEvent::kParamType,
                        &type)) {
    if (type ==
        devtools::Input::dispatchMouseEvent::Type::kEnumMousePressed)
      mouse_event.type = WebInputEvent::MouseDown;
    else if (type ==
        devtools::Input::dispatchMouseEvent::Type::kEnumMouseReleased)
      mouse_event.type = WebInputEvent::MouseUp;
    else if (type ==
        devtools::Input::dispatchMouseEvent::Type::kEnumMouseMoved)
      mouse_event.type = WebInputEvent::MouseMove;
    else
      return NULL;
  } else {
    return NULL;
  }

  if (!params->GetInteger(devtools::Input::dispatchMouseEvent::kParamX,
                          &mouse_event.x) ||
      !params->GetInteger(devtools::Input::dispatchMouseEvent::kParamY,
                          &mouse_event.y)) {
    return NULL;
  }

  mouse_event.windowX = mouse_event.x;
  mouse_event.windowY = mouse_event.y;
  mouse_event.globalX = mouse_event.x;
  mouse_event.globalY = mouse_event.y;

  params->GetInteger(devtools::Input::dispatchMouseEvent::kParamClickCount,
                     &mouse_event.clickCount);

  std::string button;
  if (!params->GetString(devtools::Input::dispatchMouseEvent::kParamButton,
                         &button)) {
    return NULL;
  }

  if (button == "none") {
    mouse_event.button = WebMouseEvent::ButtonNone;
  } else if (button == "left") {
    mouse_event.button = WebMouseEvent::ButtonLeft;
    mouse_event.modifiers |= WebInputEvent::LeftButtonDown;
  } else if (button == "middle") {
    mouse_event.button = WebMouseEvent::ButtonMiddle;
    mouse_event.modifiers |= WebInputEvent::MiddleButtonDown;
  } else if (button == "right") {
    mouse_event.button = WebMouseEvent::ButtonRight;
    mouse_event.modifiers |= WebInputEvent::RightButtonDown;
  } else {
    return NULL;
  }

  host->ForwardMouseEvent(mouse_event);
  return command->SuccessResponse(NULL);
}

scoped_refptr<DevToolsProtocol::Response>
RendererOverridesHandler::InputDispatchGestureEvent(
    scoped_refptr<DevToolsProtocol::Command> command) {
  base::DictionaryValue* params = command->params();
  if (!params)
    return NULL;

  RenderViewHostImpl* host = static_cast<RenderViewHostImpl*>(
      agent_->GetRenderViewHost());
  blink::WebGestureEvent event;
  ParseGenericInputParams(params, &event);

  std::string type;
  if (params->GetString(devtools::Input::dispatchGestureEvent::kParamType,
                        &type)) {
    if (type ==
        devtools::Input::dispatchGestureEvent::Type::kEnumScrollBegin)
      event.type = WebInputEvent::GestureScrollBegin;
    else if (type ==
        devtools::Input::dispatchGestureEvent::Type::kEnumScrollUpdate)
      event.type = WebInputEvent::GestureScrollUpdate;
    else if (type ==
        devtools::Input::dispatchGestureEvent::Type::kEnumScrollEnd)
      event.type = WebInputEvent::GestureScrollEnd;
    else if (type ==
        devtools::Input::dispatchGestureEvent::Type::kEnumTapDown)
      event.type = WebInputEvent::GestureTapDown;
    else if (type ==
        devtools::Input::dispatchGestureEvent::Type::kEnumTap)
      event.type = WebInputEvent::GestureTap;
    else if (type ==
        devtools::Input::dispatchGestureEvent::Type::kEnumPinchBegin)
      event.type = WebInputEvent::GesturePinchBegin;
    else if (type ==
        devtools::Input::dispatchGestureEvent::Type::kEnumPinchUpdate)
      event.type = WebInputEvent::GesturePinchUpdate;
    else if (type ==
        devtools::Input::dispatchGestureEvent::Type::kEnumPinchEnd)
      event.type = WebInputEvent::GesturePinchEnd;
    else
      return NULL;
  } else {
    return NULL;
  }

  if (!params->GetInteger(devtools::Input::dispatchGestureEvent::kParamX,
                          &event.x) ||
      !params->GetInteger(devtools::Input::dispatchGestureEvent::kParamY,
                          &event.y)) {
    return NULL;
  }
  event.globalX = event.x;
  event.globalY = event.y;

  if (type == "scrollUpdate") {
    int dx;
    int dy;
    if (!params->GetInteger(
            devtools::Input::dispatchGestureEvent::kParamDeltaX, &dx) ||
        !params->GetInteger(
            devtools::Input::dispatchGestureEvent::kParamDeltaY, &dy)) {
      return NULL;
    }
    event.data.scrollUpdate.deltaX = dx;
    event.data.scrollUpdate.deltaY = dy;
  }

  if (type == "pinchUpdate") {
    double scale;
    if (!params->GetDouble(
        devtools::Input::dispatchGestureEvent::kParamPinchScale,
        &scale)) {
      return NULL;
    }
    event.data.pinchUpdate.scale = static_cast<float>(scale);
  }

  host->ForwardGestureEvent(event);
  return command->SuccessResponse(NULL);
}

}  // namespace content