// Copyright (c) 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 "chrome/renderer/external_host_bindings.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/values.h"
#include "chrome/common/render_messages.h"
#include "third_party/WebKit/public/web/WebBindings.h"
#include "third_party/WebKit/public/web/WebDocument.h"
#include "third_party/WebKit/public/web/WebFrame.h"
using blink::WebBindings;
using webkit_glue::CppArgumentList;
using webkit_glue::CppVariant;
ExternalHostBindings::ExternalHostBindings(IPC::Sender* sender, int routing_id)
: frame_(NULL), sender_(sender), routing_id_(routing_id) {
BindCallback("postMessage", base::Bind(&ExternalHostBindings::PostMessage,
base::Unretained(this)));
BindProperty("onmessage", &on_message_handler_);
}
ExternalHostBindings::~ExternalHostBindings() {
}
void ExternalHostBindings::PostMessage(
const CppArgumentList& args, CppVariant* result) {
DCHECK(result);
// We need at least one argument (message) and at most 2 arguments.
// Also, the first argument must be a string
if (args.size() < 1 || args.size() > 2 || !args[0].isString()) {
result->Set(false);
return;
}
const std::string& message = args[0].ToString();
std::string target;
if (args.size() >= 2 && args[1].isString()) {
target = args[1].ToString();
if (target.compare("*") != 0) {
GURL resolved(target);
if (!resolved.is_valid()) {
DLOG(WARNING) << "Unable to parse the specified target URL. " << target;
result->Set(false);
return;
}
target = resolved.spec();
}
} else {
target = "*";
}
std::string origin = frame_->document().securityOrigin().toString().utf8();
result->Set(sender_->Send(
new ChromeViewHostMsg_ForwardMessageToExternalHost(
routing_id_, message, origin, target)));
}
bool ExternalHostBindings::ForwardMessageFromExternalHost(
const std::string& message, const std::string& origin,
const std::string& target) {
if (!on_message_handler_.isObject())
return false;
bool status = false;
if (target.compare("*") != 0) {
// TODO(abarth): This code should use WebSecurityOrigin::toString to
// make origin strings. GURL::GetOrigin() doesn't understand all the
// cases that WebSecurityOrigin::toString understands.
GURL document_url(frame_->document().url());
GURL document_origin(document_url.GetOrigin());
GURL target_origin(GURL(target).GetOrigin());
// We want to compare the origins of the two URLs but first
// we need to make sure that we don't compare an invalid one
// to a valid one.
bool drop = (document_origin.is_valid() != target_origin.is_valid());
if (!drop) {
if (!document_origin.is_valid()) {
// Both origins are invalid, so compare the URLs as opaque strings.
drop = (document_url.spec().compare(target) != 0);
} else {
drop = (document_origin != target_origin);
}
}
if (drop) {
DLOG(WARNING) << "Dropping posted message. Origins don't match";
return false;
}
}
// Construct an event object, assign the origin to the origin member and
// assign message parameter to the 'data' member of the event.
NPObject* event_obj = NULL;
CreateMessageEvent(&event_obj);
if (!event_obj) {
NOTREACHED() << "CreateMessageEvent failed";
} else {
NPIdentifier init_message_event =
WebBindings::getStringIdentifier("initMessageEvent");
NPVariant init_args[8];
STRINGN_TO_NPVARIANT("message", sizeof("message") - 1,
init_args[0]); // type
BOOLEAN_TO_NPVARIANT(false, init_args[1]); // canBubble
BOOLEAN_TO_NPVARIANT(true, init_args[2]); // cancelable
STRINGN_TO_NPVARIANT(message.c_str(), message.length(), \
init_args[3]); // data
STRINGN_TO_NPVARIANT(origin.c_str(), origin.length(), \
init_args[4]); // origin
STRINGN_TO_NPVARIANT("", 0, init_args[5]); // lastEventId
NULL_TO_NPVARIANT(init_args[6]); // source
NULL_TO_NPVARIANT(init_args[7]); // messagePort
NPVariant result;
NULL_TO_NPVARIANT(result);
status = WebBindings::invoke(NULL, event_obj, init_message_event, init_args,
arraysize(init_args), &result);
DCHECK(status) << "Failed to initialize MessageEvent";
WebBindings::releaseVariantValue(&result);
if (status) {
NPVariant event_arg;
OBJECT_TO_NPVARIANT(event_obj, event_arg);
status = WebBindings::invokeDefault(NULL,
on_message_handler_.value.objectValue,
&event_arg, 1, &result);
// Don't DCHECK here in case the reason for the failure is a script error.
DLOG_IF(ERROR, !status) << "NPN_InvokeDefault failed";
WebBindings::releaseVariantValue(&result);
}
WebBindings::releaseObject(event_obj);
}
return status;
}
void ExternalHostBindings::BindToJavascript(blink::WebFrame* frame,
const std::string& classname) {
frame_ = frame;
CppBoundClass::BindToJavascript(frame, classname);
}
bool ExternalHostBindings::CreateMessageEvent(NPObject** message_event) {
DCHECK(message_event != NULL);
DCHECK(frame_ != NULL);
NPObject* window = frame_->windowObject();
if (!window) {
NOTREACHED() << "frame_->windowObject";
return false;
}
const char* identifier_names[] = {
"document",
"createEvent",
};
NPIdentifier identifiers[arraysize(identifier_names)] = {0};
WebBindings::getStringIdentifiers(identifier_names,
arraysize(identifier_names), identifiers);
CppVariant document;
bool ok = WebBindings::getProperty(NULL, window, identifiers[0], &document);
DCHECK(document.isObject());
bool success = false;
if (ok && document.isObject()) {
NPVariant result, event_type;
STRINGN_TO_NPVARIANT("MessageEvent", sizeof("MessageEvent") - 1, \
event_type);
success = WebBindings::invoke(NULL, document.value.objectValue,
identifiers[1], &event_type, 1, &result);
DCHECK(!success || result.type == NPVariantType_Object);
if (result.type != NPVariantType_Object) {
DCHECK(success == false);
} else {
DCHECK(success != false);
// Pass the ownership to the caller (don't call ReleaseVariantValue).
*message_event = result.value.objectValue;
}
}
return success;
}