classList.toggle(name, force) treats undefined `force` argument as false
authorcdumez@apple.com <cdumez@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 18 Sep 2015 16:53:10 +0000 (16:53 +0000)
committercdumez@apple.com <cdumez@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 18 Sep 2015 16:53:10 +0000 (16:53 +0000)
https://bugs.webkit.org/show_bug.cgi?id=148582
<rdar://problem/22545600>

Reviewed by Ryosuke Niwa.

Source/WebCore:

classList.toggle(name, force) treats undefined `force` argument as false.
However, according to the Web IDL specification, we should treat undefined
as if the value was missing for optional parameters that do not have a
default value:
https://heycam.github.io/webidl/#dfn-overload-resolution-algorithm (Step 14.4).

For optional parameters that have a default value, undefined should be
converted into the default value. This is supported as of r189957.

In this patch, we use custom bindings to provide a spec-compliant version
of DOMTokenList.toggle(). Unfortunately, adding such support in the
bindings generator would be a non-trivial task (I guess, we would have to
generalize using WTF::Optional<> type for all optional parameters in our
implementation. Also we cannot use the default value support added in
r189957 because the toggle() implementation needs to be able to
distinguish all 3 states for the 'force' parameter: true, false or
missing.

The new behavior matches the behavior of Firefox and the specification.

Test: fast/dom/Element/class-list-toggle.html

* CMakeLists.txt:
* WebCore.vcxproj/WebCore.vcxproj:
* WebCore.vcxproj/WebCore.vcxproj.filters:
* WebCore.xcodeproj/project.pbxproj:
* bindings/js/JSBindingsAllInOne.cpp:
* bindings/js/JSDOMTokenListCustom.cpp: Added.
(WebCore::JSDOMTokenList::toggle):
* html/DOMTokenList.h:
* html/DOMTokenList.idl:

LayoutTests:

Add decent test coverage for DOMTokenList.toggle() via Element.classList.

* fast/dom/Element/class-list-toggle-expected.txt: Added.
* fast/dom/Element/class-list-toggle.html: Added.

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

12 files changed:
LayoutTests/ChangeLog
LayoutTests/fast/dom/Element/class-list-toggle-expected.txt [new file with mode: 0644]
LayoutTests/fast/dom/Element/class-list-toggle.html [new file with mode: 0644]
Source/WebCore/CMakeLists.txt
Source/WebCore/ChangeLog
Source/WebCore/WebCore.vcxproj/WebCore.vcxproj
Source/WebCore/WebCore.vcxproj/WebCore.vcxproj.filters
Source/WebCore/WebCore.xcodeproj/project.pbxproj
Source/WebCore/bindings/js/JSBindingsAllInOne.cpp
Source/WebCore/bindings/js/JSDOMTokenListCustom.cpp [new file with mode: 0644]
Source/WebCore/html/DOMTokenList.h
Source/WebCore/html/DOMTokenList.idl

index 29ede4a..c1b1f8e 100644 (file)
@@ -1,3 +1,16 @@
+2015-09-18  Chris Dumez  <cdumez@apple.com>
+
+        classList.toggle(name, force) treats undefined `force` argument as false
+        https://bugs.webkit.org/show_bug.cgi?id=148582
+        <rdar://problem/22545600>
+
+        Reviewed by Ryosuke Niwa.
+
+        Add decent test coverage for DOMTokenList.toggle() via Element.classList.
+
+        * fast/dom/Element/class-list-toggle-expected.txt: Added.
+        * fast/dom/Element/class-list-toggle.html: Added.
+
 2015-09-17  Commit Queue  <commit-queue@webkit.org>
 
         Unreviewed, rolling out r189962.
diff --git a/LayoutTests/fast/dom/Element/class-list-toggle-expected.txt b/LayoutTests/fast/dom/Element/class-list-toggle-expected.txt
new file mode 100644 (file)
index 0000000..53ed51c
--- /dev/null
@@ -0,0 +1,53 @@
+Tests the behavior of DOMTokenList.toggle()
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+* Not enough parameters
+PASS div.classList.toggle() threw exception TypeError: Not enough arguments.
+
+* Token is an empty string
+PASS div.classList.toggle('') threw exception Error: SyntaxError: DOM Exception 12.
+
+Token contains an ASCII white space
+PASS div.classList.toggle(stringWithSpace) threw exception Error: InvalidCharacterError: DOM Exception 5.
+PASS div.classList.toggle(stringWithSpace) threw exception Error: InvalidCharacterError: DOM Exception 5.
+PASS div.classList.toggle(stringWithSpace) threw exception Error: InvalidCharacterError: DOM Exception 5.
+PASS div.classList.toggle(stringWithSpace) threw exception Error: InvalidCharacterError: DOM Exception 5.
+PASS div.classList.toggle(stringWithSpace) threw exception Error: InvalidCharacterError: DOM Exception 5.
+
+* 'force' parameter omitted, token does not exist
+div.classList.toggle('a')
+PASS div.classList[0] is "a"
+
+* 'force' parameter omitted, token exists
+div.classList.toggle('a')
+PASS div.classList.length is 0
+
+* 'force' parameter is undefined, token does not exist
+div.classList.toggle('a', undefined)
+PASS div.classList[0] is "a"
+
+* 'force' parameter is undefined, token exists
+div.classList.toggle('a', undefined)
+PASS div.classList.length is 0
+
+* 'force' parameter is false, token does not exist
+div.classList.toggle('a', false)
+PASS div.classList.length is 0
+
+* 'force' parameter is true, token does not exist
+div.classList.toggle('a', true)
+PASS div.classList[0] is "a"
+
+* 'force' parameter is true, token exists
+div.classList.toggle('a', true)
+PASS div.classList[0] is "a"
+
+* 'force' parameter is false, token exists
+div.classList.toggle('a', false)
+PASS div.classList.length is 0
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/fast/dom/Element/class-list-toggle.html b/LayoutTests/fast/dom/Element/class-list-toggle.html
new file mode 100644 (file)
index 0000000..1dc3079
--- /dev/null
@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<html>
+<head>
+<link rel="help" href="https://dom.spec.whatwg.org/#dom-domtokenlist-toggle"/>
+<script src="../../../resources/js-test-pre.js"></script>
+</head>
+<body>
+<script>
+description("Tests the behavior of DOMTokenList.toggle()");
+
+var div = document.createElement('div');
+
+debug("* Not enough parameters");
+shouldThrow("div.classList.toggle()", "'TypeError: Not enough arguments'");
+
+debug("");
+debug("* Token is an empty string");
+shouldThrow("div.classList.toggle('')", "'Error: SyntaxError: DOM Exception 12'");
+
+debug("");
+debug("Token contains an ASCII white space");
+var stringWithSpace = "a b";
+shouldThrow("div.classList.toggle(stringWithSpace)", "'Error: InvalidCharacterError: DOM Exception 5'");
+stringWithSpace = "a\nb";
+shouldThrow("div.classList.toggle(stringWithSpace)", "'Error: InvalidCharacterError: DOM Exception 5'");
+stringWithSpace = "a\tb";
+shouldThrow("div.classList.toggle(stringWithSpace)", "'Error: InvalidCharacterError: DOM Exception 5'");
+stringWithSpace = "a\rb";
+shouldThrow("div.classList.toggle(stringWithSpace)", "'Error: InvalidCharacterError: DOM Exception 5'");
+stringWithSpace = "a\fb";
+shouldThrow("div.classList.toggle(stringWithSpace)", "'Error: InvalidCharacterError: DOM Exception 5'");
+
+debug("");
+debug("* 'force' parameter omitted, token does not exist");
+evalAndLog("div.classList.toggle('a')");
+shouldBeEqualToString("div.classList[0]", "a"); // Should add the class.
+
+debug("");
+debug("* 'force' parameter omitted, token exists");
+evalAndLog("div.classList.toggle('a')");
+shouldBe("div.classList.length", "0"); // Should remove the class.
+
+debug("");
+debug("* 'force' parameter is undefined, token does not exist");
+evalAndLog("div.classList.toggle('a', undefined)");
+shouldBeEqualToString("div.classList[0]", "a"); // Should add the class.
+
+debug("");
+debug("* 'force' parameter is undefined, token exists");
+evalAndLog("div.classList.toggle('a', undefined)");
+shouldBe("div.classList.length", "0"); // Should remove the class.
+
+debug("");
+debug("* 'force' parameter is false, token does not exist");
+evalAndLog("div.classList.toggle('a', false)");
+shouldBe("div.classList.length", "0"); // Should not add the class.
+
+debug("");
+debug("* 'force' parameter is true, token does not exist");
+evalAndLog("div.classList.toggle('a', true)");
+shouldBeEqualToString("div.classList[0]", "a"); // Should add the class.
+
+debug("");
+debug("* 'force' parameter is true, token exists");
+evalAndLog("div.classList.toggle('a', true)");
+shouldBeEqualToString("div.classList[0]", "a"); // Should not remove the class.
+
+debug("");
+debug("* 'force' parameter is false, token exists");
+evalAndLog("div.classList.toggle('a', false)");
+shouldBe("div.classList.length", "0"); // Should remove the class.
+</script>
+<script src="../../../resources/js-test-post.js"></script>
+</body>
+</html>
index 9d65e79..ce259d9 100644 (file)
@@ -1121,6 +1121,7 @@ set(WebCore_SOURCES
     bindings/js/JSDOMPromise.cpp
     bindings/js/JSDOMStringListCustom.cpp
     bindings/js/JSDOMStringMapCustom.cpp
+    bindings/js/JSDOMTokenListCustom.cpp
     bindings/js/JSDOMWindowBase.cpp
     bindings/js/JSDOMWindowCustom.cpp
     bindings/js/JSDOMWindowShell.cpp
index f15ddbb..276b280 100644 (file)
@@ -1,3 +1,43 @@
+2015-09-18  Chris Dumez  <cdumez@apple.com>
+
+        classList.toggle(name, force) treats undefined `force` argument as false
+        https://bugs.webkit.org/show_bug.cgi?id=148582
+        <rdar://problem/22545600>
+
+        Reviewed by Ryosuke Niwa.
+
+        classList.toggle(name, force) treats undefined `force` argument as false.
+        However, according to the Web IDL specification, we should treat undefined
+        as if the value was missing for optional parameters that do not have a
+        default value:
+        https://heycam.github.io/webidl/#dfn-overload-resolution-algorithm (Step 14.4).
+
+        For optional parameters that have a default value, undefined should be
+        converted into the default value. This is supported as of r189957.
+
+        In this patch, we use custom bindings to provide a spec-compliant version
+        of DOMTokenList.toggle(). Unfortunately, adding such support in the
+        bindings generator would be a non-trivial task (I guess, we would have to
+        generalize using WTF::Optional<> type for all optional parameters in our
+        implementation. Also we cannot use the default value support added in
+        r189957 because the toggle() implementation needs to be able to
+        distinguish all 3 states for the 'force' parameter: true, false or
+        missing.
+
+        The new behavior matches the behavior of Firefox and the specification.
+
+        Test: fast/dom/Element/class-list-toggle.html
+
+        * CMakeLists.txt:
+        * WebCore.vcxproj/WebCore.vcxproj:
+        * WebCore.vcxproj/WebCore.vcxproj.filters:
+        * WebCore.xcodeproj/project.pbxproj:
+        * bindings/js/JSBindingsAllInOne.cpp:
+        * bindings/js/JSDOMTokenListCustom.cpp: Added.
+        (WebCore::JSDOMTokenList::toggle):
+        * html/DOMTokenList.h:
+        * html/DOMTokenList.idl:
+
 2015-09-17  Joseph Pecoraro  <pecoraro@apple.com>
 
         Web Inspector: Remove unused canClearBrowserCookies / canClearBrowserCache protocol methods
index b4f0b56..b30191f 100644 (file)
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Production|Win32'">true</ExcludedFromBuild>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Production|x64'">true</ExcludedFromBuild>
     </ClCompile>
+    <ClCompile Include="..\bindings\js\JSDOMTokenListCustom.cpp">
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug_WinCairo|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug_WinCairo|x64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='DebugSuffix|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='DebugSuffix|x64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release_WinCairo|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release_WinCairo|x64'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Production|Win32'">true</ExcludedFromBuild>
+      <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Production|x64'">true</ExcludedFromBuild>
+    </ClCompile>
     <ClCompile Include="..\bindings\js\JSDOMWindowBase.cpp">
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
       <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
index 4a04885..742f797 100644 (file)
     <ClCompile Include="..\bindings\js\JSDOMStringMapCustom.cpp">
       <Filter>bindings\js</Filter>
     </ClCompile>
+    <ClCompile Include="..\bindings\js\JSDOMTokenListCustom.cpp">
+      <Filter>bindings\js</Filter>
+    </ClCompile>
     <ClCompile Include="..\bindings\js\JSErrorHandler.cpp">
       <Filter>bindings\js</Filter>
     </ClCompile>
index cc53d8b..51e0d2e 100644 (file)
                835D363719FF6193004C93AB /* StyleBuilderCustom.h in Headers */ = {isa = PBXBuildFile; fileRef = 835D363619FF6193004C93AB /* StyleBuilderCustom.h */; };
                836FBCEA178C113200B21A15 /* SVGAnimatedTypeAnimator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 836FBCE9178C113200B21A15 /* SVGAnimatedTypeAnimator.cpp */; };
                836FBCEC178C117F00B21A15 /* SVGAnimatedProperty.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 836FBCEB178C117F00B21A15 /* SVGAnimatedProperty.cpp */; };
+               837131781BAB7A720009363B /* JSDOMTokenListCustom.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 837131771BAB7A720009363B /* JSDOMTokenListCustom.cpp */; };
                8372DB311A6780A800C697C5 /* DiagnosticLoggingResultType.h in Headers */ = {isa = PBXBuildFile; fileRef = 8372DB301A6780A800C697C5 /* DiagnosticLoggingResultType.h */; settings = {ATTRIBUTES = (Private, ); }; };
                8386A96D19F61B2E00E1EC4A /* StyleBuilder.h in Headers */ = {isa = PBXBuildFile; fileRef = 8386A96C19F61B2E00E1EC4A /* StyleBuilder.h */; };
                8386A97019F61E4F00E1EC4A /* StyleBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8386A96E19F61E4F00E1EC4A /* StyleBuilder.cpp */; };
                8369E58F1AFDD0300087DF68 /* NonDocumentTypeChildNode.idl */ = {isa = PBXFileReference; lastKnownFileType = text; path = NonDocumentTypeChildNode.idl; sourceTree = "<group>"; };
                836FBCE9178C113200B21A15 /* SVGAnimatedTypeAnimator.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SVGAnimatedTypeAnimator.cpp; sourceTree = "<group>"; };
                836FBCEB178C117F00B21A15 /* SVGAnimatedProperty.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SVGAnimatedProperty.cpp; sourceTree = "<group>"; };
+               837131771BAB7A720009363B /* JSDOMTokenListCustom.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSDOMTokenListCustom.cpp; sourceTree = "<group>"; };
                8372DB301A6780A800C697C5 /* DiagnosticLoggingResultType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DiagnosticLoggingResultType.h; sourceTree = "<group>"; };
                8386A96C19F61B2E00E1EC4A /* StyleBuilder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StyleBuilder.h; sourceTree = "<group>"; };
                8386A96E19F61E4F00E1EC4A /* StyleBuilder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StyleBuilder.cpp; sourceTree = "<group>"; };
                                46F2768E1B85297F005C2556 /* JSDOMNamedFlowCollectionCustom.cpp */,
                                E172AF8D1811BC3700FBADB9 /* JSDOMPromise.cpp */,
                                E172AF8E1811BC3700FBADB9 /* JSDOMPromise.h */,
+                               837131771BAB7A720009363B /* JSDOMTokenListCustom.cpp */,
                                BC6932710D7E293900AE44D1 /* JSDOMWindowBase.cpp */,
                                BC6932720D7E293900AE44D1 /* JSDOMWindowBase.h */,
                                BCBFB53A0DCD29CF0019B3E5 /* JSDOMWindowShell.cpp */,
                                F34742DC134362F000531BC2 /* PageDebuggerAgent.cpp in Sources */,
                                9302B0BD0D79F82900C7EE83 /* PageGroup.cpp in Sources */,
                                7A674BDB0F9EBF4E006CF099 /* PageGroupLoadDeferrer.cpp in Sources */,
+                               837131781BAB7A720009363B /* JSDOMTokenListCustom.cpp in Sources */,
                                1C26497C0D7E24EC00BD10F2 /* PageMac.cpp in Sources */,
                                5103C2B61BA233BF00E26337 /* IDBRequest.cpp in Sources */,
                                2D5C9CFF19C7B52E00B3C5C1 /* PageOverlay.cpp in Sources */,
index 016efb0..ac10ec7 100644 (file)
@@ -61,6 +61,7 @@
 #include "JSDOMPromise.cpp"
 #include "JSDOMStringListCustom.cpp"
 #include "JSDOMStringMapCustom.cpp"
+#include "JSDOMTokenListCustom.cpp"
 #include "JSDOMWindowBase.cpp"
 #include "JSDOMWindowCustom.cpp"
 #include "JSDOMWindowShell.cpp"
diff --git a/Source/WebCore/bindings/js/JSDOMTokenListCustom.cpp b/Source/WebCore/bindings/js/JSDOMTokenListCustom.cpp
new file mode 100644 (file)
index 0000000..0a19fc6
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#include "config.h"
+#include "JSDOMTokenList.h"
+
+#include "JSDOMBinding.h"
+#include <runtime/Error.h>
+#include <wtf/Optional.h>
+
+using namespace JSC;
+
+namespace WebCore {
+
+JSValue JSDOMTokenList::toggle(ExecState* state)
+{
+    if (UNLIKELY(state->argumentCount() < 1))
+        return state->vm().throwException(state, createNotEnoughArgumentsError(state));
+
+    ExceptionCode ec = 0;
+    String token = state->argument(0).toString(state)->value(state);
+    if (UNLIKELY(state->hadException()))
+        return jsUndefined();
+
+    // toggle() needs to be able to distinguish undefined/missing from the false value for the 'force' parameter.
+    Optional<bool> force = state->argument(1).isUndefined() ? Nullopt : Optional<bool>(state->uncheckedArgument(1).toBoolean(state));
+    if (UNLIKELY(state->hadException()))
+        return jsUndefined();
+    JSValue result = jsBoolean(impl().toggle(token, force, ec));
+
+    setDOMException(state, ec);
+    return result;
+}
+
+
+}
index e1f5232..dc6ddfe 100644 (file)
@@ -52,7 +52,6 @@ public:
     void add(const AtomicString&, ExceptionCode&);
     void remove(const Vector<String>&, ExceptionCode&);
     void remove(const AtomicString&, ExceptionCode&);
-    bool toggle(const AtomicString&, ExceptionCode&);
     bool toggle(const AtomicString&, Optional<bool> force, ExceptionCode&);
 
     const AtomicString& toString() const { return value(); }
@@ -86,11 +85,6 @@ inline const AtomicString& DOMTokenList::item(unsigned index) const
     return index < m_tokens.size() ? m_tokens[index] : nullAtom;
 }
 
-inline bool DOMTokenList::toggle(const AtomicString& token, ExceptionCode& ec)
-{
-    return toggle(token, Nullopt, ec);
-}
-
 } // namespace WebCore
 
 #endif // DOMTokenList_h
index d7ace2e..322718b 100644 (file)
@@ -31,7 +31,7 @@
     [RaisesException] boolean contains(DOMString token);
     [RaisesException] void add(DOMString... tokens);
     [RaisesException] void remove(DOMString... tokens);
-    [RaisesException] boolean toggle(DOMString token, optional boolean force);
+    [RaisesException, Custom] boolean toggle(DOMString token, optional boolean force);
 
 #if defined(LANGUAGE_JAVASCRIPT) && LANGUAGE_JAVASCRIPT
     [NotEnumerable] DOMString toString();