ctx.font = "" asserts in CSS parser
https://bugs.webkit.org/show_bug.cgi?id=203127
<rdar://problem/56391016>

Reviewed by Devin Rousso.

Source/WebCore:

The HTML specification says:
"values that cannot be parsed as CSS font values are ignored", so
return early if we get an empty string, otherwise the CSS parser
will assert. This was the only case I could find where we sidestepped
most of the parsing infrastructure and injected a raw string.

Test: http/wpt/2dcontext/text-styles/2d.text.font.parse.invalid.html

* html/canvas/CanvasRenderingContext2D.cpp:
(WebCore::CanvasRenderingContext2D::setFont):

LayoutTests:

Copy the updated test from my WPT pull request. Once this
is merged into WPT, we won't need this version.

* http/wpt/2dcontext/text-styles/2d.text.font.parse.invalid-expected.txt: Added.
* http/wpt/2dcontext/text-styles/2d.text.font.parse.invalid.html: Added.
* http/wpt/resources/canvas-tests.css: Added.
* http/wpt/resources/canvas-tests.js: Added.

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@251270 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog
index 1cb4a106..654ea6b 100644
--- a/LayoutTests/ChangeLog
+++ b/LayoutTests/ChangeLog
@@ -1,3 +1,19 @@
+2019-10-17  Dean Jackson  <dino@apple.com>
+
+        ctx.font = "" asserts in CSS parser
+        https://bugs.webkit.org/show_bug.cgi?id=203127
+        <rdar://problem/56391016>
+
+        Reviewed by Devin Rousso.
+
+        Copy the updated test from my WPT pull request. Once this
+        is merged into WPT, we won't need this version.
+
+        * http/wpt/2dcontext/text-styles/2d.text.font.parse.invalid-expected.txt: Added.
+        * http/wpt/2dcontext/text-styles/2d.text.font.parse.invalid.html: Added.
+        * http/wpt/resources/canvas-tests.css: Added.
+        * http/wpt/resources/canvas-tests.js: Added.
+
 2019-10-17  Ryosuke Niwa  <rniwa@webkit.org>
 
         Integrate resize event with HTML5 event loop
diff --git a/LayoutTests/http/wpt/2dcontext/text-styles/2d.text.font.parse.invalid-expected.txt b/LayoutTests/http/wpt/2dcontext/text-styles/2d.text.font.parse.invalid-expected.txt
new file mode 100644
index 0000000..de4719a
--- /dev/null
+++ b/LayoutTests/http/wpt/2dcontext/text-styles/2d.text.font.parse.invalid-expected.txt
@@ -0,0 +1,5 @@
+2d.text.font.parse.invalid
+Actual output:
+
+PASS Canvas test: 2d.text.font.parse.invalid 
+
diff --git a/LayoutTests/http/wpt/2dcontext/text-styles/2d.text.font.parse.invalid.html b/LayoutTests/http/wpt/2dcontext/text-styles/2d.text.font.parse.invalid.html
new file mode 100644
index 0000000..caecbab
--- /dev/null
+++ b/LayoutTests/http/wpt/2dcontext/text-styles/2d.text.font.parse.invalid.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<title>Canvas test: 2d.text.font.parse.invalid</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../resources/canvas-tests.js"></script>
+<link rel="stylesheet" href="../../resources/canvas-tests.css">
+<body class="show_output">
+
+<h1>2d.text.font.parse.invalid</h1>
+<p class="desc"></p>
+
+
+<p class="output">Actual output:</p>
+<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+
+<ul id="d"></ul>
+<script>
+var t = async_test("");
+_addTest(function(canvas, ctx) {
+
+ctx.font = '20px serif';
+_assertSame(ctx.font, '20px serif', "ctx.font", "'20px serif'");
+
+ctx.font = '20px serif';
+ctx.font = '';
+_assertSame(ctx.font, '20px serif', "ctx.font", "'20px serif'");
+
+ctx.font = '20px serif';
+ctx.font = 'bogus';
+_assertSame(ctx.font, '20px serif', "ctx.font", "'20px serif'");
+
+ctx.font = '20px serif';
+ctx.font = 'inherit';
+_assertSame(ctx.font, '20px serif', "ctx.font", "'20px serif'");
+
+ctx.font = '20px serif';
+ctx.font = '10px {bogus}';
+_assertSame(ctx.font, '20px serif', "ctx.font", "'20px serif'");
+
+ctx.font = '20px serif';
+ctx.font = '10px initial';
+_assertSame(ctx.font, '20px serif', "ctx.font", "'20px serif'");
+
+ctx.font = '20px serif';
+ctx.font = '10px default';
+_assertSame(ctx.font, '20px serif', "ctx.font", "'20px serif'");
+
+ctx.font = '20px serif';
+ctx.font = '10px inherit';
+_assertSame(ctx.font, '20px serif', "ctx.font", "'20px serif'");
+
+ctx.font = '20px serif';
+ctx.font = '1em serif; background: green; margin: 10px';
+_assertSame(ctx.font, '20px serif', "ctx.font", "'20px serif'");
+
+
+});
+</script>
+
diff --git a/LayoutTests/http/wpt/resources/canvas-tests.css b/LayoutTests/http/wpt/resources/canvas-tests.css
new file mode 100644
index 0000000..e006e81
--- /dev/null
+++ b/LayoutTests/http/wpt/resources/canvas-tests.css
@@ -0,0 +1,134 @@
+html.fail {
+    background: #f66;
+}
+html.pass {
+    background: #6f6;
+}
+html.needs_check {
+    background: #99f;
+}
+
+body {
+    font-size: small;
+    font-family: sans-serif;
+    color: black;
+}
+
+a:link {
+    color: #00c;
+}
+a:visited {
+    color: #808;
+}
+
+body.framed {
+    font-size: x-small;
+}
+
+h1 {
+    font-size: larger;
+    margin: 0;
+    padding-left: 0.5em;
+    text-indent: -0.5em;
+}
+
+p {
+    margin: 0;
+}
+
+p.notes {
+    margin-bottom: 0.5em;
+    font-style: italic;
+}
+
+ul {
+    margin: 0;
+    margin-bottom: 0.5em;
+    padding: 0;
+    padding-left: 1em;
+}
+
+.refs {
+    font-style: italic;
+    margin-bottom: 0.5em;
+}
+
+.refs ul {
+    display: inline;
+    margin: 0;
+    padding: 0;
+}
+
+.refs li {
+    display: inline;
+    list-style-type: none;
+    margin: 0;
+    padding: 0;
+}
+
+canvas {
+    display: none;
+    visibility: hidden;
+    border: 2px #f0f solid;
+    background: url(../images/background.png);
+}
+
+img.expected {
+    display: none;
+    border: 2px #f0f solid;
+    background: url(../images/background.png);
+}
+
+iframe {
+    border: 2px #f0f solid;
+}
+
+.output {
+    display: none;
+}
+
+.show_output .output, .needs_check .output  {
+    display: block !important;
+    visibility: visible !important;
+}
+
+.show_output #show_output {
+    display: none;
+}
+
+.resource {
+    visibility: hidden;
+    height: 0;
+}
+
+.fallback {
+    font-size: 2em;
+    font-weight: bold;
+    color: #a00;
+}
+
+
+html.minimal body {
+    color: white;
+}
+html.fail.minimal {
+    background: #f00;
+}
+html.pass.minimal {
+    background: #080;
+}
+html.needs_check.minimal {
+    background: #008;
+}
+.minimal #d {
+    display: none !important;
+}
+.minimal .expectedtext {
+    visibility: hidden !important;
+}
+#passtext, #failtext {
+    display: none;
+}
+.minimal.pass #passtext, .minimal.fail #failtext {
+    display: block;
+}
diff --git a/LayoutTests/http/wpt/resources/canvas-tests.js b/LayoutTests/http/wpt/resources/canvas-tests.js
new file mode 100644
index 0000000..76313bc
--- /dev/null
+++ b/LayoutTests/http/wpt/resources/canvas-tests.js
@@ -0,0 +1,200 @@
+function _valToString(val)
+{
+    if (val === undefined || val === null)
+        return '[' + typeof(val) + ']';
+    return val.toString() + '[' + typeof(val) + ']';
+}
+
+function _assert(cond, text)
+{
+    assert_true(!!cond, text);
+}
+
+function _assertSame(a, b, text_a, text_b)
+{
+    var msg = text_a + ' === ' + text_b + ' (got ' + _valToString(a) +
+              ', expected ' + _valToString(b) + ')';
+    assert_equals(a, b, msg);
+}
+
+function _assertDifferent(a, b, text_a, text_b)
+{
+    var msg = text_a + ' !== ' + text_b + ' (got ' + _valToString(a) +
+              ', expected not ' + _valToString(b) + ')';
+    assert_not_equals(a, b, msg);
+}
+
+
+function _getPixel(canvas, x,y)
+{
+    var ctx = canvas.getContext('2d');
+    var imgdata = ctx.getImageData(x, y, 1, 1);
+    return [ imgdata.data[0], imgdata.data[1], imgdata.data[2], imgdata.data[3] ];
+}
+
+function _assertPixel(canvas, x,y, r,g,b,a, pos, colour)
+{
+    var c = _getPixel(canvas, x,y);
+    assert_equals(c[0], r, 'Red channel of the pixel at (' + x + ', ' + y + ')');
+    assert_equals(c[1], g, 'Green channel of the pixel at (' + x + ', ' + y + ')');
+    assert_equals(c[2], b, 'Blue channel of the pixel at (' + x + ', ' + y + ')');
+    assert_equals(c[3], a, 'Alpha channel of the pixel at (' + x + ', ' + y + ')');
+}
+
+function _assertPixelApprox(canvas, x,y, r,g,b,a, pos, colour, tolerance)
+{
+    var c = _getPixel(canvas, x,y);
+    assert_approx_equals(c[0], r, tolerance, 'Red channel of the pixel at (' + x + ', ' + y + ')');
+    assert_approx_equals(c[1], g, tolerance, 'Green channel of the pixel at (' + x + ', ' + y + ')');
+    assert_approx_equals(c[2], b, tolerance, 'Blue channel of the pixel at (' + x + ', ' + y + ')');
+    assert_approx_equals(c[3], a, tolerance, 'Alpha channel of the pixel at (' + x + ', ' + y + ')');
+}
+
+let _deferred = false;
+
+function deferTest() {
+  _deferred = true;
+}
+
+function _addTest(testFn)
+{
+    on_event(window, "load", function()
+    {
+        t.step(function() {
+            var canvas = document.getElementById('c');
+            var ctx = canvas.getContext('2d');
+            t.step(testFn, window, canvas, ctx);
+        });
+
+        if (!_deferred) {
+            t.done();
+        }
+    });
+}
+
+function _assertGreen(ctx, canvasWidth, canvasHeight)
+{
+    var testColor = function(d, idx, expected) {
+        assert_equals(d[idx], expected, "d[" + idx + "]", String(expected));
+    };
+    var imagedata = ctx.getImageData(0, 0, canvasWidth, canvasHeight);
+    var w = imagedata.width, h = imagedata.height, d = imagedata.data;
+    for (var i = 0; i < h; ++i) {
+        for (var j = 0; j < w; ++j) {
+            testColor(d, 4 * (w * i + j) + 0, 0);
+            testColor(d, 4 * (w * i + j) + 1, 255);
+            testColor(d, 4 * (w * i + j) + 2, 0);
+            testColor(d, 4 * (w * i + j) + 3, 255);
+        }
+    }
+}
+
+function addCrossOriginYellowImage()
+{
+    var img = new Image();
+    img.id = "yellow.png";
+    img.className = "resource";
+    img.src = get_host_info().HTTP_REMOTE_ORIGIN + "/images/yellow.png";
+    document.body.appendChild(img);
+}
+
+function addCrossOriginRedirectYellowImage()
+{
+    var img = new Image();
+    img.id = "yellow.png";
+    img.className = "resource";
+    img.src = get_host_info().HTTP_ORIGIN + "/common/redirect.py?location=" +
+        get_host_info().HTTP_REMOTE_ORIGIN + "/images/yellow.png";
+    document.body.appendChild(img);
+}
+
+function forEachCanvasSource(crossOriginUrl, sameOriginUrl, callback) {
+  function makeImage() {
+    return new Promise((resolve, reject) => {
+      const image = new Image();
+      image.onload = () => resolve(image);
+      image.onerror = reject;
+      image.src = crossOriginUrl + "/images/red.png";
+    });
+  }
+
+  const arguments = [
+    {
+      name: "cross-origin HTMLImageElement",
+      factory: makeImage,
+    },
+
+    {
+      name: "cross-origin SVGImageElement",
+      factory: () => {
+        return new Promise((resolve, reject) => {
+          const image = document.createElementNS("http://www.w3.org/2000/svg", "image");
+          image.onload = () => resolve(image);
+          image.onerror = reject;
+          image.setAttribute("externalResourcesRequired", "true");
+          image.setAttributeNS("http://www.w3.org/1999/xlink", 'xlink:href', crossOriginUrl + "/images/red.png");
+          document.body.appendChild(image);
+        });
+      },
+    },
+
+    {
+      name: "cross-origin HTMLVideoElement",
+      factory: () => {
+        return new Promise((resolve, reject) => {
+          const video = document.createElement("video");
+          video.oncanplaythrough = () => resolve(video);
+          video.onerror = reject;
+          video.src = getVideoURI(crossOriginUrl + "/media/movie_300");
+        });
+      },
+    },
+
+    {
+      name: "redirected to cross-origin HTMLVideoElement",
+      factory: () => {
+        return new Promise((resolve, reject) => {
+          const video = document.createElement("video");
+          video.oncanplaythrough = () => resolve(video);
+          video.onerror = reject;
+          video.src = "/common/redirect.py?location=" + getVideoURI(crossOriginUrl + "/media/movie_300");
+        });
+      },
+    },
+
+    {
+      name: "redirected to same-origin HTMLVideoElement",
+      factory: () => {
+        return new Promise((resolve, reject) => {
+          const video = document.createElement("video");
+          video.oncanplaythrough = () => resolve(video);
+          video.onerror = reject;
+          video.src = crossOriginUrl + "/common/redirect.py?location=" + getVideoURI(sameOriginUrl + "/media/movie_300");
+        });
+      },
+    },
+
+    {
+      name: "unclean HTMLCanvasElement",
+      factory: () => {
+        return makeImage().then(image => {
+          const canvas = document.createElement("canvas");
+          const context = canvas.getContext("2d");
+          context.drawImage(image, 0, 0);
+          return canvas;
+        });
+      },
+    },
+
+    {
+      name: "unclean ImageBitmap",
+      factory: () => {
+        return makeImage().then(createImageBitmap);
+      },
+    },
+  ];
+
+  for (let { name, factory } of arguments) {
+    callback(name, factory);
+  }
+}
diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog
index d8af05f..7d56871 100644
--- a/Source/WebCore/ChangeLog
+++ b/Source/WebCore/ChangeLog
@@ -1,3 +1,22 @@
+2019-10-17  Dean Jackson  <dino@apple.com>
+
+        ctx.font = "" asserts in CSS parser
+        https://bugs.webkit.org/show_bug.cgi?id=203127
+        <rdar://problem/56391016>
+
+        Reviewed by Devin Rousso.
+
+        The HTML specification says:
+        "values that cannot be parsed as CSS font values are ignored", so
+        return early if we get an empty string, otherwise the CSS parser
+        will assert. This was the only case I could find where we sidestepped
+        most of the parsing infrastructure and injected a raw string.
+
+        Test: http/wpt/2dcontext/text-styles/2d.text.font.parse.invalid.html
+
+        * html/canvas/CanvasRenderingContext2D.cpp:
+        (WebCore::CanvasRenderingContext2D::setFont):
+
 2019-10-17  Ryosuke Niwa  <rniwa@webkit.org>
 
         Integrate resize event with HTML5 event loop
diff --git a/Source/WebCore/html/canvas/CanvasRenderingContext2D.cpp b/Source/WebCore/html/canvas/CanvasRenderingContext2D.cpp
index 9ebf12f..95b7b1c 100644
--- a/Source/WebCore/html/canvas/CanvasRenderingContext2D.cpp
+++ b/Source/WebCore/html/canvas/CanvasRenderingContext2D.cpp
@@ -127,6 +127,9 @@
 
 void CanvasRenderingContext2D::setFont(const String& newFont)
 {
+    if (newFont.isEmpty())
+        return;
+
     if (newFont == state().unparsedFont && state().font.realized())
         return;