blob: a22d5d790f8c550f4e7955f4cde1e06df07d272e [file] [log] [blame]
// Copyright 2018 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 "third_party/blink/renderer/bindings/core/v8/js_event_handler_for_content_attribute.h"
#include "third_party/blink/renderer/bindings/core/v8/script_controller.h"
#include "third_party/blink/renderer/bindings/core/v8/source_location.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/core/dom/events/event_target.h"
#include "third_party/blink/renderer/core/events/error_event.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/html/forms/html_form_element.h"
namespace blink {
JSEventHandlerForContentAttribute* JSEventHandlerForContentAttribute::Create(
ExecutionContext* context,
const QualifiedName& name,
const AtomicString& value,
HandlerType type) {
if (!context || !context->CanExecuteScripts(kAboutToExecuteScript))
return nullptr;
if (value.IsNull())
return nullptr;
DCHECK(IsA<LocalDOMWindow>(context));
return MakeGarbageCollected<JSEventHandlerForContentAttribute>(context, name,
value, type);
}
JSEventHandlerForContentAttribute::JSEventHandlerForContentAttribute(
ExecutionContext* context,
const QualifiedName& name,
const AtomicString& value,
HandlerType type)
: JSEventHandler(type),
did_compile_(false),
function_name_(name.LocalName()),
script_body_(value),
source_url_(context->Url().GetString()),
position_(To<LocalDOMWindow>(context)
->GetScriptController()
.EventHandlerPosition()),
isolate_(context->GetIsolate()) {}
v8::Local<v8::Value> JSEventHandlerForContentAttribute::GetListenerObject(
EventTarget& event_target) {
// Step 3. of get the current value of the event handler should be executed
// only if EventHandler's value is an internal raw uncompiled handler and it
// has never tried to get compiled.
if (HasCompiledHandler())
return JSEventHandler::GetListenerObject(event_target);
if (did_compile_)
return v8::Null(GetIsolate());
return GetCompiledHandler(event_target);
}
// Implements Step 3. of "get the current value of the event handler"
// https://html.spec.whatwg.org/C/#getting-the-current-value-of-the-event-handler
v8::Local<v8::Value> JSEventHandlerForContentAttribute::GetCompiledHandler(
EventTarget& event_target) {
// Do not compile the same code twice.
DCHECK(!did_compile_);
did_compile_ = true;
ExecutionContext* execution_context_of_event_target =
event_target.GetExecutionContext();
if (!execution_context_of_event_target)
return v8::Null(GetIsolate());
v8::Local<v8::Context> v8_context_of_event_target =
ToV8Context(execution_context_of_event_target, GetWorld());
if (v8_context_of_event_target.IsEmpty())
return v8::Null(GetIsolate());
ScriptState* script_state_of_event_target =
ScriptState::From(v8_context_of_event_target);
if (!script_state_of_event_target->ContextIsValid())
return v8::Null(GetIsolate());
// Step 1. If eventTarget is an element, then let element be eventTarget, and
// document be element's node document. Otherwise, eventTarget is a Window
// object, let element be null, and document be eventTarget's associated
// Document.
Element* element = nullptr;
const LocalDOMWindow* window = nullptr;
Document* document = nullptr;
if (Node* node = event_target.ToNode()) {
if (node->IsDocumentNode()) {
// Some of content attributes for |HTMLBodyElement| are treated as ones
// for |Document| unlike the definition in HTML standard. Those content
// attributes are not listed in the Window-reflecting body element event
// handler set.
// https://html.spec.whatwg.org/C/#window-reflecting-body-element-event-handler-set
document = &node->GetDocument();
} else {
element = To<Element>(node);
document = &node->GetDocument();
}
// EventTarget::GetExecutionContext() sometimes returns the document which
// created the EventTarget, and sometimes returns the document to which
// the EventTarget is currently attached. The former might be different
// from |document|.
} else {
window = event_target.ToLocalDOMWindow();
DCHECK(window);
DCHECK_EQ(window, execution_context_of_event_target);
document = window->document();
}
DCHECK(document);
// Step 6. Let settings object be the relevant settings object of document.
// Step 9. Push settings object's realm execution context onto the JavaScript
// execution context stack; it is now the running JavaScript execution
// context.
//
// |document->AllowInlineEventHandler()| checks the world of current context,
// so this scope needs to be defined before calling it.
v8::Context::Scope event_target_context_scope(v8_context_of_event_target);
// Step 2. If scripting is disabled for document, then return null.
if (!document->AllowInlineEventHandler(element, this, source_url_,
position_.line_))
return v8::Null(GetIsolate());
// Step 5. If element is not null and element has a form owner, let form owner
// be that form owner. Otherwise, let form owner be null.
HTMLFormElement* form_owner = nullptr;
if (auto* html_element = DynamicTo<HTMLElement>(element)) {
form_owner = html_element->formOwner();
// https://html.spec.whatwg.org/C/#window-reflecting-body-element-event-handler-set
// The Event handlers on HTMLBodyElement and HTMLFrameSetElement which are
// listed in the Window-reflecting body element event handler set should be
// treated as if they are the corresponding event handlers of the window
// object.
if (html_element->IsHTMLBodyElement() ||
html_element->IsHTMLFrameSetElement()) {
window = To<LocalDOMWindow>(execution_context_of_event_target);
}
}
// Step 10. Let function be the result of calling FunctionCreate, with
// arguments:
// kind
// Normal
// ParameterList
// If eventHandler is an onerror event handler of a Window object
// Let the function have five arguments, named event, source, lineno,
// colno, and error.
// Otherwise
// Let the function have a single argument called event.
// Body
// The result of parsing body above.
// Scope
// 1. If eventHandler is an element's event handler, then let Scope be
// NewObjectEnvironment(document, the global environment). Otherwise,
// eventHandler is a Window object's event handler: let Scope be the
// global environment.
// 2. If form owner is not null, let Scope be NewObjectEnvironment(form
// owner, Scope).
// 3. If element is not null, let Scope be NewObjectEnvironment(element,
// Scope).
// Strict
// The value of strict.
//
// Note: Strict is set by V8.
v8::Isolate* isolate = script_state_of_event_target->GetIsolate();
v8::Local<v8::String> parameter_list[5];
size_t parameter_list_size = 0;
if (IsOnErrorEventHandler() && window) {
// SVG requires to introduce evt as an alias to event in event handlers.
// See ANNOTATION 3: https://www.w3.org/TR/SVG/interact.html#SVGEvents
parameter_list[parameter_list_size++] =
V8String(isolate, element && element->IsSVGElement() ? "evt" : "event");
parameter_list[parameter_list_size++] = V8String(isolate, "source");
parameter_list[parameter_list_size++] = V8String(isolate, "lineno");
parameter_list[parameter_list_size++] = V8String(isolate, "colno");
parameter_list[parameter_list_size++] = V8String(isolate, "error");
} else {
// SVG requires to introduce evt as an alias to event in event handlers.
// See ANNOTATION 3: https://www.w3.org/TR/SVG/interact.html#SVGEvents
parameter_list[parameter_list_size++] =
V8String(isolate, element && element->IsSVGElement() ? "evt" : "event");
}
DCHECK_LE(parameter_list_size, base::size(parameter_list));
v8::Local<v8::Object> scopes[3];
size_t scopes_size = 0;
if (element) {
scopes[scopes_size++] =
ToV8(document, script_state_of_event_target).As<v8::Object>();
}
if (form_owner) {
scopes[scopes_size++] =
ToV8(form_owner, script_state_of_event_target).As<v8::Object>();
}
if (element) {
scopes[scopes_size++] =
ToV8(element, script_state_of_event_target).As<v8::Object>();
}
DCHECK_LE(scopes_size, base::size(scopes));
v8::ScriptOrigin origin(
V8String(isolate, source_url_), position_.line_.ZeroBasedInt(),
position_.column_.ZeroBasedInt(),
true); // true as |SanitizeScriptErrors::kDoNotSanitize|
v8::ScriptCompiler::Source source(V8String(isolate, script_body_), origin);
v8::Local<v8::Function> compiled_function;
{
v8::TryCatch block(isolate);
block.SetVerbose(true);
v8::MaybeLocal<v8::Function> maybe_result =
v8::ScriptCompiler::CompileFunctionInContext(
v8_context_of_event_target, &source, parameter_list_size,
parameter_list, scopes_size, scopes);
// Step 7. If body is not parsable as FunctionBody or if parsing detects an
// early error, then follow these substeps:
// 1. Set eventHandler's value to null.
// 2. Report the error for the appropriate script and with the appropriate
// position (line number and column number) given by location, using
// settings object's global object. If the error is still not handled
// after this, then the error may be reported to a developer console.
// 3. Return null.
if (!maybe_result.ToLocal(&compiled_function))
return v8::Null(isolate);
}
// Step 12. Set eventHandler's value to the result of creating a Web IDL
// EventHandler callback function object whose object reference is function
// and whose callback context is settings object.
compiled_function->SetName(V8String(isolate, function_name_));
SetCompiledHandler(script_state_of_event_target, compiled_function);
return JSEventHandler::GetListenerObject(event_target);
}
std::unique_ptr<SourceLocation>
JSEventHandlerForContentAttribute::GetSourceLocation(EventTarget& target) {
auto source_location = JSEventHandler::GetSourceLocation(target);
if (source_location)
return source_location;
// Fallback to uncompiled source info.
return std::make_unique<SourceLocation>(
source_url_, position_.line_.ZeroBasedInt(),
position_.column_.ZeroBasedInt(), nullptr);
}
} // namespace blink