[Apple Pay] Payment APIs should be completely disabled in web views into which client...
authoraestes@apple.com <aestes@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 14 May 2019 22:50:21 +0000 (22:50 +0000)
committeraestes@apple.com <aestes@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 14 May 2019 22:50:21 +0000 (22:50 +0000)
https://bugs.webkit.org/show_bug.cgi?id=197751
<rdar://problem/50631563>

Reviewed by Alex Christensen.

Source/WebCore:

In r243324, when a document has had user agent scripts injected into it, payment APIs were
disabled at runtime by having all entry points return falsy values or throw exceptions
(e.g., ApplePaySession.canMakePayments() returns false).

In the case of user scripts in particular (e.g., WKUserScript), since we know whether these
exist at the time we create a document's DOMWindow, we can do better than r243324 by
completely disabling the payment APIs in the presence of user scripts.

To achieve this, this change introduces the 'EnabledByContext' extended attribute for
interfaces, which instructs the bindings generator to add a conjunct to the payment API
constructors that asks the interface's implementation class whether it should be enabled for
a given ScriptExecutionContext. The PaymentRequest and ApplePaySession interfaces adopt this
new extended attribute to implement the new user script check.

Added new API tests.

* Modules/applepay/ApplePaySession.idl:
* Modules/applepay/PaymentCoordinator.cpp:
(WebCore::PaymentCoordinator::shouldEnableApplePayAPIs const):
* Modules/applepay/PaymentCoordinator.h:
* Modules/applepay/PaymentSession.cpp:
(WebCore::PaymentSession::enabledForContext):
* Modules/applepay/PaymentSession.h:
* Modules/paymentrequest/PaymentHandler.cpp:
(WebCore::PaymentHandler::enabledForContext):
* Modules/paymentrequest/PaymentHandler.h:
* Modules/paymentrequest/PaymentRequest.cpp:
(WebCore::PaymentRequest::enabledForContext):
* Modules/paymentrequest/PaymentRequest.h:
* Modules/paymentrequest/PaymentRequest.idl:
* bindings/scripts/CodeGeneratorJS.pm:
(NeedsRuntimeCheck):
(GenerateRuntimeEnableConditionalString):
* bindings/scripts/IDLAttributes.json:
* bindings/scripts/preprocess-idls.pl:
(GenerateConstructorAttributes):
* bindings/scripts/test/JS/JSTestEnabledForContext.cpp: Added.
* bindings/scripts/test/JS/JSTestEnabledForContext.h: Added.
* bindings/scripts/test/JS/JSTestGlobalObject.cpp:
(WebCore::JSTestGlobalObject::finishCreation):
(WebCore::jsTestGlobalObjectTestEnabledForContextConstructorGetter):
(WebCore::jsTestGlobalObjectTestEnabledForContextConstructor):
(WebCore::setJSTestGlobalObjectTestEnabledForContextConstructorSetter):
(WebCore::setJSTestGlobalObjectTestEnabledForContextConstructor):
* bindings/scripts/test/TestEnabledForContext.idl: Added.

Tools:

Added new API tests.

* TestWebKitAPI/Tests/WebKitCocoa/ApplePay.mm:
(-[TestApplePayScriptMessageHandler initWithAPIsAvailableExpectation:canMakePaymentsExpectation:]):
(-[TestApplePayScriptMessageHandler userContentController:didReceiveScriptMessage:]):
(TestWebKitAPI::TEST):
(-[TestApplePayScriptMessageHandler initWithExpectation:]): Deleted.
* TestWebKitAPI/Tests/WebKitCocoa/apple-pay-availability.html:

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

21 files changed:
Source/WebCore/ChangeLog
Source/WebCore/Modules/applepay/ApplePaySession.idl
Source/WebCore/Modules/applepay/PaymentCoordinator.cpp
Source/WebCore/Modules/applepay/PaymentCoordinator.h
Source/WebCore/Modules/applepay/PaymentSession.cpp
Source/WebCore/Modules/applepay/PaymentSession.h
Source/WebCore/Modules/paymentrequest/PaymentHandler.cpp
Source/WebCore/Modules/paymentrequest/PaymentHandler.h
Source/WebCore/Modules/paymentrequest/PaymentRequest.cpp
Source/WebCore/Modules/paymentrequest/PaymentRequest.h
Source/WebCore/Modules/paymentrequest/PaymentRequest.idl
Source/WebCore/bindings/scripts/CodeGeneratorJS.pm
Source/WebCore/bindings/scripts/IDLAttributes.json
Source/WebCore/bindings/scripts/preprocess-idls.pl
Source/WebCore/bindings/scripts/test/JS/JSTestEnabledForContext.cpp [new file with mode: 0644]
Source/WebCore/bindings/scripts/test/JS/JSTestEnabledForContext.h [new file with mode: 0644]
Source/WebCore/bindings/scripts/test/JS/JSTestGlobalObject.cpp
Source/WebCore/bindings/scripts/test/TestEnabledForContext.idl [new file with mode: 0644]
Tools/ChangeLog
Tools/TestWebKitAPI/Tests/WebKitCocoa/ApplePay.mm
Tools/TestWebKitAPI/Tests/WebKitCocoa/apple-pay-availability.html

index c1fabc0..ae125e9 100644 (file)
@@ -1,3 +1,57 @@
+2019-05-14  Andy Estes  <aestes@apple.com>
+
+        [Apple Pay] Payment APIs should be completely disabled in web views into which clients have injected user scripts
+        https://bugs.webkit.org/show_bug.cgi?id=197751
+        <rdar://problem/50631563>
+
+        Reviewed by Alex Christensen.
+
+        In r243324, when a document has had user agent scripts injected into it, payment APIs were
+        disabled at runtime by having all entry points return falsy values or throw exceptions
+        (e.g., ApplePaySession.canMakePayments() returns false).
+
+        In the case of user scripts in particular (e.g., WKUserScript), since we know whether these
+        exist at the time we create a document's DOMWindow, we can do better than r243324 by
+        completely disabling the payment APIs in the presence of user scripts.
+
+        To achieve this, this change introduces the 'EnabledByContext' extended attribute for
+        interfaces, which instructs the bindings generator to add a conjunct to the payment API
+        constructors that asks the interface's implementation class whether it should be enabled for
+        a given ScriptExecutionContext. The PaymentRequest and ApplePaySession interfaces adopt this
+        new extended attribute to implement the new user script check.
+
+        Added new API tests.
+
+        * Modules/applepay/ApplePaySession.idl:
+        * Modules/applepay/PaymentCoordinator.cpp:
+        (WebCore::PaymentCoordinator::shouldEnableApplePayAPIs const):
+        * Modules/applepay/PaymentCoordinator.h:
+        * Modules/applepay/PaymentSession.cpp:
+        (WebCore::PaymentSession::enabledForContext):
+        * Modules/applepay/PaymentSession.h:
+        * Modules/paymentrequest/PaymentHandler.cpp:
+        (WebCore::PaymentHandler::enabledForContext):
+        * Modules/paymentrequest/PaymentHandler.h:
+        * Modules/paymentrequest/PaymentRequest.cpp:
+        (WebCore::PaymentRequest::enabledForContext):
+        * Modules/paymentrequest/PaymentRequest.h:
+        * Modules/paymentrequest/PaymentRequest.idl:
+        * bindings/scripts/CodeGeneratorJS.pm:
+        (NeedsRuntimeCheck):
+        (GenerateRuntimeEnableConditionalString):
+        * bindings/scripts/IDLAttributes.json:
+        * bindings/scripts/preprocess-idls.pl:
+        (GenerateConstructorAttributes):
+        * bindings/scripts/test/JS/JSTestEnabledForContext.cpp: Added.
+        * bindings/scripts/test/JS/JSTestEnabledForContext.h: Added.
+        * bindings/scripts/test/JS/JSTestGlobalObject.cpp:
+        (WebCore::JSTestGlobalObject::finishCreation):
+        (WebCore::jsTestGlobalObjectTestEnabledForContextConstructorGetter):
+        (WebCore::jsTestGlobalObjectTestEnabledForContextConstructor):
+        (WebCore::setJSTestGlobalObjectTestEnabledForContextConstructorSetter):
+        (WebCore::setJSTestGlobalObjectTestEnabledForContextConstructor):
+        * bindings/scripts/test/TestEnabledForContext.idl: Added.
+
 2019-05-14  Robin Morisset  <rmorisset@apple.com>
 
         [WHLSL] parseEffectfulSuffix() is never called
index 66a89f8..0850bd5 100644 (file)
@@ -30,6 +30,7 @@
     ConstructorCallWith=Document,
     ConstructorMayThrowException,
     EnabledBySetting=ApplePay,
+    EnabledForContext,
 ] interface ApplePaySession : EventTarget {
     const unsigned short STATUS_SUCCESS = 0;
     const unsigned short STATUS_FAILURE = 1;
index 524f436..d559948 100644 (file)
 #include "Document.h"
 #include "LinkIconCollector.h"
 #include "Logging.h"
+#include "Page.h"
 #include "PaymentAuthorizationStatus.h"
 #include "PaymentCoordinatorClient.h"
 #include "PaymentSession.h"
+#include "UserContentProvider.h"
 #include <wtf/CompletionHandler.h>
 #include <wtf/URL.h>
 
@@ -251,6 +253,22 @@ Optional<String> PaymentCoordinator::validatedPaymentNetwork(Document& document,
     return m_client.validatedPaymentNetwork(paymentNetwork);
 }
 
+bool PaymentCoordinator::shouldEnableApplePayAPIs(Document& document) const
+{
+    if (m_client.supportsUnrestrictedApplePay()) {
+        RELEASE_LOG_IF_ALLOWED("shouldEnableApplePayAPIs() -> true (unrestricted client)");
+        return true;
+    }
+
+    bool shouldEnableAPIs = true;
+    document.page()->userContentProvider().forEachUserScript([&](DOMWrapperWorld&, const UserScript&) {
+        shouldEnableAPIs = false;
+    });
+
+    RELEASE_LOG_IF_ALLOWED("shouldEnableApplePayAPIs() -> %d", shouldEnableAPIs);
+    return shouldEnableAPIs;
+}
+
 bool PaymentCoordinator::shouldAllowApplePay(Document& document) const
 {
     if (m_client.supportsUnrestrictedApplePay()) {
index 3b321a2..260a3e2 100644 (file)
@@ -78,6 +78,7 @@ public:
 
     Optional<String> validatedPaymentNetwork(Document&, unsigned version, const String&) const;
 
+    bool shouldEnableApplePayAPIs(Document&) const;
     WEBCORE_EXPORT bool shouldAllowApplePay(Document&) const;
     WEBCORE_EXPORT bool shouldAllowUserAgentScripts(Document&) const;
 
index 87a44f4..65d9f86 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 Apple Inc. All rights reserved.
+ * Copyright (C) 2017-2019 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -30,6 +30,7 @@
 
 #include "Document.h"
 #include "DocumentLoader.h"
+#include "Page.h"
 #include "SecurityOrigin.h"
 
 namespace WebCore {
@@ -72,6 +73,15 @@ ExceptionOr<void> PaymentSession::canCreateSession(Document& document)
     return { };
 }
 
+bool PaymentSession::enabledForContext(ScriptExecutionContext& context)
+{
+    auto& document = downcast<Document>(context);
+    if (auto page = document.page())
+        return page->paymentCoordinator().shouldEnableApplePayAPIs(document);
+
+    return false;
+}
+
 } // namespace WebCore
 
 #endif // ENABLE(APPLE_PAY)
index 8f7a4b6..3222f59 100644 (file)
@@ -41,6 +41,7 @@ class PaymentMethod;
 class PaymentSession : public virtual PaymentSessionBase {
 public:
     static ExceptionOr<void> canCreateSession(Document&);
+    static bool enabledForContext(ScriptExecutionContext&);
 
     virtual unsigned version() const = 0;
     virtual void validateMerchant(URL&&) = 0;
index 0488eae..a76d4b4 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 Apple Inc. All rights reserved.
+ * Copyright (C) 2017-2019 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -61,6 +61,16 @@ ExceptionOr<void> PaymentHandler::canCreateSession(Document& document)
     return { };
 }
 
+bool PaymentHandler::enabledForContext(ScriptExecutionContext& context)
+{
+#if ENABLE(APPLE_PAY)
+    return PaymentSession::enabledForContext(context);
+#else
+    UNUSED_PARAM(context);
+    return false;
+#endif
+}
+
 bool PaymentHandler::hasActiveSession(Document& document)
 {
 #if ENABLE(APPLE_PAY)
index 0b4df03..b48ba73 100644 (file)
@@ -46,6 +46,7 @@ class PaymentHandler : public virtual PaymentSessionBase {
 public:
     static RefPtr<PaymentHandler> create(Document&, PaymentRequest&, const PaymentRequest::MethodIdentifier&);
     static ExceptionOr<void> canCreateSession(Document&);
+    static bool enabledForContext(ScriptExecutionContext&);
     static bool hasActiveSession(Document&);
 
     virtual ExceptionOr<void> convertData(JSC::JSValue&&) = 0;
index 61ff5be..d2ef7a6 100644 (file)
@@ -34,7 +34,9 @@
 #include "JSDOMPromise.h"
 #include "JSPaymentDetailsUpdate.h"
 #include "JSPaymentResponse.h"
+#include "Page.h"
 #include "PaymentAddress.h"
+#include "PaymentCoordinator.h"
 #include "PaymentCurrencyAmount.h"
 #include "PaymentDetailsInit.h"
 #include "PaymentHandler.h"
@@ -351,6 +353,11 @@ ExceptionOr<Ref<PaymentRequest>> PaymentRequest::create(Document& document, Vect
     return adoptRef(*new PaymentRequest(document, WTFMove(options), WTFMove(details), WTFMove(std::get<1>(shippingOptionAndModifierData)), WTFMove(serializedMethodData), WTFMove(std::get<0>(shippingOptionAndModifierData))));
 }
 
+bool PaymentRequest::enabledForContext(ScriptExecutionContext& context)
+{
+    return PaymentHandler::enabledForContext(context);
+}
+
 PaymentRequest::PaymentRequest(Document& document, PaymentOptions&& options, PaymentDetailsInit&& details, Vector<String>&& serializedModifierData, Vector<Method>&& serializedMethodData, String&& selectedShippingOption)
     : ActiveDOMObject { document }
     , m_options { WTFMove(options) }
index cc0bbcb..f7c3426 100644 (file)
@@ -58,6 +58,7 @@ public:
     using ShowPromise = DOMPromiseDeferred<IDLInterface<PaymentResponse>>;
 
     static ExceptionOr<Ref<PaymentRequest>> create(Document&, Vector<PaymentMethodData>&&, PaymentDetailsInit&&, PaymentOptions&&);
+    static bool enabledForContext(ScriptExecutionContext&);
     ~PaymentRequest();
 
     void show(Document&, RefPtr<DOMPromise>&& detailsPromise, ShowPromise&&);
index b9c1264..918c108 100644 (file)
@@ -30,6 +30,7 @@
     ConstructorCallWith=Document,
     ConstructorMayThrowException,
     EnabledBySetting=PaymentRequest,
+    EnabledForContext,
     SecureContext,
 ] interface PaymentRequest : EventTarget {
     [CallWith=Document] Promise<PaymentResponse> show(optional Promise<PaymentDetailsUpdate> detailsPromise);
index 505c4f2..f309a4b 100644 (file)
@@ -1705,6 +1705,7 @@ sub NeedsRuntimeCheck
     }
 
     return $context->extendedAttributes->{EnabledAtRuntime}
+        || $context->extendedAttributes->{EnabledForContext}
         || $context->extendedAttributes->{EnabledForWorld}
         || $context->extendedAttributes->{EnabledBySetting}
         || $context->extendedAttributes->{DisabledByQuirk}
@@ -3788,6 +3789,15 @@ sub GenerateRuntimeEnableConditionalString
         }
     }
 
+    if ($context->extendedAttributes->{EnabledForContext}) {
+        assert("Must not specify value for EnabledForContext.") unless $context->extendedAttributes->{EnabledForContext} eq "VALUE_IS_MISSING";
+        assert("EnabledForContext must be an interface or constructor attribute.") unless $codeGenerator->IsConstructorType($context->type);
+
+        my $contextRef = "*jsCast<JSDOMGlobalObject*>(" . $globalObjectPtr . ")->scriptExecutionContext()";
+        my $name = $context->name;
+        push(@conjuncts,  "${name}::enabledForContext(" . $contextRef . ")");
+    }
+
     my $result = join(" && ", @conjuncts);
     $result = "($result)" if @conjuncts > 1;
     return $result;
index efe5697..469fd2d 100644 (file)
             "contextsAllowed": ["interface", "dictionary", "enum", "attribute", "operation", "constant"],
             "values": ["*"]
         },
+        "EnabledForContext": {
+            "contextsAllowed": ["attribute", "interface"]
+        },
         "EnabledForWorld": {
             "contextsAllowed": ["attribute", "operation"],
             "values": ["*"]
index 1ac8fed..9772130 100644 (file)
@@ -283,7 +283,7 @@ sub GenerateConstructorAttributes
     foreach my $attributeName (sort keys %{$extendedAttributes}) {
       next unless ($attributeName eq "Conditional" || $attributeName eq "EnabledAtRuntime" || $attributeName eq "EnabledForWorld"
         || $attributeName eq "EnabledBySetting" || $attributeName eq "SecureContext" || $attributeName eq "PrivateIdentifier"
-        || $attributeName eq "PublicIdentifier" || $attributeName eq "DisabledByQuirk" || $attributeName eq "EnabledByQuirk");
+        || $attributeName eq "PublicIdentifier" || $attributeName eq "DisabledByQuirk" || $attributeName eq "EnabledByQuirk" || $attributeName eq "EnabledForContext");
       my $extendedAttribute = $attributeName;
       $extendedAttribute .= "=" . $extendedAttributes->{$attributeName} unless $extendedAttributes->{$attributeName} eq "VALUE_IS_MISSING";
       push(@extendedAttributesList, $extendedAttribute);
diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestEnabledForContext.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestEnabledForContext.cpp
new file mode 100644 (file)
index 0000000..9b7fb7d
--- /dev/null
@@ -0,0 +1,267 @@
+/*
+    This file is part of the WebKit open source project.
+    This file has been generated by generate-bindings.pl. DO NOT MODIFY!
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Library General Public
+    License as published by the Free Software Foundation; either
+    version 2 of the License, or (at your option) any later version.
+
+    This library is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Library General Public License for more details.
+
+    You should have received a copy of the GNU Library General Public License
+    along with this library; see the file COPYING.LIB.  If not, write to
+    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+    Boston, MA 02110-1301, USA.
+*/
+
+#include "config.h"
+#include "JSTestEnabledForContext.h"
+
+#include "Document.h"
+#include "JSDOMAttribute.h"
+#include "JSDOMBinding.h"
+#include "JSDOMConstructorNotConstructable.h"
+#include "JSDOMExceptionHandling.h"
+#include "JSDOMWrapperCache.h"
+#include "JSTestSubObj.h"
+#include "ScriptExecutionContext.h"
+#include "Settings.h"
+#include "WebCoreJSClientData.h"
+#include <JavaScriptCore/FunctionPrototype.h>
+#include <JavaScriptCore/HeapSnapshotBuilder.h>
+#include <JavaScriptCore/JSCInlines.h>
+#include <wtf/GetPtr.h>
+#include <wtf/PointerPreparations.h>
+#include <wtf/URL.h>
+
+
+namespace WebCore {
+using namespace JSC;
+
+// Attributes
+
+JSC::EncodedJSValue jsTestEnabledForContextConstructor(JSC::ExecState*, JSC::EncodedJSValue, JSC::PropertyName);
+bool setJSTestEnabledForContextConstructor(JSC::ExecState*, JSC::EncodedJSValue, JSC::EncodedJSValue);
+JSC::EncodedJSValue jsTestEnabledForContextTestSubObjEnabledForContextConstructor(JSC::ExecState*, JSC::EncodedJSValue, JSC::PropertyName);
+bool setJSTestEnabledForContextTestSubObjEnabledForContextConstructor(JSC::ExecState*, JSC::EncodedJSValue, JSC::EncodedJSValue);
+
+class JSTestEnabledForContextPrototype : public JSC::JSNonFinalObject {
+public:
+    using Base = JSC::JSNonFinalObject;
+    static JSTestEnabledForContextPrototype* create(JSC::VM& vm, JSDOMGlobalObject* globalObject, JSC::Structure* structure)
+    {
+        JSTestEnabledForContextPrototype* ptr = new (NotNull, JSC::allocateCell<JSTestEnabledForContextPrototype>(vm.heap)) JSTestEnabledForContextPrototype(vm, globalObject, structure);
+        ptr->finishCreation(vm);
+        return ptr;
+    }
+
+    DECLARE_INFO;
+    static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
+    {
+        return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
+    }
+
+private:
+    JSTestEnabledForContextPrototype(JSC::VM& vm, JSC::JSGlobalObject*, JSC::Structure* structure)
+        : JSC::JSNonFinalObject(vm, structure)
+    {
+    }
+
+    void finishCreation(JSC::VM&);
+};
+
+using JSTestEnabledForContextConstructor = JSDOMConstructorNotConstructable<JSTestEnabledForContext>;
+
+template<> JSValue JSTestEnabledForContextConstructor::prototypeForStructure(JSC::VM& vm, const JSDOMGlobalObject& globalObject)
+{
+    UNUSED_PARAM(vm);
+    return globalObject.functionPrototype();
+}
+
+template<> void JSTestEnabledForContextConstructor::initializeProperties(VM& vm, JSDOMGlobalObject& globalObject)
+{
+    putDirect(vm, vm.propertyNames->prototype, JSTestEnabledForContext::prototype(vm, globalObject), JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum);
+    putDirect(vm, vm.propertyNames->name, jsNontrivialString(&vm, String("TestEnabledForContext"_s)), JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum);
+    putDirect(vm, vm.propertyNames->length, jsNumber(0), JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum);
+}
+
+template<> const ClassInfo JSTestEnabledForContextConstructor::s_info = { "TestEnabledForContext", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSTestEnabledForContextConstructor) };
+
+/* Hash table for prototype */
+
+static const HashTableValue JSTestEnabledForContextPrototypeTableValues[] =
+{
+    { "constructor", static_cast<unsigned>(JSC::PropertyAttribute::DontEnum), NoIntrinsic, { (intptr_t)static_cast<PropertySlot::GetValueFunc>(jsTestEnabledForContextConstructor), (intptr_t) static_cast<PutPropertySlot::PutValueFunc>(setJSTestEnabledForContextConstructor) } },
+};
+
+const ClassInfo JSTestEnabledForContextPrototype::s_info = { "TestEnabledForContextPrototype", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSTestEnabledForContextPrototype) };
+
+void JSTestEnabledForContextPrototype::finishCreation(VM& vm)
+{
+    Base::finishCreation(vm);
+    reifyStaticProperties(vm, JSTestEnabledForContext::info(), JSTestEnabledForContextPrototypeTableValues, *this);
+}
+
+const ClassInfo JSTestEnabledForContext::s_info = { "TestEnabledForContext", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSTestEnabledForContext) };
+
+JSTestEnabledForContext::JSTestEnabledForContext(Structure* structure, JSDOMGlobalObject& globalObject, Ref<TestEnabledForContext>&& impl)
+    : JSDOMWrapper<TestEnabledForContext>(structure, globalObject, WTFMove(impl))
+{
+}
+
+void JSTestEnabledForContext::finishCreation(VM& vm)
+{
+    Base::finishCreation(vm);
+    ASSERT(inherits(vm, info()));
+
+    if ((downcast<Document>(jsCast<JSDOMGlobalObject*>(globalObject())->scriptExecutionContext())->settings().testSettingEnabled() && TestSubObjEnabledForContext::enabledForContext(*jsCast<JSDOMGlobalObject*>(globalObject())->scriptExecutionContext())))
+        putDirectCustomAccessor(vm, static_cast<JSVMClientData*>(vm.clientData)->builtinNames().TestSubObjEnabledForContextPublicName(), CustomGetterSetter::create(vm, jsTestEnabledForContextTestSubObjEnabledForContextConstructor, setJSTestEnabledForContextTestSubObjEnabledForContextConstructor), attributesForStructure(static_cast<unsigned>(JSC::PropertyAttribute::DontEnum)));
+}
+
+JSObject* JSTestEnabledForContext::createPrototype(VM& vm, JSDOMGlobalObject& globalObject)
+{
+    return JSTestEnabledForContextPrototype::create(vm, &globalObject, JSTestEnabledForContextPrototype::createStructure(vm, &globalObject, globalObject.objectPrototype()));
+}
+
+JSObject* JSTestEnabledForContext::prototype(VM& vm, JSDOMGlobalObject& globalObject)
+{
+    return getDOMPrototype<JSTestEnabledForContext>(vm, globalObject);
+}
+
+JSValue JSTestEnabledForContext::getConstructor(VM& vm, const JSGlobalObject* globalObject)
+{
+    return getDOMConstructor<JSTestEnabledForContextConstructor>(vm, *jsCast<const JSDOMGlobalObject*>(globalObject));
+}
+
+void JSTestEnabledForContext::destroy(JSC::JSCell* cell)
+{
+    JSTestEnabledForContext* thisObject = static_cast<JSTestEnabledForContext*>(cell);
+    thisObject->JSTestEnabledForContext::~JSTestEnabledForContext();
+}
+
+template<> inline JSTestEnabledForContext* IDLAttribute<JSTestEnabledForContext>::cast(ExecState& state, EncodedJSValue thisValue)
+{
+    return jsDynamicCast<JSTestEnabledForContext*>(state.vm(), JSValue::decode(thisValue));
+}
+
+EncodedJSValue jsTestEnabledForContextConstructor(ExecState* state, EncodedJSValue thisValue, PropertyName)
+{
+    VM& vm = state->vm();
+    auto throwScope = DECLARE_THROW_SCOPE(vm);
+    auto* prototype = jsDynamicCast<JSTestEnabledForContextPrototype*>(vm, JSValue::decode(thisValue));
+    if (UNLIKELY(!prototype))
+        return throwVMTypeError(state, throwScope);
+    return JSValue::encode(JSTestEnabledForContext::getConstructor(state->vm(), prototype->globalObject()));
+}
+
+bool setJSTestEnabledForContextConstructor(ExecState* state, EncodedJSValue thisValue, EncodedJSValue encodedValue)
+{
+    VM& vm = state->vm();
+    auto throwScope = DECLARE_THROW_SCOPE(vm);
+    auto* prototype = jsDynamicCast<JSTestEnabledForContextPrototype*>(vm, JSValue::decode(thisValue));
+    if (UNLIKELY(!prototype)) {
+        throwVMTypeError(state, throwScope);
+        return false;
+    }
+    // Shadowing a built-in constructor
+    return prototype->putDirect(vm, vm.propertyNames->constructor, JSValue::decode(encodedValue));
+}
+
+static inline JSValue jsTestEnabledForContextTestSubObjEnabledForContextConstructorGetter(ExecState& state, JSTestEnabledForContext& thisObject, ThrowScope& throwScope)
+{
+    UNUSED_PARAM(throwScope);
+    UNUSED_PARAM(state);
+    return JSTestSubObj::getConstructor(state.vm(), thisObject.globalObject());
+}
+
+EncodedJSValue jsTestEnabledForContextTestSubObjEnabledForContextConstructor(ExecState* state, EncodedJSValue thisValue, PropertyName)
+{
+    return IDLAttribute<JSTestEnabledForContext>::get<jsTestEnabledForContextTestSubObjEnabledForContextConstructorGetter>(*state, thisValue, "TestSubObjEnabledForContext");
+}
+
+static inline bool setJSTestEnabledForContextTestSubObjEnabledForContextConstructorSetter(ExecState& state, JSTestEnabledForContext& thisObject, JSValue value, ThrowScope& throwScope)
+{
+    UNUSED_PARAM(throwScope);
+    // Shadowing a built-in constructor.
+    return thisObject.putDirect(state.vm(), Identifier::fromString(&state.vm(), reinterpret_cast<const LChar*>("TestSubObjEnabledForContext"), strlen("TestSubObjEnabledForContext")), value);
+}
+
+bool setJSTestEnabledForContextTestSubObjEnabledForContextConstructor(ExecState* state, EncodedJSValue thisValue, EncodedJSValue encodedValue)
+{
+    return IDLAttribute<JSTestEnabledForContext>::set<setJSTestEnabledForContextTestSubObjEnabledForContextConstructorSetter>(*state, thisValue, encodedValue, "TestSubObjEnabledForContext");
+}
+
+void JSTestEnabledForContext::heapSnapshot(JSCell* cell, HeapSnapshotBuilder& builder)
+{
+    auto* thisObject = jsCast<JSTestEnabledForContext*>(cell);
+    builder.setWrappedObjectForCell(cell, &thisObject->wrapped());
+    if (thisObject->scriptExecutionContext())
+        builder.setLabelForCell(cell, "url " + thisObject->scriptExecutionContext()->url().string());
+    Base::heapSnapshot(cell, builder);
+}
+
+bool JSTestEnabledForContextOwner::isReachableFromOpaqueRoots(JSC::Handle<JSC::Unknown> handle, void*, SlotVisitor& visitor, const char** reason)
+{
+    UNUSED_PARAM(handle);
+    UNUSED_PARAM(visitor);
+    UNUSED_PARAM(reason);
+    return false;
+}
+
+void JSTestEnabledForContextOwner::finalize(JSC::Handle<JSC::Unknown> handle, void* context)
+{
+    auto* jsTestEnabledForContext = static_cast<JSTestEnabledForContext*>(handle.slot()->asCell());
+    auto& world = *static_cast<DOMWrapperWorld*>(context);
+    uncacheWrapper(world, &jsTestEnabledForContext->wrapped(), jsTestEnabledForContext);
+}
+
+#if ENABLE(BINDING_INTEGRITY)
+#if PLATFORM(WIN)
+#pragma warning(disable: 4483)
+extern "C" { extern void (*const __identifier("??_7TestEnabledForContext@WebCore@@6B@")[])(); }
+#else
+extern "C" { extern void* _ZTVN7WebCore21TestEnabledForContextE[]; }
+#endif
+#endif
+
+JSC::JSValue toJSNewlyCreated(JSC::ExecState*, JSDOMGlobalObject* globalObject, Ref<TestEnabledForContext>&& impl)
+{
+
+#if ENABLE(BINDING_INTEGRITY)
+    void* actualVTablePointer = *(reinterpret_cast<void**>(impl.ptr()));
+#if PLATFORM(WIN)
+    void* expectedVTablePointer = WTF_PREPARE_VTBL_POINTER_FOR_INSPECTION(__identifier("??_7TestEnabledForContext@WebCore@@6B@"));
+#else
+    void* expectedVTablePointer = WTF_PREPARE_VTBL_POINTER_FOR_INSPECTION(&_ZTVN7WebCore21TestEnabledForContextE[2]);
+#endif
+
+    // If this fails TestEnabledForContext does not have a vtable, so you need to add the
+    // ImplementationLacksVTable attribute to the interface definition
+    static_assert(std::is_polymorphic<TestEnabledForContext>::value, "TestEnabledForContext is not polymorphic");
+
+    // If you hit this assertion you either have a use after free bug, or
+    // TestEnabledForContext has subclasses. If TestEnabledForContext has subclasses that get passed
+    // to toJS() we currently require TestEnabledForContext you to opt out of binding hardening
+    // by adding the SkipVTableValidation attribute to the interface IDL definition
+    RELEASE_ASSERT(actualVTablePointer == expectedVTablePointer);
+#endif
+    return createWrapper<TestEnabledForContext>(globalObject, WTFMove(impl));
+}
+
+JSC::JSValue toJS(JSC::ExecState* state, JSDOMGlobalObject* globalObject, TestEnabledForContext& impl)
+{
+    return wrap(state, globalObject, impl);
+}
+
+TestEnabledForContext* JSTestEnabledForContext::toWrapped(JSC::VM& vm, JSC::JSValue value)
+{
+    if (auto* wrapper = jsDynamicCast<JSTestEnabledForContext*>(vm, value))
+        return &wrapper->wrapped();
+    return nullptr;
+}
+
+}
diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestEnabledForContext.h b/Source/WebCore/bindings/scripts/test/JS/JSTestEnabledForContext.h
new file mode 100644 (file)
index 0000000..b6387d4
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+    This file is part of the WebKit open source project.
+    This file has been generated by generate-bindings.pl. DO NOT MODIFY!
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Library General Public
+    License as published by the Free Software Foundation; either
+    version 2 of the License, or (at your option) any later version.
+
+    This library is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Library General Public License for more details.
+
+    You should have received a copy of the GNU Library General Public License
+    along with this library; see the file COPYING.LIB.  If not, write to
+    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+    Boston, MA 02110-1301, USA.
+*/
+
+#pragma once
+
+#include "JSDOMWrapper.h"
+#include "TestEnabledForContext.h"
+#include <wtf/NeverDestroyed.h>
+
+namespace WebCore {
+
+class JSTestEnabledForContext : public JSDOMWrapper<TestEnabledForContext> {
+public:
+    using Base = JSDOMWrapper<TestEnabledForContext>;
+    static JSTestEnabledForContext* create(JSC::Structure* structure, JSDOMGlobalObject* globalObject, Ref<TestEnabledForContext>&& impl)
+    {
+        JSTestEnabledForContext* ptr = new (NotNull, JSC::allocateCell<JSTestEnabledForContext>(globalObject->vm().heap)) JSTestEnabledForContext(structure, *globalObject, WTFMove(impl));
+        ptr->finishCreation(globalObject->vm());
+        return ptr;
+    }
+
+    static JSC::JSObject* createPrototype(JSC::VM&, JSDOMGlobalObject&);
+    static JSC::JSObject* prototype(JSC::VM&, JSDOMGlobalObject&);
+    static TestEnabledForContext* toWrapped(JSC::VM&, JSC::JSValue);
+    static void destroy(JSC::JSCell*);
+
+    DECLARE_INFO;
+
+    static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
+    {
+        return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
+    }
+
+    static JSC::JSValue getConstructor(JSC::VM&, const JSC::JSGlobalObject*);
+    static void heapSnapshot(JSCell*, JSC::HeapSnapshotBuilder&);
+public:
+    static const unsigned StructureFlags = Base::StructureFlags | JSC::HasStaticPropertyTable;
+protected:
+    JSTestEnabledForContext(JSC::Structure*, JSDOMGlobalObject&, Ref<TestEnabledForContext>&&);
+
+    void finishCreation(JSC::VM&);
+};
+
+class JSTestEnabledForContextOwner : public JSC::WeakHandleOwner {
+public:
+    virtual bool isReachableFromOpaqueRoots(JSC::Handle<JSC::Unknown>, void* context, JSC::SlotVisitor&, const char**);
+    virtual void finalize(JSC::Handle<JSC::Unknown>, void* context);
+};
+
+inline JSC::WeakHandleOwner* wrapperOwner(DOMWrapperWorld&, TestEnabledForContext*)
+{
+    static NeverDestroyed<JSTestEnabledForContextOwner> owner;
+    return &owner.get();
+}
+
+inline void* wrapperKey(TestEnabledForContext* wrappableObject)
+{
+    return wrappableObject;
+}
+
+JSC::JSValue toJS(JSC::ExecState*, JSDOMGlobalObject*, TestEnabledForContext&);
+inline JSC::JSValue toJS(JSC::ExecState* state, JSDOMGlobalObject* globalObject, TestEnabledForContext* impl) { return impl ? toJS(state, globalObject, *impl) : JSC::jsNull(); }
+JSC::JSValue toJSNewlyCreated(JSC::ExecState*, JSDOMGlobalObject*, Ref<TestEnabledForContext>&&);
+inline JSC::JSValue toJSNewlyCreated(JSC::ExecState* state, JSDOMGlobalObject* globalObject, RefPtr<TestEnabledForContext>&& impl) { return impl ? toJSNewlyCreated(state, globalObject, impl.releaseNonNull()) : JSC::jsNull(); }
+
+template<> struct JSDOMWrapperConverterTraits<TestEnabledForContext> {
+    using WrapperClass = JSTestEnabledForContext;
+    using ToWrappedReturnType = TestEnabledForContext*;
+};
+
+} // namespace WebCore
index 35d16ee..b8914f9 100644 (file)
@@ -39,6 +39,7 @@
 #include "JSTestDOMJIT.h"
 #include "JSTestDomainSecurity.h"
 #include "JSTestEnabledBySetting.h"
+#include "JSTestEnabledForContext.h"
 #include "JSTestEventConstructor.h"
 #include "JSTestEventTarget.h"
 #include "JSTestException.h"
@@ -172,6 +173,8 @@ JSC::EncodedJSValue jsTestGlobalObjectTestDomainSecurityConstructor(JSC::ExecSta
 bool setJSTestGlobalObjectTestDomainSecurityConstructor(JSC::ExecState*, JSC::EncodedJSValue, JSC::EncodedJSValue);
 JSC::EncodedJSValue jsTestGlobalObjectTestEnabledBySettingConstructor(JSC::ExecState*, JSC::EncodedJSValue, JSC::PropertyName);
 bool setJSTestGlobalObjectTestEnabledBySettingConstructor(JSC::ExecState*, JSC::EncodedJSValue, JSC::EncodedJSValue);
+JSC::EncodedJSValue jsTestGlobalObjectTestEnabledForContextConstructor(JSC::ExecState*, JSC::EncodedJSValue, JSC::PropertyName);
+bool setJSTestGlobalObjectTestEnabledForContextConstructor(JSC::ExecState*, JSC::EncodedJSValue, JSC::EncodedJSValue);
 JSC::EncodedJSValue jsTestGlobalObjectTestEventConstructorConstructor(JSC::ExecState*, JSC::EncodedJSValue, JSC::PropertyName);
 bool setJSTestGlobalObjectTestEventConstructorConstructor(JSC::ExecState*, JSC::EncodedJSValue, JSC::EncodedJSValue);
 JSC::EncodedJSValue jsTestGlobalObjectTestEventTargetConstructor(JSC::ExecState*, JSC::EncodedJSValue, JSC::PropertyName);
@@ -702,6 +705,8 @@ void JSTestGlobalObject::finishCreation(VM& vm)
     if (RuntimeEnabledFeatures::sharedFeatures().testFeatureEnabled())
         putDirectCustomAccessor(vm, static_cast<JSVMClientData*>(vm.clientData)->builtinNames().enabledAtRuntimeAttributePublicName(), CustomGetterSetter::create(vm, jsTestGlobalObjectEnabledAtRuntimeAttribute, setJSTestGlobalObjectEnabledAtRuntimeAttribute), attributesForStructure(static_cast<unsigned>(JSC::PropertyAttribute::CustomAccessor)));
 #endif
+    if ((jsCast<JSDOMGlobalObject*>(globalObject())->scriptExecutionContext()->isSecureContext() && TestEnabledForContext::enabledForContext(*jsCast<JSDOMGlobalObject*>(globalObject())->scriptExecutionContext())))
+        putDirectCustomAccessor(vm, static_cast<JSVMClientData*>(vm.clientData)->builtinNames().TestEnabledForContextPublicName(), CustomGetterSetter::create(vm, jsTestGlobalObjectTestEnabledForContextConstructor, setJSTestGlobalObjectTestEnabledForContextConstructor), attributesForStructure(static_cast<unsigned>(JSC::PropertyAttribute::DontEnum)));
     putDirectCustomAccessor(vm, static_cast<JSVMClientData*>(vm.clientData)->builtinNames().publicAndPrivateAttributePrivateName(), CustomGetterSetter::create(vm, jsTestGlobalObjectPublicAndPrivateAttribute, nullptr), attributesForStructure(JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly));
 #if ENABLE(TEST_FEATURE)
     putDirectCustomAccessor(vm, static_cast<JSVMClientData*>(vm.clientData)->builtinNames().publicAndPrivateConditionalAttributePrivateName(), CustomGetterSetter::create(vm, jsTestGlobalObjectPublicAndPrivateConditionalAttribute, nullptr), attributesForStructure(JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly));
@@ -1116,6 +1121,30 @@ bool setJSTestGlobalObjectTestEnabledBySettingConstructor(ExecState* state, Enco
     return IDLAttribute<JSTestGlobalObject>::set<setJSTestGlobalObjectTestEnabledBySettingConstructorSetter>(*state, thisValue, encodedValue, "TestEnabledBySetting");
 }
 
+static inline JSValue jsTestGlobalObjectTestEnabledForContextConstructorGetter(ExecState& state, JSTestGlobalObject& thisObject, ThrowScope& throwScope)
+{
+    UNUSED_PARAM(throwScope);
+    UNUSED_PARAM(state);
+    return JSTestEnabledForContext::getConstructor(state.vm(), thisObject.globalObject());
+}
+
+EncodedJSValue jsTestGlobalObjectTestEnabledForContextConstructor(ExecState* state, EncodedJSValue thisValue, PropertyName)
+{
+    return IDLAttribute<JSTestGlobalObject>::get<jsTestGlobalObjectTestEnabledForContextConstructorGetter>(*state, thisValue, "TestEnabledForContext");
+}
+
+static inline bool setJSTestGlobalObjectTestEnabledForContextConstructorSetter(ExecState& state, JSTestGlobalObject& thisObject, JSValue value, ThrowScope& throwScope)
+{
+    UNUSED_PARAM(throwScope);
+    // Shadowing a built-in constructor.
+    return thisObject.putDirect(state.vm(), Identifier::fromString(&state.vm(), reinterpret_cast<const LChar*>("TestEnabledForContext"), strlen("TestEnabledForContext")), value);
+}
+
+bool setJSTestGlobalObjectTestEnabledForContextConstructor(ExecState* state, EncodedJSValue thisValue, EncodedJSValue encodedValue)
+{
+    return IDLAttribute<JSTestGlobalObject>::set<setJSTestGlobalObjectTestEnabledForContextConstructorSetter>(*state, thisValue, encodedValue, "TestEnabledForContext");
+}
+
 static inline JSValue jsTestGlobalObjectTestEventConstructorConstructorGetter(ExecState& state, JSTestGlobalObject& thisObject, ThrowScope& throwScope)
 {
     UNUSED_PARAM(throwScope);
diff --git a/Source/WebCore/bindings/scripts/test/TestEnabledForContext.idl b/Source/WebCore/bindings/scripts/test/TestEnabledForContext.idl
new file mode 100644 (file)
index 0000000..2e70944
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+[
+    EnabledForContext,
+    SecureContext,
+] interface TestEnabledForContext {
+    [EnabledBySetting=TestSetting, EnabledForContext] attribute TestSubObjConstructor TestSubObjEnabledForContext;
+};
index 79e91b5..40a60b5 100644 (file)
@@ -1,3 +1,20 @@
+2019-05-14  Andy Estes  <aestes@apple.com>
+
+        [Apple Pay] Payment APIs should be completely disabled in web views into which clients have injected user scripts
+        https://bugs.webkit.org/show_bug.cgi?id=197751
+        <rdar://problem/50631563>
+
+        Reviewed by Alex Christensen.
+
+        Added new API tests.
+
+        * TestWebKitAPI/Tests/WebKitCocoa/ApplePay.mm:
+        (-[TestApplePayScriptMessageHandler initWithAPIsAvailableExpectation:canMakePaymentsExpectation:]):
+        (-[TestApplePayScriptMessageHandler userContentController:didReceiveScriptMessage:]):
+        (TestWebKitAPI::TEST):
+        (-[TestApplePayScriptMessageHandler initWithExpectation:]): Deleted.
+        * TestWebKitAPI/Tests/WebKitCocoa/apple-pay-availability.html:
+
 2019-05-14  Youenn Fablet  <youenn@apple.com>
 
         A service worker process should app nap when all its clients app nap
index a2b5801..061b331 100644 (file)
@@ -41,29 +41,33 @@ static bool isDone;
 @interface TestApplePayScriptMessageHandler : NSObject <WKScriptMessageHandler>
 
 - (instancetype)init NS_UNAVAILABLE;
-- (instancetype)initWithExpectation:(BOOL)expectation;
+- (instancetype)initWithAPIsAvailableExpectation:(BOOL)apisAvailableExpectation canMakePaymentsExpectation:(BOOL)canMakePaymentsExpectation;
+
+@property (nonatomic, setter=setAPIsAvailableExpectation:) BOOL apisAvailableExpectation;
+@property (nonatomic) BOOL canMakePaymentsExpectation;
 
 @end
 
-@implementation TestApplePayScriptMessageHandler {
-    BOOL _expectation;
-}
+@implementation TestApplePayScriptMessageHandler
 
-- (instancetype)initWithExpectation:(BOOL)expectation
+- (instancetype)initWithAPIsAvailableExpectation:(BOOL)apisAvailableExpectation canMakePaymentsExpectation:(BOOL)canMakePaymentsExpectation
 {
     if (!(self = [super init]))
         return nil;
 
-    _expectation = expectation;
+    _apisAvailableExpectation = apisAvailableExpectation;
+    _canMakePaymentsExpectation = canMakePaymentsExpectation;
     return self;
 }
 
 - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
 {
-    EXPECT_EQ(_expectation, [[message.body objectForKey:@"supportsVersion"] boolValue]);
-    EXPECT_EQ(_expectation, [[message.body objectForKey:@"canMakePayments"] boolValue]);
-    EXPECT_EQ(_expectation, [[message.body objectForKey:@"canMakePaymentsWithActiveCard"] boolValue]);
-    EXPECT_EQ(_expectation, [[message.body objectForKey:@"canMakePayment"] boolValue]);
+    EXPECT_EQ(_apisAvailableExpectation, [[message.body objectForKey:@"applePaySessionAvailable"] boolValue]);
+    EXPECT_EQ(_apisAvailableExpectation, [[message.body objectForKey:@"paymentRequestAvailable"] boolValue]);
+    EXPECT_EQ(_canMakePaymentsExpectation, [[message.body objectForKey:@"supportsVersion"] boolValue]);
+    EXPECT_EQ(_canMakePaymentsExpectation, [[message.body objectForKey:@"canMakePayments"] boolValue]);
+    EXPECT_EQ(_canMakePaymentsExpectation, [[message.body objectForKey:@"canMakePaymentsWithActiveCard"] boolValue]);
+    EXPECT_EQ(_canMakePaymentsExpectation, [[message.body objectForKey:@"canMakePayment"] boolValue]);
     isDone = true;
 }
 
@@ -75,7 +79,7 @@ TEST(ApplePay, ApplePayAvailableByDefault)
 {
     [TestProtocol registerWithScheme:@"https"];
 
-    auto messageHandler = adoptNS([[TestApplePayScriptMessageHandler alloc] initWithExpectation:YES]);
+    auto messageHandler = adoptNS([[TestApplePayScriptMessageHandler alloc] initWithAPIsAvailableExpectation:YES canMakePaymentsExpectation:YES]);
 
     WKWebViewConfiguration *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"WebProcessPlugInWithInternals"];
     [configuration.userContentController addScriptMessageHandler:messageHandler.get() name:@"testApplePay"];
@@ -88,11 +92,11 @@ TEST(ApplePay, ApplePayAvailableByDefault)
     [TestProtocol unregister];
 }
 
-TEST(ApplePay, UserScriptDisablesApplePay)
+TEST(ApplePay, UserScriptAtDocumentStartDisablesApplePay)
 {
     [TestProtocol registerWithScheme:@"https"];
 
-    auto messageHandler = adoptNS([[TestApplePayScriptMessageHandler alloc] initWithExpectation:NO]);
+    auto messageHandler = adoptNS([[TestApplePayScriptMessageHandler alloc] initWithAPIsAvailableExpectation:NO canMakePaymentsExpectation:NO]);
     auto userScript = adoptNS([[WKUserScript alloc] initWithSource:@"window.wkUserScriptInjected = true" injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES]);
 
     WKWebViewConfiguration *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"WebProcessPlugInWithInternals"];
@@ -109,11 +113,32 @@ TEST(ApplePay, UserScriptDisablesApplePay)
     [TestProtocol unregister];
 }
 
+TEST(ApplePay, UserScriptAtDocumentEndDisablesApplePay)
+{
+    [TestProtocol registerWithScheme:@"https"];
+    
+    auto messageHandler = adoptNS([[TestApplePayScriptMessageHandler alloc] initWithAPIsAvailableExpectation:NO canMakePaymentsExpectation:NO]);
+    auto userScript = adoptNS([[WKUserScript alloc] initWithSource:@"window.wkUserScriptInjected = true" injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES]);
+    
+    WKWebViewConfiguration *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"WebProcessPlugInWithInternals"];
+    [configuration.userContentController addUserScript:userScript.get()];
+    [configuration.userContentController addScriptMessageHandler:messageHandler.get() name:@"testApplePay"];
+    
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectZero configuration:configuration]);
+    [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://bundle-html-file/apple-pay-availability"]]];
+    
+    Util::run(&isDone);
+    
+    EXPECT_EQ(YES, [[webView objectByEvaluatingJavaScript:@"window.wkUserScriptInjected"] boolValue]);
+    
+    [TestProtocol unregister];
+}
+
 TEST(ApplePay, UserAgentScriptEvaluationDisablesApplePay)
 {
     [TestProtocol registerWithScheme:@"https"];
 
-    auto messageHandler = adoptNS([[TestApplePayScriptMessageHandler alloc] initWithExpectation:NO]);
+    auto messageHandler = adoptNS([[TestApplePayScriptMessageHandler alloc] initWithAPIsAvailableExpectation:YES canMakePaymentsExpectation:NO]);
 
     WKWebViewConfiguration *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"WebProcessPlugInWithInternals"];
     [configuration.userContentController addScriptMessageHandler:messageHandler.get() name:@"testApplePay"];
@@ -130,6 +155,29 @@ TEST(ApplePay, UserAgentScriptEvaluationDisablesApplePay)
     [TestProtocol unregister];
 }
 
+TEST(ApplePay, UserAgentScriptEvaluationDisablesApplePayInExistingObjects)
+{
+    [TestProtocol registerWithScheme:@"https"];
+
+    auto messageHandler = adoptNS([[TestApplePayScriptMessageHandler alloc] initWithAPIsAvailableExpectation:YES canMakePaymentsExpectation:YES]);
+
+    WKWebViewConfiguration *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"WebProcessPlugInWithInternals"];
+    [configuration.userContentController addScriptMessageHandler:messageHandler.get() name:@"testApplePay"];
+
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectZero configuration:configuration]);
+    [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://bundle-html-file/apple-pay-availability"]]];
+
+    Util::run(&isDone);
+
+    isDone = false;
+    [messageHandler setCanMakePaymentsExpectation:NO];
+    [webView evaluateJavaScript:@"document.location.hash = '#test'" completionHandler:nil];
+
+    Util::run(&isDone);
+
+    [TestProtocol unregister];
+}
+
 TEST(ApplePay, ActiveSessionBlocksUserAgentScripts)
 {
     [TestProtocol registerWithScheme:@"https"];
index f302c6a..6a8bc5a 100644 (file)
         };
     };
 
-    window.addEventListener('load', async () => {
+    const eventListener = async () => {
         internals.mockPaymentCoordinator.supportsUnrestrictedApplePay = false;
 
+        const applePaySessionAvailable = !!window.ApplePaySession;
+        const paymentRequestAvailable = !!window.PaymentRequest;
+        if (!applePaySessionAvailable || !paymentRequestAvailable) {
+            window.webkit.messageHandlers.testApplePay.postMessage({ applePaySessionAvailable, paymentRequestAvailable });
+            return;
+        }
+
         const supportsVersion = ApplePaySession.supportsVersion(1);
         const canMakePayments = ApplePaySession.canMakePayments();
         const canMakePaymentsWithActiveCard = await ApplePaySession.canMakePaymentsWithActiveCard('');
 
-        const paymentRequest = new PaymentRequest([applePayMethod()], {
-            total: {
-                label: 'total',
-                amount: { currency: 'USD', value: '0.00' },
-            },
-        });
+        if (!window.wkPaymentRequest) {
+            wkPaymentRequest = new PaymentRequest([applePayMethod()], {
+                total: {
+                    label: 'total',
+                    amount: { currency: 'USD', value: '0.00' },
+                },
+            });
+        }
 
-        const canMakePayment = await paymentRequest.canMakePayment();
+        const canMakePayment = await wkPaymentRequest.canMakePayment();
 
         window.webkit.messageHandlers.testApplePay.postMessage({
+            applePaySessionAvailable,
+            paymentRequestAvailable,
             supportsVersion,
             canMakePayments,
             canMakePaymentsWithActiveCard,
             canMakePayment,
         });
-    });
+    };
+
+    window.addEventListener('load', eventListener);
+    window.addEventListener('hashchange', eventListener);
 </script>