Bindings: Support runtime-enabled features in specific worlds
authordbates@webkit.org <dbates@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 19 May 2017 20:13:16 +0000 (20:13 +0000)
committerdbates@webkit.org <dbates@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 19 May 2017 20:13:16 +0000 (20:13 +0000)
https://bugs.webkit.org/show_bug.cgi?id=172235

Reviewed by Chris Dumez.

Currently a function, attribute, or interface can be annotated with either EnabledAtRuntime
or EnabledForWorld (not both) to expose/conceal it depending on the state of a runtime
feature flag or the DOM world associated with the running JavaScript code, respectively.
Even though we do not have any functions, attributes, or interfaces that are annotated
with both EnabledAtRuntime and EnabledForWorld at the time of writing, it seems reasonable
to support such a combination of annotations. This also has the benefit of making it
straightforward to support the extended attribute SecureContext by generalizing the logic
that generates the code to expose/conceal a function, attribute, or interface.

* bindings/scripts/CodeGeneratorJS.pm:
(GenerateRuntimeEnableConditionalString): Use an array to build up all the conjuncts in
the conditional expression.
(GenerateImplementation): Substitute GenerateRuntimeEnableConditionalString() and $runtimeEnableConditionalString
for GetRuntimeEnableFunctionName() and $enable_function_result, respectively.
(GetRuntimeEnableFunctionName): Deleted.

* bindings/scripts/test/JS/JSTestGlobalObject.cpp:
 (WebCore::JSTestGlobalObject::finishCreation):
 (WebCore::jsTestGlobalObjectInstanceFunctionEnabledInSpecificWorldWhenRuntimeFeatureEnabled):
 (WebCore::jsTestGlobalObjectInstanceFunctionEnabledInSpecificWorldWhenRuntimeFeatureEnabledCaller):
 (WebCore::jsTestGlobalObjectInstanceFunctionEnabledInSpecificWorldWhenRuntimeFeaturesEnabled):
 (WebCore::jsTestGlobalObjectInstanceFunctionEnabledInSpecificWorldWhenRuntimeFeaturesEnabledCaller):
 Update expected results.

 * bindings/scripts/test/JS/JSTestObj.cpp:
 (WebCore::JSTestObjPrototype::finishCreation):
 (WebCore::jsTestObjPrototypeFunctionEnabledInSpecificWorldWhenRuntimeFeatureEnabled):
 (WebCore::jsTestObjPrototypeFunctionEnabledInSpecificWorldWhenRuntimeFeatureEnabledCaller):
 Ditto.

 * bindings/scripts/test/TestGlobalObject.idl: Added test cases.
 * bindings/scripts/test/TestObj.idl: Added test case.

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@217146 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebCore/ChangeLog
Source/WebCore/bindings/scripts/CodeGeneratorJS.pm
Source/WebCore/bindings/scripts/test/JS/JSTestGlobalObject.cpp
Source/WebCore/bindings/scripts/test/JS/JSTestObj.cpp
Source/WebCore/bindings/scripts/test/TestGlobalObject.idl
Source/WebCore/bindings/scripts/test/TestObj.idl

index 5b92a9c..4e10731 100644 (file)
@@ -1,3 +1,43 @@
+2017-05-19  Daniel Bates  <dabates@apple.com>
+
+        Bindings: Support runtime-enabled features in specific worlds
+        https://bugs.webkit.org/show_bug.cgi?id=172235
+
+        Reviewed by Chris Dumez.
+
+        Currently a function, attribute, or interface can be annotated with either EnabledAtRuntime
+        or EnabledForWorld (not both) to expose/conceal it depending on the state of a runtime
+        feature flag or the DOM world associated with the running JavaScript code, respectively.
+        Even though we do not have any functions, attributes, or interfaces that are annotated
+        with both EnabledAtRuntime and EnabledForWorld at the time of writing, it seems reasonable
+        to support such a combination of annotations. This also has the benefit of making it
+        straightforward to support the extended attribute SecureContext by generalizing the logic
+        that generates the code to expose/conceal a function, attribute, or interface.
+
+        * bindings/scripts/CodeGeneratorJS.pm:
+        (GenerateRuntimeEnableConditionalString): Use an array to build up all the conjuncts in
+        the conditional expression.
+        (GenerateImplementation): Substitute GenerateRuntimeEnableConditionalString() and $runtimeEnableConditionalString
+        for GetRuntimeEnableFunctionName() and $enable_function_result, respectively.
+        (GetRuntimeEnableFunctionName): Deleted.
+
+        * bindings/scripts/test/JS/JSTestGlobalObject.cpp:
+         (WebCore::JSTestGlobalObject::finishCreation):
+         (WebCore::jsTestGlobalObjectInstanceFunctionEnabledInSpecificWorldWhenRuntimeFeatureEnabled):
+         (WebCore::jsTestGlobalObjectInstanceFunctionEnabledInSpecificWorldWhenRuntimeFeatureEnabledCaller):
+         (WebCore::jsTestGlobalObjectInstanceFunctionEnabledInSpecificWorldWhenRuntimeFeaturesEnabled):
+         (WebCore::jsTestGlobalObjectInstanceFunctionEnabledInSpecificWorldWhenRuntimeFeaturesEnabledCaller):
+         Update expected results.
+
+         * bindings/scripts/test/JS/JSTestObj.cpp:
+         (WebCore::JSTestObjPrototype::finishCreation):
+         (WebCore::jsTestObjPrototypeFunctionEnabledInSpecificWorldWhenRuntimeFeatureEnabled):
+         (WebCore::jsTestObjPrototypeFunctionEnabledInSpecificWorldWhenRuntimeFeatureEnabledCaller):
+         Ditto.
+
+         * bindings/scripts/test/TestGlobalObject.idl: Added test cases.
+         * bindings/scripts/test/TestObj.idl: Added test case.
+
 2017-05-19  Jeremy Jones  <jeremyj@apple.com>
 
         Fix macos build after r217143
index 4aa2fb1..c37faf1 100644 (file)
@@ -2966,31 +2966,34 @@ sub ToMethodName
     return $ret;
 }
 
-# Returns the RuntimeEnabledFeatures function name that is hooked up to check if a method/attribute is enabled.
+# Returns the conditional string that determines whether a method/attribute is enabled at runtime.
+# A method/attribute is enabled at runtime if either its RuntimeEnabledFeatures function returns
+# true or its EnabledForWorld function returns true (or both).
 # NOTE: Parameter passed in must have an 'extendedAttributes' property.
-#  (e.g. DOMInterface, DOMAttribute, DOMOperation, DOMIterable, etc.)
-sub GetRuntimeEnableFunctionName
+# (e.g. DOMInterface, DOMAttribute, DOMOperation, DOMIterable, etc.)
+sub GenerateRuntimeEnableConditionalString
 {
     my $context = shift;
 
     AddToImplIncludes("RuntimeEnabledFeatures.h");
 
+    my @conjuncts;
     if ($context->extendedAttributes->{EnabledForWorld}) {
         assert("Must specify value for EnabledForWorld.") if $context->extendedAttributes->{EnabledForWorld} eq "VALUE_IS_MISSING";
-        return "worldForDOMObject(this)." . ToMethodName($context->extendedAttributes->{EnabledForWorld}) . "()";
+        push @conjuncts, "worldForDOMObject(this)." . ToMethodName($context->extendedAttributes->{EnabledForWorld}) . "()";
     }
 
     if ($context->extendedAttributes->{EnabledAtRuntime}) {
         assert("Must specify value for EnabledAtRuntime.") if $context->extendedAttributes->{EnabledAtRuntime} eq "VALUE_IS_MISSING";
         my @flags = split /&/, $context->extendedAttributes->{EnabledAtRuntime};
-        my $result = "";
         foreach my $flag (@flags) {
-            $result .= " && " unless length $result eq 0;
-            $result .= "RuntimeEnabledFeatures::sharedFeatures()." . ToMethodName($flag) . "Enabled()"
+            push @conjuncts, "RuntimeEnabledFeatures::sharedFeatures()." . ToMethodName($flag) . "Enabled()";
         }
-        $result = "(" . $result . ")" unless scalar @flags eq 1;
-        return $result;
     }
+
+    my $result = join(" && ", @conjuncts);
+    $result = "($result)" if @conjuncts > 1;
+    return $result;
 }
 
 sub GetCastingHelperForThisObject
@@ -3418,9 +3421,9 @@ sub GenerateImplementation
         foreach my $functionOrAttribute (@runtimeEnabledProperties) {
             my $conditionalString = $codeGenerator->GenerateConditionalString($functionOrAttribute);
             push(@implContent, "#if ${conditionalString}\n") if $conditionalString;
-            my $enable_function_result = GetRuntimeEnableFunctionName($functionOrAttribute);
+            my $runtimeEnableConditionalString = GenerateRuntimeEnableConditionalString($functionOrAttribute);
             my $name = $functionOrAttribute->name;
-            push(@implContent, "    if (!${enable_function_result}) {\n");
+            push(@implContent, "    if (!${runtimeEnableConditionalString}) {\n");
             push(@implContent, "        Identifier propertyName = Identifier::fromString(&vm, reinterpret_cast<const LChar*>(\"$name\"), strlen(\"$name\"));\n");
             push(@implContent, "        VM::DeletePropertyModeScope scope(vm, VM::DeletePropertyMode::IgnoreConfigurable);\n");
             push(@implContent, "        JSObject::deleteProperty(this, globalObject()->globalExec(), propertyName);\n");
@@ -3561,9 +3564,9 @@ sub GenerateImplementation
 
         my $conditionalString = $codeGenerator->GenerateConditionalString($attribute);
         push(@implContent, "#if ${conditionalString}\n") if $conditionalString;
-        my $enable_function_result = GetRuntimeEnableFunctionName($attribute);
+        my $runtimeEnableConditionalString = GenerateRuntimeEnableConditionalString($attribute);
         my $attributeName = $attribute->name;
-        push(@implContent, "    if (${enable_function_result}) {\n");
+        push(@implContent, "    if (${runtimeEnableConditionalString}) {\n");
         my $getter = GetAttributeGetterName($interface, $className, $attribute);
         my $setter = IsReadonly($attribute) ? "nullptr" : GetAttributeSetterName($interface, $className, $attribute);
         push(@implContent, "        auto* customGetterSetter = CustomGetterSetter::create(vm, $getter, $setter);\n");
@@ -3596,12 +3599,12 @@ sub GenerateImplementation
 
         my $conditionalString = $codeGenerator->GenerateConditionalString($function);
         push(@implContent, "#if ${conditionalString}\n") if $conditionalString;
-        my $enable_function_result = GetRuntimeEnableFunctionName($function);
+        my $runtimeEnableConditionalString = GenerateRuntimeEnableConditionalString($function);
         my $functionName = $function->name;
         my $implementationFunction = GetFunctionName($interface, $className, $function);
         my $functionLength = GetFunctionLength($function);
         my $jsAttributes = ComputeFunctionSpecial($interface, $function);
-        push(@implContent, "    if (${enable_function_result})\n");
+        push(@implContent, "    if (${runtimeEnableConditionalString})\n");
 
         my $propertyName = "vm.propertyNames->$functionName";
         $propertyName = "static_cast<JSVMClientData*>(vm.clientData)->builtinNames()." . $functionName . "PrivateName()" if $function->extendedAttributes->{PrivateIdentifier};
index c378dc6..6a40c5c 100644 (file)
@@ -48,6 +48,8 @@ JSC::EncodedJSValue JSC_HOST_CALL jsTestGlobalObjectInstanceFunctionRegularOpera
 JSC::EncodedJSValue JSC_HOST_CALL jsTestGlobalObjectInstanceFunctionEnabledAtRuntimeOperation(JSC::ExecState*);
 #endif
 JSC::EncodedJSValue JSC_HOST_CALL jsTestGlobalObjectInstanceFunctionEnabledInSpecificWorld(JSC::ExecState*);
+JSC::EncodedJSValue JSC_HOST_CALL jsTestGlobalObjectInstanceFunctionEnabledInSpecificWorldWhenRuntimeFeatureEnabled(JSC::ExecState*);
+JSC::EncodedJSValue JSC_HOST_CALL jsTestGlobalObjectInstanceFunctionEnabledInSpecificWorldWhenRuntimeFeaturesEnabled(JSC::ExecState*);
 #if ENABLE(TEST_FEATURE)
 JSC::EncodedJSValue JSC_HOST_CALL jsTestGlobalObjectInstanceFunctionTestPrivateFunction(JSC::ExecState*);
 #endif
@@ -165,6 +167,10 @@ void JSTestGlobalObject::finishCreation(VM& vm)
 #endif
     if (worldForDOMObject(this).specificWorld())
         putDirectNativeFunction(vm, this, vm.propertyNames->enabledInSpecificWorld, 1, jsTestGlobalObjectInstanceFunctionEnabledInSpecificWorld, NoIntrinsic, attributesForStructure(JSC::Function));
+    if ((worldForDOMObject(this).specificWorld() && RuntimeEnabledFeatures::sharedFeatures().testFeatureEnabled()))
+        putDirectNativeFunction(vm, this, vm.propertyNames->enabledInSpecificWorldWhenRuntimeFeatureEnabled, 1, jsTestGlobalObjectInstanceFunctionEnabledInSpecificWorldWhenRuntimeFeatureEnabled, NoIntrinsic, attributesForStructure(JSC::Function));
+    if ((worldForDOMObject(this).specificWorld() && RuntimeEnabledFeatures::sharedFeatures().testFeatureEnabled() && RuntimeEnabledFeatures::sharedFeatures().testFeature1Enabled()))
+        putDirectNativeFunction(vm, this, vm.propertyNames->enabledInSpecificWorldWhenRuntimeFeaturesEnabled, 1, jsTestGlobalObjectInstanceFunctionEnabledInSpecificWorldWhenRuntimeFeaturesEnabled, NoIntrinsic, attributesForStructure(JSC::Function));
 #if ENABLE(TEST_FEATURE)
     if (RuntimeEnabledFeatures::sharedFeatures().testFeatureEnabled())
         putDirectNativeFunction(vm, this, static_cast<JSVMClientData*>(vm.clientData)->builtinNames().testPrivateFunctionPrivateName(), 0, jsTestGlobalObjectInstanceFunctionTestPrivateFunction, NoIntrinsic, attributesForStructure(JSC::Function));
@@ -477,6 +483,46 @@ static inline JSC::EncodedJSValue jsTestGlobalObjectInstanceFunctionEnabledInSpe
     return JSValue::encode(jsUndefined());
 }
 
+static inline JSC::EncodedJSValue jsTestGlobalObjectInstanceFunctionEnabledInSpecificWorldWhenRuntimeFeatureEnabledCaller(JSC::ExecState*, JSTestGlobalObject*, JSC::ThrowScope&);
+
+EncodedJSValue JSC_HOST_CALL jsTestGlobalObjectInstanceFunctionEnabledInSpecificWorldWhenRuntimeFeatureEnabled(ExecState* state)
+{
+    return BindingCaller<JSTestGlobalObject>::callOperation<jsTestGlobalObjectInstanceFunctionEnabledInSpecificWorldWhenRuntimeFeatureEnabledCaller>(state, "enabledInSpecificWorldWhenRuntimeFeatureEnabled");
+}
+
+static inline JSC::EncodedJSValue jsTestGlobalObjectInstanceFunctionEnabledInSpecificWorldWhenRuntimeFeatureEnabledCaller(JSC::ExecState* state, JSTestGlobalObject* castedThis, JSC::ThrowScope& throwScope)
+{
+    UNUSED_PARAM(state);
+    UNUSED_PARAM(throwScope);
+    auto& impl = castedThis->wrapped();
+    if (UNLIKELY(state->argumentCount() < 1))
+        return throwVMError(state, throwScope, createNotEnoughArgumentsError(state));
+    auto testParam = convert<IDLLong>(*state, state->uncheckedArgument(0));
+    RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
+    impl.enabledInSpecificWorldWhenRuntimeFeatureEnabled(WTFMove(testParam));
+    return JSValue::encode(jsUndefined());
+}
+
+static inline JSC::EncodedJSValue jsTestGlobalObjectInstanceFunctionEnabledInSpecificWorldWhenRuntimeFeaturesEnabledCaller(JSC::ExecState*, JSTestGlobalObject*, JSC::ThrowScope&);
+
+EncodedJSValue JSC_HOST_CALL jsTestGlobalObjectInstanceFunctionEnabledInSpecificWorldWhenRuntimeFeaturesEnabled(ExecState* state)
+{
+    return BindingCaller<JSTestGlobalObject>::callOperation<jsTestGlobalObjectInstanceFunctionEnabledInSpecificWorldWhenRuntimeFeaturesEnabledCaller>(state, "enabledInSpecificWorldWhenRuntimeFeaturesEnabled");
+}
+
+static inline JSC::EncodedJSValue jsTestGlobalObjectInstanceFunctionEnabledInSpecificWorldWhenRuntimeFeaturesEnabledCaller(JSC::ExecState* state, JSTestGlobalObject* castedThis, JSC::ThrowScope& throwScope)
+{
+    UNUSED_PARAM(state);
+    UNUSED_PARAM(throwScope);
+    auto& impl = castedThis->wrapped();
+    if (UNLIKELY(state->argumentCount() < 1))
+        return throwVMError(state, throwScope, createNotEnoughArgumentsError(state));
+    auto testParam = convert<IDLLong>(*state, state->uncheckedArgument(0));
+    RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
+    impl.enabledInSpecificWorldWhenRuntimeFeaturesEnabled(WTFMove(testParam));
+    return JSValue::encode(jsUndefined());
+}
+
 #if ENABLE(TEST_FEATURE)
 static inline JSC::EncodedJSValue jsTestGlobalObjectInstanceFunctionTestPrivateFunctionCaller(JSC::ExecState*, JSTestGlobalObject*, JSC::ThrowScope&);
 
index 07d924d..8f7de37 100644 (file)
@@ -1108,6 +1108,7 @@ template<> TestObj::ConditionalDictionaryC convertDictionary<TestObj::Conditiona
 #if ENABLE(TEST_FEATURE)
 JSC::EncodedJSValue JSC_HOST_CALL jsTestObjPrototypeFunctionEnabledAtRuntimeOperation(JSC::ExecState*);
 #endif
+JSC::EncodedJSValue JSC_HOST_CALL jsTestObjPrototypeFunctionEnabledInSpecificWorldWhenRuntimeFeatureEnabled(JSC::ExecState*);
 JSC::EncodedJSValue JSC_HOST_CALL jsTestObjPrototypeFunctionWorldSpecificMethod(JSC::ExecState*);
 #if ENABLE(TEST_FEATURE)
 JSC::EncodedJSValue JSC_HOST_CALL jsTestObjPrototypeFunctionEnabledBySettingOperation(JSC::ExecState*);
@@ -1765,6 +1766,7 @@ static const HashTableValue JSTestObjPrototypeTableValues[] =
 #else
     { 0, 0, NoIntrinsic, { 0, 0 } },
 #endif
+    { "enabledInSpecificWorldWhenRuntimeFeatureEnabled", JSC::Function, NoIntrinsic, { (intptr_t)static_cast<NativeFunction>(jsTestObjPrototypeFunctionEnabledInSpecificWorldWhenRuntimeFeatureEnabled), (intptr_t) (1) } },
     { "worldSpecificMethod", JSC::Function, NoIntrinsic, { (intptr_t)static_cast<NativeFunction>(jsTestObjPrototypeFunctionWorldSpecificMethod), (intptr_t) (1) } },
 #if ENABLE(TEST_FEATURE)
     { "enabledBySettingOperation", JSC::Function, NoIntrinsic, { (intptr_t)static_cast<NativeFunction>(jsTestObjPrototypeFunctionEnabledBySettingOperation), (intptr_t) (1) } },
@@ -1960,6 +1962,11 @@ void JSTestObjPrototype::finishCreation(VM& vm, JSDOMGlobalObject& globalObject)
         JSObject::deleteProperty(this, globalObject()->globalExec(), propertyName);
     }
 #endif
+    if (!(worldForDOMObject(this).someWorld() && RuntimeEnabledFeatures::sharedFeatures().testFeatureEnabled())) {
+        Identifier propertyName = Identifier::fromString(&vm, reinterpret_cast<const LChar*>("enabledInSpecificWorldWhenRuntimeFeatureEnabled"), strlen("enabledInSpecificWorldWhenRuntimeFeatureEnabled"));
+        VM::DeletePropertyModeScope scope(vm, VM::DeletePropertyMode::IgnoreConfigurable);
+        JSObject::deleteProperty(this, globalObject()->globalExec(), propertyName);
+    }
     if (!worldForDOMObject(this).someWorld()) {
         Identifier propertyName = Identifier::fromString(&vm, reinterpret_cast<const LChar*>("worldSpecificMethod"), strlen("worldSpecificMethod"));
         VM::DeletePropertyModeScope scope(vm, VM::DeletePropertyMode::IgnoreConfigurable);
@@ -5373,6 +5380,26 @@ EncodedJSValue JSC_HOST_CALL jsTestObjPrototypeFunctionEnabledAtRuntimeOperation
 }
 #endif
 
+static inline JSC::EncodedJSValue jsTestObjPrototypeFunctionEnabledInSpecificWorldWhenRuntimeFeatureEnabledCaller(JSC::ExecState*, JSTestObj*, JSC::ThrowScope&);
+
+EncodedJSValue JSC_HOST_CALL jsTestObjPrototypeFunctionEnabledInSpecificWorldWhenRuntimeFeatureEnabled(ExecState* state)
+{
+    return BindingCaller<JSTestObj>::callOperation<jsTestObjPrototypeFunctionEnabledInSpecificWorldWhenRuntimeFeatureEnabledCaller>(state, "enabledInSpecificWorldWhenRuntimeFeatureEnabled");
+}
+
+static inline JSC::EncodedJSValue jsTestObjPrototypeFunctionEnabledInSpecificWorldWhenRuntimeFeatureEnabledCaller(JSC::ExecState* state, JSTestObj* castedThis, JSC::ThrowScope& throwScope)
+{
+    UNUSED_PARAM(state);
+    UNUSED_PARAM(throwScope);
+    auto& impl = castedThis->wrapped();
+    if (UNLIKELY(state->argumentCount() < 1))
+        return throwVMError(state, throwScope, createNotEnoughArgumentsError(state));
+    auto testParam = convert<IDLLong>(*state, state->uncheckedArgument(0));
+    RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
+    impl.enabledInSpecificWorldWhenRuntimeFeatureEnabled(WTFMove(testParam));
+    return JSValue::encode(jsUndefined());
+}
+
 static inline JSC::EncodedJSValue jsTestObjPrototypeFunctionWorldSpecificMethodCaller(JSC::ExecState*, JSTestObj*, JSC::ThrowScope&);
 
 EncodedJSValue JSC_HOST_CALL jsTestObjPrototypeFunctionWorldSpecificMethod(ExecState* state)
index 1245aad..3a08fba 100644 (file)
@@ -37,6 +37,9 @@
 
     [EnabledForWorld=specificWorld] void enabledInSpecificWorld(long testParam);
 
+    [EnabledForWorld=specificWorld, EnabledAtRuntime=TestFeature] void enabledInSpecificWorldWhenRuntimeFeatureEnabled(long testParam);
+    [EnabledForWorld=specificWorld, EnabledAtRuntime=TestFeature&TestFeature1] void enabledInSpecificWorldWhenRuntimeFeaturesEnabled(long testParam);
+
     [PrivateIdentifier, Conditional=TEST_FEATURE, EnabledAtRuntime=TestFeature] void testPrivateFunction();
     [JSBuiltin, Conditional=TEST_FEATURE, EnabledAtRuntime=TestFeature] void testJSBuiltinFunction();
 };
index d544765..0ad7ce6 100644 (file)
@@ -111,6 +111,8 @@ enum TestConfidence { "high", "kinda-low" };
     [Conditional=TEST_FEATURE, EnabledAtRuntime=TestFeature] void enabledAtRuntimeOperation(DOMString testParam);
     [Conditional=TEST_FEATURE, EnabledAtRuntime=TestFeature] void enabledAtRuntimeOperation(long testParam);
 
+    [EnabledForWorld=someWorld, EnabledAtRuntime=TestFeature] void enabledInSpecificWorldWhenRuntimeFeatureEnabled(long testParam);
+
     [EnabledForWorld=someWorld] void worldSpecificMethod(long testParam);
 
     // [EnabledBySetting] attributes and operations.