// Copyright 2012 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/renderer/web_ui_extension.h"

#include <memory>
#include <utility>

#include "base/values.h"
#include "content/common/frame_messages.h"
#include "content/public/common/bindings_policy.h"
#include "content/public/common/url_constants.h"
#include "content/public/renderer/chrome_object_extensions_utils.h"
#include "content/public/renderer/render_frame.h"
#include "content/public/renderer/render_thread.h"
#include "content/public/renderer/render_view.h"
#include "content/public/renderer/v8_value_converter.h"
#include "content/renderer/web_ui_extension_data.h"
#include "gin/arguments.h"
#include "gin/function_template.h"
#include "third_party/blink/public/web/blink.h"
#include "third_party/blink/public/web/web_document.h"
#include "third_party/blink/public/web/web_local_frame.h"
#include "third_party/blink/public/web/web_view.h"
#include "url/gurl.h"
#include "v8/include/v8.h"

namespace content {

namespace {

bool ShouldRespondToRequest(blink::WebLocalFrame** frame_ptr,
                            RenderFrame** render_frame_ptr) {
  blink::WebLocalFrame* frame = blink::WebLocalFrame::FrameForCurrentContext();
  if (!frame || !frame->View())
    return false;

  GURL frame_url = frame->GetDocument().Url();

  RenderFrame* render_frame = RenderFrame::FromWebFrame(frame);
  if (!render_frame)
    return false;

  bool webui_enabled =
      (render_frame->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI) &&
      (frame_url.SchemeIs(kChromeUIScheme) ||
       frame_url.SchemeIs(url::kDataScheme));

  if (!webui_enabled)
    return false;

  *frame_ptr = frame;
  *render_frame_ptr = render_frame;
  return true;
}

}  // namespace

// Exposes two methods:
//  - chrome.send: Used to send messages to the browser. Requires the message
//      name as the first argument and can have an optional second argument that
//      should be an array.
//  - chrome.getVariableValue: Returns value for the input variable name if such
//      a value was set by the browser. Else will return an empty string.
void WebUIExtension::Install(blink::WebLocalFrame* frame) {
  v8::Isolate* isolate = blink::MainThreadIsolate();
  v8::HandleScope handle_scope(isolate);
  v8::Local<v8::Context> context = frame->MainWorldScriptContext();
  if (context.IsEmpty())
    return;

  v8::Context::Scope context_scope(context);

  v8::Local<v8::Object> chrome = GetOrCreateChromeObject(isolate,
                                                          context->Global());
  chrome->Set(gin::StringToSymbol(isolate, "send"),
              gin::CreateFunctionTemplate(
                  isolate, base::Bind(&WebUIExtension::Send))->GetFunction());
  chrome->Set(gin::StringToSymbol(isolate, "getVariableValue"),
              gin::CreateFunctionTemplate(
                  isolate, base::Bind(&WebUIExtension::GetVariableValue))
                  ->GetFunction());
}

// static
void WebUIExtension::Send(gin::Arguments* args) {
  blink::WebLocalFrame* frame;
  RenderFrame* render_frame;
  if (!ShouldRespondToRequest(&frame, &render_frame))
    return;

  std::string message;
  if (!args->GetNext(&message)) {
    args->ThrowError();
    return;
  }

  // If they've provided an optional message parameter, convert that into a
  // Value to send to the browser process.
  std::unique_ptr<base::ListValue> content;
  if (args->PeekNext().IsEmpty() || args->PeekNext()->IsUndefined()) {
    content.reset(new base::ListValue());
  } else {
    v8::Local<v8::Object> obj;
    if (!args->GetNext(&obj)) {
      args->ThrowError();
      return;
    }

    content = base::ListValue::From(V8ValueConverter::Create()->FromV8Value(
        obj, frame->MainWorldScriptContext()));
    DCHECK(content);
    // The conversion of |obj| could have triggered arbitrary JavaScript code,
    // so check that the frame is still valid to avoid dereferencing a stale
    // pointer.
    if (frame != blink::WebLocalFrame::FrameForCurrentContext()) {
      NOTREACHED();
      return;
    }
  }

  // Send the message up to the browser.
  render_frame->Send(new FrameHostMsg_WebUISend(render_frame->GetRoutingID(),
                                                message, *content));
}

// static
std::string WebUIExtension::GetVariableValue(const std::string& name) {
  blink::WebLocalFrame* frame;
  RenderFrame* render_frame;
  if (!ShouldRespondToRequest(&frame, &render_frame))
    return std::string();

  return WebUIExtensionData::Get(render_frame->GetRenderView())->GetValue(name);
}

}  // namespace content
