Bug 18994 - LANG/LC_ALL influences the result of element.style.opacity
: LANG/LC_ALL influences the result of element.style.opacity
Status: RESOLVED FIXED
Product: WebKit
Classification: Unclassified
Component: Platform
: 528+ (Nightly build)
: All All
: P2 Major
Assigned To: Nobody
: Cairo, Gtk
Depends on: 30238 47467 47493 47538 47584 47664 47714
Blocks: 43832
  Show dependency treegraph
 
Reported: 2008-05-11 01:43 PDT by Aaron Digulla
Modified: 2010-10-17 13:07 PDT (History)
23 users (show)

See Also:


Attachments
Deprecate String::format() (34.04 KB, patch)
2008-09-16 07:18 PDT, Alp Toker
no flags Details | Formatted Diff | Diff
patch v1 (6.31 KB, patch)
2009-09-30 17:36 PDT, Evan Martin
no flags Details | Formatted Diff | Diff
patch (5.74 KB, patch)
2009-10-05 11:18 PDT, Evan Martin
no flags Details | Formatted Diff | Diff
patch (7.79 KB, patch)
2009-10-05 11:45 PDT, Evan Martin
no flags Details | Formatted Diff | Diff
add a regression test (7.82 KB, patch)
2009-10-05 11:48 PDT, Evan Martin
no flags Details | Formatted Diff | Diff
add a regression test (7.83 KB, patch)
2009-10-05 14:32 PDT, Evan Martin
no flags Details | Formatted Diff | Diff
add a regression test (8.24 KB, patch)
2009-10-06 16:32 PDT, Evan Martin
no flags Details | Formatted Diff | Diff
start patching out CSS floats with a Vector<UChar> (7.18 KB, patch)
2009-10-06 18:04 PDT, Evan Martin
no flags Details | Formatted Diff | Diff
use dtoa directly in CSS units (6.80 KB, patch)
2009-10-07 16:41 PDT, Evan Martin
no flags Details | Formatted Diff | Diff
use dtoa directly in CSS units (9.30 KB, patch)
2009-10-08 15:30 PDT, Evan Martin
no flags Details | Formatted Diff | Diff
use dtoa directly in CSS units (10.19 KB, patch)
2009-10-08 19:06 PDT, Evan Martin
no flags Details | Formatted Diff | Diff
use dtoa directly in CSS units (10.17 KB, patch)
2009-10-08 19:18 PDT, Evan Martin
no flags Details | Formatted Diff | Diff
stringify CSS units manually to match CSS spec (13.61 KB, patch)
2009-10-12 11:22 PDT, Evan Martin
no flags Details | Formatted Diff | Diff
stringify CSS units manually to match CSS spec (13.68 KB, patch)
2009-10-12 12:10 PDT, Evan Martin
no flags Details | Formatted Diff | Diff
stringify CSS units manually to match CSS spec (13.60 KB, patch)
2009-10-12 14:27 PDT, Evan Martin
no flags Details | Formatted Diff | Diff
stringify CSS units manually to match CSS spec (14.19 KB, patch)
2009-10-12 14:46 PDT, Evan Martin
no flags Details | Formatted Diff | Diff
stringify CSS units manually to match CSS spec (15.80 KB, patch)
2009-10-13 15:03 PDT, Evan Martin
no flags Details | Formatted Diff | Diff
Work in progress (2.02 KB, patch)
2010-10-10 04:59 PDT, Nikolas Zimmermann
simon.fraser: review-
Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Aaron Digulla 2008-05-11 01:43:22 PDT
For some reason, the fading of the editingToolbar demo (go to http://webkit.org/demos/editingToolbar/ and click on the text) is broken on my system with r33026.

The effect is that the toolbar appears but stops at opacity 0.1. The UI is sluggish after that which indicates an infinite loop.

After some prodding at the code in FancyToolbar.js, I'm pretty sure that the bug is somewhere in FancyToolbar.prototype.animateToolbar(). Is there a way to log something to the console so I can figure out why the opacity doesn't change anymore after the first loop?
Comment 1 Alexey Proskuryakov 2008-05-11 03:19:26 PDT
I couldn't reproduce with r33029 nightly.
Comment 2 Aaron Digulla 2008-05-11 03:35:32 PDT
I had the same reply from someone from the Qt/Trolltech team. My guess is that there is some incompatibility with my system (openSUSE 10.3, X11 7.2-143.11,
libXrender-7.2-65, gcc 4.2 24, NVIDIA-Linux-x86_64-169.09).

r33026 and 33030 don't seem to contain changes which might influence this.

I'd like to put some log statements into FancyToolbar.prototype.animateToolbar() to see where it clogs. How do I do that?
Comment 3 Matt Lilek (pewtermoose) 2008-05-11 06:32:27 PDT
(In reply to comment #2)
> I'd like to put some log statements into
> FancyToolbar.prototype.animateToolbar() to see where it clogs. How do I do
> that?
> 

console.log() will log to the JS console, but depending on which port you're using, that may require some minor work in ChromeClient to actually get logged somewhere.
Comment 4 Aaron Digulla 2008-05-11 08:25:14 PDT
Okay, I found it. On my system, LANG is "de_DE.UTF-8". So I see this in the console:

this.toolbarElement.style.opacity=0,1

parseFloat("0,1") returns 0 (it expects "0.1"), so the value stays at "0.1" plus I get a nice busy loop. When I set LANG=C, the example works as expected.

So the problem is that float values returned by querying properties are formatted according to the locale. I don't think that this is a good idea.
Comment 5 Alexey Proskuryakov 2008-05-12 04:58:32 PDT
These numbers are formatted with vsnprintf in WebCore::String::format(). Should be easy to fix by using vsnprintf_l - but does Linux have this or equivalent functionality?
Comment 6 Aaron Digulla 2008-05-12 12:09:18 PDT
I couldn't find anything :/ I doubt that glibc offers this after finally offering automatic locale specific formatting.

Why does the parseDouble() function expect "." even when the locale specifies ","?

Also, I wonder what a fix in String::format() would break. Will it work for JavaScript, too? Like:

var x = 1.5;
var s = "" + x;
assert "1.5" == s;

My next idea was to set LANG/LC_ALL in the startup script but that would break if someone would embed WebKit in an application.

Or we could patch the field "decimal_point" in "struct lconv". But that might introduce even more hideous bugs during embedding, especially when threads are used.

Which leads to another question: Might the web developer want to *show* float values to the user? In this case, which locale should be used to format them? C? The locale of the page? The locale in which the browser runs?

Argh... a Pandora Bug!

If everyone was using String::number(double) to format double values, we could replace "," by "." in there. But that isn't very safe as long as String::format() is not private (someone might try to format double values manually, for example in a more complex format string).

The GNU header file locale.h mentions that there is some proof of concept code to allow per-thread locales. But I'm not sure how fast this is (i.e. getting the current locale, replace it with C, set it back after the double value has been formatted).

*sigh* It would be easier if style.opacity would simply return a double value :)

I'll keep my eyes open for a solution for this but half an hour on Google didn't turn anything really useful up. Maybe ask the glibc maintainers for help?
Comment 7 Alexey Proskuryakov 2008-05-12 12:54:15 PDT
(In reply to comment #6)
> Why does the parseDouble() function expect "." even when the locale specifies
> ","?

Well, a script downloaded from the Web can always pass a double as a literal string, so we should worry about that case, too, not just round-tripping. User locale should have no effect on scripts, except for methods that are specifically documented as locale-sensitive.

> Also, I wonder what a fix in String::format() would break. 

On Mac OS X, locale does not affect printf/scanf by default, so such a fix is unlikely to cause any problems.

> Will it work for JavaScript, too?

No, Platform::String is not used in JavaScriptCore. I am not aware of similar bugs remaining in JavaScriptCore (although there used to be some, see bug 16162).

> My next idea was to set LANG/LC_ALL in the startup script but that would break
> if someone would embed WebKit in an application.

Precisely.

> Or we could patch the field "decimal_point" in "struct lconv". But that might
> introduce even more hideous bugs during embedding, especially when threads are
> used.

Ugh, I was assuming that locale was per-thread on Linux, and thought of this as the most promising approach.

> Which leads to another question: Might the web developer want to *show* float
> values to the user? In this case, which locale should be used to format them?
> C? The locale of the page? The locale in which the browser runs?

ECMAScript-262 talks about "host environment's current locale" (see e.g. 15.5.4.9), which is what JavaScriptCore does. Other specifications have different approaches, which are not always explicitly spelled out and which we don't always fully implement yet. But let's keep discussion in this bug focused on the specific issue at hand!

> I'll keep my eyes open for a solution for this but half an hour on Google
> didn't turn anything really useful up. Maybe ask the glibc maintainers for
> help?

Sounds like a good idea. I would also expect that Qt/gtk/wx have their solutions for this issue, which we could potentially use.
Comment 8 Alexey Proskuryakov 2008-05-20 00:03:37 PDT
See also: bug 18985.
Comment 9 Dirk Schulze 2008-05-20 12:11:46 PDT
(In reply to comment #6)
> I couldn't find anything :/ I doubt that glibc offers this after finally
> offering automatic locale specific formatting.
> 
> Why does the parseDouble() function expect "." even when the locale specifies
> ","?
> 
> Also, I wonder what a fix in String::format() would break. Will it work for
> JavaScript, too? Like:
> 
> var x = 1.5;
> var s = "" + x;
> assert "1.5" == s;
> 
> My next idea was to set LANG/LC_ALL in the startup script but that would break
> if someone would embed WebKit in an application.
> 
> Or we could patch the field "decimal_point" in "struct lconv". But that might
> introduce even more hideous bugs during embedding, especially when threads are
> used.
> 
> Which leads to another question: Might the web developer want to *show* float
> values to the user? In this case, which locale should be used to format them?
> C? The locale of the page? The locale in which the browser runs?
> 
> Argh... a Pandora Bug!
> 
> If everyone was using String::number(double) to format double values, we could
> replace "," by "." in there. But that isn't very safe as long as
> String::format() is not private (someone might try to format double values
> manually, for example in a more complex format string).
> 
> The GNU header file locale.h mentions that there is some proof of concept code
> to allow per-thread locales. But I'm not sure how fast this is (i.e. getting
> the current locale, replace it with C, set it back after the double value has
> been formatted).
> 
> *sigh* It would be easier if style.opacity would simply return a double value
> :)
> 
> I'll keep my eyes open for a solution for this but half an hour on Google
> didn't turn anything really useful up. Maybe ask the glibc maintainers for
> help?
> 

It's not only the decimal-point: (from man printf)

For  some  numeric conversions a radix character (‘decimal point’) or thousands’ grouping character is used.  The actual character used depends on the LC_NUMERIC part of the locale.  The POSIX locale uses ‘.’ as radix character, and does not have a grouping character.  Thus,

               printf("%’.2f", 1234567.89);

results in ‘1234567.89’ in the POSIX locale, in ‘1234567,89’ in the nl_NL locale, and in ‘1.234.567,89’ in the da_DK locale.

Comment 10 Simon Hausmann 2008-06-16 05:40:20 PDT
(In reply to comment #5)
> These numbers are formatted with vsnprintf in WebCore::String::format(). Should
> be easy to fix by using vsnprintf_l - but does Linux have this or equivalent
> functionality?

Hmm, as far as I can see the numbers parsed by the lexer of the CSS parser are formatted with WebCore::charactersToDouble. This function, implemented in platform/text/String.cpp uses KJS::strtod for the conversion, which is locale safe. In fact I'm having trouble to reproduce this bug now with trunk.
Comment 11 Alexey Proskuryakov 2008-06-16 07:58:14 PDT
See e.g. CSSPrimitiveValue::cssText(), which uses String::format(). I think that this should still be reproducible on Linux.
Comment 12 Aaron Digulla 2008-06-19 13:55:59 PDT
As of rev 34662, the bug is fixed on Linux.

Did you add a test case to make sure opacity is never formatted with a comma?
Comment 13 Dirk Schulze 2008-06-20 01:31:50 PDT
(In reply to comment #12)
> As of rev 34662, the bug is fixed on Linux.

I still get the problem, like described in the first comment. And String::format() is a general problem. Some more files where String::format is used with float:

FTPDirectoryDocument.cpp
PathCairo.cpp
SVGUseElement.cpp

SVGUseElement: see https://bugs.webkit.org/show_bug.cgi?id=18985
Comment 14 Simon Hausmann 2008-06-26 10:54:10 PDT
For the Qt port we could implement String::format using QString::vsprintf.
Comment 15 Simon Hausmann 2008-08-13 06:16:55 PDT
For the Qt port we have switched to the locale-independent QString::vsprintf in String::format in revision 35712
Comment 16 Alexey Proskuryakov 2008-09-13 07:28:45 PDT
Eventually, we need a cross-platform solution. Although this doesn't affect Safari, this is still a problem even on Mac OS X for 3rd party clients that may call setlocale().
Comment 17 Alp Toker 2008-09-15 18:48:18 PDT
Alexey, could we use the string formatting/parsing features provided by ICU here? This would also have the benefit of working directly in UChars.
Comment 18 Alp Toker 2008-09-16 07:18:07 PDT
Created attachment 23471 [details]
Deprecate String::format()

        Use UString::from() instead of String::format() in String::number()
        functions where possible.

        Start to replace uses of String::format() in WebCore with
        StringBuilder and direct concatenation.

        As a side-effect, several incorrect or needless string conversions
        that were done by String::format() callers are now avoided.

        String::format() should not be used in new code.

Note: These changes need to be checked for regressions with the Mac layout tests before going in.
Comment 19 Alp Toker 2008-09-16 07:36:42 PDT
This patch converts most, but not all String::format() callers to instead build strings directly. There are still ~50 calls yet to be ported (listed below) though the remaining ones are low-risk (no float/double formatting). It'd be great to drop String::format() completely or move it well out of the way some day since it clearly leads to sloppy code.


WebCore/css/CSSParser.cpp:        String str = String::format("%06d", (int)(value->fValue+.5));
WebCore/dom/StyledElement.cpp:            color = String::format("#%02x%02x%02x", colors[0], colors[1], colors[2]);
WebCore/page/Console.cpp:    String message = String(title) + String::format(": %.0fms", elapsed);
WebCore/storage/DatabaseTracker.cpp:        filename = pathByAppendingComponent(originPath, String::format("%016llx.db", seq));
WebCore/storage/DatabaseTracker.cpp:    if (!addDatabase(origin, name, String::format("%016llx.db", seq)))
WebCore/loader/FTPDirectoryDocument.cpp:        return String::format("%.2f KB", static_cast<float>(bytes)/1000);
WebCore/loader/FTPDirectoryDocument.cpp:        return String::format("%.2f MB", static_cast<float>(bytes)/1000000);
WebCore/loader/FTPDirectoryDocument.cpp:    return String::format("%.2f GB", static_cast<float>(bytes)/1000000000);
WebCore/loader/FTPDirectoryDocument.cpp:            timeOfDay = String::format(", %i:%02i AM", hour, fileTime.tm_min);
WebCore/loader/FTPDirectoryDocument.cpp:            timeOfDay = String::format(", %i:%02i PM", hour, fileTime.tm_min);
WebCore/loader/FTPDirectoryDocument.cpp:        dateString = String::format("%s %i, %i", months[month], fileTime.tm_mday, fileTime.tm_year);
WebCore/loader/FTPDirectoryDocument.cpp:        dateString = String::format("%s %i, %i", months[month], fileTime.tm_mday, now.tm_year);
WebCore/loader/archive/cf/LegacyWebArchive.cpp:    String iframeMarkup = String::format("<iframe frameborder=\"no\" marginwidth=\"0\" marginheight=\"0\" width=\"98%%\" height=\"98%%\" src=\"%s\"></iframe>", 
WebCore/platform/qt/PlatformKeyboardEventQt.cpp:            return String::format("U+%04X", toupper(keyCode));
WebCore/platform/wx/KeyboardEventWx.cpp:            return String::format("U+%04X", toupper(keyCode));
WebCore/platform/gtk/KeyEventGtk.cpp:            return String::format("U+%04X", gdk_keyval_to_unicode(gdk_keyval_to_upper(keyCode)));
WebCore/platform/mac/KeyEventMac.mm:            return String::format("U+%04X", toASCIIUpper(c));
WebCore/platform/sql/SQLiteDatabase.cpp:    executeCommand(String::format("PRAGMA synchronous = %i", sync));
WebCore/platform/win/ClipboardUtilitiesWin.cpp:    append(result, String::format(header, startHTMLOffset, endHTMLOffset, startFragmentOffset, endFragmentOffset).utf8());
WebCore/platform/win/GDIObjectCounter.cpp:    init(String::format("%s (%p)", className.latin1().data(), instance));
WebCore/platform/win/Language.cpp:        computedDefaultLanguage = String::format("%s-%s", languageName.latin1().data(), countryName.latin1().data());
WebCore/platform/win/KeyEventWin.cpp:            return String::format("U+%04X", toASCIIUpper(keyCode));
WebCore/platform/text/String.cpp:String String::format(const char *format, ...)
WebCore/platform/text/String.cpp:    return String::format("%d", n);
WebCore/platform/text/String.cpp:    return String::format("%u", n);
WebCore/platform/text/String.cpp:    return String::format("%ld", n);
WebCore/platform/text/String.cpp:    return String::format("%lu", n);
WebCore/platform/text/String.cpp:    return String::format("%I64i", n);
WebCore/platform/text/String.cpp:    return String::format("%lli", n);
WebCore/platform/text/String.cpp:    return String::format("%I64u", n);
WebCore/platform/text/String.cpp:    return String::format("%llu", n);
WebCore/platform/text/String.cpp:    return String::format("%.6lg", n);
WebCore/platform/graphics/cg/ImageBufferCG.cpp:    return String::format("data:%s;base64,%s", mimeType.utf8().data(), out.data());
WebCore/platform/graphics/mac/MediaPlayerPrivateQTKit.mm:            String text = String::format("%1.2f", frameRate);
WebCore/platform/graphics/win/MediaPlayerPrivateQuickTimeWin.cpp:            String text = String::format("%1.2f", frameRate);
WebCore/platform/graphics/cairo/PathCairo.cpp:            string += String::format("M %.2f,%.2f",
WebCore/platform/graphics/cairo/PathCairo.cpp:            string += String::format("L %.2f,%.2f",
WebCore/platform/graphics/cairo/PathCairo.cpp:            string += String::format("C %.2f,%.2f,%.2f,%.2f,%.2f,%.2f",
WebCore/platform/graphics/Color.cpp:        return String::format("#%02X%02X%02X%02X", red(), green(), blue(), alpha());
WebCore/platform/graphics/Color.cpp:    return String::format("#%02X%02X%02X", red(), green(), blue());
WebCore/rendering/RenderMedia.cpp:    return String::format("%02d:%02d:%02d", hours, minutes, seconds);
WebCore/rendering/RenderTreeAsText.cpp:                String hex = String::format("\\x{%X}", u);
WebKit/gtk/WebCoreSupport/FrameLoaderClientGtk.cpp:        return String::format("%s %s", name.sysname, name.machine);
WebKit/win/WebKitStatistics.cpp:        append(vector, String::format("%4u", current->second));
WebKit/win/WebCoreSupport/WebInspectorClient.cpp:    // FIXME: The series of appends should be replaced with a call to String::format()
WebKit/win/WebView.cpp:        osVersion = String::format("Windows NT %d.%d", versionInfo.dwMajorVersion, versionInfo.dwMinorVersion);
WebKit/win/WebView.cpp:        osVersion = String::format("Windows %d.%d", versionInfo.dwMajorVersion, versionInfo.dwMinorVersion);
WebKit/win/WebView.cpp:        m_userAgentStandard = String::format("Mozilla/5.0 (Windows; U; %s; %s) AppleWebKit/%s (KHTML, like Gecko)%s%s", osVersion().latin1().data(), defaultLanguage().latin1().data(), webKitVersion().latin1().data(), (m_applicationName.length() ? " " : ""), m_applicationName.latin1().data());
Comment 20 Darin Adler 2008-09-16 10:06:28 PDT
Comment on attachment 23471 [details]
Deprecate String::format()

I think it's a great idea to do this and the patch looks great.

But we need to make sure the new idiom is at least as fast as the old or faster. If we're going to write new code for many call sites I want to be sure it's designed to be fast enough that we won't have to visit them all again to rewrite them again.

+    String message = "Unsafe JavaScript attempt to access frame with URL ";
+    message += targetURL.string();
+    message += " from frame with URL ";
+    message += originURL.string();
+    message += ". Domains, protocols and ports must match.\n";
+    return message;

This is a bad idiom for performance. The String class is very slow to append to, so changing calls that previously used String::format to code that uses String appends is not generally a good idea, unless the code is not at all performance critical. I'm concerned that overall this is going to slow things down.

Idioms that are fast are using StringBuffer or Vector<UChar> to create the string and then String::adopt to make a String out of it.

-            text = String::format("%.6lg%%", m_value.num);
+            text = String::number(m_value.num) + "%";

Same thing here. Given the String implementation, this is a particularly inefficient idiom and I'd prefer not to add lots of instances of it without considering performance.

-        String m_errorMessages;
+        StringBuilder m_errorMessages;

This is the right idea! Although I do slightly prefer Vector<UChar> (see below).

 String String::number(unsigned n)
 {
+#if USE(JSC)
+    return String(UString::from(n));
+#else
     return String::format("%u", n);
+#endif
 }

The above is not efficient because it involves allocating an unnecessary temporary UString. We need to make these numeric conversions operations efficient. I think we need a form of number formatting that appends to a Vector<UChar> or a StringBuffer or both, and we also want String::number() to be efficient for the occasions where we really do need to create an entire String out of a number. We should consider what we need to do in JavaScriptCore to offer the right kind of efficient numeric conversion for use elsewhere without requiring allocation of a UString for the result.

+    // TODO: Use UString::from()
     return String::format("%lu", n);

Why is that still a TODO?

-        String statusLine = String::format("HTTP %lu OK\n", m_resourceResponse.httpStatusCode());
+        String statusLine = "HTTP ";
+        statusLine += String::number(static_cast<unsigned>(m_resourceResponse.httpStatusCode()));
+        statusLine += " OK\n";
 
         stringBuilder.append(statusLine.characters(), statusLine.length());

Here we need to append right to the stringBuilder local variable, not go out of our way to instead create a String. I also don't understand the reason for the static_cast. That seems wrong.

I think we should standardize on either StringBuilder or Vector<UChar> (sorry that there are two!). I personally prefer Vector<UChar> because StringBuilder forces us to allocate separate strings for each piece, whereas Vector<UChar> uses a single buffer. On the other hand, StringBuilder has a more attractive name and is more clearly for this purpose. Maybe we can improve StringBuilder so that it is as efficient as Vector<UChar> when the pieces to append aren't already in WebCore::String objects and then we don't have a difficult choice.

Anyway, we would add an operation for appending a formatted number to a Vector<UChar> or StringBuilder.

And then convert the String::format call sites to that approach.

Sorry, I'm not trying to be difficult; but if we're going to rewrite these I'd really like the new idiom to be as efficient as possible.

I hope this doesn't make the task take too long! I'd love to hear your thoughts.
Comment 21 Alp Toker 2008-09-18 03:08:37 PDT
(In reply to comment #20)
> Idioms that are fast are using StringBuffer or Vector<UChar> to create the
> string and then String::adopt to make a String out of it.

How about using TextStream.h? Although it uses snprintf() now it looks like it has good optimisation potential, as long as code like this isn't objectionable:

ts << "text run at (" << run.m_x << "," << run.m_y << ") width " << run.m_width;

Right now TextStream is only used by RenderTreeAsText and a couple of places in SVG.

If we go this way we'll need to extend TextStream to choose the decimal precision when formatting doubles etc.
Comment 22 Darin Adler 2008-09-18 09:15:43 PDT
(In reply to comment #21)
> How about using TextStream.h? Although it uses snprintf() now it looks like it
> has good optimisation potential, as long as code like this isn't objectionable:

Yes, that'd be OK. I do worry that we end up with too many different options, between Vector<UChar>, StringBuffer, and TextStream. I was hoping to phase out TextStream. If we can make it efficient and a great option then I think many will like using the stream-style syntax. I don't think the name of TextStream is very clear -- the fact that it's for constructing a string seems subtle.

Maybe we can add the streaming syntax to StringBuffer instead?
Comment 23 Jan Alonzo 2009-07-06 04:39:22 PDT
*** Bug 26984 has been marked as a duplicate of this bug. ***
Comment 24 Evan Martin 2009-09-28 09:41:41 PDT
This came up on the Chrome bug tracker:
http://code.google.com/p/chromium/issues/detail?id=22782

Same issue as above.  Since we're not the Qt port, String::format is still locale-dependent.  I will try to create a layout test for this.
Comment 25 Evan Martin 2009-09-30 17:36:34 PDT
Created attachment 40411 [details]
patch v1
Comment 26 Evan Martin 2009-09-30 17:37:33 PDT
I've attached a patch for consideration -- it doesn't quite work yet, the style's wrong, but feedback on the general approach is appreciated.  I'm new to this WebKit stuff.
Comment 27 Evan Martin 2009-09-30 17:43:40 PDT
Eric explained to me the multitude of ways in which I fail.  I will post a reasonable patch later, please disregard that last one.
Comment 28 Evan Martin 2009-10-05 11:18:39 PDT
Created attachment 40646 [details]
patch
Comment 29 Evan Martin 2009-10-05 11:20:21 PDT
Ok, please take a look at my new patch.  I will now try to verify the test fails on a WebKitGtk build.
Comment 30 Evan Martin 2009-10-05 11:45:49 PDT
Created attachment 40648 [details]
patch
Comment 31 Evan Martin 2009-10-05 11:48:20 PDT
Created attachment 40649 [details]
add a regression test
Comment 32 Evan Martin 2009-10-05 11:49:34 PDT
Since last review:
- eliminated tabs
- setLocale -> setPOSIXLocale
- reset locale in platform-specific resetter functions rather than in layout test
Comment 33 Evan Martin 2009-10-05 14:32:36 PDT
Created attachment 40662 [details]
add a regression test
Comment 34 Evan Martin 2009-10-05 14:34:13 PDT
I have now verified this fails on Linux in the expected way.
I also upped the locale buffer size on recommendation from kov in #webkit-gtk.
I think this is ready to go.
Comment 35 Eric Seidel 2009-10-06 08:35:22 PDT
Comment on attachment 40662 [details]
add a regression test

I would have wrapped:
setlocale(LC_ALL, "");
into some shared:
resetToDefaultPOSIXLocale()
function or similar.

But in general this looks fine.
Comment 36 Evan Martin 2009-10-06 11:32:35 PDT
Out of curiosity, I ran the existing layout tests from a German (commas for decimals) locale under Chrome on Linux.  I had 412 tests fail (but some of them are due to differing plugins on my system vs our tester bots...); many of the locale-caused failures were under svg, but also a random set of others.

Here are a few selections, just to give you a feel for it:
  LayoutTests/fast/text/shadow-translucent-fill.html = FAIL
  LayoutTests/transforms/2d/transform-2d.html = FAIL
  LayoutTests/animations/matrix-anim.html = FAIL
Comment 37 WebKit Commit Bot 2009-10-06 14:31:31 PDT
Comment on attachment 40662 [details]
add a regression test

Rejecting patch 40662 from commit-queue.

Failed to run "['WebKitTools/Scripts/run-webkit-tests', '--no-launch-safari', '--quiet', '--exit-after-n-failures=1']" exit_code: 1
Running build-dumprendertree
Running tests from /Users/eseidel/Projects/CommitQueue/LayoutTests
Testing 11396 test cases.
fast/css/opacity-float.html -> failed

Exiting early after 1 failures. 5136 tests run.
90.65s total testing time

5135 test cases (99%) succeeded
1 test case (<1%) had incorrect layout
1 test case (<1%) had stderr output
Comment 38 Evan Martin 2009-10-06 16:00:25 PDT
It seems the reason this failed is that OS X is vulnerable to the same problem other platforms are.  I suspect the reason it hasn't happened in the past is apparently people don't call setlocale() in normal OS X apps.  (?)

So I think the test can't be checked in until WebKit is setlocale() safe.
Comment 39 Darin Adler 2009-10-06 16:12:36 PDT
The test can be checked in with an expected failure.
Comment 40 Evan Martin 2009-10-06 16:32:36 PDT
Created attachment 40749 [details]
add a regression test
Comment 41 Evan Martin 2009-10-06 16:33:08 PDT
Comment on attachment 40749 [details]
add a regression test

with mac expected-fail baseline
Comment 42 Evan Martin 2009-10-06 18:04:32 PDT
Created attachment 40753 [details]
start patching out CSS floats with a Vector<UChar>
Comment 43 Evan Martin 2009-10-06 18:06:53 PDT
Comment on attachment 40753 [details]
start patching out CSS floats with a Vector<UChar>

Hey Darin,

Could you take a glance at this and see if it's in the direction of where you were suggesting Alp go?  Obviously I'd need to relocate these functions to the appropriate places.

Note that dtoa works with chars so we need to do a conversion step when we use it, so appending a letter at a time to the uchar vector doesn't seem so horrible.

However, the matrix line that I highlighted in the patch probably does call for some sort of builder class.  I looked at the existing classes you mentioned but it's not clear to me what the right thing to do is.
Comment 44 Darin Adler 2009-10-07 08:44:23 PDT
Comment on attachment 40753 [details]
start patching out CSS floats with a Vector<UChar>

General direction looks right here.

I agree there's no obvious way to use a builder class for the matrix case. Instead, I suggest you write the code out longhand, including some way to reserve the capacity for the string, and then consider how to factor it into some kind of builder afterward.

I think we should consider eliminating String::format entirely.
Comment 45 WebKit Commit Bot 2009-10-07 10:48:01 PDT
Comment on attachment 40749 [details]
add a regression test

Clearing flags on attachment: 40749

Committed r49253: <http://trac.webkit.org/changeset/49253>
Comment 46 WebKit Commit Bot 2009-10-07 10:48:07 PDT
All reviewed patches have been landed.  Closing bug.
Comment 47 Evan Martin 2009-10-07 10:53:30 PDT
Reopening because the commitbot is overzealous -- I just had provided a test.  :)
Comment 48 Eric Seidel 2009-10-07 11:21:41 PDT
Yeah, the commit-queue has no concept of "land this and keep the bug open".  It assumes a one-patch-per-bug workflow.  There is at least bug 28230 filed on this.
Comment 49 Evan Martin 2009-10-07 16:41:12 PDT
Created attachment 40828 [details]
use dtoa directly in CSS units
Comment 50 Evan Martin 2009-10-07 16:44:49 PDT
Comment on attachment 40828 [details]
use dtoa directly in CSS units

This fixes both of the tests I recently added, but doesn't resolve this bug completely.  I'd prefer to write more failing tests before I speculatively touch other parts of the code.

LayoutTests/fast/css/opacity-float.html LayoutTests/fast/css/large-number-round-trip.html

Note that this changes the output for a few other tests, which I'll need to regenerate baselines for.  The TextStream logging functions have slightly different output requirements than the CSS output ones do.  :(

The changes will change DumpRenderTree outputs like this:

     RenderBody {BODY} at (8,8) size 784x584
-      RenderBlock {DIV} at (0,0) size 784x152 [textFillColor=#800080] [textStrokeWidth=2.00]
+      RenderBlock {DIV} at (0,0) size 784x152 [textFillColor=#800080] [textStrokeWidth=2]
         RenderText {#text} at (0,1) size 755x149
           text run at (0,1) width 612: "Purple\x{300} fill, black stroke,"
           text run at (0,77) width 755: "complex text, black underline"
-      RenderBlock {DIV} at (0,152) size 784x152 [textStrokeColor=#FFA500] [textStrokeWidth=1.33]
+      RenderBlock {DIV} at (0,152) size 784x152 [textStrokeColor=#FFA500] [textStrokeWidth=1.3]

I don't know what you think of that.
Comment 51 Evan Martin 2009-10-07 16:52:52 PDT
Having thought about this for a day (I've had the patch for a while), I fear that it will be difficult to share code for this appendFloat function; the output required by CSS (max 6 significant digits, no exponents) is different than the output apparently produced by TextStream (min 2 digits after the decimal).

I can add more options to appendFloat but maybe that gets us back into performance worry territory.  Another option is to just write a specific implementation of it for each call site.
Comment 52 Darin Adler 2009-10-07 16:54:00 PDT
Comment on attachment 40828 [details]
use dtoa directly in CSS units

> +void appendDouble(Vector<UChar>& vector, double value, int digits)
> +{
> +    // From the CSS specification section titled "Integers and real numbers",
> +    // real numbers are only formatted as [sign] [digits] "." [digits], with
> +    // no special handling of exponents, NaN, etc.

This is a confusing thing to say here. This says what the specification says, but not what the function does. What does this function do for NaN and exponents and such? Should it simply never be called on such numbers? Don't leave us hanging!

I think the name of this function should contain the words CSS if the behavior is something driven by the CSS specification. The name appendDouble makes it sound more general-purpose. Or maybe there is a term of art for this type of numeric formatting?

Sounds fine for CSS, but doesn't this change the behavior of the TextStream operators? Why is that OK? Do we have test coverage for those? Including edge cases like exponents, infinity, and NAN?

> +    vector.reserveCapacity(vector.size() + length + 2);

Where does the 2 come from here? From reading the code below it looks like you can have a lot more characters than "length" added. Can we do the reserveCapacity completely accurately instead of approximately?

Do we have test cases that cover all the branches of the appendDouble function?

I'll say review- because of some of the comments above, but this looks really good and on the right track.
Comment 53 Darin Adler 2009-10-07 16:59:52 PDT
(In reply to comment #51)
> I can add more options to appendFloat but maybe that gets us back into
> performance worry territory.

I'm not too worried about that. More options would probably be OK. It's more of a clarity than performance issue, in my opinion.

As far as regression test output is concerned, the best approach is to not change that format if possible so we don't have to revise all our tests.
Comment 54 Evan Martin 2009-10-08 12:42:36 PDT
Blah, I keep going in circles on this.

UString.cpp's concatenate() is almost exactly what I want (it's where I got the dtoa handling from) except that it does exponents and works with UStrings.  I could maybe factor that out into something that writes into a char* buffer (which is what it does, then convert to a wide buffer as the last step) but then I'd also need to add special-casing for whether we want exponents or not.  To handle the TextStream case fully it also needs to behave differently for significant digits -- it's basically the difference between how %f and %g in printf handle the precision argument.

I think I bit off more than I should have in my last patch; I will write a new one that *only* touches the CSS floats (what this bug was initially about) and not float handling in general.  Since those floats need special handling (no exponents), it seems reasonable to me to have a special function for it.  I think even in that case I'll need new baselines for a bunch of SVG tests where the 10e-6th digit of a float changes by one due to rounding.  :(

Regarding reserving the correct buffer from the start: I think it could be done, at the cost of uglifying the code a bit.  I will write it and see what you think.
Comment 55 Darin Adler 2009-10-08 13:35:55 PDT
(In reply to comment #54)
> I will write a new
> one that *only* touches the CSS floats (what this bug was initially about) and
> not float handling in general.  Since those floats need special handling (no
> exponents), it seems reasonable to me to have a special function for it.  I
> think even in that case I'll need new baselines for a bunch of SVG tests where
> the 10e-6th digit of a float changes by one due to rounding.

Sounds good.

> Regarding reserving the correct buffer from the start: I think it could be
> done, at the cost of uglifying the code a bit.  I will write it and see what
> you think.

Thanks for taking a crack at it. Probably not super-important.
Comment 56 Evan Martin 2009-10-08 15:30:28 PDT
Created attachment 40916 [details]
use dtoa directly in CSS units
Comment 57 Evan Martin 2009-10-08 15:32:28 PDT
Comment on attachment 40916 [details]
use dtoa directly in CSS units

This is my current plan.  Only four tests fail after this patch: all of them are due to the %fs in the matrix printing.

The size-reservation code is a bit silly; I wonder if it'd be better to just always reserve 8 characters (the expected number + decimal point + sign) and have CSS where the values are obscene (like 20 digits long) cause us to realloc.
Comment 58 Darin Adler 2009-10-08 16:37:43 PDT
(In reply to comment #57)
> This is my current plan.  Only four tests fail after this patch: all of them
> are due to the %fs in the matrix printing.

Looks good.

> The size-reservation code is a bit silly; I wonder if it'd be better to just
> always reserve 8 characters (the expected number + decimal point + sign) and
> have CSS where the values are obscene (like 20 digits long) cause us to
> realloc.

I think it would be better. Sorry for steering you wrong before!
Comment 59 Evan Martin 2009-10-08 19:06:51 PDT
Created attachment 40923 [details]
use dtoa directly in CSS units
Comment 60 Evan Martin 2009-10-08 19:13:44 PDT
Still three tests failing, all under transforms/2d.

Two of them are that the last digit in matrices changes by 1. (Rounding error.)

The last is transforms/2d/transform-accuracy:
transform "rotate(-10deg) rotate(10deg) rotate(360deg) rotate(-360deg) rotate(360deg)" expected
"matrix(1, 0, 0, 1, 0, 0)", got
"matrix(0.999999, -0.000000000000000244929, 0.000000000000000244929, 0.999999, 0, 0)" : FAIL

I think I could maybe introduce a rounding step into the testing library they share to work around it.  I think it's a bit sketchy to be doing equality tests on floats.
Comment 61 Evan Martin 2009-10-08 19:15:15 PDT
(PS: the %fs in the matrix code earlier were a red herring: matrices are printed as a list of CSS floats so this is still the code path they use.)
Comment 62 Evan Martin 2009-10-08 19:18:03 PDT
Created attachment 40924 [details]
use dtoa directly in CSS units
Comment 63 Dirk Schulze 2009-10-09 03:11:39 PDT
(In reply to comment #62)
> Created an attachment (id=40924) [details]
> use dtoa directly in CSS units

Maybe I missed something but where did you run the test, on Gtk? Mac?
Comment 64 Evan Martin 2009-10-09 08:38:15 PDT
(In reply to comment #63)
> Maybe I missed something but where did you run the test, on Gtk? Mac?

Which test?  You mean the ones I rebaselined in the patch?  I did them on a Mac.
Comment 65 Evan Martin 2009-10-12 11:22:07 PDT
Created attachment 41052 [details]
stringify CSS units manually to match CSS spec
Comment 66 Evan Martin 2009-10-12 11:24:35 PDT
Comment on attachment 41052 [details]
stringify CSS units manually to match CSS spec

I had a flash of inspiration over the weekend.  Since we explicitly want to round to 6 figures, we can skip all the dtoa business.  New patch is significantly simpler, makes two tests start passing, and I only had to rebaseline one test due to rounding differences.
Comment 67 Darin Adler 2009-10-12 11:58:35 PDT
Comment on attachment 41052 [details]
stringify CSS units manually to match CSS spec

Looks good.

> +    // For compatibility with existing layout tests, we target 6 digits of precision.

This seems strange to me, even though we've been discussing this one back and forth. I hope that eventually we can make a choice based on something other than "layout tests". Perhaps the best way to word the comment would be to say "compatibility with what was returned by older versions of WebKit"?

> +    const int digits = 6;

This name is too terse. Maybe it should be digitsAfterDecimalPoint?

> +    long long rounded = round(fabs(value) * 1000000.0);

The best function to use to round a double and put the result in a long long would be llround, not round.

What makes long long big enough? If you are trying to avoid overflow, long long is not sufficient for that. There are definitely values that fit in a double that are too large to fit in a long long. How does this handle super-large numbers? Do we have test cases that cover that?

> +    if (rounded == 0) {
> +      vector.append('0');
> +      return;
> +    }

Need to indent by four spaces here, not two.

> +    char buf[80];
> +    int length = snprintf(buf, sizeof(buf), "%lld", rounded);

Since snprintf has rather poor performance, it would be better to use some other code here to convert the integer to a string. Functions like UString::from in JavaScriptCore do this job, and I'd like to see this use a similar function in the String class rather than relying on snprintf.

> +    // Reserve an estimate of space for the number of digits we anticipate
> +    // along with a minus sign/initial zero/decimal point.

That sounds like length + 3.

> +    vector.reserveCapacity(vector.size() + 2 + length);

And this says 2 + length. Why doesn't the code match the comment?

I'd like to see test cases for the edge cases of this code. I worry that this will do the wrong thing for extremely large and extremely small numbers -- test cases could make it clear that the behavior hasn't changed, or that it has improved.

I'm going to say review- because I'd at least like you to rename "digits", but please consider the rest of the comments too.
Comment 68 Evan Martin 2009-10-12 12:10:59 PDT
Created attachment 41055 [details]
stringify CSS units manually to match CSS spec
Comment 69 Evan Martin 2009-10-12 12:21:27 PDT
(In reply to comment #67)
> (From update of attachment 41052 [details])
> Looks good.

Thank you a ton for your patience and careful reviews.

> 
> > +    // For compatibility with existing layout tests, we target 6 digits of precision.
> 
> This seems strange to me, even though we've been discussing this one back and
> forth. I hope that eventually we can make a choice based on something other
> than "layout tests". Perhaps the best way to word the comment would be to say
> "compatibility with what was returned by older versions of WebKit"?

Done.

> 
> > +    const int digits = 6;
> 
> This name is too terse. Maybe it should be digitsAfterDecimalPoint?

Done.

> 
> > +    long long rounded = round(fabs(value) * 1000000.0);
> 
> The best function to use to round a double and put the result in a long long
> would be llround, not round.

Ah, I wasn't even aware of this function.

> 
> What makes long long big enough? If you are trying to avoid overflow, long long
> is not sufficient for that. There are definitely values that fit in a double
> that are too large to fit in a long long. How does this handle super-large
> numbers? Do we have test cases that cover that?

It is true.  Here's my thinking.
One of the test cases I'm fixing here is "large-number-round-trip", which is an old test that predates me discovering this bug.  (I checked it in recently since I discovered it was related.)  It shows that WebKit currently gets numbers as small as 90010000 (roughly 10e8) wrong.

If you're willing to assume a long long is 64 bits (call it 10e19), and my multiply-by-10e6 trick eats 6 of that exponent, I'm making the upper bound around 10e13, which is at least better than where we were before.  Since we're talking about CSS units, I don't think that's too bad of a bound; the spec is silent on the bounds of these values.
  http://www.w3.org/TR/CSS2/syndata.html#value-def-number

Using a plain long is 32 bits, or 10e9, which would bound the values to 10e3 after the rounding trick, which is clearly enough.

> > +    if (rounded == 0) {
> > +      vector.append('0');
> > +      return;
> > +    }
> 
> Need to indent by four spaces here, not two.

This is really embarrassing.  I am normally really finicky about this sort of thing.  My only excuse is that the other code I work on has 2 space tabs stops.

> 
> > +    char buf[80];
> > +    int length = snprintf(buf, sizeof(buf), "%lld", rounded);
> 
> Since snprintf has rather poor performance, it would be better to use some
> other code here to convert the integer to a string. Functions like
> UString::from in JavaScriptCore do this job, and I'd like to see this use a
> similar function in the String class rather than relying on snprintf.

Oops, I missed this before I uploaded my new patch.  Will investigate.

> > +    // Reserve an estimate of space for the number of digits we anticipate
> > +    // along with a minus sign/initial zero/decimal point.
> 
> That sounds like length + 3.
> 
> > +    vector.reserveCapacity(vector.size() + 2 + length);
> 
> And this says 2 + length. Why doesn't the code match the comment?

Overzealous premature optimization (most numbers only have at most two of the three mentioned characteristics).  Fixed.

> I'd like to see test cases for the edge cases of this code. I worry that this
> will do the wrong thing for extremely large and extremely small numbers -- test
> cases could make it clear that the behavior hasn't changed, or that it has
> improved.

The opacity-float test changes try to.  Was there anything else you had in mind?  Or do you mean testing the failure cases -- when the values exceed the allowed inputs?


I will upload a patch fixing the snprintf issue once I figure out a proper solution.  (In my defense, the old code used snprintf for floats.)
Comment 70 Evan Martin 2009-10-12 12:37:54 PDT
(In reply to comment #69)
> Using a plain long is 32 bits, or 10e9, which would bound the values to 10e3
> after the rounding trick, which is clearly enough.

Er, that should be "clearly *not* enough".
Comment 71 Darin Adler 2009-10-12 12:49:12 PDT
(In reply to comment #69)
> I will upload a patch fixing the snprintf issue once I figure out a proper
> solution.  (In my defense, the old code used snprintf for floats.)

We can do that in a separate step. I don't consider it a show-stopper.
Comment 72 Darin Adler 2009-10-12 12:51:07 PDT
(In reply to comment #69)
> > I'd like to see test cases for the edge cases of this code. I worry that this
> > will do the wrong thing for extremely large and extremely small numbers -- test
> > cases could make it clear that the behavior hasn't changed, or that it has
> > improved.
> 
> The opacity-float test changes try to.  Was there anything else you had in
> mind?  Or do you mean testing the failure cases -- when the values exceed the
> allowed inputs?

My approach would be to have tests at the various boundaries: highest value that works, lowest that does not work, that sort of thing. Make sure all the code paths are tested, even the less important ones.

I think we're OK. Not sure. I didn't re-review the tests.
Comment 73 Evan Martin 2009-10-12 14:27:40 PDT
Created attachment 41059 [details]
stringify CSS units manually to match CSS spec
Comment 74 Evan Martin 2009-10-12 14:30:18 PDT
In the process of adding more tests, I discovered some bugs, so that was a wise suggestion.  I rearranged opacity-float so it's more clear what it's testing (pairs of [expected, input]), so any other suggestions for that are welcome.

Should I file a bug about the snprintf thing?  I could write an integer->string loop myself, I suppose.  My Unix-wizard officemate says there are no POSIX builtins that do this, and volunteered some code.
Comment 75 Evan Martin 2009-10-12 14:46:55 PDT
Created attachment 41061 [details]
stringify CSS units manually to match CSS spec
Comment 76 Evan Martin 2009-10-12 14:49:20 PDT
Comment on attachment 41061 [details]
stringify CSS units manually to match CSS spec

Graahhh, now I've confused myself again.  Will reinvestigate this failing test.
Comment 77 Evan Martin 2009-10-12 14:55:56 PDT
Comment on attachment 41061 [details]
stringify CSS units manually to match CSS spec

Actually, I'm kind of exhausted on this bug.

From the printf docs %.6g means you show up to 6 significant digits, but then somehow it doesn't happen when your number very is close to zero and they also arbitrarily chop off trailing digits if they are 0 even when they are significant.  This patch passes all the layout tests, but I had to change the precision on one, but I find it hard to care too much about it.
Comment 78 Darin Adler 2009-10-12 15:23:08 PDT
Comment on attachment 41061 [details]
stringify CSS units manually to match CSS spec

> -FAIL: read 90010000px back as 9.001e+07px, read again as 0px
> +PASS: read 90010000px back as 90010000px, read again as 90010000px

This succeeds only because the large number isn't large enough, right? With a still larger number we'd have some other problem. I'd like to have a test showing that we still fail with even larger numbers.

>  This test verifies that reading a floating-point opacity from CSS attributes gets back a properly-formatted float. Improperly handling locales that cause decimals to be written as commas might break it.
>  
>  PASS
> +PASS
> +PASS
> +PASS
> +PASS
> +PASS
> +PASS
> +PASS
> +PASS
> +PASS
> +PASS
> +PASS
> +PASS
> +PASS

In the future, you should use the script-tests approach to build tests like this. This style of tests that write out more about what they're testing and why they pass that you get automatically from that are superior. A good example of how to do this sort of test is LayoutTests/fast/dom/HTMLFormElement/script-tests/elements-not-in-document.js and you may be able to see from that how this test could have been done that way.

> From the printf docs %.6g means you show up to 6 significant digits, but then
> somehow it doesn't happen when your number very is close to zero and they also
> arbitrarily chop off trailing digits if they are 0 even when they are
> significant.  This patch passes all the layout tests, but I had to change the
> precision on one, but I find it hard to care too much about it.

As I understand it, 0.00000123456 is an example with 6 significant digits. Maybe you misunderstood what it meant. But exactly matching "%.6lg" isn't the point. Matching what we used to do is a good idea, but matching other browsers or matching specifications are even more important.

I think at this point the patch is OK. Here are areas for future improvement and refinement:

    - check on how other browsers handle formatting fractional values in CSS; how many digits of precision, etc.
    - figure out if we want to stick with the 6-digits-of-precision thing forever, or if we should have a different formatting rule

    - eliminate the use of snprintf -- as I said, JavaScriptCore has a function with the relevant code already, JSC::UString::from, so you could just port that to live in String.cpp

    - change the test to use script-tests for better clarity
    - add quite a few more kinds of numbers to the test cases
    - test all the different types of units -- a test should fail if we made any mistakes in the switch statements

r=me
Comment 79 Evan Martin 2009-10-13 14:51:42 PDT
> > From the printf docs %.6g means you show up to 6 significant digits, but then
> > somehow it doesn't happen when your number very is close to zero and they also
> > arbitrarily chop off trailing digits if they are 0 even when they are
> > significant.  This patch passes all the layout tests, but I had to change the
> > precision on one, but I find it hard to care too much about it.
> 
> As I understand it, 0.00000123456 is an example with 6 significant digits.
> Maybe you misunderstood what it meant. But exactly matching "%.6lg" isn't the
> point. Matching what we used to do is a good idea, but matching other browsers
> or matching specifications are even more important.

That is my understanding of significant digits as well.  But from one of my earlier iterations of the patch, I was printing 0.00000000000123456 at the end of some error-accumultating computation; the printf implementation prints 0 for that.  See comment #60.  This is what prompted my new approach, which chops to 6 digits effectively by rounding to the nearest 10e-6.

>     - check on how other browsers handle formatting fractional values in CSS;
> how many digits of precision, etc.
>     - figure out if we want to stick with the 6-digits-of-precision thing
> forever, or if we should have a different formatting rule

Filed https://bugs.webkit.org/show_bug.cgi?id=30341

>     - eliminate the use of snprintf -- as I said, JavaScriptCore has a function
> with the relevant code already, JSC::UString::from, so you could just port that
> to live in String.cpp

Filed https://bugs.webkit.org/show_bug.cgi?id=30342

>     - change the test to use script-tests for better clarity
>     - add quite a few more kinds of numbers to the test cases
>     - test all the different types of units -- a test should fail if we made
> any mistakes in the switch statements

https://bugs.webkit.org/show_bug.cgi?id=30340


I can't promise I'll fix all of those, but I will try to at least improve the test soon since I'm the one who wrote it.
Comment 80 Adam Barth 2009-10-13 14:59:17 PDT
Comment on attachment 41061 [details]
stringify CSS units manually to match CSS spec

This patch does not have a ChangeLog :)
Comment 81 Evan Martin 2009-10-13 15:03:30 PDT
Created attachment 41131 [details]
stringify CSS units manually to match CSS spec
Comment 82 Evan Martin 2009-10-13 15:04:07 PDT
Comment on attachment 41131 [details]
stringify CSS units manually to match CSS spec

Identical patch to the one Darin r+'d above; now includes ChangeLogs.
Comment 83 Adam Barth 2009-10-13 15:06:14 PDT
Comment on attachment 41131 [details]
stringify CSS units manually to match CSS spec

Forwarding Darin's r+ to the new patch with a ChangeLog.
Comment 84 WebKit Commit Bot 2009-10-13 16:02:43 PDT
Comment on attachment 41131 [details]
stringify CSS units manually to match CSS spec

Rejecting patch 41131 from commit-queue.

Failed to run "['WebKitTools/Scripts/run-webkit-tests', '--no-launch-safari', '--quiet', '--exit-after-n-failures=1']" exit_code: 1
Last 500 characters of output:
tTests
Testing 11429 test cases.
http/tests/loading/bad-server-subframe.html -> timed out
Sampling process 18462 for 10 seconds with 10 milliseconds of run time between samples
Sampling completed, processing symbols...
Sample analysis of process 18462 written to file /Users/eseidel/Library/Logs/DumpRenderTree/HangReport.txt

Exiting early after 1 failures. 8510 tests run.
552.63s total testing time

8509 test cases (99%) succeeded
1 test case (<1%) timed out
4 test cases (<1%) had stderr output
Comment 85 Evan Martin 2009-10-13 16:03:54 PDT
Comment on attachment 41131 [details]
stringify CSS units manually to match CSS spec

setting cq+ because i believe that failure was flakiness
Comment 86 Evan Martin 2009-10-13 16:04:18 PDT
Comment on attachment 41131 [details]
stringify CSS units manually to match CSS spec

actually, probably needs someone else to set cq+ since i'm not a committer
Comment 87 WebKit Commit Bot 2009-10-13 21:09:11 PDT
Comment on attachment 41131 [details]
stringify CSS units manually to match CSS spec

Clearing flags on attachment: 41131

Committed r49554: <http://trac.webkit.org/changeset/49554>
Comment 88 WebKit Commit Bot 2009-10-13 21:09:22 PDT
All reviewed patches have been landed.  Closing bug.
Comment 89 Dimitri Glazkov (Google) 2009-10-13 21:44:45 PDT
Rolled out as http://trac.webkit.org/changeset/49554 due to Win and Chromium compile break.
Comment 90 Darin Adler 2009-10-14 08:22:47 PDT
Maybe llround is not available on Windows? If so, the fix could be to add it to <wtf/MathExtras.h>.
Comment 91 Darin Adler 2009-10-14 08:23:20 PDT
I didn't actually look at the Windows failure -- it might not have been llround.
Comment 92 Evan Martin 2009-10-14 09:29:41 PDT
I think I am going to sit on this patch until I have commit access, since breaking the tree and being unable to revert it myself is not very helpful.

If anyone else would like to take up the crusade I welcome your help.  Until then, I recommend "export LC_NUMERIC=en_US.UTF-8".  ;)
Comment 93 Evan Martin 2009-10-14 09:34:10 PDT
For future reference, the Windows breakage was:
CSSPrimitiveValue.cpp
..\css\CSSPrimitiveValue.cpp(698) : error C3861: 'llround': identifier not found
..\css\CSSPrimitiveValue.cpp(705) : error C3861: 'snprintf': identifier not found

I'm trying to find the failed Linux logs but I'm not seeing them; maybe I am getting my failures mixed up.
Comment 94 Darin Adler 2009-10-14 13:58:40 PDT
Dmitri said “Win and Chromium”. I suspect it was Windows in both cases -- not sure it broke on Linux.
Comment 95 Dimitri Glazkov (Google) 2009-10-14 14:02:05 PDT
Right, I don't think it broke anything non-Windows.
Comment 96 Darin Adler 2009-10-14 14:05:18 PDT
I'll check this in.

The fix for snprintf is to include <wtf/StringExtras.h>.

The fix for llround is to include <wtf/MathExtras.h> and add llround to MathExtras.h alongside lround.
Comment 97 Darin Adler 2009-10-14 14:46:06 PDT
http://trac.webkit.org/changeset/49585
Comment 98 Eric Seidel 2009-10-15 10:01:51 PDT
(In reply to comment #84)
> (From update of attachment 41131 [details])
> Rejecting patch 41131 from commit-queue.
> http/tests/loading/bad-server-subframe.html -> timed out

Filed bug 30392 about the false failure.
Comment 99 Dirk Schulze 2009-11-14 00:45:44 PST
We still have this bug on WebKitGtk. I noticed it on http://www.svgbasics.com/radial_gradients.html. Maybe just on SVG's <use> element. Because the example on the first comment works.
Comment 100 Evan Martin 2009-11-14 08:40:55 PST
(In reply to comment #99)
> We still have this bug on WebKitGtk. I noticed it on
> http://www.svgbasics.com/radial_gradients.html. Maybe just on SVG's <use>
> element. Because the example on the first comment works.

I attempted to split this bug into smaller bugs just so it wouldn't go on forever.  See comment 79.  Is it a problem with element.style.opacity?
Comment 101 Dirk Schulze 2009-11-14 08:53:13 PST
(In reply to comment #100)
> (In reply to comment #99)
> > We still have this bug on WebKitGtk. I noticed it on
> > http://www.svgbasics.com/radial_gradients.html. Maybe just on SVG's <use>
> > element. Because the example on the first comment works.
> 
> I attempted to split this bug into smaller bugs just so it wouldn't go on
> forever.  See comment 79.  Is it a problem with element.style.opacity?

You find String::format("translate(%f, %f)", x().value(this), y().value(this)); and some other String::format and String::number in WebCore/SVGUseElement.cpp. This causes the wrong position in this case.

But also some animations don't work like:
http://www.w3.org/Graphics/SVG/Test/20061213/htmlObjectHarness/full-animate-elem-22-b.html
http://www.w3.org/Graphics/SVG/Test/20061213/htmlObjectHarness/full-animate-elem-29-b.html

I can't say what causes the wrong behavior on the SVG animations.
Comment 102 Adele Peterson 2009-12-02 11:17:57 PST
r49585 caused a regression at ziprealty.com - please see https://bugs.webkit.org/show_bug.cgi?id=32078.
Comment 103 Adele Peterson 2009-12-13 15:02:06 PST
Reopening.  I reverted the change in r52071.

I noticed is that the new tests that were added to opacity-float.html didn't pass in Firefox.  I wonder if the JS library used in ziprealty.com was expecting wrong behavior.  Might be something investigate when re-fixing this bug.
Comment 104 Dirk Schulze 2010-01-07 10:57:57 PST
*** Bug 33322 has been marked as a duplicate of this bug. ***
Comment 105 Darin Adler 2010-01-07 11:03:09 PST
We can fix this! We just need to go out of our way to keep behavior changes to a minimum. Someone should take a crack at it.
Comment 106 Evan Martin 2010-01-07 11:27:19 PST
Since I'm responsible for most of the noise on this bug, I should probably write up my current thoughts.

1) I attached a page that shows a bunch of cases as comment #2 on
  https://bugs.webkit.org/show_bug.cgi?id=30341
if you intend to change WebKit's behavior, you should probably check whether you match other browsers using that page, as well as the real website that prompted reverting my previous attempt at this bug.  I noticed Firefox is different than WebKit both before and after my change.  (But see my comment #5 on there.)


2) glib actually has a function that takes a printf-style format and does a locale-inspecific decimal formatted string.  I was curious how it worked:
  http://www.google.com/codesearch/p?hl=en#RGCD84x9Jg0/trunk/xbmc/lib/libmms/glib-2.20.4/glib/gstrfuncs.c&q=g_ascii_formatd&sa=N&cd=1&ct=rc&l=609
(If that link breaks, it's a code search for g_ascii_formatd.)
It works by checking if your locale is one that doesn't use '.' for decimal points, and if so, does a search and replace on the string result of calling printf!  This feels like a huge hack but actually doesn't seem so bad to me at this point.
Comment 107 Aaron Digulla 2010-01-07 11:41:34 PST
Seemed like a little bug when I opened it. This is getting rather silly. So many brains and we can't come up with a solution. Okay, here is one:

- Find the source code for atof.c and printf()-style formatting from a version of glibc which either doesn't support a locale or just omit the locale-specific part.

- Cut'n'paste. Yeah, it's a pain but it's also only a couple of lines of code. So why bother.

- Remove support for the old formatting code in String() to cause compile errors.

- Check whether ACID passes. When ACID passes and some internal tests fail, then fix the internal tests. It's bad when some tests fail because items move by a pixel but then, an important feature of the browser doesn't work at all. On top of that, the fixed code should produce the same results as the old one except for the locale specific decimal point. So tests *should* not fail. If they do, then probably something else broke.

But the focus should be on fixing this bug. If it breaks old tests, that's unfortunate and probably worth an investigation because it shouldn't (= there is no reason why it should except for rounding errors which probably means that the new code has a bug). But it shouldn't stop the fix.
Comment 108 Vincent Alquier 2010-03-24 05:50:33 PDT
*** Bug 36485 has been marked as a duplicate of this bug. ***
Comment 109 Alexey Proskuryakov 2010-07-02 20:20:49 PDT
*** Bug 41541 has been marked as a duplicate of this bug. ***
Comment 110 Dirk Schulze 2010-07-08 22:40:19 PDT
*** Bug 41658 has been marked as a duplicate of this bug. ***
Comment 111 Alexey Proskuryakov 2010-07-21 10:04:53 PDT
See also: bug 42755.
Comment 112 Simon Fraser (smfr) 2010-10-08 11:31:29 PDT
Gavin is working on some num -> string code that might help here.
Comment 113 Nikolas Zimmermann 2010-10-10 04:59:02 PDT
Created attachment 70394 [details]
Work in progress

Attaching a first work in progress patch, and my plan how I want to fix this problem (and related SVG problems):
List of the problems:
- String::format() may be localy dependant.
- String::number(double) uses String::format("%.6lg") and thus is affected as well

Related problems, that affect SVG, is rounding. The String::format() usage truncated, instead of rounding properly to say N significant figures.
- TextStream::operator<<(float/double) is affected.
- SVGPahtStringBuilder (most prominent area of problems)
- SVGPointList/SVGTransformList/SVGAnimatedPropertyTraits (all using String::format)

The way I plan to fix this is outlined in the attached "Work in progress" patch.
Step #1) Patch String::number(double) to utilize wtf/DecimalNumber (which uses dtoa) to convert the double into a string. Apply some extra operations to make it mimic the String::format("%.6lg") output (cut off trailing zeros 1.5000 -> 1.5, leave integers unchanged 12.000 -> 12)

Step #2) Convert all users of String::format() to use String::number() + StringBuilder/Vector<Uchar> based string creation.

Step #3) Kill String::format(), and it's platform specific implementations (like Qt did).

What do you think?
Comment 114 Nikolas Zimmermann 2010-10-10 05:13:26 PDT
When removing String::format(), following code has to change.

String::format() users that don't dump floating-point numbers, and can be converted to a String::number() + StringBuilder/Vector<UChar> based solution right now:

bindings/js/JSDOMWindowBase.cpp:    return String::format("Unsafe JavaScript attempt to access frame with URL %s from frame with URL %s. Domains, protocols and ports must match.\n",
bindings/v8/V8Proxy.cpp:    String str = String::format("Unsafe JavaScript attempt to access frame "
css/CSSOMUtils.cpp:    append(appendTo, String::format("\\%x ", c));
dom/Document.cpp:    return String::format("%02d/%02d/%04d %02d:%02d:%02d", date.month() + 1, date.monthDay(), date.fullYear(), date.hour(), date.minute(), date.second());
dom/ExceptionBase.cpp:        m_message = String::format("%s: %s Exception %d", description.name, description.typeName, description.code);
dom/ExceptionBase.cpp:        m_message = String::format("%s Exception %d", description.typeName, description.code);
dom/StyledElement.cpp:            color = String::format("#%02x%02x%02x", colors[0], colors[1], colors[2]);
dom/XMLDocumentParser.cpp:                m_errorMessages += String::format("warning on line %d at column %d: %s", lineNumber, columnNumber, m);
dom/XMLDocumentParser.cpp:                m_errorMessages += String::format("error on line %d at column %d: %s", lineNumber, columnNumber, m);
html/DateComponents.cpp:        return String::format("%02d:%02d", m_hour, m_minute);
html/DateComponents.cpp:        return String::format("%02d:%02d:%02d", m_hour, m_minute, m_second);
html/DateComponents.cpp:        return String::format("%02d:%02d:%02d.%03d", m_hour, m_minute, m_second, m_millisecond);
html/DateComponents.cpp:        return String::format("%04d-%02d-%02d", m_year, m_month + 1, m_monthDay);
html/DateComponents.cpp:        return String::format("%04d-%02d-%02dT", m_year, m_month + 1, m_monthDay)
html/DateComponents.cpp:        return String::format("%04d-%02d-%02dT", m_year, m_month + 1, m_monthDay)
html/DateComponents.cpp:        return String::format("%04d-%02d", m_year, m_month + 1);
html/DateComponents.cpp:        return String::format("%04d-W%02d", m_year, m_week);
html/FTPDirectoryDocument.cpp:            timeOfDay = String::format(", %i:%02i AM", hour, fileTime.tm_min);
html/FTPDirectoryDocument.cpp:            timeOfDay = String::format(", %i:%02i PM", hour, fileTime.tm_min);
html/FTPDirectoryDocument.cpp:        dateString = String::format("%s %i, %i", months[month], fileTime.tm_mday, fileTime.tm_year);
html/FTPDirectoryDocument.cpp:        dateString = String::format("%s %i, %i", months[month], fileTime.tm_mday, now.tm_year);
inspector/CodeGeneratorInspector.pm:    push(@function, "        protocolErrors->pushString(String::format(\"Protocol Error: %s handler is not available.\", \"$domain\"));");
inspector/CodeGeneratorInspector.pm:        push(@function, "        protocolErrors->pushString(String::format(\"Protocol Error: 'arguments' property with type 'object' was not found.\"));");
inspector/CodeGeneratorInspector.pm:            push(@function, "            protocolErrors->pushString(String::format(\"Protocol Error: Argument '%s' with type '%s' was not found.\", \"$name\", \"$JSONType\"));");
inspector/CodeGeneratorInspector.pm:            push(@function, "                protocolErrors->pushString(String::format(\"Protocol Error: Argument '%s' has wrong type. It should be '%s'.\", \"$name\", \"$JSONType\"));");
inspector/CodeGeneratorInspector.pm:        reportProtocolError(callId, String::format("Protocol Error: Invalid command was received. '%s' wasn't found.", command.utf8().data()));
inspector/InspectorController.cpp:        String message = String::format("Failed to load resource: the server responded with a status of %u (", response.httpStatusCode()) + response.httpStatusText() + ")";
inspector/InspectorController.cpp:    String identifier = title + String::format("@%s:%d", sourceID.utf8().data(), lineNumber);
inspector/InspectorController.cpp:    String message = String::format("%s: %d", title.utf8().data(), count);
inspector/InspectorDebuggerAgent.cpp:    return String::format("%s:%d", sourceID.utf8().data(), lineNumber);
inspector/InspectorProfilerAgent.cpp:    String message = String::format("Profile \"webkit-profile://%s/%s#%d\" finished.", CPUProfileType, encodeWithURLEscapeSequences(title).utf8().data(), profile->uid());
inspector/InspectorProfilerAgent.cpp:    String message = String::format("Profile \"webkit-profile://%s/%s#0\" started.", CPUProfileType, encodeWithURLEscapeSequences(title).utf8().data());
inspector/InspectorProfilerAgent.cpp:    return String::format("%s.%d", UserInitiatedProfileName, m_currentUserInitiatedProfileNumber);
inspector/InspectorProfilerAgent.cpp:    String title = String::format("%s.%d", UserInitiatedProfileName, m_nextUserInitiatedHeapSnapshotNumber++);
inspector/InspectorValues.cpp:                String symbolCode = String::format("\\u%04X", symbol);
loader/archive/cf/LegacyWebArchive.cpp:    String iframeMarkup = String::format("<iframe frameborder=\"no\" marginwidth=\"0\" marginheight=\"0\" width=\"98%%\" height=\"98%%\" src=\"%s\"></iframe>", 
loader/CachedResourceLoader.cpp:        String::format("Unsafe attempt to load URL %s.",
loader/CachedResourceLoader.cpp:        String::format("Unsafe attempt to load URL %s from frame with URL %s. "
loader/FrameLoader.cpp:    String message = String::format("The page at %s displayed insecure content from %s.\n",
loader/FrameLoader.cpp:    String message = String::format("The page at %s ran insecure content from %s.\n",
loader/FrameLoader.cpp:        String message = String::format("Unsafe JavaScript attempt to initiate a navigation change for frame with URL %s from frame with URL %s.\n",
page/DOMWindow.cpp:            String message = String::format("Unable to post message to %s. Recipient has origin %s.\n", 
page/PrintContext.cpp:        return String::format("%d", style->marginLeft().rawValue());
page/PrintContext.cpp:        return String::format("%d", style->lineHeight().rawValue());
page/PrintContext.cpp:        return String::format("%d", style->fontDescription().computedPixelSize());
page/PrintContext.cpp:        return String::format("%s", style->fontDescription().family().family().string().utf8().data());
page/PrintContext.cpp:        return String::format("%d %d", style->pageSize().width().rawValue(), style->pageSize().height().rawValue());
page/PrintContext.cpp:    return String::format("pageProperty() unimplemented for: %s", propertyName);
page/PrintContext.cpp:    return String::format("(%d, %d) %d %d %d %d", pageSize.width(), pageSize.height(), marginTop, marginRight, marginBottom, marginLeft);
page/XSSAuditor.cpp:        String consoleMessage = String::format("Refused to load an object. URL found within request: \"%s\".\n", url.utf8().data());
platform/audio/HRTFElevation.cpp:    String resourceName = String::format("IRC_%s_C_R0195_T%03d_P%03d", subjectName.utf8().data(), azimuth, positiveElevation);
platform/brew/FileSystemBrew.cpp:    return String::format("fs:/~0X%08X/", webViewClassId);
platform/brew/PlatformKeyboardEventBrew.cpp:        return String::format("U+%04X", toASCIIUpper(keyCode));
platform/cocoa/KeyEventCocoa.mm:            return String::format("U+%04X", toASCIIUpper(charCode));
platform/efl/PlatformKeyboardEventEfl.cpp:        String key = String::format("F%d", i);
platform/efl/PlatformKeyboardEventEfl.cpp:        String key = String::format("%c", alphabet[i]);
platform/efl/PlatformKeyboardEventEfl.cpp:        String key = String::format("%d", i);
platform/efl/PlatformKeyboardEventEfl.cpp:        String key = String::format("F%d", i);
platform/graphics/brew/ImageBrew.cpp:    String resourcePath = homeDirectoryPath() + String::format("res/%s.png", name);
platform/graphics/cg/ImageBufferCG.cpp:    return String::format("data:%s;base64,%s", mimeType.utf8().data(), out.data());
platform/graphics/chromium/FontUtilsChromiumWin.cpp:    String fontKey = String::format("%1d:%d:%ls", style, logfont->lfHeight, family);
platform/graphics/chromium/GraphicsLayerChromium.cpp:    String name = String::format("GraphicsLayerChromium(%p) GraphicsLayer(%p) ", m_layer.get(), this) + inName;
platform/graphics/Color.cpp:        return String::format("#%02x%02x%02x", red(), green(), blue());
platform/graphics/Color.cpp:        return String::format("rgba(%u, %u, %u, 0.0)", red(), green(), blue());
platform/graphics/Color.cpp:        return String::format("#%02X%02X%02X%02X", red(), green(), blue(), alpha());
platform/graphics/Color.cpp:    return String::format("#%02X%02X%02X", red(), green(), blue());
platform/graphics/efl/ImageEfl.cpp:    RefPtr<SharedBuffer> buffer = SharedBuffer::createWithContentsOfFile(String::format(DATA_DIR "/webkit-1.0/images/%s.png", name));
platform/graphics/GraphicsLayer.cpp:    return String::format("-|transition%c-", property);
platform/graphics/gtk/ImageBufferGtk.cpp:    return String::format("data:%s;base64,%s", mimeType.utf8().data(), out.data());
platform/graphics/haiku/ImageBufferHaiku.cpp:    return String::format("data:%s;base64,%s", mimeType.utf8().data(),
platform/graphics/mac/GraphicsLayerCA.mm:    return animationName + String::format("_%d_%d", property, index);
platform/graphics/mac/GraphicsLayerCA.mm:    String longName = String::format("CALayer(%p) GraphicsLayer(%p) ", m_layer.get(), this) + name;
platform/graphics/mac/GraphicsLayerCA.mm:    String name = String::format("CALayer(%p) GraphicsLayer(%p) ", m_layer.get(), this) + m_name;
platform/graphics/qt/ImageBufferQt.cpp:    return String::format("data:%s;base64,%s", mimeType.utf8().data(), data.toBase64().data());
platform/graphics/skia/ImageBufferSkia.cpp:    return String::format("data:image/png;base64,%s", base64EncodedData.data());
platform/graphics/win/GraphicsLayerCACF.cpp:    String longName = String::format("CALayer(%p) GraphicsLayer(%p) ", m_layer.get(), this) + name;
platform/graphics/win/GraphicsLayerCACF.cpp:    String name = String::format("CALayer(%p) GraphicsLayer(%p) %s", m_layer.get(), this, m_usingTiledLayer ? "[Tiled Layer] " : "") + m_name;
platform/graphics/win/WebLayer.cpp:        String text = String::format("%d", m_owner->incrementRepaintCount());;
platform/gtk/KeyEventGtk.cpp:            return String::format("U+%04X", gdk_keyval_to_unicode(gdk_keyval_to_upper(keyCode)));
platform/haiku/PlatformKeyboardEventHaiku.cpp:    return String::format("U+%04X", toASCIIUpper(singleByte));
platform/network/CredentialStorage.cpp:        return url.protocol() + "://" + url.host() + String::format(":%i/", url.port());
platform/qt/PlatformKeyboardEventQt.cpp:        return String::format("U+%04X", toupper(keyCode));
platform/sql/SQLiteDatabase.cpp:    executeCommand(String::format("PRAGMA synchronous = %i", sync));
platform/sql/SQLiteFileSystem.cpp:        fileName = pathByAppendingComponent(dbDir, String::format("%016"PRIx64".db", seq));
platform/sql/SQLiteFileSystem.cpp:    return String::format("%016"PRIx64".db", seq);
platform/text/wince/TextCodecWinCE.cpp:                String cpName = String::format("cp%d", cpInfo.uiCodePage);
platform/win/ClipboardUtilitiesWin.cpp:    append(result, String::format(header, startHTMLOffset, endHTMLOffset, startFragmentOffset, endFragmentOffset).utf8());
platform/win/GDIObjectCounter.cpp:    init(String::format("%s (%p)", className.latin1().data(), instance));
platform/win/KeyEventWin.cpp:            return String::format("U+%04X", toASCIIUpper(keyCode));
platform/win/Language.cpp:        computedDefaultLanguage = String::format("%s-%s", languageName.latin1().data(), countryName.latin1().data());
platform/wx/KeyboardEventWx.cpp:            return String::format("U+%04X", toupper(keyCode));
plugins/PluginStream.cpp:        String statusLine = String::format("HTTP %d OK\n", m_resourceResponse.httpStatusCode());
rendering/RenderTheme.cpp:            return String::format("%s%02d:%02d:%02d", (time < 0 ? "-" : ""), hours, minutes, seconds);
rendering/RenderTheme.cpp:        return String::format("%s%01d:%02d:%02d", (time < 0 ? "-" : ""), hours, minutes, seconds);
rendering/RenderTheme.cpp:    return String::format("%s%02d:%02d", (time < 0 ? "-" : ""), minutes, seconds);
rendering/RenderTreeAsText.cpp:                String hex = String::format("\\x{%X}", u);
svg/SVGUseElement.cpp:    text += String::format("SVGElementInstance this=%p, (parentNode=%s (%p), firstChild=%s (%p), correspondingElement=%s (%p), shadowTreeElement=%s (%p), id=%s)\n",
svg/SVGUseElement.cpp:    text += String::format("Corresponding element is associated with %i instance(s):\n", elementInstances.size());
svg/SVGUseElement.cpp:        text += String::format(" -> SVGElementInstance this=%p, (refCount: %i, shadowTreeElement in document? %i)\n",
websockets/WebSocket.cpp:            builder.append(String::format("\\u%04X", protocol[i]));
websockets/WebSocket.cpp:        scriptExecutionContext()->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, String::format("WebSocket port %d blocked", url.port()), 0, scriptExecutionContext()->securityOrigin()->toString());
websockets/WebSocketChannel.cpp:    m_context->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, String::format("WebSocket frame (at %lu bytes) is too long.", static_cast<unsigned long>(newBufferSize)), 0, m_handshake.clientOrigin());
websockets/WebSocketHandshake.cpp:        m_context->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, String::format("Unexpected response code: %d", statusCode), 0, clientOrigin());

String::format() users that dump floating-point numbers, and would need my patch for String::number() before they could be converted:

css/CSSParser.cpp:        String str = String::format("%06d", (int)(value->fValue+.5));
css/CSSPrimitiveValue.cpp:            text = String::format("%.6lg%%", m_value.num);
css/CSSPrimitiveValue.cpp:            text = String::format("%.6lgem", m_value.num);
css/CSSPrimitiveValue.cpp:            text = String::format("%.6lgex", m_value.num);
css/CSSPrimitiveValue.cpp:            text = String::format("%.6lgrem", m_value.num);
css/CSSPrimitiveValue.cpp:            text = String::format("%.6lgpx", m_value.num);
css/CSSPrimitiveValue.cpp:            text = String::format("%.6lgcm", m_value.num);
css/CSSPrimitiveValue.cpp:            text = String::format("%.6lgmm", m_value.num);
css/CSSPrimitiveValue.cpp:            text = String::format("%.6lgin", m_value.num);
css/CSSPrimitiveValue.cpp:            text = String::format("%.6lgpt", m_value.num);
css/CSSPrimitiveValue.cpp:            text = String::format("%.6lgpc", m_value.num);
css/CSSPrimitiveValue.cpp:            text = String::format("%.6lgdeg", m_value.num);
css/CSSPrimitiveValue.cpp:            text = String::format("%.6lgrad", m_value.num);
css/CSSPrimitiveValue.cpp:            text = String::format("%.6lggrad", m_value.num);
css/CSSPrimitiveValue.cpp:            text = String::format("%.6lgms", m_value.num);
css/CSSPrimitiveValue.cpp:            text = String::format("%.6lgs", m_value.num);
css/CSSPrimitiveValue.cpp:            text = String::format("%.6lghz", m_value.num);
css/CSSPrimitiveValue.cpp:            text = String::format("%.6lgkhz", m_value.num);
css/CSSPrimitiveValue.cpp:            text = String::format("%.6lgturn", m_value.num);
css/WebKitCSSMatrix.cpp:        return String::format("matrix(%f, %f, %f, %f, %f, %f)",
css/WebKitCSSMatrix.cpp:    return String::format("matrix3d(%f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f)",
html/FTPDirectoryDocument.cpp:        return String::format("%.2f KB", static_cast<float>(bytes)/1000);
html/FTPDirectoryDocument.cpp:        return String::format("%.2f MB", static_cast<float>(bytes)/1000000);
html/FTPDirectoryDocument.cpp:    return String::format("%.2f GB", static_cast<float>(bytes)/1000000000);
inspector/InspectorValues.cpp:        String value = String::format("%f", m_doubleValue);
page/Console.cpp:    String message = title + String::format(": %.0fms", elapsed);
platform/graphics/Color.cpp:    return String::format("rgba(%u, %u, %u, %.5f)", red(), green(), blue(), alpha() / 255.0f);
platform/graphics/gtk/ImageBufferGtk.cpp:        String qualityString = String::format("%f", *quality);
platform/graphics/mac/MediaPlayerPrivateQTKit.mm:            String text = String::format("%1.2f", frameRate);
svg/SVGAnimatedPropertyTraits.h:    static String toString(PassType type) { return String::format("%f %f %f %f", type.x(), type.y(), type.width(), type.height()); }
svg/SVGTransformList.cpp:        return String::format("matrix(%f %f %f %f %f %f)", matrix.a(), matrix.b(), matrix.c(), matrix.d(), matrix.e(), matrix.f());

If my patch is considered good, I'd change the usages of String::format("%f") to the new String::number() implementation combined StringBuilder/Vector<UChar> to replace the String::format() calls. That would be a good first step, fixing this bug, and then later on we can kill String::format().

I've just realized that not all code wants to round to 6 significant figures, some explicitely demand 2 decimals, some code just uses %f. That means my patch has to be adapted to that. The code in FTPDirectoryDocument for example doesn't want to print exponential numbers, never, that is also not handled yet, when using String::number(double). So maybe we want another enum, that precisely describes the conversion mode.

enum FloatingPointConversion {
    RoundToSignificantFigures,
    RoundToDecimalPlaces,
    AvoidExponentials
};

String String::number(double number, unsigned = RoundToSignificantFigures, unsigned significantFiguresOrDecimalPlaces = 6). The second argument could be used to combine eg. RoundToSiginificantFigures | AvoidExponentials.

Enough for today, what do others think? :-)
Comment 115 Simon Fraser (smfr) 2010-10-10 10:07:18 PDT
I think it's a good general direction. This bug is related to bug 20674.
Comment 116 Gavin Barraclough 2010-10-11 11:21:34 PDT
Hi Nikolas,

I've been looking a bit at strings & number/string conversions lately, and had a couple of comments on this:

> Step #3) Kill String::format(), and it's platform specific implementations (like Qt did).

Yes!  We we absolutely should get rid of this.

> Step #2) Convert all users of String::format() to use String::number() + StringBuilder/Vector<Uchar> based string creation.

I think there is something slightly better we could do here.  StringBuilder is great if you're appending an arbitrary number of strings together, but being able to handle arbitrary amounts of data adds overhead (currently in the form of building the Vector, converting all C-strings to Strings.  In the near future we're likely to move from using a rope to an overcapacity based approach, which will remove these cost but possibly add some copying cost instead).

So one thing I've been meaning to do for a while is to add a String::cat() method to WTF::String.  Logically this would be a varargs function, but in practice since varargs provides no type information & only handles POD data, there would actually be multiple implementations, with methods handling something like 2-to-8 arguments.

In a trivial implementation all arguments would be 'const String&'s, and String::cat() could just wrap StringBuilder (rather than using StringBuilder directly) such that the internal implementation can be improved later.

A better implementation we could make the arguments to cat be templated such that data could be added to the string without converting all arguments to String format first.  We already have this implemented in JavaScriptCore (and since we're close to unifying the string implementations we could ultimately fully share this code), in a set of methods called makeString.  These allow an arbitrary mix of UStrings and plain C-strings to be concatenated together, without first converting the C-strings to Strings.

> Step #1) Patch String::number(double) to utilize wtf/DecimalNumber (which uses dtoa) to convert the double into a string. Apply some extra operations to make it mimic the String::format("%.6lg") output (cut off trailing zeros 1.5000 -> 1.5, leave integers unchanged 12.000 -> 12)

So, the question here is, what do we want String::number() to do?

* The rules for number to String conversion are different in different web technology specifications.
* SVG has an uncommon requirement, in that it never overflows to an exponential representation.
* HTML5 and ECMAScript agree on number-to-string conversion (in that the HTML5 spec requires ECMAScript conversion).

My thought here is that we want String::number() to match HTML5/ECMAScript number-to-string conversion.  That is to say, like UString::number it should call WTF::numberToString in dtoa.h, which wraps DecimalNumber with appropriate bounds (<10^-6 & >=10^21) spilling to exponential representation.

We should probably add a different method specifically for SVG's number-to-string behaviour.  This could be a free function within the svg code, taking a double and returning an String.  This method should also be able to use DecimalNumber in its implementation, but would likely chose to always call toStringDecimal, rather than toStringExponential.

If elsewhere in the code we do require numbers formatted in a specific fashion (with a fixed number of digits, or significant figures) we should add methods to convert with appropriate constraints as necessary.  Hopefully we shouldn't need to mimic "%.6lg" behaviour, since this is not spec defined, probably doesn't match other browsers, and as such is hopefully not relied on anywhere.  Changing this should probably only fix spec compliance.

How does this sound?

cheers,
G.
Comment 117 Nikolas Zimmermann 2010-10-11 12:54:25 PDT
(In reply to comment #116)
> Hi Nikolas,
> 
> I've been looking a bit at strings & number/string conversions lately, and had a couple of comments on this:
Thanks!

> 
> > Step #3) Kill String::format(), and it's platform specific implementations (like Qt did).
> 
> Yes!  We we absolutely should get rid of this.
Great.

> 
> > Step #2) Convert all users of String::format() to use String::number() + StringBuilder/Vector<Uchar> based string creation.
> 
> I think there is something slightly better we could do here.  StringBuilder is great if you're appending an arbitrary number of strings together, but being able to handle arbitrary amounts of data adds overhead (currently in the form of building the Vector, converting all C-strings to Strings.  In the near future we're likely to move from using a rope to an overcapacity based approach, which will remove these cost but possibly add some copying cost instead).
> 
> So one thing I've been meaning to do for a while is to add a String::cat() method to WTF::String.  Logically this would be a varargs function, but in practice since varargs provides no type information & only handles POD data, there would actually be multiple implementations, with methods handling something like 2-to-8 arguments.
> 
> In a trivial implementation all arguments would be 'const String&'s, and String::cat() could just wrap StringBuilder (rather than using StringBuilder directly) such that the internal implementation can be improved later.
Excellent, that would be really handy. Do you have already started coding this?

> 
> A better implementation we could make the arguments to cat be templated such that data could be added to the string without converting all arguments to String format first.  We already have this implemented in JavaScriptCore (and since we're close to unifying the string implementations we could ultimately fully share this code), in a set of methods called makeString.  These allow an arbitrary mix of UStrings and plain C-strings to be concatenated together, without first converting the C-strings to Strings.
I see, seems like it would be very wise to share the code in wtf/.

> 
> > Step #1) Patch String::number(double) to utilize wtf/DecimalNumber (which uses dtoa) to convert the double into a string. Apply some extra operations to make it mimic the String::format("%.6lg") output (cut off trailing zeros 1.5000 -> 1.5, leave integers unchanged 12.000 -> 12)
> 
> So, the question here is, what do we want String::number() to do?
Right.

> 
> * The rules for number to String conversion are different in different web technology specifications.
> * SVG has an uncommon requirement, in that it never overflows to an exponential representation.
> * HTML5 and ECMAScript agree on number-to-string conversion (in that the HTML5 spec requires ECMAScript conversion).
SVGs requirement is uncommon right, but we don't want this for the render path dumps, we definately want exponential numbers in the SVG DRT dumps.

> 
> My thought here is that we want String::number() to match HTML5/ECMAScript number-to-string conversion.  That is to say, like UString::number it should call WTF::numberToString in dtoa.h, which wraps DecimalNumber with appropriate bounds (<10^-6 & >=10^21) spilling to exponential representation.
Correct.

> 
> We should probably add a different method specifically for SVG's number-to-string behaviour.  This could be a free function within the svg code, taking a double and returning an String.  This method should also be able to use DecimalNumber in its implementation, but would likely chose to always call toStringDecimal, rather than toStringExponential.
I'm not sure if we need a special SVG method, I'd rather prefer an enum that describes how the number conversion should happen. The default should match HTML5/ECMAScript.

> 
> If elsewhere in the code we do require numbers formatted in a specific fashion (with a fixed number of digits, or significant figures) we should add methods to convert with appropriate constraints as necessary.  Hopefully we shouldn't need to mimic "%.6lg" behaviour, since this is not spec defined, probably doesn't match other browsers, and as such is hopefully not relied on anywhere.  Changing this should probably only fix spec compliance.
I tried to mimic the output initially to reduce the DRT changes for SVG, to see wheter the patch would fix the rounding issues between gtk 32bit and 64bit. It's not a requirment to mimic the output of String::format, agreed.

> 
> How does this sound?
Excellent! I'm happy to work on this, I hope you already wrote the String::cat stuff? If yes, I'd work on a String::number() implementation (like the one in my attached patch) which gives us more flexibility (add an FloatingPointConversionMode enum, with AvoidExponentials, RoundSiginifcantFigures, RoundDecimalPlaces modes, that can be OR'ed together, etc..)

How does this sound? :-)
Comment 118 Gavin Barraclough 2010-10-11 14:18:53 PDT
> > In a trivial implementation all arguments would be 'const String&'s, and String::cat() could just wrap StringBuilder (rather than using StringBuilder directly) such that the internal implementation can be improved later.
> Excellent, that would be really handy. Do you have already started coding this?

I haven't, but this should be pretty trivial:

String String::cat(const String& s1, const String& s2)
{
    StringBuilder builder;
    builder.append(s1);
    builder.append(s2);
    return builder.toSting();
}

String String::cat(const String& s1, const String& s2, const String& s3)
{
    StringBuilder builder;
    builder.append(s1);
    builder.append(s2);
    builder.append(s3);
    return builder.toSting();
}

And so on for more more arguments.  One problem, I think StringBuilder is currently in WebCore, not WTF, if should move to wtf/text with the rest of the string code.  (I have a patch I'm working on to rewrite StringBuilder, so we may conflict a little there, but if you're just moving the code should be easy to resolve for whoever lands second!)

> > A better implementation we could make the arguments to cat be templated such that data could be added to the string without converting all arguments to String format first.  We already have this implemented in JavaScriptCore (and since we're close to unifying the string implementations we could ultimately fully share this code), in a set of methods called makeString.  These allow an arbitrary mix of UStrings and plain C-strings to be concatenated together, without first converting the C-strings to Strings.
> I see, seems like it would be very wise to share the code in wtf/.

*nod.  The only reason this code is currently JSC specific is because it works on UStrings not Strings, and the difference in String::number & UString::number is one of the few remaining issues to resolve before we can remove UString, so this change should get us closer.

> > * The rules for number to String conversion are different in different web technology specifications.
> > * SVG has an uncommon requirement, in that it never overflows to an exponential representation.
> > * HTML5 and ECMAScript agree on number-to-string conversion (in that the HTML5 spec requires ECMAScript conversion).
> SVGs requirement is uncommon right, but we don't want this for the render path dumps, we definately want exponential numbers in the SVG DRT dumps.

Ah I see, yes okay.  so for these cases the HTML5/ECMAScript behaviour should probably be okay?

> > We should probably add a different method specifically for SVG's number-to-string behaviour.  This could be a free function within the svg code, taking a double and returning an String.  This method should also be able to use DecimalNumber in its implementation, but would likely chose to always call toStringDecimal, rather than toStringExponential.
> I'm not sure if we need a special SVG method, I'd rather prefer an enum that describes how the number conversion should happen. The default should match HTML5/ECMAScript.
>
> Excellent! I'm happy to work on this, I hope you already wrote the String::cat stuff? If yes, I'd work on a String::number() implementation (like the one in my attached patch) which gives us more flexibility (add an FloatingPointConversionMode enum, with AvoidExponentials, RoundSiginifcantFigures, RoundDecimalPlaces modes, that can be OR'ed together, etc..)

My main concern here would be that UString::number can be a hot function, so it would probably be undesirable to add unnecessary overhead to switch on a parameter to check the conversion type.  Also, there may be some subtleties to the requirements of any other conversion functions, so we may be better adding them as we need them.  For example, the decimal number conversion uses a fixed size buffer on the stack, which would not be safe to convert large double values to decimal format.  To safely use this conversion the simple solution would probably be to truncate values into a range (which I believe is explicitly permitted in the SVG spec, Simon?) – however String::number having this truncation built in would be a little weird and arbirtary.  Also, to-fixed methods may require a precision argument.

So perhaps it would just be best to switch String::number to have the HTML5/ECMAScript behaviour (matching UString::number) for all uses, for now?
Comment 119 Nikolas Zimmermann 2010-10-14 05:05:49 PDT
Linking some bug reports, that I've worked on in order to prepare fixing this bug.
Comment 120 Nikolas Zimmermann 2010-10-14 05:15:11 PDT
(In reply to comment #118)

Here are some news, for those that don't follow all webkit commits:

> > Excellent, that would be really handy. Do you have already started coding this?
> 
> I haven't, but this should be pretty trivial:
> 
> String String::cat(const String& s1, const String& s2)
..

This is obsolete now: we have wtf/text/StringConcatenate, solving this problem in an elegant and performant way.

> And so on for more more arguments.  One problem, I think StringBuilder is currently in WebCore, not WTF, if should move to wtf/text with the rest of the string code.  (I have a patch I'm working on to rewrite StringBuilder, so we may conflict a little there, but if you're just moving the code should be easy to resolve for whoever lands second!)
StringBuilder lives in wtf/text now, but using the more performant implementation based on runtime/StringBuilder.

> 
> > SVGs requirement is uncommon right, but we don't want this for the render path dumps, we definately want exponential numbers in the SVG DRT dumps.
> 
> Ah I see, yes okay.  so for these cases the HTML5/ECMAScript behaviour should probably be okay?
Exactly, the only drawback is that each expected.txt file is affected, but I think we can live with that, instead of inventing extra hacks (remove trailing zeros etc..) just for the sake of maintaining compatibility between the new & current DRT dump style.

> > Excellent! I'm happy to work on this, I hope you already wrote the String::cat stuff? If yes, I'd work on a String::number() implementation (like the one in my attached patch) which gives us more flexibility (add an FloatingPointConversionMode enum, with AvoidExponentials, RoundSiginifcantFigures, RoundDecimalPlaces modes, that can be OR'ed together, etc..)
> 
> My main concern here would be that UString::number can be a hot function, so it would probably be undesirable to add unnecessary overhead to switch on a parameter to check the conversion type.  Also, there may be some subtleties to the requirements of any other conversion functions, so we may be better adding them as we need them.  For example, the decimal number conversion uses a fixed size buffer on the stack, which would not be safe to convert large double values to decimal format.  To safely use this conversion the simple solution would probably be to truncate values into a range (which I believe is explicitly permitted in the SVG spec, Simon?) – however String::number having this truncation built in would be a little weird and arbirtary.  Also, to-fixed methods may require a precision argument.

I made up my mind, and think we should have a simple String::number(double) method which serializes the double using the numberToString() method (which uses DecimalNumber) to match the HTML5/ECMAScript default way of doing this. I'm concerned as well to add any extra logic, given the fact we ideally want to switch from UString to WTFString soon.

We can add another method, which takes a precision argument, with explicit naming, whenever we need it (String::numberRoundingSignificantFigures(number, 6)) etc..

> 
> So perhaps it would just be best to switch String::number to have the HTML5/ECMAScript behaviour (matching UString::number) for all uses, for now?
Definately.

I'm still preparing patches, before I can actually work on fixing String::number.
My current plan is:
1) Remove all "simple" String::format usage (no floating-point formatting involved, no pointer dumps) from all of WebCore/WebKit - replace by StringConcatenate (see bug 47664)

2) Identify a common set of missing methods that would be needed to replace the rest of the String::format usages (String::numberAsHex, etc.)

3) Implement them, kill String::format completly.

4) Be happy! :-)
Comment 121 WebKit Review Bot 2010-10-16 05:44:48 PDT
Attachment 70394 [details] did not build on win:
Build output: http://queues.webkit.org/results/4436061
Comment 122 Simon Fraser (smfr) 2010-10-16 13:45:30 PDT
Comment on attachment 70394 [details]
Work in progress

DecimalNumber is the correct approach. See bug 20674.
Comment 123 Nikolas Zimmermann 2010-10-17 02:19:37 PDT
(In reply to comment #122)
> (From update of attachment 70394 [details])
> DecimalNumber is the correct approach. See bug 20674.

Yeah, just as we discussed above. I forgot to clear the r?.
Comment 124 Simon Fraser (smfr) 2010-10-17 13:07:26 PDT
Fixed in http://trac.webkit.org/changeset/69928