blob: 439034ae245de4e2e6d90f18f96da5cfe50b6c26 [file] [log] [blame]
// Copyright 2014 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 <map>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "base/compiler_specific.h"
#include "base/macros.h"
#include "extensions/renderer/native_handler.h"
#include "extensions/renderer/object_backed_native_handler.h"
#include "extensions/renderer/script_injection_callback.h"
#include "v8/include/v8.h"
namespace extensions {
class ScriptContext;
class SourceMap;
// A module system for JS similar to node.js' require() function.
// Each module has three variables in the global scope:
// - exports, an object returned to dependencies who require() this
// module.
// - require, a function that takes a module name as an argument and returns
// that module's exports object.
// - requireNative, a function that takes the name of a registered
// NativeHandler and returns an object that contains the functions the
// NativeHandler defines.
// Each module in a ModuleSystem is executed at most once and its exports
// object cached.
// Note that a ModuleSystem must be used only in conjunction with a single
// v8::Context.
// TODO(koz): Rename this to JavaScriptModuleSystem.
class ModuleSystem : public ObjectBackedNativeHandler {
class ExceptionHandler {
explicit ExceptionHandler(ScriptContext* context) : context_(context) {}
virtual ~ExceptionHandler() {}
virtual void HandleUncaughtException(const v8::TryCatch& try_catch) = 0;
// Formats |try_catch| as a nice string.
std::string CreateExceptionString(const v8::TryCatch& try_catch);
// A script context associated with this handler. Owned by the module
// system.
ScriptContext* context_;
// Enables native bindings for the duration of its lifetime.
class NativesEnabledScope {
explicit NativesEnabledScope(ModuleSystem* module_system);
ModuleSystem* module_system_;
// |source_map| is a weak pointer.
ModuleSystem(ScriptContext* context, const SourceMap* source_map);
~ModuleSystem() override;
// ObjectBackedNativeHandler:
void AddRoutes() override;
// Require the specified module. This is the equivalent of calling
// require('module_name') from the loaded JS files.
v8::MaybeLocal<v8::Object> Require(const std::string& module_name);
void Require(const v8::FunctionCallbackInfo<v8::Value>& args);
// Run |code| in the current context with the name |name| used for stack
// traces.
v8::Local<v8::Value> RunString(v8::Local<v8::String> code,
v8::Local<v8::String> name);
// Calls the specified method exported by the specified module. This is
// equivalent to calling require('module_name').method_name() from JS. Note:
// this may result in asynchronous execution if javascript is presently
// disabled.
// TODO(devlin): Rename this to just CallModuleMethod()?
void CallModuleMethodSafe(const std::string& module_name,
const std::string& method_name);
void CallModuleMethodSafe(const std::string& module_name,
const std::string& method_name,
std::vector<v8::Local<v8::Value>>* args);
void CallModuleMethodSafe(const std::string& module_name,
const std::string& method_name,
int argc,
v8::Local<v8::Value> argv[]);
void CallModuleMethodSafe(
const std::string& module_name,
const std::string& method_name,
int argc,
v8::Local<v8::Value> argv[],
const ScriptInjectionCallback::CompleteCallback& callback);
// Register |native_handler| as a potential target for requireNative(), so
// calls to requireNative(|name|) from JS will return a new object created by
// |native_handler|.
void RegisterNativeHandler(const std::string& name,
std::unique_ptr<NativeHandler> native_handler);
// Causes requireNative(|name|) to look for its module in |source_map_|
// instead of using a registered native handler. This can be used in unit
// tests to mock out native modules.
void OverrideNativeHandlerForTest(const std::string& name);
// Make |object|.|field| lazily evaluate to the result of
// require(|module_name|)[|module_field|].
// TODO(kalman): All targets for this method are ObjectBackedNativeHandlers,
// move this logic into those classes (in fact, the chrome
// object is the only client, only that needs to implement it).
void SetLazyField(v8::Local<v8::Object> object,
const std::string& field,
const std::string& module_name,
const std::string& module_field);
void SetLazyField(v8::Local<v8::Object> object,
const std::string& field,
const std::string& module_name,
const std::string& module_field,
v8::AccessorNameGetterCallback getter);
// Make |object|.|field| lazily evaluate to the result of
// requireNative(|module_name|)[|module_field|].
// TODO(kalman): Same as above.
void SetNativeLazyField(v8::Local<v8::Object> object,
const std::string& field,
const std::string& module_name,
const std::string& module_field);
// Passes exceptions to |handler| rather than console::Fatal.
void SetExceptionHandlerForTest(std::unique_ptr<ExceptionHandler> handler) {
exception_handler_ = std::move(handler);
// Called when a native binding is created in order to run any custom binding
// code to set up various hooks.
// TODO(devlin): We can get rid of this once we convert all our custom
// bindings.
void OnNativeBindingCreated(const std::string& api_name,
v8::Local<v8::Value> api_bridge_value);
void SetGetInternalAPIHook(v8::Local<v8::FunctionTemplate> get_internal_api);
using JSBindingUtilGetter =
base::Callback<void(v8::Local<v8::Context>, v8::Local<v8::Value>*)>;
void SetJSBindingUtilGetter(const JSBindingUtilGetter& getter);
friend class ModuleSystemTestEnvironment;
friend class ScriptContext;
void Invalidate() override;
typedef std::map<std::string, std::unique_ptr<NativeHandler>>
// Retrieves the lazily defined field specified by |property|.
static void LazyFieldGetter(v8::Local<v8::Name> property,
const v8::PropertyCallbackInfo<v8::Value>& info);
// Retrieves the lazily defined field specified by |property| on a native
// object.
static void NativeLazyFieldGetter(
v8::Local<v8::Name> property,
const v8::PropertyCallbackInfo<v8::Value>& info);
// Called when an exception is thrown but not caught.
void HandleException(const v8::TryCatch& try_catch);
void RequireForJs(const v8::FunctionCallbackInfo<v8::Value>& args);
// Returns the module with the given |module_name|. If |create| is true, the
// module will be loaded if it hasn't been already. Otherwise, the module
// will only be returned if it has already been loaded.
v8::Local<v8::Value> RequireForJsInner(v8::Local<v8::String> module_name,
bool create);
typedef v8::MaybeLocal<v8::Object>(ModuleSystem::*RequireFunction)(
const std::string&);
// Base implementation of a LazyFieldGetter which uses |require_fn| to require
// modules.
static void LazyFieldGetterInner(
v8::Local<v8::String> property,
const v8::PropertyCallbackInfo<v8::Value>& info,
RequireFunction require_function);
// Return the named source file stored in the source map.
// |args[0]| - the name of a source file in source_map_.
v8::Local<v8::Value> GetSource(const std::string& module_name);
// Return an object that contains the native methods defined by the named
// NativeHandler.
// |args[0]| - the name of a native handler object.
v8::MaybeLocal<v8::Object> RequireNativeFromString(
const std::string& native_name);
void RequireNative(const v8::FunctionCallbackInfo<v8::Value>& args);
// |args[0]| - the name of a module.
// This method directly executes the script in the current scope.
void LoadScript(const v8::FunctionCallbackInfo<v8::Value>& args);
// Wraps |source| in a (function(define, require, requireNative, ...) {...}).
v8::Local<v8::String> WrapSource(v8::Local<v8::String> source);
// NativeHandler implementation which returns the private area of an Object.
void Private(const v8::FunctionCallbackInfo<v8::Value>& args);
// Loads and runs a Javascript module.
v8::Local<v8::Value> LoadModule(const std::string& module_name);
v8::Local<v8::Value> LoadModuleWithNativeAPIBridge(
const std::string& module_name,
v8::Local<v8::Value> api_object);
// Marks any existing NativeHandler named |name| as clobbered.
// See |clobbered_native_handlers_|.
void ClobberExistingNativeHandler(const std::string& name);
// Returns the v8::Function associated with the given module and method name.
// This will *not* load a module if it hasn't been loaded already.
v8::Local<v8::Function> GetModuleFunction(const std::string& module_name,
const std::string& method_name);
ScriptContext* context_;
// A map from module names to the JS source for that module. GetSource()
// performs a lookup on this map.
const SourceMap* const source_map_;
// A map from native handler names to native handlers.
NativeHandlerMap native_handler_map_;
// When 0, natives are disabled, otherwise indicates how many callers have
// pinned natives as enabled.
int natives_enabled_;
// Called when an exception is thrown but not caught in JS. Overridable by
// tests.
std::unique_ptr<ExceptionHandler> exception_handler_;
// A set of native handlers that should actually be require()d as non-native
// handlers. This is used for tests to mock out native handlers in JS.
std::set<std::string> overridden_native_handlers_;
// A list of NativeHandlers that have been clobbered, either due to
// registering a NativeHandler when one was already registered with the same
// name, or due to OverrideNativeHandlerForTest. This is needed so that they
// can be later Invalidated. It should only happen in tests.
std::vector<std::unique_ptr<NativeHandler>> clobbered_native_handlers_;
// The template to be used for retrieving an internal API.
v8::Eternal<v8::FunctionTemplate> get_internal_api_;
JSBindingUtilGetter js_binding_util_getter_;
// The set of modules that we've attempted to load.
std::set<std::string> loaded_modules_;
// Whether to lazily initialize native handlers on first access. We do this
// when native bindings are enabled.
bool lazily_initialize_handlers_;
} // namespace extensions