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