1 /*
2 * Copyright (C) 2013 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31 #include "third_party/blink/renderer/bindings/core/v8/v8_v0_custom_element_lifecycle_callbacks.h"
32
33 #include <memory>
34 #include "third_party/blink/renderer/bindings/core/v8/script_controller.h"
35 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
36 #include "third_party/blink/renderer/bindings/core/v8/v8_element.h"
37 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
38 #include "third_party/blink/renderer/platform/bindings/dom_data_store.h"
39 #include "third_party/blink/renderer/platform/bindings/v0_custom_element_binding.h"
40 #include "third_party/blink/renderer/platform/bindings/v8_per_context_data.h"
41 #include "third_party/blink/renderer/platform/bindings/v8_private_property.h"
42
43 namespace blink {
44
45 #define CALLBACK_LIST(V) \
46 V(created, CreatedCallback) \
47 V(attached, AttachedCallback) \
48 V(detached, DetachedCallback) \
49 V(attribute_changed, AttributeChangedCallback)
50
FlagSet(v8::MaybeLocal<v8::Function> attached,v8::MaybeLocal<v8::Function> detached,v8::MaybeLocal<v8::Function> attribute_changed)51 static V0CustomElementLifecycleCallbacks::CallbackType FlagSet(
52 v8::MaybeLocal<v8::Function> attached,
53 v8::MaybeLocal<v8::Function> detached,
54 v8::MaybeLocal<v8::Function> attribute_changed) {
55 // V8 Custom Elements always run created to swizzle prototypes.
56 int flags = V0CustomElementLifecycleCallbacks::kCreatedCallback;
57
58 if (!attached.IsEmpty())
59 flags |= V0CustomElementLifecycleCallbacks::kAttachedCallback;
60
61 if (!detached.IsEmpty())
62 flags |= V0CustomElementLifecycleCallbacks::kDetachedCallback;
63
64 if (!attribute_changed.IsEmpty())
65 flags |= V0CustomElementLifecycleCallbacks::kAttributeChangedCallback;
66
67 return V0CustomElementLifecycleCallbacks::CallbackType(flags);
68 }
69
V8V0CustomElementLifecycleCallbacks(ScriptState * script_state,v8::Local<v8::Object> prototype,v8::MaybeLocal<v8::Function> created,v8::MaybeLocal<v8::Function> attached,v8::MaybeLocal<v8::Function> detached,v8::MaybeLocal<v8::Function> attribute_changed)70 V8V0CustomElementLifecycleCallbacks::V8V0CustomElementLifecycleCallbacks(
71 ScriptState* script_state,
72 v8::Local<v8::Object> prototype,
73 v8::MaybeLocal<v8::Function> created,
74 v8::MaybeLocal<v8::Function> attached,
75 v8::MaybeLocal<v8::Function> detached,
76 v8::MaybeLocal<v8::Function> attribute_changed)
77 : V0CustomElementLifecycleCallbacks(
78 FlagSet(attached, detached, attribute_changed)),
79 script_state_(script_state),
80 prototype_(script_state->GetIsolate(), prototype){
81 v8::Isolate* isolate = script_state->GetIsolate();
82 v8::Local<v8::Function> function;
83
84 // A given object can only be used as a Custom Element prototype
85 // once; see customElementIsInterfacePrototypeObject
86 #define SET_PRIVATE_PROPERTY(Maybe, Name) \
87 static const V8PrivateProperty::SymbolKey kPrivateProperty##Name; \
88 V8PrivateProperty::Symbol symbol##Name = \
89 V8PrivateProperty::GetSymbol(isolate, kPrivateProperty##Name); \
90 DCHECK(!symbol##Name.HasValue(prototype)); \
91 { \
92 if (Maybe.ToLocal(&function)) \
93 symbol##Name.Set(prototype, function); \
94 }
95
96 CALLBACK_LIST(SET_PRIVATE_PROPERTY)
97 #undef SET_PRIVATE_PROPERTY
98
99 #define SET_FIELD(maybe, ignored) \
100 if (maybe.ToLocal(&function)) \
101 maybe##_.Set(isolate, function);
102
103 CALLBACK_LIST(SET_FIELD)
104 #undef SET_FIELD
105 }
106
CreationContextData()107 V8PerContextData* V8V0CustomElementLifecycleCallbacks::CreationContextData() {
108 if (!script_state_->ContextIsValid())
109 return nullptr;
110
111 v8::Local<v8::Context> context = script_state_->GetContext();
112 if (context.IsEmpty())
113 return nullptr;
114
115 return V8PerContextData::From(context);
116 }
117
118 V8V0CustomElementLifecycleCallbacks::~V8V0CustomElementLifecycleCallbacks() =
119 default;
120
SetBinding(std::unique_ptr<V0CustomElementBinding> binding)121 bool V8V0CustomElementLifecycleCallbacks::SetBinding(
122 std::unique_ptr<V0CustomElementBinding> binding) {
123 V8PerContextData* per_context_data = CreationContextData();
124 if (!per_context_data)
125 return false;
126
127 // The context is responsible for keeping the prototype
128 // alive. This in turn keeps callbacks alive through hidden
129 // references; see CALLBACK_LIST(SET_HIDDEN_VALUE).
130 per_context_data->AddCustomElementBinding(std::move(binding));
131 return true;
132 }
133
Created(Element * element)134 void V8V0CustomElementLifecycleCallbacks::Created(Element* element) {
135 // FIXME: callbacks while paused should be queued up for execution to
136 // continue then be delivered in order rather than delivered immediately.
137 // Bug 329665 tracks similar behavior for other synchronous events.
138 if (!script_state_->ContextIsValid())
139 return;
140
141 element->SetV0CustomElementState(Element::kV0Upgraded);
142
143 ScriptState::Scope scope(script_state_);
144 v8::Isolate* isolate = script_state_->GetIsolate();
145 v8::Local<v8::Context> context = script_state_->GetContext();
146 v8::Local<v8::Value> receiver_value =
147 ToV8(element, context->Global(), isolate);
148 if (receiver_value.IsEmpty())
149 return;
150 v8::Local<v8::Object> receiver = receiver_value.As<v8::Object>();
151
152 // Swizzle the prototype of the wrapper.
153 v8::Local<v8::Object> prototype = prototype_.NewLocal(isolate);
154 bool set_prototype;
155 if (prototype.IsEmpty() ||
156 !receiver->SetPrototype(context, prototype).To(&set_prototype) ||
157 !set_prototype) {
158 return;
159 }
160
161 v8::Local<v8::Function> callback = created_.NewLocal(isolate);
162 if (callback.IsEmpty())
163 return;
164
165 v8::TryCatch exception_catcher(isolate);
166 exception_catcher.SetVerbose(true);
167 V8ScriptRunner::CallFunction(callback, ExecutionContext::From(script_state_),
168 receiver, 0, nullptr, isolate);
169 }
170
Attached(Element * element)171 void V8V0CustomElementLifecycleCallbacks::Attached(Element* element) {
172 Call(attached_, element);
173 }
174
Detached(Element * element)175 void V8V0CustomElementLifecycleCallbacks::Detached(Element* element) {
176 Call(detached_, element);
177 }
178
AttributeChanged(Element * element,const AtomicString & name,const AtomicString & old_value,const AtomicString & new_value)179 void V8V0CustomElementLifecycleCallbacks::AttributeChanged(
180 Element* element,
181 const AtomicString& name,
182 const AtomicString& old_value,
183 const AtomicString& new_value) {
184 // FIXME: callbacks while paused should be queued up for execution to
185 // continue then be delivered in order rather than delivered immediately.
186 // Bug 329665 tracks similar behavior for other synchronous events.
187 if (!script_state_->ContextIsValid())
188 return;
189 ScriptState::Scope scope(script_state_);
190 v8::Isolate* isolate = script_state_->GetIsolate();
191 v8::Local<v8::Context> context = script_state_->GetContext();
192 v8::Local<v8::Value> receiver = ToV8(element, context->Global(), isolate);
193 if (receiver.IsEmpty())
194 return;
195
196 v8::Local<v8::Function> callback = attribute_changed_.NewLocal(isolate);
197 if (callback.IsEmpty())
198 return;
199
200 v8::Local<v8::Value> argv[] = {
201 V8String(isolate, name),
202 old_value.IsNull() ? v8::Local<v8::Value>(v8::Null(isolate))
203 : v8::Local<v8::Value>(V8String(isolate, old_value)),
204 new_value.IsNull() ? v8::Local<v8::Value>(v8::Null(isolate))
205 : v8::Local<v8::Value>(V8String(isolate, new_value))};
206
207 v8::TryCatch exception_catcher(isolate);
208 exception_catcher.SetVerbose(true);
209 V8ScriptRunner::CallFunction(callback, ExecutionContext::From(script_state_),
210 receiver, base::size(argv), argv, isolate);
211 }
212
Call(const TraceWrapperV8Reference<v8::Function> & callback_reference,Element * element)213 void V8V0CustomElementLifecycleCallbacks::Call(
214 const TraceWrapperV8Reference<v8::Function>& callback_reference,
215 Element* element) {
216 // FIXME: callbacks while paused should be queued up for execution to
217 // continue then be delivered in order rather than delivered immediately.
218 // Bug 329665 tracks similar behavior for other synchronous events.
219 if (!script_state_->ContextIsValid())
220 return;
221 ScriptState::Scope scope(script_state_);
222 v8::Isolate* isolate = script_state_->GetIsolate();
223 v8::Local<v8::Context> context = script_state_->GetContext();
224 v8::Local<v8::Function> callback = callback_reference.NewLocal(isolate);
225 if (callback.IsEmpty())
226 return;
227
228 v8::Local<v8::Value> receiver = ToV8(element, context->Global(), isolate);
229 if (receiver.IsEmpty())
230 return;
231
232 v8::TryCatch exception_catcher(isolate);
233 exception_catcher.SetVerbose(true);
234 V8ScriptRunner::CallFunction(callback, ExecutionContext::From(script_state_),
235 receiver, 0, nullptr, isolate);
236 }
237
Trace(Visitor * visitor) const238 void V8V0CustomElementLifecycleCallbacks::Trace(Visitor* visitor) const {
239 visitor->Trace(script_state_);
240 visitor->Trace(prototype_);
241 visitor->Trace(created_);
242 visitor->Trace(attached_);
243 visitor->Trace(detached_);
244 visitor->Trace(attribute_changed_);
245 V0CustomElementLifecycleCallbacks::Trace(visitor);
246 }
247
248 } // namespace blink
249