[webkit-changes] cvs commit: WebCore/kwq KWQCharsets.mm KWQString.h
KWQString.mm KWQTextCodec.mm
Maciej
mjs at opensource.apple.com
Mon Jun 27 18:54:36 PDT 2005
mjs 05/06/27 18:54:36
Modified: . ChangeLog
WebCore.pbproj project.pbxproj
khtml/xml dom_atomicstring.cpp dom_atomicstring.h
dom_stringimpl.cpp
kwq KWQCharsets.mm KWQString.h KWQString.mm
KWQTextCodec.mm
Log:
Reviewed by Darin.
- replaced all our hash functions with the state of the art in hashing
- ~1% speedup on cvs-base
No test cases added, perf effects only.
* khtml/xml/dom_atomicstring.cpp:
(DOM::AtomicStringStatisticsExitLogger::~AtomicStringStatisticsExitLogger):
Improved stats gathering to track collisions in more detail and count reinserts
on remove.
(DOM::addToCollisionCount): ditto
(DOM::AtomicString::add): ditto
(DOM::AtomicString::insert): ditto
(DOM::AtomicString::remove): ditto
* khtml/xml/dom_stringimpl.cpp:
(DOM::DOMStringImpl::computeHash): Replace with SuperFastHash algorithm.
* kwq/KWQCharsets.mm:
(encodingNameHash): Clean up mistaken shift in the wrong direction.
* kwq/KWQString.h: Removed unused hashing code.
* kwq/KWQString.mm: ditto
* kwq/KWQTextCodec.mm:
(QTextCodec::hash): Use a variant of the SuperFastHash algorithm.
Revision Changes Path
1.4319 +26 -0 WebCore/ChangeLog
Index: ChangeLog
===================================================================
RCS file: /cvs/root/WebCore/ChangeLog,v
retrieving revision 1.4318
retrieving revision 1.4319
diff -u -r1.4318 -r1.4319
--- ChangeLog 27 Jun 2005 19:12:30 -0000 1.4318
+++ ChangeLog 28 Jun 2005 01:54:29 -0000 1.4319
@@ -1,3 +1,29 @@
+2005-06-26 Maciej Stachowiak <mjs at apple.com>
+
+ Reviewed by Darin.
+
+ - replaced all our hash functions with the state of the art in hashing
+ - ~1% speedup on cvs-base
+
+ No test cases added, perf effects only.
+
+ * khtml/xml/dom_atomicstring.cpp:
+ (DOM::AtomicStringStatisticsExitLogger::~AtomicStringStatisticsExitLogger):
+ Improved stats gathering to track collisions in more detail and count reinserts
+ on remove.
+ (DOM::addToCollisionCount): ditto
+ (DOM::AtomicString::add): ditto
+ (DOM::AtomicString::insert): ditto
+ (DOM::AtomicString::remove): ditto
+ * khtml/xml/dom_stringimpl.cpp:
+ (DOM::DOMStringImpl::computeHash): Replace with SuperFastHash algorithm.
+ * kwq/KWQCharsets.mm:
+ (encodingNameHash): Clean up mistaken shift in the wrong direction.
+ * kwq/KWQString.h: Removed unused hashing code.
+ * kwq/KWQString.mm: ditto
+ * kwq/KWQTextCodec.mm:
+ (QTextCodec::hash): Use a variant of the SuperFastHash algorithm.
+
2005-06-27 David Harrison <harrison at apple.com>
Reviewed by Ken.
1.557 +48 -0 WebCore/WebCore.pbproj/project.pbxproj
Index: project.pbxproj
===================================================================
RCS file: /cvs/root/WebCore/WebCore.pbproj/project.pbxproj,v
retrieving revision 1.556
retrieving revision 1.557
diff -u -r1.556 -r1.557
--- project.pbxproj 22 Jun 2005 15:31:57 -0000 1.556
+++ project.pbxproj 28 Jun 2005 01:54:34 -0000 1.557
@@ -1279,6 +1279,48 @@
settings = {
};
};
+ 65F378260870D958000B2F94 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.c.h;
+ path = hashset.h;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 65F378270870D958000B2F94 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.cpp.cpp;
+ path = hashtable.cpp;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 65F378280870D958000B2F94 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.c.h;
+ path = hashtable.h;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 65F378290870D958000B2F94 = {
+ fileRef = 65F378260870D958000B2F94;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 65F3782A0870D958000B2F94 = {
+ fileRef = 65F378270870D958000B2F94;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 65F3782B0870D958000B2F94 = {
+ fileRef = 65F378280870D958000B2F94;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
65F80697054D9F86008BF776 = {
fileEncoding = 30;
isa = PBXFileReference;
@@ -2724,6 +2766,8 @@
1A69D381085627410009880D,
550A0BCA085F6039007353D6,
550A0BCE085F604D007353D6,
+ 65F378290870D958000B2F94,
+ 65F3782B0870D958000B2F94,
);
isa = PBXHeadersBuildPhase;
runOnlyForDeploymentPostprocessing = 0;
@@ -5097,6 +5141,7 @@
1A69D382085627410009880D,
550A0BC9085F6039007353D6,
550A0BCD085F604D007353D6,
+ 65F3782A0870D958000B2F94,
);
isa = PBXSourcesBuildPhase;
runOnlyForDeploymentPostprocessing = 0;
@@ -8790,6 +8835,9 @@
};
F523D29C02DE43D9018635CA = {
children = (
+ 65F378260870D958000B2F94,
+ 65F378270870D958000B2F94,
+ 65F378280870D958000B2F94,
BC7294F703804B3C00A80166,
BC7294F803804B3C00A80166,
F523D27802DE43D7018635CA,
1.4 +70 -16 WebCore/khtml/xml/dom_atomicstring.cpp
Index: dom_atomicstring.cpp
===================================================================
RCS file: /cvs/root/WebCore/khtml/xml/dom_atomicstring.cpp,v
retrieving revision 1.3
retrieving revision 1.4
diff -u -r1.3 -r1.4
--- dom_atomicstring.cpp 5 Feb 2004 01:45:16 -0000 1.3
+++ dom_atomicstring.cpp 28 Jun 2005 01:54:34 -0000 1.4
@@ -43,8 +43,13 @@
#if DUMP_STATISTICS
-static int numProbes;
+static int numAccesses;
static int numCollisions;
+static int collisionGraph[4096];
+static int maxCollisions;
+static int numRehashes;
+static int numRemoves;
+static int numReinserts;
struct AtomicStringStatisticsExitLogger { ~AtomicStringStatisticsExitLogger(); };
@@ -52,11 +57,24 @@
AtomicStringStatisticsExitLogger::~AtomicStringStatisticsExitLogger()
{
- printf("\nDOM::AtomicString statistics\n\n");
- printf("%d probes\n", numProbes);
- printf("%d collisions (%.1f%%)\n", numCollisions, 100.0 * numCollisions / numProbes);
+ printf("\nkhtml::HashTable statistics\n\n");
+ printf("%d accesses\n", numAccesses);
+ printf("%d total collisions, average %.2f probes per access\n", numCollisions, 1.0 * (numAccesses + numCollisions) / numAccesses);
+ printf("longest collision chain: %d\n", maxCollisions);
+ for (int i = 1; i <= maxCollisions; i++) {
+ printf(" %d lookups with exactly %d collisions (%.2f%% , %.2f%% with this many or more)\n", collisionGraph[i], i, 100.0 * (collisionGraph[i] - collisionGraph[i+1]) / numAccesses, 100.0 * collisionGraph[i] / numAccesses);
+ }
+ printf("%d rehashes\n", numRehashes);
+ printf("%d removes, %d reinserts\n", numRemoves, numReinserts);
}
+static void recordCollisionAtCount(int count)
+{
+ if (count > maxCollisions)
+ maxCollisions = count;
+ numCollisions++;
+ collisionGraph[count]++;
+}
#endif
const int _minTableSize = 64;
@@ -79,6 +97,11 @@
return s[length] == 0;
}
+bool AtomicString::equal(const AtomicString &a, const char *b)
+{
+ return equal(a.m_string.implementation(), b);
+}
+
bool AtomicString::equal(DOMStringImpl *r, const QChar *s, uint length)
{
if (!r && !s) return true;
@@ -123,12 +146,16 @@
int i = hash & _tableSizeMask;
#if DUMP_STATISTICS
- ++numProbes;
- numCollisions += _table[i] && !equal(_table[i], c);
+ ++numAccesses;
+ int collisionCount = 0;
#endif
while (DOMStringImpl *key = _table[i]) {
if (equal(key, c))
return key;
+#if DUMP_STATISTICS
+ ++collisionCount;
+ recordCollisionAtCount(collisionCount);
+#endif
i = (i + 1) & _tableSizeMask;
}
@@ -160,12 +187,16 @@
int i = hash & _tableSizeMask;
#if DUMP_STATISTICS
- ++numProbes;
- numCollisions += _table[i] && !equal(_table[i], s, length);
+ ++numAccesses;
+ int collisionCount = 0;
#endif
while (DOMStringImpl *key = _table[i]) {
if (equal(key, s, length))
return key;
+#if DUMP_STATISTICS
+ ++collisionCount;
+ recordCollisionAtCount(collisionCount);
+#endif
i = (i + 1) & _tableSizeMask;
}
@@ -197,13 +228,17 @@
int i = hash & _tableSizeMask;
#if DUMP_STATISTICS
- ++numProbes;
- numCollisions += _table[i] && !equal(_table[i], r);
+ ++numAccesses;
+ int collisionCount = 0;
#endif
while (DOMStringImpl *key = _table[i]) {
if (equal(key, r)) {
return key;
}
+#if DUMP_STATISTICS
+ ++collisionCount;
+ recordCollisionAtCount(collisionCount);
+#endif
i = (i + 1) & _tableSizeMask;
}
@@ -223,12 +258,16 @@
int i = hash & _tableSizeMask;
#if DUMP_STATISTICS
- ++numProbes;
- numCollisions += _table[i] != 0;
+ ++numAccesses;
+ int collisionCount = 0;
+#endif
+ while (_table[i] && !(key == _table[i])) {
+#if DUMP_STATISTICS
+ ++collisionCount;
+ recordCollisionAtCount(collisionCount);
#endif
- while (_table[i])
i = (i + 1) & _tableSizeMask;
-
+ }
_table[i] = key;
}
@@ -240,12 +279,17 @@
int i = hash & _tableSizeMask;
#if DUMP_STATISTICS
- ++numProbes;
- numCollisions += _table[i] && equal(_table[i], r);
+ ++numRemoves;
+ ++numAccesses;
+ int collisionCount = 0;
#endif
while ((key = _table[i])) {
if (key == r)
break;
+#if DUMP_STATISTICS
+ ++collisionCount;
+ recordCollisionAtCount(collisionCount);
+#endif
i = (i + 1) & _tableSizeMask;
}
if (!key)
@@ -265,6 +309,9 @@
key = _table[i];
if (!key)
break;
+#if DUMP_STATISTICS
+ ++numReinserts;
+#endif
_table[i] = 0;
insert(key);
}
@@ -272,11 +319,13 @@
void AtomicString::expand()
{
+ printf("grow from size %d to size %d at keyCount %d\n", m_tableSize, m_minTableSize : m_tableSize * 2, m_keyCount);
rehash(_tableSize == 0 ? _minTableSize : _tableSize * 2);
}
void AtomicString::shrink()
{
+ printf("shrink from size %d to size %d at keyCount %d\n", m_tableSize, m_tableSize/2, m_keyCount);
rehash(_tableSize / 2);
}
@@ -285,6 +334,11 @@
int oldTableSize = _tableSize;
DOMStringImpl **oldTable = _table;
+#if DUMP_STATISTICS
+ if (oldTableSize != 0)
+ ++numRehashes;
+#endif
+
_tableSize = newTableSize;
_tableSizeMask = newTableSize - 1;
_table = (DOMStringImpl **)calloc(newTableSize, sizeof(DOMStringImpl *));
1.4 +1 -2 WebCore/khtml/xml/dom_atomicstring.h
Index: dom_atomicstring.h
===================================================================
RCS file: /cvs/root/WebCore/khtml/xml/dom_atomicstring.h,v
retrieving revision 1.3
retrieving revision 1.4
diff -u -r1.3 -r1.4
--- dom_atomicstring.h 21 Jun 2005 22:27:55 -0000 1.3
+++ dom_atomicstring.h 28 Jun 2005 01:54:34 -0000 1.4
@@ -80,8 +80,7 @@
static bool equal(const AtomicString &a, const AtomicString &b)
{ return a.m_string.implementation() == b.m_string.implementation(); }
- static bool equal(const AtomicString &a, const char *b)
- { return equal(a.m_string.implementation(), b); }
+ static bool equal(const AtomicString &a, const char *b);
static DOMStringImpl *add(const char *);
static DOMStringImpl *add(const QChar *, int length);
1.28 +77 -56 WebCore/khtml/xml/dom_stringimpl.cpp
Index: dom_stringimpl.cpp
===================================================================
RCS file: /cvs/root/WebCore/khtml/xml/dom_stringimpl.cpp,v
retrieving revision 1.27
retrieving revision 1.28
diff -u -r1.27 -r1.28
--- dom_stringimpl.cpp 21 Jun 2005 22:27:55 -0000 1.27
+++ dom_stringimpl.cpp 28 Jun 2005 01:54:34 -0000 1.28
@@ -443,73 +443,94 @@
// or anything like that.
const unsigned PHI = 0x9e3779b9U;
-// This hash algorithm comes from:
-// http://burtleburtle.net/bob/hash/hashfaq.html
-// http://burtleburtle.net/bob/hash/doobs.html
-unsigned DOMStringImpl::computeHash(const QChar *s, int length)
+// Paul Hsieh's SuperFastHash
+// http://www.azillionmonkeys.com/qed/hash.html
+unsigned DOMStringImpl::computeHash(const QChar *s, int len)
{
- int prefixLength = length < 8 ? length : 8;
- int suffixPosition = length < 16 ? 8 : length - 8;
-
- unsigned h = PHI;
- h += length;
- h += (h << 10);
- h ^= (h << 6);
-
- for (int i = 0; i < prefixLength; i++) {
- h += s[i].unicode();
- h += (h << 10);
- h ^= (h << 6);
- }
- for (int i = suffixPosition; i < length; i++){
- h += s[i].unicode();
- h += (h << 10);
- h ^= (h << 6);
+ unsigned l = len;
+ uint32_t hash = PHI;
+ uint32_t tmp;
+
+ int rem = l & 1;
+ l >>= 1;
+
+ // Main loop
+ for (; l > 0; l--) {
+ hash += s[0].unicode();
+ tmp = (s[1].unicode() << 11) ^ hash;
+ hash = (hash << 16) ^ tmp;
+ s += 2;
+ hash += hash >> 11;
}
- h += (h << 3);
- h ^= (h >> 11);
- h += (h << 15);
-
- if (h == 0)
- h = 0x80000000;
+ // Handle end case
+ if (rem) {
+ hash += s[0].unicode();
+ hash ^= hash << 11;
+ hash += hash >> 17;
+ }
+
+ // Force "avalanching" of final 127 bits
+ hash ^= hash << 3;
+ hash += hash >> 5;
+ hash ^= hash << 2;
+ hash += hash >> 15;
+ hash ^= hash << 10;
+
+ // this avoids ever returning a hash code of 0, since that is used to
+ // signal "hash not computed yet", using a value that is likely to be
+ // effectively the same as 0 when the low bits are masked
+ if (hash == 0)
+ hash = 0x80000000;
- return h;
+ return hash;
}
-// This hash algorithm comes from:
-// http://burtleburtle.net/bob/hash/hashfaq.html
-// http://burtleburtle.net/bob/hash/doobs.html
+// Paul Hsieh's SuperFastHash
+// http://www.azillionmonkeys.com/qed/hash.html
unsigned DOMStringImpl::computeHash(const char *s)
{
- int length = strlen(s);
- int prefixLength = length < 8 ? length : 8;
- int suffixPosition = length < 16 ? 8 : length - 8;
-
- unsigned h = PHI;
- h += length;
- h += (h << 10);
- h ^= (h << 6);
-
- for (int i = 0; i < prefixLength; i++) {
- h += (unsigned char)s[i];
- h += (h << 10);
- h ^= (h << 6);
- }
- for (int i = suffixPosition; i < length; i++) {
- h += (unsigned char)s[i];
- h += (h << 10);
- h ^= (h << 6);
+ // This hash is designed to work on 16-bit chunks at a time. But since the normal case
+ // (above) is to hash UTF-16 characters, we just treat the 8-bit chars as if they
+ // were 16-bit chunks, which should give matching results
+
+ unsigned l = strlen(s);
+ uint32_t hash = PHI;
+ uint32_t tmp;
+
+ int rem = l & 1;
+ l >>= 1;
+
+ // Main loop
+ for (; l > 0; l--) {
+ hash += (unsigned char)s[0];
+ tmp = ((unsigned char)s[1] << 11) ^ hash;
+ hash = (hash << 16) ^ tmp;
+ s += 2;
+ hash += hash >> 11;
}
- h += (h << 3);
- h ^= (h >> 11);
- h += (h << 15);
-
- if (h == 0)
- h = 0x80000000;
+ // Handle end case
+ if (rem) {
+ hash += (unsigned char)s[0];
+ hash ^= hash << 11;
+ hash += hash >> 17;
+ }
+
+ // Force "avalanching" of final 127 bits
+ hash ^= hash << 3;
+ hash += hash >> 5;
+ hash ^= hash << 2;
+ hash += hash >> 15;
+ hash ^= hash << 10;
+
+ // this avoids ever returning a hash code of 0, since that is used to
+ // signal "hash not computed yet", using a value that is likely to be
+ // effectively the same as 0 when the low bits are masked
+ if (hash == 0)
+ hash = 0x80000000;
- return h;
+ return hash;
}
const char *DOMStringImpl::ascii() const
1.22 +1 -1 WebCore/kwq/KWQCharsets.mm
Index: KWQCharsets.mm
===================================================================
RCS file: /cvs/root/WebCore/kwq/KWQCharsets.mm,v
retrieving revision 1.21
retrieving revision 1.22
diff -u -r1.21 -r1.22
--- KWQCharsets.mm 20 Aug 2003 22:31:04 -0000 1.21
+++ KWQCharsets.mm 28 Jun 2005 01:54:35 -0000 1.22
@@ -142,7 +142,7 @@
}
h += tolower(c);
h += (h << 10);
- h ^= (h << 6);
+ h ^= (h >> 6);
}
h += (h << 3);
1.113 +0 -4 WebCore/kwq/KWQString.h
Index: KWQString.h
===================================================================
RCS file: /cvs/root/WebCore/kwq/KWQString.h,v
retrieving revision 1.112
retrieving revision 1.113
diff -u -r1.112 -r1.113
--- KWQString.h 24 Jun 2005 18:57:22 -0000 1.112
+++ KWQString.h 28 Jun 2005 01:54:35 -0000 1.113
@@ -561,8 +561,6 @@
void reserve(uint);
- uint hash() const;
-
bool operator!() const;
const QChar operator[](int) const;
@@ -763,6 +761,4 @@
const QString &string() const { return *this; }
};
-extern const CFDictionaryKeyCallBacks CFDictionaryQStringKeyCallBacks;
-
#endif
1.139 +0 -81 WebCore/kwq/KWQString.mm
Index: KWQString.mm
===================================================================
RCS file: /cvs/root/WebCore/kwq/KWQString.mm,v
retrieving revision 1.138
retrieving revision 1.139
diff -u -r1.138 -r1.139
--- KWQString.mm 26 Apr 2005 18:46:04 -0000 1.138
+++ KWQString.mm 28 Jun 2005 01:54:35 -0000 1.139
@@ -2755,60 +2755,6 @@
return chs[len] == '\0';
}
-// Golden ratio - arbitrary start value to avoid mapping all 0's to all 0's
-// or anything like that.
-const unsigned PHI = 0x9e3779b9U;
-
-// This hash algorithm comes from:
-// http://burtleburtle.net/bob/hash/hashfaq.html
-// http://burtleburtle.net/bob/hash/doobs.html
-uint QString::hash() const
-{
- uint len = length();
-
- uint h = PHI;
- h += len;
- h += (h << 10);
- h ^= (h << 6);
-
- if (len) {
- uint prefixLength = len < 8 ? len : 8;
- uint suffixPosition = len < 16 ? 8 : len - 8;
-
- if (dataHandle[0]->_isAsciiValid) {
- const char *s = ascii();
- for (uint i = 0; i < prefixLength; i++) {
- h += (unsigned char)s[i];
- h += (h << 10);
- h ^= (h << 6);
- }
- for (uint i = suffixPosition; i < len; i++) {
- h += (unsigned char)s[i];
- h += (h << 10);
- h ^= (h << 6);
- }
- } else {
- const QChar *s = unicode();
- for (uint i = 0; i < prefixLength; i++) {
- h += s[i].unicode();
- h += (h << 10);
- h ^= (h << 6);
- }
- for (uint i = suffixPosition; i < len; i++) {
- h += s[i].unicode();
- h += (h << 10);
- h ^= (h << 6);
- }
- }
- }
-
- h += (h << 3);
- h ^= (h >> 11);
- h += (h << 15);
-
- return h;
-}
-
QString operator+(const QString &qs1, const QString &qs2)
{
return QString(qs1) += qs2;
@@ -2870,33 +2816,6 @@
}
}
-const void *retainQString(CFAllocatorRef allocator, const void *value)
-{
- return new QString(*(QString *)value);
-}
-
-void releaseQString(CFAllocatorRef allocator, const void *value)
-{
- delete (QString *)value;
-}
-
-CFStringRef describeQString(const void *value)
-{
- return ((QString *)value)->getCFString();
-}
-
-Boolean equalQString(const void *value1, const void *value2)
-{
- return *(QString *)value1 == *(QString *)value2;
-}
-
-CFHashCode hashQString(const void *value)
-{
- return ((QString *)value)->hash();
-}
-
-const CFDictionaryKeyCallBacks CFDictionaryQStringKeyCallBacks = { 0, retainQString, releaseQString, describeQString, equalQString, hashQString };
-
#define NODE_BLOCK_SIZE ((vm_page_size)/sizeof(HandleNode))
#define TO_NODE_OFFSET(ptr) ((uint)(((uint)ptr - (uint)base)/sizeof(HandleNode)))
1.51 +25 -10 WebCore/kwq/KWQTextCodec.mm
Index: KWQTextCodec.mm
===================================================================
RCS file: /cvs/root/WebCore/kwq/KWQTextCodec.mm,v
retrieving revision 1.50
retrieving revision 1.51
diff -u -r1.50 -r1.51
--- KWQTextCodec.mm 17 Jun 2005 16:41:05 -0000 1.50
+++ KWQTextCodec.mm 28 Jun 2005 01:54:35 -0000 1.51
@@ -187,20 +187,35 @@
return a._encoding == b._encoding && a._flags == b._flags;
}
+// Golden ratio - arbitrary start value to avoid mapping all 0's to all 0's
+// or anything like that.
+const unsigned PHI = 0x9e3779b9U;
+
+// Paul Hsieh's SuperFastHash
+// http://www.azillionmonkeys.com/qed/hash.html
+// Adapted assuming _encoding is 32 bits and _flags is at most 16 bits
unsigned QTextCodec::hash() const
{
- unsigned h = _encoding;
-
- h += (h << 10);
- h ^= (h << 6);
+ uint32_t hash = PHI;
+ uint32_t tmp;
- h ^= _flags;
+ hash += _encoding & 0xffff;
+ tmp = ((_encoding >> 16) << 11) ^ hash;
+ hash = (hash << 16) ^ tmp;
+ hash += hash >> 11;
+
+ hash += _flags & 0xffff;
+ hash ^= hash << 11;
+ hash += hash >> 17;
+
+ // Force "avalanching" of final 127 bits
+ hash ^= hash << 3;
+ hash += hash >> 5;
+ hash ^= hash << 2;
+ hash += hash >> 15;
+ hash ^= hash << 10;
- h += (h << 3);
- h ^= (h >> 11);
- h += (h << 15);
-
- return h;
+ return hash;
}
static Boolean QTextCodecsEqual(const void *a, const void *b)
More information about the webkit-changes
mailing list