// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// A simple C++ Pepper plugin for exercising deprecated PPAPI interfaces in
// Blink layout tests.
//
// Most layout tests should prefer to use the normal Blink test plugin, with the
// MIME type application/x-blink-test-plugin. For layout tests that absolutely
// need to test deprecated synchronous scripting interfaces, this plugin can be
// instantiated using the application/x-blink-deprecated-test-plugin MIME type.
//
// The plugin exposes the following interface:
//
// Attributes:
// testwindowopen: if set, the plugin will synchronously attempt to open a
// window from DidCreateInstance, and log a message if successful.
//
// keydownscript: if set, the plugin will execute the value of the attribute as
// a script on a key down.
//
// mousedownscript: if set, the plugin will execute the value of the attribute
// as a script on a mouse button down.
//
//
// Functions:
// * plugin.normalize(): synchronously calls window.pluginCallback.
//
// * plugin.remember(value): keeps a reference on |value| in the plugin.
//
// * plugin.testCloneObject(): creates and returns another instance of the
// plugin object.
//
// * plugin.testCreateTestObject(): creates and returns a new TestObject
// instance (see below).
//
// * plugin.testExecuteScript(script): synchronously evaluates |script| and
// returns the result.
//
// * plugin.testGetProperty(property): returns the property named |property|
// from the window object.
//
// * plugin.testPassTestObject(function, object): synchronously calls the
// function named |function| on the window object, passing it |object| as a
// parameter, and returns its result.
//
// * plugin.testScriptObjectInvoke(function, value): synchronously calls the
// function named |function| on the window object, passing it |value| as a
// parameter, and returns its result.
//
//
// Properties:
// * plugin.testObject (read-only): a TestObject instance (see below).
//
// * plugin.testObjectCount (read-only): the number of TestObject instance
// created.
//
// * plugin.testGetUndefined (read-only): returns undefined.
//
//
// TestObject exposes the following interface:
// Properties:
// * object.testObject (read-only: another TestObject instance.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/351564777): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include <stdint.h>
#include <map>
#include <sstream>
#include <unordered_map>
#include <utility>
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/strings/stringprintf.h"
#include "ppapi/cpp/dev/scriptable_object_deprecated.h"
#include "ppapi/cpp/input_event.h"
#include "ppapi/cpp/module.h"
#include "ppapi/cpp/private/instance_private.h"
#include "ppapi/cpp/private/var_private.h"
#include "ppapi/cpp/var.h"
namespace {
class ScriptableBase : public pp::deprecated::ScriptableObject {
public:
explicit ScriptableBase(pp::InstancePrivate* instance)
: instance_(instance) {}
~ScriptableBase() override {}
// pp::deprecated::ScriptableObject overrides:
bool HasMethod(const pp::Var& name, pp::Var* exception) override {
return FindMethod(name) != methods_.end();
}
bool HasProperty(const pp::Var& name, pp::Var* exception) override {
return FindProperty(name) != properties_.end();
}
pp::Var Call(const pp::Var& method_name,
const std::vector<pp::Var>& args,
pp::Var* exception) override {
auto method = FindMethod(method_name);
if (method != methods_.end()) {
return method->second.Run(args, exception);
}
return ScriptableObject::Call(method_name, args, exception);
}
pp::Var GetProperty(const pp::Var& name, pp::Var* exception) override {
auto accessor = FindProperty(name);
if (accessor != properties_.end()) {
pp::Var value;
accessor->second.Run(false, &value);
return value;
}
return ScriptableObject::GetProperty(name, exception);
}
void SetProperty(const pp::Var& name,
const pp::Var& value,
pp::Var* exception) override {
auto accessor = FindProperty(name);
if (accessor != properties_.end())
accessor->second.Run(true, const_cast<pp::Var*>(&value));
else
ScriptableObject::SetProperty(name, value, exception);
}
protected:
using MethodMap = std::map<
std::string,
base::RepeatingCallback<pp::Var(const std::vector<pp::Var>&, pp::Var*)>>;
using PropertyMap =
std::map<std::string, base::RepeatingCallback<void(bool, pp::Var*)>>;
MethodMap::iterator FindMethod(const pp::Var& name) {
if (!name.is_string())
return methods_.end();
return methods_.find(name.AsString());
}
PropertyMap::iterator FindProperty(const pp::Var& name) {
if (!name.is_string())
return properties_.end();
return properties_.find(name.AsString());
}
pp::InstancePrivate* const instance_;
MethodMap methods_;
PropertyMap properties_;
};
class TestObjectSO : public ScriptableBase {
public:
explicit TestObjectSO(pp::InstancePrivate* instance)
: ScriptableBase(instance) {
++count_;
properties_.insert(std::make_pair(
"testObject", base::BindRepeating(&TestObjectSO::TestObjectAccessor,
base::Unretained(this))));
}
~TestObjectSO() override {
--count_;
}
static int32_t count() { return count_; }
private:
void TestObjectAccessor(bool set, pp::Var* var) {
if (set)
return;
if (test_object_.is_undefined())
test_object_ = pp::VarPrivate(instance_, new TestObjectSO(instance_));
*var = test_object_;
}
static int32_t count_;
pp::VarPrivate test_object_;
};
int32_t TestObjectSO::count_ = 0;
class InstanceSO : public ScriptableBase {
public:
explicit InstanceSO(pp::InstancePrivate* instance)
: ScriptableBase(instance) {
methods_.insert(std::make_pair(
"normalize",
base::BindRepeating(&InstanceSO::Normalize, base::Unretained(this))));
methods_.insert(std::make_pair(
"remember",
base::BindRepeating(&InstanceSO::Remember, base::Unretained(this))));
methods_.insert(std::make_pair(
"testCloneObject", base::BindRepeating(&InstanceSO::TestCloneObject,
base::Unretained(this))));
methods_.insert(
std::make_pair("testCreateTestObject",
base::BindRepeating(&InstanceSO::TestCreateTestObject,
base::Unretained(this))));
methods_.insert(std::make_pair(
"testExecuteScript", base::BindRepeating(&InstanceSO::TestExecuteScript,
base::Unretained(this))));
methods_.insert(std::make_pair(
"testGetProperty", base::BindRepeating(&InstanceSO::TestGetProperty,
base::Unretained(this))));
methods_.insert(
std::make_pair("testPassTestObject",
base::BindRepeating(&InstanceSO::TestPassTestObject,
base::Unretained(this))));
// Note: the semantics of testScriptObjectInvoke are identical to the
// semantics of testPassTestObject: call args[0] with args[1] as a
// parameter.
methods_.insert(
std::make_pair("testScriptObjectInvoke",
base::BindRepeating(&InstanceSO::TestPassTestObject,
base::Unretained(this))));
properties_.insert(std::make_pair(
"testObject", base::BindRepeating(&InstanceSO::TestObjectAccessor,
base::Unretained(this))));
properties_.insert(
std::make_pair("testObjectCount",
base::BindRepeating(&InstanceSO::TestObjectCountAccessor,
base::Unretained(this))));
properties_.insert(std::make_pair(
"testGetUndefined",
base::BindRepeating(&InstanceSO::TestGetUndefinedAccessor,
base::Unretained(this))));
}
~InstanceSO() override = default;
private:
// Requires no argument.
pp::Var Normalize(const std::vector<pp::Var>& args, pp::Var* exception) {
pp::VarPrivate object = instance_->GetWindowObject();
return object.Call(pp::Var("pluginCallback"), exception);
}
// Requires 1 argument. The argument is retained into remembered_
pp::Var Remember(const std::vector<pp::Var>& args, pp::Var* exception) {
if (args.size() != 1) {
*exception = pp::Var("remember requires one argument");
return pp::Var();
}
remembered_ = args[0];
return pp::Var();
}
// Requires no argument.
pp::Var TestCloneObject(const std::vector<pp::Var>& args,
pp::Var* exception) {
return pp::VarPrivate(instance_, new InstanceSO(instance_));
}
// Requires no argument.
pp::Var TestCreateTestObject(const std::vector<pp::Var>& args,
pp::Var* exception) {
return pp::VarPrivate(instance_, new TestObjectSO(instance_));
}
// Requires one argument. The argument is passed through as-is to
// pp::InstancePrivate::ExecuteScript().
pp::Var TestExecuteScript(const std::vector<pp::Var>& args,
pp::Var* exception) {
if (args.size() != 1) {
*exception = pp::Var("testExecuteScript requires one argument");
return pp::Var();
}
return instance_->ExecuteScript(args[0], exception);
}
// Requires one or more arguments. Roughly analogous to NPN_GetProperty.
// The arguments are the chain of properties to traverse, starting with the
// global context.
pp::Var TestGetProperty(const std::vector<pp::Var>& args,
pp::Var* exception) {
if (args.size() < 1) {
*exception = pp::Var("testGetProperty requires at least one argument");
return pp::Var();
}
pp::VarPrivate object = instance_->GetWindowObject();
for (const auto& arg : args) {
if (!object.HasProperty(arg, exception))
return pp::Var();
object = object.GetProperty(arg, exception);
}
return object;
}
// Requires 2 or more arguments. The first argument is the name of a function
// to invoke, and the second argument is a value to pass to that function.
pp::Var TestPassTestObject(const std::vector<pp::Var>& args,
pp::Var* exception) {
if (args.size() < 2) {
*exception = pp::Var("testPassTestObject requires at least 2 arguments");
return pp::Var();
}
pp::VarPrivate object = instance_->GetWindowObject();
return object.Call(args[0], args[1], exception);
}
void TestObjectAccessor(bool set, pp::Var* var) {
if (set)
return;
if (test_object_.is_undefined())
test_object_ = pp::VarPrivate(instance_, new TestObjectSO(instance_));
*var = test_object_;
}
void TestObjectCountAccessor(bool set, pp::Var* var) {
if (set)
return;
*var = pp::Var(TestObjectSO::count());
}
void TestGetUndefinedAccessor(bool set, pp::Var* var) {
if (set)
return;
*var = pp::Var();
}
pp::VarPrivate test_object_;
pp::Var remembered_;
};
class BlinkDeprecatedTestInstance : public pp::InstancePrivate {
public:
explicit BlinkDeprecatedTestInstance(PP_Instance instance)
: pp::InstancePrivate(instance) {}
~BlinkDeprecatedTestInstance() override {
LogMessage("%s", "Destroying");
}
// pp::Instance overrides
bool Init(uint32_t argc, const char* argn[], const char* argv[]) override {
for (uint32_t i = 0; i < argc; ++i)
attributes_[argn[i]] = argv[i];
if (HasAttribute("testwindowopen"))
return TestWindowOpen();
if (HasAttribute("initscript"))
ExecuteScript(attributes_["initscript"]);
uint32_t event_classes = 0;
if (HasAttribute("keydownscript"))
event_classes |= PP_INPUTEVENT_CLASS_KEYBOARD;
if (HasAttribute("mousedownscript"))
event_classes |= PP_INPUTEVENT_CLASS_MOUSE;
RequestFilteringInputEvents(event_classes);
return true;
}
virtual bool HandleInputEvent(const pp::InputEvent& event) override {
switch (event.GetType()) {
case PP_INPUTEVENT_TYPE_MOUSEDOWN:
if (HasAttribute("mousedownscript"))
ExecuteScript(attributes_["mousedownscript"]);
return true;
case PP_INPUTEVENT_TYPE_KEYDOWN:
if (HasAttribute("keydownscript"))
ExecuteScript(attributes_["keydownscript"]);
return true;
default:
return false;
}
}
// pp::InstancePrivate overrides:
pp::Var GetInstanceObject() override {
if (instance_var_.is_undefined()) {
instance_so_ = new InstanceSO(this);
instance_var_ = pp::VarPrivate(this, instance_so_);
}
return instance_var_;
}
void NotifyTestCompletion() {
ExecuteScript("window.testRunner.notifyDone()");
}
bool TestWindowOpen() {
pp::Var result = GetWindowObject().Call(
pp::Var("open"), pp::Var("about:blank"), pp::Var("_blank"));
if (result.is_object())
LogMessage("PLUGIN: WINDOW OPEN SUCCESS");
NotifyTestCompletion();
return true;
}
void LogMessage(const char* format...) {
va_list args;
va_start(args, format);
LogToConsoleWithSource(PP_LOGLEVEL_LOG,
pp::Var("Blink Deprecated Test Plugin"),
pp::Var(base::StringPrintV(format, args)));
va_end(args);
}
private:
bool HasAttribute(const std::string& name) {
return attributes_.find(name) != attributes_.end();
}
std::unordered_map<std::string, std::string> attributes_;
pp::VarPrivate instance_var_;
// Owned by |instance_var_|.
InstanceSO* instance_so_;
};
class BlinkDeprecatedTestModule : public pp::Module {
public:
BlinkDeprecatedTestModule() {}
~BlinkDeprecatedTestModule() override {}
pp::Instance* CreateInstance(PP_Instance instance) override {
return new BlinkDeprecatedTestInstance(instance);
}
};
} // namespace
namespace pp {
Module* CreateModule() {
return new BlinkDeprecatedTestModule();
}
} // namespace pp