1/*
2 * Copyright (C) 2018 Apple 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
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "PaintWorkletGlobalScope.h"
28
29#if ENABLE(CSS_PAINTING_API)
30
31#include "DOMWindow.h"
32#include "Document.h"
33#include "JSCSSPaintCallback.h"
34#include "JSDOMConvertCallbacks.h"
35#include "JSDOMConvertSequences.h"
36#include "RenderView.h"
37#include <wtf/IsoMallocInlines.h>
38#include <wtf/SetForScope.h>
39
40namespace WebCore {
41using namespace JSC;
42
43WTF_MAKE_ISO_ALLOCATED_IMPL(PaintWorkletGlobalScope);
44
45Ref<PaintWorkletGlobalScope> PaintWorkletGlobalScope::create(Document& document, ScriptSourceCode&& code)
46{
47 return adoptRef(*new PaintWorkletGlobalScope(document, WTFMove(code)));
48}
49
50PaintWorkletGlobalScope::PaintWorkletGlobalScope(Document& document, ScriptSourceCode&& code)
51 : WorkletGlobalScope(document, WTFMove(code))
52{
53}
54
55double PaintWorkletGlobalScope::devicePixelRatio() const
56{
57 if (!responsibleDocument() || !responsibleDocument()->domWindow())
58 return 1.0;
59 return responsibleDocument()->domWindow()->devicePixelRatio();
60}
61
62PaintWorkletGlobalScope::PaintDefinition::PaintDefinition(const AtomicString& name, JSC::JSObject* paintConstructor, Ref<CSSPaintCallback>&& paintCallback, Vector<String>&& inputProperties, Vector<String>&& inputArguments)
63 : name(name)
64 , paintConstructor(paintConstructor)
65 , paintCallback(WTFMove(paintCallback))
66 , inputProperties(WTFMove(inputProperties))
67 , inputArguments(WTFMove(inputArguments))
68{
69}
70
71// https://drafts.css-houdini.org/css-paint-api/#registering-custom-paint
72ExceptionOr<void> PaintWorkletGlobalScope::registerPaint(JSC::ExecState& state, JSDOMGlobalObject& globalObject, const String& name, Strong<JSObject> paintConstructor)
73{
74 auto& vm = *paintConstructor->vm();
75 JSC::JSLockHolder lock(vm);
76 auto scope = DECLARE_THROW_SCOPE(vm);
77
78 // Validate that paintConstructor is a VoidFunction
79 CallData callData;
80 if (JSC::getCallData(vm, paintConstructor.get(), callData) == JSC::CallType::None)
81 return Exception { TypeError, "paintConstructor must be callable" };
82
83 if (name.isEmpty())
84 return Exception { TypeError, "The first argument must not be the empty string" };
85
86 {
87 auto locker = holdLock(paintDefinitionLock());
88
89 if (paintDefinitionMap().contains(name))
90 return Exception { InvalidModificationError, "This name has already been registered" };
91
92 Vector<String> inputProperties;
93
94 JSValue inputPropertiesIterableValue = paintConstructor->get(&state, Identifier::fromString(&vm, "inputProperties"));
95 RETURN_IF_EXCEPTION(scope, Exception { ExistingExceptionError });
96
97 if (!inputPropertiesIterableValue.isUndefined())
98 inputProperties = convert<IDLSequence<IDLDOMString>>(state, inputPropertiesIterableValue);
99 RETURN_IF_EXCEPTION(scope, Exception { ExistingExceptionError });
100
101 // FIXME: Validate input properties here (step 7).
102
103 Vector<String> inputArguments;
104
105 JSValue inputArgumentsIterableValue = paintConstructor->get(&state, Identifier::fromString(&vm, "inputArguments"));
106 RETURN_IF_EXCEPTION(scope, Exception { ExistingExceptionError });
107
108 if (!inputArgumentsIterableValue.isUndefined())
109 inputArguments = convert<IDLSequence<IDLDOMString>>(state, inputArgumentsIterableValue);
110 RETURN_IF_EXCEPTION(scope, Exception { ExistingExceptionError });
111
112 // FIXME: Parse syntax for inputArguments here (steps 11 and 12).
113
114 JSValue contextOptionsValue = paintConstructor->get(&state, Identifier::fromString(&vm, "contextOptions"));
115 RETURN_IF_EXCEPTION(scope, Exception { ExistingExceptionError });
116 UNUSED_PARAM(contextOptionsValue);
117
118 // FIXME: Convert to PaintRenderingContext2DSettings here (step 14).
119
120 if (!paintConstructor->isConstructor(vm))
121 return Exception { TypeError, "The second argument must be a constructor" };
122
123 JSValue prototypeValue = paintConstructor->get(&state, vm.propertyNames->prototype);
124 RETURN_IF_EXCEPTION(scope, Exception { ExistingExceptionError });
125
126 if (!prototypeValue.isObject())
127 return Exception { TypeError, "The second argument must have a prototype that is an object" };
128
129 JSValue paintValue = prototypeValue.get(&state, Identifier::fromString(&vm, "paint"));
130 RETURN_IF_EXCEPTION(scope, Exception { ExistingExceptionError });
131
132 if (paintValue.isUndefined())
133 return Exception { TypeError, "The class must have a paint method" };
134
135 RefPtr<JSCSSPaintCallback> paint = convert<IDLCallbackFunction<JSCSSPaintCallback>>(state, paintValue, globalObject);
136 RETURN_IF_EXCEPTION(scope, Exception { ExistingExceptionError });
137
138 auto paintDefinition = std::make_unique<PaintDefinition>(name, paintConstructor.get(), paint.releaseNonNull(), WTFMove(inputProperties), WTFMove(inputArguments));
139 paintDefinitionMap().add(name, WTFMove(paintDefinition));
140 }
141
142 // This is for the case when we have already visited the paint definition map, and the GC is currently running in the background.
143 vm.heap.writeBarrier(&globalObject);
144
145 // FIXME: construct documentDefinition (step 22).
146
147 // FIXME: we should only repaint affected custom paint images <https://bugs.webkit.org/show_bug.cgi?id=192322>.
148 if (responsibleDocument() && responsibleDocument()->renderView())
149 responsibleDocument()->renderView()->repaintRootContents();
150
151 return { };
152}
153
154} // namespace WebCore
155
156#endif
157