Add Correctness tests for CanvasKit

Also make a CPU only and GPU only build (although
the latter still has a lot of CPU logic).

Bug: skia:
Change-Id: I857c2300021c2adb5344865c28e4ad3e8d332954
Reviewed-on: https://skia-review.googlesource.com/c/162022
Reviewed-by: Kevin Lubick <kjlubick@google.com>
diff --git a/experimental/canvaskit/Makefile b/experimental/canvaskit/Makefile
index 65764fb..f63395d 100644
--- a/experimental/canvaskit/Makefile
+++ b/experimental/canvaskit/Makefile
@@ -1,25 +1,30 @@
 clean:
 	rm -rf ../../out/canvaskit_wasm
-	rm -rf ./canvas-kit/bin
+	rm -rf ./canvaskit/bin
 	$(MAKE) release
 
 release:
 	# Does an incremental build where possible.
 	./compile.sh
-	mkdir -p ./canvas-kit/bin
-	cp ../../out/canvaskit_wasm/canvaskit.js   ./canvas-kit/bin
-	cp ../../out/canvaskit_wasm/canvaskit.wasm ./canvas-kit/bin
+	mkdir -p ./canvaskit/bin
+	cp ../../out/canvaskit_wasm/canvaskit.js   ./canvaskit/bin
+	cp ../../out/canvaskit_wasm/canvaskit.wasm ./canvaskit/bin
 
 debug:
 	# Does an incremental build where possible.
 	./compile.sh debug
-	mkdir -p ./canvas-kit/bin
-	cp ../../out/canvaskit_wasm/canvaskit.js   ./canvas-kit/bin
-	cp ../../out/canvaskit_wasm/canvaskit.wasm ./canvas-kit/bin
+	mkdir -p ./canvaskit/bin
+	cp ../../out/canvaskit_wasm_debug/canvaskit.js   ./canvaskit/bin
+	cp ../../out/canvaskit_wasm_debug/canvaskit.wasm ./canvaskit/bin
 
 local-example:
-	rm -rf node_modules/canvas-kit
+	rm -rf node_modules/canvaskit
 	mkdir -p node_modules
-	ln -s -T ../canvas-kit  node_modules/canvas-kit
-	echo "Go check out http://localhost:8000/canvas-kit/example.html"
+	ln -s -T ../canvaskit node_modules/canvaskit
+	echo "Go check out http://localhost:8000/canvaskit/example.html"
 	python serve.py
+
+test-continuous:
+	echo "Assuming npm install has been run by user"
+	echo "Also assuming make debug or release has also been run by a user (if needed)"
+	npx karma start ./karma.conf.js --no-single-run --watch-poll
\ No newline at end of file
diff --git a/experimental/canvaskit/canvas-kit/.gitignore b/experimental/canvaskit/canvas-kit/.gitignore
deleted file mode 100644
index 6dd29b7..0000000
--- a/experimental/canvaskit/canvas-kit/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-bin/
\ No newline at end of file
diff --git a/experimental/canvaskit/canvaskit/.gitignore b/experimental/canvaskit/canvaskit/.gitignore
new file mode 100644
index 0000000..d3c3499
--- /dev/null
+++ b/experimental/canvaskit/canvaskit/.gitignore
@@ -0,0 +1,3 @@
+bin/
+package-lock.json
+node_modules/
\ No newline at end of file
diff --git a/experimental/canvaskit/canvas-kit/cpu_example.html b/experimental/canvaskit/canvaskit/cpu_example.html
similarity index 100%
rename from experimental/canvaskit/canvas-kit/cpu_example.html
rename to experimental/canvaskit/canvaskit/cpu_example.html
diff --git a/experimental/canvaskit/canvas-kit/example.html b/experimental/canvaskit/canvaskit/example.html
similarity index 100%
rename from experimental/canvaskit/canvas-kit/example.html
rename to experimental/canvaskit/canvaskit/example.html
diff --git a/experimental/canvaskit/canvas-kit/package.json b/experimental/canvaskit/canvaskit/package.json
similarity index 100%
rename from experimental/canvaskit/canvas-kit/package.json
rename to experimental/canvaskit/canvaskit/package.json
diff --git a/experimental/canvaskit/canvaskit_bindings.cpp b/experimental/canvaskit/canvaskit_bindings.cpp
index 82e65c7..fcf0042 100644
--- a/experimental/canvaskit/canvaskit_bindings.cpp
+++ b/experimental/canvaskit/canvaskit_bindings.cpp
@@ -13,7 +13,6 @@
 #endif
 
 #include "SkCanvas.h"
-#include "SkCanvas.h"
 #include "SkDashPathEffect.h"
 #include "SkCornerPathEffect.h"
 #include "SkDiscretePathEffect.h"
@@ -211,10 +210,12 @@
     function("_getWebGLSurface", &getWebGLSurface, allow_raw_pointers());
     function("currentContext", &emscripten_webgl_get_current_context);
     function("setCurrentContext", &emscripten_webgl_make_context_current);
-#endif
+    constant("gpu", true);
+#else
     function("_getRasterN32PremulSurface", optional_override([](int width, int height)->sk_sp<SkSurface> {
         return SkSurface::MakeRasterN32Premul(width, height, nullptr);
     }), allow_raw_pointers());
+#endif
     function("MakeSkCornerPathEffect", &SkCornerPathEffect::Make, allow_raw_pointers());
     function("MakeSkDiscretePathEffect", &SkDiscretePathEffect::Make, allow_raw_pointers());
     // Won't be called directly, there's a JS helper to deal with typed arrays.
@@ -236,6 +237,9 @@
         .function("drawPath", &SkCanvas::drawPath)
         .function("drawRect", &SkCanvas::drawRect)
         .function("drawText", optional_override([](SkCanvas& self, std::string text, SkScalar x, SkScalar y, const SkPaint& p) {
+            // TODO(kjlubick): This does not work well for non-ascii
+            // Need to maybe add a helper in interface.js that supports UTF-8
+            // Otherwise, go with std::wstring and set UTF-32 encoding.
             self.drawText(text.c_str(), text.length(), x, y, p);
         }))
         .function("flush", &SkCanvas::flush)
@@ -271,7 +275,6 @@
     class_<SkPathEffect>("SkPathEffect")
         .smart_ptr<sk_sp<SkPathEffect>>("sk_sp<SkPathEffect>");
 
-    //TODO make these chainable like PathKit
     class_<SkPath>("SkPath")
         .constructor<>()
         .constructor<const SkPath&>()
@@ -297,6 +300,7 @@
         .smart_ptr<sk_sp<SkSurface>>("sk_sp<SkSurface>")
         .function("width", &SkSurface::width)
         .function("height", &SkSurface::height)
+        .function("_flush", &SkSurface::flush)
         .function("makeImageSnapshot", &SkSurface::makeImageSnapshot)
         .function("_readPixels", optional_override([](SkSurface& self, int width, int height, uintptr_t /* uint8_t* */ cptr)->bool {
             auto* dst = reinterpret_cast<uint8_t*>(cptr);
@@ -358,5 +362,6 @@
         }), allow_raw_pointers());
 
     function("MakeAnimation", &MakeAnimation);
+    constant("skottie", true);
 #endif
 }
diff --git a/experimental/canvaskit/compile.sh b/experimental/canvaskit/compile.sh
index 67be659..7cfdd04 100755
--- a/experimental/canvaskit/compile.sh
+++ b/experimental/canvaskit/compile.sh
@@ -13,8 +13,6 @@
   exit 1
 fi
 
-BUILD_DIR=${BUILD_DIR:="out/canvaskit_wasm"}
-mkdir -p $BUILD_DIR
 # Navigate to SKIA_HOME from where this file is located.
 pushd $BASE_DIR/../..
 
@@ -24,17 +22,24 @@
 
 RELEASE_CONF="-Oz --closure 1 --llvm-lto 3 -DSK_RELEASE"
 EXTRA_CFLAGS="\"-DSK_RELEASE\""
+
 if [[ $@ == *debug* ]]; then
   echo "Building a Debug build"
   EXTRA_CFLAGS="\"-DSK_DEBUG\""
-  RELEASE_CONF="-O0 --js-opts 0 -s SAFE_HEAP=1 -s ASSERTIONS=1 -s GL_ASSERTIONS=1 -g3 -DPATHKIT_TESTING -DSK_DEBUG"
+  RELEASE_CONF="-O0 --js-opts 0 -s DEMANGLE_SUPPORT=1 -s SAFE_HEAP=1 -s ASSERTIONS=1 -s GL_ASSERTIONS=1 -g3 -DPATHKIT_TESTING -DSK_DEBUG"
+  BUILD_DIR=${BUILD_DIR:="out/canvaskit_wasm_debug"}
+else
+  BUILD_DIR=${BUILD_DIR:="out/canvaskit_wasm"}
 fi
 
+mkdir -p $BUILD_DIR
+
 GN_GPU="skia_enable_gpu=true"
 WASM_GPU="-lEGL -lGLESv2 -DSK_SUPPORT_GPU=1"
-if [[ $@ == *no_gpu* ]]; then
-  echo "Omitting the GPU backend"
+if [[ $@ == *cpu* ]]; then
+  echo "Using the CPU backend instead of the GPU backend"
   GN_GPU="skia_enable_gpu=false"
+  GN_GPU_FLAGS=""
   WASM_GPU="-DSK_SUPPORT_GPU=0"
 fi
 
diff --git a/experimental/canvaskit/externs.js b/experimental/canvaskit/externs.js
index c0f6c66..801bad6 100644
--- a/experimental/canvaskit/externs.js
+++ b/experimental/canvaskit/externs.js
@@ -31,6 +31,8 @@
 	MakeSkDashPathEffect: function(intervals, phase) {},
 	setCurrentContext: function() {},
 	LTRBRect: function(l, t, r, b) {},
+	gpu: {},
+	skottie: {},
 
 	// private API (i.e. things declared in the bindings that we use
 	// in the pre-js file)
@@ -78,9 +80,11 @@
 	SkSurface: {
 		// public API should go below because closure still will
 		// remove things declared here and not on the prototype.
+		flush: function() {},
 
 		// private API
 		_readPixels: function(w, h, ptr) {},
+		_flush: function() {},
 	}
 }
 
diff --git a/experimental/canvaskit/interface.js b/experimental/canvaskit/interface.js
index 98be5ef..938ef3e 100644
--- a/experimental/canvaskit/interface.js
+++ b/experimental/canvaskit/interface.js
@@ -107,49 +107,55 @@
       return this;
     };
 
-    CanvasKit.SkSurface.prototype.flush = function() {
-      var success = this._readPixels(this._width, this._height, this._pixelPtr);
-      if (!success) {
-        console.err('could not read pixels');
-        return;
+    if (CanvasKit.gpu) {
+      CanvasKit.getWebGLSurface = function(htmlID) {
+        var canvas = document.getElementById(htmlID);
+        if (!canvas) {
+          throw 'Canvas with id ' + htmlID + ' was not found';
+        }
+        // Maybe better to use clientWidth/height.  See:
+        // https://webglfundamentals.org/webgl/lessons/webgl-anti-patterns.html
+        return this._getWebGLSurface(htmlID, canvas.width, canvas.height);
+      };
+
+      CanvasKit.SkSurface.prototype.flush = function() {
+        this._flush();
       }
+    } else {
+      CanvasKit.getRasterN32PremulSurface = function(htmlID) {
+        var canvas = document.getElementById(htmlID);
+        if (!canvas) {
+          throw 'Canvas with id ' + htmlID + ' was not found';
+        }
+        // Maybe better to use clientWidth/height.  See:
+        // https://webglfundamentals.org/webgl/lessons/webgl-anti-patterns.html
+        var surface = this._getRasterN32PremulSurface(canvas.width, canvas.height);
+        if (surface) {
+          surface.canvas = canvas;
+          surface._width = canvas.width;
+          surface._height = canvas.height;
+          surface._pixelLen = surface._width * surface._height * 4; // it's 8888
+          // Allocate the buffer of pixels to be used to draw back and forth.
+          surface._pixelPtr = CanvasKit._malloc(surface._pixelLen);
+        }
+        return surface;
+      };
 
-      var pixels = new Uint8ClampedArray(CanvasKit.buffer, this._pixelPtr, this._pixelLen);
-      var imageData = new ImageData(pixels, this._width, this._height);
+      CanvasKit.SkSurface.prototype.flush = function() {
+        this._flush();
+        var success = this._readPixels(this._width, this._height, this._pixelPtr);
+        if (!success) {
+          console.err('could not read pixels');
+          return;
+        }
 
-      this.canvas.getContext('2d').putImageData(imageData, 0, 0);
+        var pixels = new Uint8ClampedArray(CanvasKit.buffer, this._pixelPtr, this._pixelLen);
+        var imageData = new ImageData(pixels, this._width, this._height);
 
-    };
-  }
-
-  CanvasKit.getWebGLSurface = function(htmlID) {
-    var canvas = document.getElementById(htmlID);
-    if (!canvas) {
-      throw 'Canvas with id ' + htmlID + ' was not found';
+        this.canvas.getContext('2d').putImageData(imageData, 0, 0);
+      };
     }
-    // Maybe better to use clientWidth/height.  See:
-    // https://webglfundamentals.org/webgl/lessons/webgl-anti-patterns.html
-    return this._getWebGLSurface(htmlID, canvas.width, canvas.height);
-  }
-
-  CanvasKit.getRasterN32PremulSurface = function(htmlID) {
-    var canvas = document.getElementById(htmlID);
-    if (!canvas) {
-      throw 'Canvas with id ' + htmlID + ' was not found';
-    }
-    // Maybe better to use clientWidth/height.  See:
-    // https://webglfundamentals.org/webgl/lessons/webgl-anti-patterns.html
-    var surface = this._getRasterN32PremulSurface(canvas.width, canvas.height);
-    if (surface) {
-      surface.canvas = canvas;
-      surface._width = canvas.width;
-      surface._height = canvas.height;
-      surface._pixelLen = surface._width * surface._height * 4; // it's 8888
-      // Allocate the buffer of pixels to be used to draw back and forth.
-      surface._pixelPtr = CanvasKit._malloc(surface._pixelLen);
-    }
-    return surface;
-  }
+  } // end CanvasKit.onRuntimeInitialized, that is, anything changing prototypes or dynamic.
 
   // Likely only used for tests.
   CanvasKit.LTRBRect = function(l, t, r, b) {
diff --git a/experimental/canvaskit/karma.conf.js b/experimental/canvaskit/karma.conf.js
new file mode 100644
index 0000000..31f5b46
--- /dev/null
+++ b/experimental/canvaskit/karma.conf.js
@@ -0,0 +1,72 @@
+const isDocker = require('is-docker')();
+
+module.exports = function(config) {
+  // Set the default values to be what are needed when testing the
+  // WebAssembly build locally.
+  let cfg = {
+    // frameworks to use
+    // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
+    frameworks: ['jasmine'],
+
+    // list of files / patterns to load in the browser
+    files: [
+      { pattern: 'canvaskit/bin/canvaskit.wasm', included:false, served:true},
+      '../../modules/pathkit/tests/testReporter.js',
+      'canvaskit/bin/canvaskit.js',
+      'tests/*.spec.js'
+    ],
+
+    proxies: {
+      '/canvaskit/': '/base/canvaskit/bin/'
+    },
+
+    // test results reporter to use
+    // possible values: 'dots', 'progress'
+    // available reporters: https://npmjs.org/browse/keyword/karma-reporter
+    reporters: ['progress'],
+
+    // web server port
+    port: 4444,
+
+    // enable / disable colors in the output (reporters and logs)
+    colors: true,
+
+    // level of logging
+    // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
+    logLevel: config.LOG_INFO,
+
+    // enable / disable watching file and executing tests whenever any file changes
+    autoWatch: true,
+
+    browserDisconnectTimeout: 15000,
+    browserNoActivityTimeout: 15000,
+
+    // start these browsers
+    browsers: ['Chrome'],
+
+    // Continuous Integration mode
+    // if true, Karma captures browsers, runs the tests and exits
+    singleRun: false,
+
+    // Concurrency level
+    // how many browser should be started simultaneous
+    concurrency: Infinity,
+  };
+
+  if (isDocker) {
+    // See https://hackernoon.com/running-karma-tests-with-headless-chrome-inside-docker-ae4aceb06ed3
+    cfg.browsers = ['ChromeHeadlessNoSandbox'],
+    cfg.customLaunchers = {
+        ChromeHeadlessNoSandbox: {
+            base: 'ChromeHeadless',
+            flags: [
+            // Without this flag, we see an error:
+            // Failed to move to new namespace: PID namespaces supported, Network namespace supported, but failed: errno = Operation not permitted
+                '--no-sandbox'
+            ],
+        },
+    };
+  }
+
+  config.set(cfg);
+}
diff --git a/experimental/canvaskit/package.json b/experimental/canvaskit/package.json
index 9986143..3a0a862 100644
--- a/experimental/canvaskit/package.json
+++ b/experimental/canvaskit/package.json
@@ -4,15 +4,14 @@
   "description": "private",
   "private": true,
   "main": "index.js",
-  "dependencies": {
-  },
+  "dependencies": {},
   "devDependencies": {
-    "is-docker": "^1.1.0",
-    "jasmine-core": "^3.1.0",
-    "karma": "^2.0.5",
-    "karma-chrome-launcher": "^2.2.0",
-    "karma-jasmine": "^1.1.2",
-    "requirejs": "^2.3.5"
+    "is-docker": "~1.1.0",
+    "jasmine-core": "~3.1.0",
+    "karma": "~3.0.0",
+    "karma-chrome-launcher": "~2.2.0",
+    "karma-jasmine": "~1.1.2",
+    "requirejs": "~2.3.5"
   },
   "scripts": {
     "test": "echo \"Error: no test specified\" && exit 1"
diff --git a/experimental/canvaskit/tests/path.spec.js b/experimental/canvaskit/tests/path.spec.js
new file mode 100644
index 0000000..0be3032
--- /dev/null
+++ b/experimental/canvaskit/tests/path.spec.js
@@ -0,0 +1,166 @@
+
+describe('CanvasKit\'s Path Behavior', function() {
+    // Note, don't try to print the CanvasKit object - it can cause Karma/Jasmine to lock up.
+    var CanvasKit = null;
+    const LoadCanvasKit = new Promise(function(resolve, reject) {
+        if (CanvasKit) {
+            resolve();
+        } else {
+            CanvasKitInit({
+                locateFile: (file) => '/canvaskit/'+file,
+            }).then((_CanvasKit) => {
+                CanvasKit = _CanvasKit;
+                CanvasKit.initFonts();
+                resolve();
+            });
+        }
+    });
+
+    let container = document.createElement('div');
+    document.body.appendChild(container);
+    const CANVAS_WIDTH = 600;
+    const CANVAS_HEIGHT = 600;
+
+    beforeEach(function() {
+        container.innerHTML = `
+            <canvas width=600 height=600 id=test></canvas>
+            <canvas width=600 height=600 id=report></canvas>`;
+    });
+
+    afterEach(function() {
+        container.innerHTML = '';
+    });
+
+    function getSurface() {
+        if (CanvasKit.gpu) {
+            return CanvasKit.getWebGLSurface('test');
+        }
+        return CanvasKit.getRasterN32PremulSurface('test');
+    }
+
+
+    function reportSurface(surface, testname, done) {
+        // In docker, the webgl canvas is blank, but the surface has the pixel
+        // data. So, we copy it out and draw it to a normal canvas to take a picture.
+        // To be consistent across CPU and GPU, we just do it for all configurations
+        // (even though the CPU canvas shows up after flush just fine).
+        let pixelLen = CANVAS_WIDTH * CANVAS_HEIGHT * 4; // 4 bytes for r,g,b,a
+        let pixelPtr = CanvasKit._malloc(pixelLen);
+        let success = surface._readPixels(CANVAS_WIDTH, CANVAS_HEIGHT, pixelPtr);
+        if (!success) {
+            done();
+            expect(success).toBeFalsy('could not read pixels');
+            return;
+        }
+        let pixels = new Uint8ClampedArray(CanvasKit.buffer, pixelPtr, pixelLen);
+        var imageData = new ImageData(pixels, CANVAS_WIDTH, CANVAS_HEIGHT);
+
+        let reportingCanvas =  document.getElementById('report');
+        reportingCanvas.getContext('2d').putImageData(imageData, 0, 0);
+        CanvasKit._free(pixelPtr);
+        reportCanvas(reportingCanvas, testname).then(() => {
+            done();
+        }).catch(reportError(done));
+    }
+
+    it('can draw a path', function(done) {
+        LoadCanvasKit.then(() => {
+            // This is taken from example.html
+            const surface = getSurface();
+            expect(surface).toBeTruthy('Could not make surface')
+            if (!surface) {
+                done();
+                return;
+            }
+            const canvas = surface.getCanvas();
+            const paint = new CanvasKit.SkPaint();
+            paint.setStrokeWidth(1.0);
+            paint.setAntiAlias(true);
+            paint.setColor(CanvasKit.Color(0, 0, 0, 1.0));
+            paint.setStyle(CanvasKit.PaintStyle.STROKE);
+
+            const path = new CanvasKit.SkPath();
+            path.moveTo(20, 5);
+            path.lineTo(30, 20);
+            path.lineTo(40, 10);
+            path.lineTo(50, 20);
+            path.lineTo(60, 0);
+            path.lineTo(20, 5);
+
+            path.moveTo(20, 80);
+            path.cubicTo(90, 10, 160, 150, 190, 10);
+
+            path.moveTo(36, 148);
+            path.quadTo(66, 188, 120, 136);
+            path.lineTo(36, 148);
+
+            path.moveTo(150, 180);
+            path.arcTo(150, 100, 50, 200, 20);
+            path.lineTo(160, 160);
+
+            path.moveTo(20, 120);
+            path.lineTo(20, 120);
+
+            path.transform([2, 0, 0,
+                            0, 2, 0,
+                            0, 0, 1 ])
+
+            canvas.drawPath(path, paint);
+            surface.flush();
+
+            path.delete();
+            paint.delete();
+
+            reportSurface(surface, 'path_api_example', done);
+        });
+        // See CanvasKit for more tests, since they share implementation
+    });
+
+    function starPath(CanvasKit, X=128, Y=128, R=116) {
+        let p = new CanvasKit.SkPath();
+        p.moveTo(X + R, Y);
+        for (let i = 1; i < 8; i++) {
+          let a = 2.6927937 * i;
+          p.lineTo(X + R * Math.cos(a), Y + R * Math.sin(a));
+        }
+        return p;
+      }
+
+    it('can apply an effect and draw text', function(done) {
+        LoadCanvasKit.then(() => {
+            const surface = getSurface();
+            expect(surface).toBeTruthy('Could not make surface')
+            if (!surface) {
+                done();
+                return;
+            }
+            const canvas = surface.getCanvas();
+            const path = starPath(CanvasKit);
+
+            const paint = new CanvasKit.SkPaint();
+
+            const textPaint = new CanvasKit.SkPaint();
+            textPaint.setColor(CanvasKit.Color(40, 0, 0, 1.0));
+            textPaint.setTextSize(30);
+            textPaint.setAntiAlias(true);
+
+            const dpe = CanvasKit.MakeSkDashPathEffect([15, 5, 5, 10], 1);
+
+            paint.setPathEffect(dpe);
+            paint.setStyle(CanvasKit.PaintStyle.STROKE);
+            paint.setStrokeWidth(5.0);
+            paint.setAntiAlias(true);
+            paint.setColor(CanvasKit.Color(66, 129, 164, 1.0));
+
+            canvas.clear(CanvasKit.Color(255, 255, 255, 1.0));
+
+            canvas.drawPath(path, paint);
+            canvas.drawText('This is text', 10, 280, textPaint);
+            surface.flush();
+            dpe.delete();
+            path.delete();
+
+            reportSurface(surface, 'effect_and_text_example', done);
+        });
+    });
+});
diff --git a/infra/bots/gen_tasks.go b/infra/bots/gen_tasks.go
index d23283a..e3b8fc9 100644
--- a/infra/bots/gen_tasks.go
+++ b/infra/bots/gen_tasks.go
@@ -333,8 +333,7 @@
 	return nil
 }
 
-// linuxGceDimensions are the Swarming dimensions for Linux GCE
-// instances.
+// linuxGceDimensions are the Swarming dimensions for Linux GCE instances.
 func linuxGceDimensions(machineType string) []string {
 	return []string{
 		// Specify CPU to avoid running builds on bots with a more unique CPU.
@@ -347,6 +346,13 @@
 	}
 }
 
+func wasmGceDimensions() []string {
+	// There's limited parallelism for WASM builds, so we can get away with the medium
+	// instance instead of the beefy large instance.
+	// Docker being intsalled is the most important part.
+	return append(linuxGceDimensions(MACHINE_TYPE_MEDIUM), "docker_installed:true")
+}
+
 // deriveCompileTaskName returns the name of a compile task based on the given
 // job name.
 func deriveCompileTaskName(jobName string, parts map[string]string) string {
@@ -396,7 +402,12 @@
 			ec = []string{"PathKit"}
 		}
 		if strings.Contains(jobName, "CanvasKit") {
-			ec = []string{"CanvasKit"}
+			if parts["cpu_or_gpu"] == "CPU" {
+				ec = []string{"CanvasKit_CPU"}
+			} else {
+				ec = []string{"CanvasKit"}
+			}
+
 		}
 		if len(ec) > 0 {
 			jobNameMap["extra_config"] = strings.Join(ec, "_")
@@ -539,7 +550,11 @@
 				d["machine_type"] = MACHINE_TYPE_MEDIUM
 			}
 		} else {
-			if strings.Contains(parts["os"], "Win") {
+			if strings.Contains(parts["extra_config"], "CanvasKit") {
+				// GPU is defined for the WebGL version of CanvasKit, but
+				// it can still run on a GCE instance.
+				return wasmGceDimensions()
+			} else if strings.Contains(parts["os"], "Win") {
 				gpu, ok := map[string]string{
 					"GT610":         "10de:104a-23.21.13.9101",
 					"GTX660":        "10de:11c0-24.21.13.9882",
@@ -606,12 +621,7 @@
 		d["gpu"] = "none"
 		if d["os"] == DEFAULT_OS_DEBIAN {
 			if strings.Contains(parts["extra_config"], "PathKit") || strings.Contains(parts["extra_config"], "CanvasKit") {
-				// The build isn't really parallelized for pathkit, so
-				// the bulky machines don't buy us much. All we really need is
-				// docker, which was manually installed on the MEDIUM and LARGE
-				// Debian machines and should be on any newly-created Debian
-				// machines (after Aug 2018).
-				return linuxGceDimensions(MACHINE_TYPE_MEDIUM)
+				return wasmGceDimensions()
 			}
 			if parts["role"] == "BuildStats" {
 				// Doesn't require a lot of resources
diff --git a/infra/bots/jobs.json b/infra/bots/jobs.json
index 8fd6810..9bebbab 100644
--- a/infra/bots/jobs.json
+++ b/infra/bots/jobs.json
@@ -58,8 +58,10 @@
   "Build-Debian9-EMCC-asmjs-Debug-PathKit",
   "Build-Debian9-EMCC-asmjs-Release-PathKit",
   "Build-Debian9-EMCC-wasm-Debug-CanvasKit",
+  "Build-Debian9-EMCC-wasm-Debug-CanvasKit_CPU",
   "Build-Debian9-EMCC-wasm-Debug-PathKit",
   "Build-Debian9-EMCC-wasm-Release-CanvasKit",
+  "Build-Debian9-EMCC-wasm-Release-CanvasKit_CPU",
   "Build-Debian9-EMCC-wasm-Release-PathKit",
   "Build-Debian9-GCC-loongson3a-Debug",
   "Build-Debian9-GCC-loongson3a-Release",
@@ -113,6 +115,7 @@
   "BuildStats-Debian9-Clang-x86_64-Release-Mini",
   "BuildStats-Debian9-EMCC-asmjs-Release-PathKit",
   "BuildStats-Debian9-EMCC-wasm-Release-CanvasKit",
+  "BuildStats-Debian9-EMCC-wasm-Release-CanvasKit_CPU",
   "BuildStats-Debian9-EMCC-wasm-Release-PathKit",
   "Calmbench-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All",
   "Calmbench-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All",
@@ -438,8 +441,12 @@
   "Test-Debian9-Clang-ShuttleA-GPU-IntelHD2000-x86_64-Release-All",
   "Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Debug-All-PathKit",
   "Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Release-All-PathKit",
+  "Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-CanvasKit",
   "Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-PathKit",
+  "Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-CanvasKit",
   "Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-PathKit",
+  "Test-Debian9-EMCC-GCE-GPU-AVX2-wasm-Debug-All-CanvasKit",
+  "Test-Debian9-EMCC-GCE-GPU-AVX2-wasm-Release-All-CanvasKit",
   "Test-Debian9-GCC-GCE-CPU-AVX2-x86-Debug-All",
   "Test-Debian9-GCC-GCE-CPU-AVX2-x86-Release-All",
   "Test-Debian9-GCC-GCE-CPU-AVX2-x86_64-Debug-All",
diff --git a/infra/bots/recipe_modules/build/canvaskit.py b/infra/bots/recipe_modules/build/canvaskit.py
index 12c54d8..ecd7440 100644
--- a/infra/bots/recipe_modules/build/canvaskit.py
+++ b/infra/bots/recipe_modules/build/canvaskit.py
@@ -13,6 +13,7 @@
 def compile_fn(api, checkout_root, _ignore):
   out_dir = api.vars.cache_dir.join('docker', 'canvaskit')
   configuration = api.vars.builder_cfg.get('configuration', '')
+  extra         = api.vars.builder_cfg.get('extra_config',   '')
 
   # We want to make sure the directories exist and were created by chrome-bot,
   # because if that isn't the case, docker will make them and they will be
@@ -34,6 +35,9 @@
   cmd = ['docker', 'run', '--rm', '-v', '%s:/SRC' % checkout_root,
          '-v', '%s:/OUT' % out_dir,
          DOCKER_IMAGE, INNER_BUILD_SCRIPT]
+  if 'CPU' in extra:
+    cmd.append('cpu') # It defaults to gpu
+
   if configuration == 'Debug':
     cmd.append('debug') # It defaults to Release
   api.run(
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-wasm-Release-CanvasKit.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-wasm-Release-CanvasKit_CPU.json
similarity index 98%
rename from infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-wasm-Release-CanvasKit.json
rename to infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-wasm-Release-CanvasKit_CPU.json
index 16da363..d32a2a0 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-wasm-Release-CanvasKit.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-wasm-Release-CanvasKit_CPU.json
@@ -24,7 +24,8 @@
       "-v",
       "[START_DIR]/cache/docker/canvaskit:/OUT",
       "gcr.io/skia-public/emsdk-release:1.38.6_jre",
-      "/SRC/skia/infra/canvaskit/build_canvaskit.sh"
+      "/SRC/skia/infra/canvaskit/build_canvaskit.sh",
+      "cpu"
     ],
     "env": {
       "CHROME_HEADLESS": "1",
@@ -37,7 +38,7 @@
       "python",
       "-u",
       "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
-      "[START_DIR]/cache/work/skia/out/Build-Debian9-EMCC-wasm-Release-CanvasKit/Release",
+      "[START_DIR]/cache/work/skia/out/Build-Debian9-EMCC-wasm-Release-CanvasKit_CPU/Release",
       "[START_DIR]/[SWARM_OUT_DIR]/out/Release"
     ],
     "infra_step": true,
diff --git a/infra/bots/recipe_modules/build/examples/full.py b/infra/bots/recipe_modules/build/examples/full.py
index 4b8bd1b..49610fa 100644
--- a/infra/bots/recipe_modules/build/examples/full.py
+++ b/infra/bots/recipe_modules/build/examples/full.py
@@ -47,7 +47,7 @@
   'Build-Debian9-EMCC-asmjs-Release-PathKit',
   'Build-Debian9-EMCC-wasm-Debug-CanvasKit',
   'Build-Debian9-EMCC-wasm-Debug-PathKit',
-  'Build-Debian9-EMCC-wasm-Release-CanvasKit',
+  'Build-Debian9-EMCC-wasm-Release-CanvasKit_CPU',
   'Build-Debian9-EMCC-wasm-Release-PathKit',
   'Build-Debian9-GCC-arm-Release-Chromecast',
   'Build-Debian9-GCC-loongson3a-Release',
diff --git a/infra/bots/recipes/perf_pathkit.expected/Perf-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Release-All-PathKit.json b/infra/bots/recipes/perf_pathkit.expected/Perf-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Release-All-PathKit.json
index 3cde98b..a9f24b5 100644
--- a/infra/bots/recipes/perf_pathkit.expected/Perf-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Release-All-PathKit.json
+++ b/infra/bots/recipes/perf_pathkit.expected/Perf-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Release-All-PathKit.json
@@ -202,7 +202,7 @@
       "[START_DIR]/[SWARM_OUT_DIR]:/OUT",
       "-e",
       "ASM_JS=1",
-      "gcr.io/skia-public/perf-karma-chrome-tests:68.0.3440.106_v1",
+      "gcr.io/skia-public/perf-karma-chrome-tests:68.0.3440.106_v6",
       "/SRC/skia/infra/pathkit/perf_pathkit.sh",
       "--builder",
       "Perf-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Release-All-PathKit",
@@ -218,6 +218,8 @@
       "Chrome",
       "--config",
       "Release",
+      "--source_type",
+      "pathkit",
       "--compiled_language",
       "asmjs"
     ],
diff --git a/infra/bots/recipes/perf_pathkit.expected/Perf-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-PathKit.json b/infra/bots/recipes/perf_pathkit.expected/Perf-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-PathKit.json
index 96ff4a3..c9d09c3 100644
--- a/infra/bots/recipes/perf_pathkit.expected/Perf-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-PathKit.json
+++ b/infra/bots/recipes/perf_pathkit.expected/Perf-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-PathKit.json
@@ -200,7 +200,7 @@
       "[START_DIR]/cache/work:/SRC",
       "-v",
       "[START_DIR]/[SWARM_OUT_DIR]:/OUT",
-      "gcr.io/skia-public/perf-karma-chrome-tests:68.0.3440.106_v1",
+      "gcr.io/skia-public/perf-karma-chrome-tests:68.0.3440.106_v6",
       "/SRC/skia/infra/pathkit/perf_pathkit.sh",
       "--builder",
       "Perf-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-PathKit",
@@ -215,7 +215,9 @@
       "--browser",
       "Chrome",
       "--config",
-      "Release"
+      "Release",
+      "--source_type",
+      "pathkit"
     ],
     "env": {
       "CHROME_HEADLESS": "1",
diff --git a/infra/bots/recipes/perf_pathkit.expected/pathkit_trybot.json b/infra/bots/recipes/perf_pathkit.expected/pathkit_trybot.json
index 82187e6..0b6bbdc 100644
--- a/infra/bots/recipes/perf_pathkit.expected/pathkit_trybot.json
+++ b/infra/bots/recipes/perf_pathkit.expected/pathkit_trybot.json
@@ -202,7 +202,7 @@
       "[START_DIR]/cache/work:/SRC",
       "-v",
       "[START_DIR]/[SWARM_OUT_DIR]:/OUT",
-      "gcr.io/skia-public/perf-karma-chrome-tests:68.0.3440.106_v1",
+      "gcr.io/skia-public/perf-karma-chrome-tests:68.0.3440.106_v6",
       "/SRC/skia/infra/pathkit/perf_pathkit.sh",
       "--builder",
       "Perf-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-PathKit",
@@ -218,6 +218,8 @@
       "Chrome",
       "--config",
       "Release",
+      "--source_type",
+      "pathkit",
       "--issue",
       "1234",
       "--patchset",
diff --git a/infra/bots/recipes/perf_pathkit.py b/infra/bots/recipes/perf_pathkit.py
index e0c5495..ba1ea93 100644
--- a/infra/bots/recipes/perf_pathkit.py
+++ b/infra/bots/recipes/perf_pathkit.py
@@ -17,7 +17,7 @@
 ]
 
 
-DOCKER_IMAGE = 'gcr.io/skia-public/perf-karma-chrome-tests:68.0.3440.106_v1'
+DOCKER_IMAGE = 'gcr.io/skia-public/perf-karma-chrome-tests:68.0.3440.106_v6'
 INNER_KARMA_SCRIPT = '/SRC/skia/infra/pathkit/perf_pathkit.sh'
 
 
@@ -105,6 +105,7 @@
          '--task_id',              api.vars.swarming_task_id,
          '--browser',              'Chrome',
          '--config',               api.vars.configuration,
+         '--source_type',          'pathkit',
          ])
 
   if 'asmjs' in api.vars.builder_name:
diff --git a/infra/bots/recipes/test_canvaskit.expected/Test-Debian9-EMCC-GCE-GPU-WEBGL1-wasm-Debug-All-CanvasKit.json b/infra/bots/recipes/test_canvaskit.expected/Test-Debian9-EMCC-GCE-GPU-WEBGL1-wasm-Debug-All-CanvasKit.json
new file mode 100644
index 0000000..1f9d196
--- /dev/null
+++ b/infra/bots/recipes/test_canvaskit.expected/Test-Debian9-EMCC-GCE-GPU-WEBGL1-wasm-Debug-All-CanvasKit.json
@@ -0,0 +1,233 @@
+[
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/cache/work"
+    ],
+    "infra_step": true,
+    "name": "makedirs checkout_path"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "remove",
+      "[START_DIR]/cache/work/.gclient_entries"
+    ],
+    "infra_step": true,
+    "name": "remove [START_DIR]/cache/work/.gclient_entries"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[depot_tools::bot_update]/resources/bot_update.py",
+      "--spec-path",
+      "cache_dir = '[START_DIR]/cache/git'\nsolutions = [{'deps_file': '.DEPS.git', 'managed': False, 'name': 'skia', 'url': 'https://skia.googlesource.com/skia.git'}]",
+      "--patch_root",
+      "skia",
+      "--revision_mapping_file",
+      "{\"got_revision\": \"skia\"}",
+      "--git-cache-dir",
+      "[START_DIR]/cache/git",
+      "--cleanup-dir",
+      "[CLEANUP]/bot_update",
+      "--output_json",
+      "/path/to/tmp/json",
+      "--revision",
+      "skia@abc123"
+    ],
+    "cwd": "[START_DIR]/cache/work",
+    "env_prefixes": {
+      "PATH": [
+        "RECIPE_PACKAGE_REPO[depot_tools]"
+      ]
+    },
+    "infra_step": true,
+    "name": "bot_update",
+    "~followup_annotations": [
+      "@@@STEP_TEXT@Some step text@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"did_run\": true, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"fixed_revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"skia\": \"abc123\"@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"manifest\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"skia\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"repository\": \"https://fake.org/skia.git\", @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"revision\": \"9046e2e693bb92a76e972b694580e5d17ad10748\"@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"patch_failure\": false, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"patch_root\": \"skia\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"properties\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"got_revision\": \"9046e2e693bb92a76e972b694580e5d17ad10748\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"got_revision_cp\": \"refs/heads/master@{#164710}\"@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"root\": \"skia\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"source_manifest\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"directories\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"skia\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"git_checkout\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@          \"repo_url\": \"https://fake.org/skia.git\", @@@",
+      "@@@STEP_LOG_LINE@json.output@          \"revision\": \"9046e2e693bb92a76e972b694580e5d17ad10748\"@@@",
+      "@@@STEP_LOG_LINE@json.output@        }@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }, @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"version\": 0@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"step_text\": \"Some step text\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@",
+      "@@@SET_BUILD_PROPERTY@got_revision@\"9046e2e693bb92a76e972b694580e5d17ad10748\"@@@",
+      "@@@SET_BUILD_PROPERTY@got_revision_cp@\"refs/heads/master@{#164710}\"@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/[SWARM_OUT_DIR]"
+    ],
+    "infra_step": true,
+    "name": "mkdirs out_dir"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "import errno\nimport os\nimport shutil\nimport sys\n\ncopy_dest = sys.argv[1]\nbase_dir = sys.argv[2]\nbundle_name = sys.argv[3]\nout_dir = sys.argv[4]\n\n# Clean out old binaries (if any)\ntry:\n  shutil.rmtree(copy_dest)\nexcept OSError as e:\n  if e.errno != errno.ENOENT:\n    raise\n\n# Make folder\ntry:\n  os.makedirs(copy_dest)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\n# Copy binaries (canvaskit.js and canvaskit.wasm) to where the karma tests\n# expect them ($SKIA_ROOT/experimental/canvaskit/canvaskit/bin/)\ndest = os.path.join(copy_dest, 'canvaskit.js')\nshutil.copyfile(os.path.join(base_dir, 'canvaskit.js'), dest)\nos.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.\n\nif bundle_name:\n  dest = os.path.join(copy_dest, bundle_name)\n  shutil.copyfile(os.path.join(base_dir, bundle_name), dest)\n  os.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.\n\n# Prepare output folder, api.file.ensure_directory doesn't touch\n# the permissions of the out directory if it already exists.\nos.chmod(out_dir, 0o777) # important, otherwise non-privileged docker can't write.\n",
+      "[START_DIR]/cache/work/skia/experimental/canvaskit/canvaskit/bin",
+      "[START_DIR]/build",
+      "canvaskit.wasm",
+      "[START_DIR]/[SWARM_OUT_DIR]"
+    ],
+    "infra_step": true,
+    "name": "Set up for docker",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@import errno@@@",
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@import shutil@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@copy_dest = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@base_dir = sys.argv[2]@@@",
+      "@@@STEP_LOG_LINE@python.inline@bundle_name = sys.argv[3]@@@",
+      "@@@STEP_LOG_LINE@python.inline@out_dir = sys.argv[4]@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@# Clean out old binaries (if any)@@@",
+      "@@@STEP_LOG_LINE@python.inline@try:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  shutil.rmtree(copy_dest)@@@",
+      "@@@STEP_LOG_LINE@python.inline@except OSError as e:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  if e.errno != errno.ENOENT:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    raise@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@# Make folder@@@",
+      "@@@STEP_LOG_LINE@python.inline@try:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  os.makedirs(copy_dest)@@@",
+      "@@@STEP_LOG_LINE@python.inline@except OSError as e:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  if e.errno != errno.EEXIST:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    raise@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@# Copy binaries (canvaskit.js and canvaskit.wasm) to where the karma tests@@@",
+      "@@@STEP_LOG_LINE@python.inline@# expect them ($SKIA_ROOT/experimental/canvaskit/canvaskit/bin/)@@@",
+      "@@@STEP_LOG_LINE@python.inline@dest = os.path.join(copy_dest, 'canvaskit.js')@@@",
+      "@@@STEP_LOG_LINE@python.inline@shutil.copyfile(os.path.join(base_dir, 'canvaskit.js'), dest)@@@",
+      "@@@STEP_LOG_LINE@python.inline@os.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@if bundle_name:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  dest = os.path.join(copy_dest, bundle_name)@@@",
+      "@@@STEP_LOG_LINE@python.inline@  shutil.copyfile(os.path.join(base_dir, bundle_name), dest)@@@",
+      "@@@STEP_LOG_LINE@python.inline@  os.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@# Prepare output folder, api.file.ensure_directory doesn't touch@@@",
+      "@@@STEP_LOG_LINE@python.inline@# the permissions of the out directory if it already exists.@@@",
+      "@@@STEP_LOG_LINE@python.inline@os.chmod(out_dir, 0o777) # important, otherwise non-privileged docker can't write.@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "import os\nprint os.environ.get('SWARMING_BOT_ID', '')\n"
+    ],
+    "name": "get swarming bot id",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_BOT_ID', '')@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "import os\nprint os.environ.get('SWARMING_TASK_ID', '')\n"
+    ],
+    "name": "get swarming task id",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_TASK_ID', '')@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "docker",
+      "run",
+      "--shm-size=2gb",
+      "--rm",
+      "--volume",
+      "[START_DIR]/cache/work:/SRC",
+      "--volume",
+      "[START_DIR]/[SWARM_OUT_DIR]:/OUT",
+      "gcr.io/skia-public/gold-karma-chrome-tests:68.0.3440.106_v6",
+      "/SRC/skia/infra/canvaskit/test_canvaskit.sh",
+      "--builder",
+      "Test-Debian9-EMCC-GCE-GPU-WEBGL1-wasm-Debug-All-CanvasKit",
+      "--git_hash",
+      "abc123",
+      "--buildbucket_build_id",
+      "",
+      "--bot_id",
+      "",
+      "--task_id",
+      "",
+      "--browser",
+      "Chrome",
+      "--config",
+      "Debug",
+      "--source_type",
+      "canvaskit"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+    },
+    "name": "Test CanvasKit with Docker"
+  },
+  {
+    "name": "$result",
+    "recipe_result": null,
+    "status_code": 0
+  }
+]
\ No newline at end of file
diff --git a/infra/bots/recipes/test_canvaskit.expected/canvaskit_trybot.json b/infra/bots/recipes/test_canvaskit.expected/canvaskit_trybot.json
new file mode 100644
index 0000000..e53c271
--- /dev/null
+++ b/infra/bots/recipes/test_canvaskit.expected/canvaskit_trybot.json
@@ -0,0 +1,239 @@
+[
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/cache/work"
+    ],
+    "infra_step": true,
+    "name": "makedirs checkout_path"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "remove",
+      "[START_DIR]/cache/work/.gclient_entries"
+    ],
+    "infra_step": true,
+    "name": "remove [START_DIR]/cache/work/.gclient_entries"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[depot_tools::bot_update]/resources/bot_update.py",
+      "--spec-path",
+      "cache_dir = '[START_DIR]/cache/git'\nsolutions = [{'deps_file': '.DEPS.git', 'managed': False, 'name': 'skia', 'url': 'https://skia.googlesource.com/skia.git'}]",
+      "--patch_root",
+      "skia",
+      "--revision_mapping_file",
+      "{\"got_revision\": \"skia\"}",
+      "--git-cache-dir",
+      "[START_DIR]/cache/git",
+      "--cleanup-dir",
+      "[CLEANUP]/bot_update",
+      "--output_json",
+      "/path/to/tmp/json",
+      "--patch_ref",
+      "https://skia.googlesource.com/skia.git@89/456789/12",
+      "--revision",
+      "skia@abc123"
+    ],
+    "cwd": "[START_DIR]/cache/work",
+    "env_prefixes": {
+      "PATH": [
+        "RECIPE_PACKAGE_REPO[depot_tools]"
+      ]
+    },
+    "infra_step": true,
+    "name": "bot_update",
+    "~followup_annotations": [
+      "@@@STEP_TEXT@Some step text@@@",
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"did_run\": true, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"fixed_revisions\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"skia\": \"abc123\"@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"manifest\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"skia\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"repository\": \"https://fake.org/skia.git\", @@@",
+      "@@@STEP_LOG_LINE@json.output@      \"revision\": \"9046e2e693bb92a76e972b694580e5d17ad10748\"@@@",
+      "@@@STEP_LOG_LINE@json.output@    }@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"patch_failure\": false, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"patch_root\": \"skia\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"properties\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"got_revision\": \"9046e2e693bb92a76e972b694580e5d17ad10748\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"got_revision_cp\": \"refs/heads/master@{#164710}\"@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"root\": \"skia\", @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"source_manifest\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"directories\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@      \"skia\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"git_checkout\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@          \"repo_url\": \"https://fake.org/skia.git\", @@@",
+      "@@@STEP_LOG_LINE@json.output@          \"revision\": \"9046e2e693bb92a76e972b694580e5d17ad10748\"@@@",
+      "@@@STEP_LOG_LINE@json.output@        }@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    }, @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"version\": 0@@@",
+      "@@@STEP_LOG_LINE@json.output@  }, @@@",
+      "@@@STEP_LOG_LINE@json.output@  \"step_text\": \"Some step text\"@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@",
+      "@@@SET_BUILD_PROPERTY@got_revision@\"9046e2e693bb92a76e972b694580e5d17ad10748\"@@@",
+      "@@@SET_BUILD_PROPERTY@got_revision_cp@\"refs/heads/master@{#164710}\"@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/[SWARM_OUT_DIR]"
+    ],
+    "infra_step": true,
+    "name": "mkdirs out_dir"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "import errno\nimport os\nimport shutil\nimport sys\n\ncopy_dest = sys.argv[1]\nbase_dir = sys.argv[2]\nbundle_name = sys.argv[3]\nout_dir = sys.argv[4]\n\n# Clean out old binaries (if any)\ntry:\n  shutil.rmtree(copy_dest)\nexcept OSError as e:\n  if e.errno != errno.ENOENT:\n    raise\n\n# Make folder\ntry:\n  os.makedirs(copy_dest)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\n# Copy binaries (canvaskit.js and canvaskit.wasm) to where the karma tests\n# expect them ($SKIA_ROOT/experimental/canvaskit/canvaskit/bin/)\ndest = os.path.join(copy_dest, 'canvaskit.js')\nshutil.copyfile(os.path.join(base_dir, 'canvaskit.js'), dest)\nos.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.\n\nif bundle_name:\n  dest = os.path.join(copy_dest, bundle_name)\n  shutil.copyfile(os.path.join(base_dir, bundle_name), dest)\n  os.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.\n\n# Prepare output folder, api.file.ensure_directory doesn't touch\n# the permissions of the out directory if it already exists.\nos.chmod(out_dir, 0o777) # important, otherwise non-privileged docker can't write.\n",
+      "[START_DIR]/cache/work/skia/experimental/canvaskit/canvaskit/bin",
+      "[START_DIR]/build",
+      "canvaskit.wasm",
+      "[START_DIR]/[SWARM_OUT_DIR]"
+    ],
+    "infra_step": true,
+    "name": "Set up for docker",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@import errno@@@",
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@import shutil@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@copy_dest = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@base_dir = sys.argv[2]@@@",
+      "@@@STEP_LOG_LINE@python.inline@bundle_name = sys.argv[3]@@@",
+      "@@@STEP_LOG_LINE@python.inline@out_dir = sys.argv[4]@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@# Clean out old binaries (if any)@@@",
+      "@@@STEP_LOG_LINE@python.inline@try:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  shutil.rmtree(copy_dest)@@@",
+      "@@@STEP_LOG_LINE@python.inline@except OSError as e:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  if e.errno != errno.ENOENT:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    raise@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@# Make folder@@@",
+      "@@@STEP_LOG_LINE@python.inline@try:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  os.makedirs(copy_dest)@@@",
+      "@@@STEP_LOG_LINE@python.inline@except OSError as e:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  if e.errno != errno.EEXIST:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    raise@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@# Copy binaries (canvaskit.js and canvaskit.wasm) to where the karma tests@@@",
+      "@@@STEP_LOG_LINE@python.inline@# expect them ($SKIA_ROOT/experimental/canvaskit/canvaskit/bin/)@@@",
+      "@@@STEP_LOG_LINE@python.inline@dest = os.path.join(copy_dest, 'canvaskit.js')@@@",
+      "@@@STEP_LOG_LINE@python.inline@shutil.copyfile(os.path.join(base_dir, 'canvaskit.js'), dest)@@@",
+      "@@@STEP_LOG_LINE@python.inline@os.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@if bundle_name:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  dest = os.path.join(copy_dest, bundle_name)@@@",
+      "@@@STEP_LOG_LINE@python.inline@  shutil.copyfile(os.path.join(base_dir, bundle_name), dest)@@@",
+      "@@@STEP_LOG_LINE@python.inline@  os.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@# Prepare output folder, api.file.ensure_directory doesn't touch@@@",
+      "@@@STEP_LOG_LINE@python.inline@# the permissions of the out directory if it already exists.@@@",
+      "@@@STEP_LOG_LINE@python.inline@os.chmod(out_dir, 0o777) # important, otherwise non-privileged docker can't write.@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "import os\nprint os.environ.get('SWARMING_BOT_ID', '')\n"
+    ],
+    "name": "get swarming bot id",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_BOT_ID', '')@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "import os\nprint os.environ.get('SWARMING_TASK_ID', '')\n"
+    ],
+    "name": "get swarming task id",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_TASK_ID', '')@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "docker",
+      "run",
+      "--shm-size=2gb",
+      "--rm",
+      "--volume",
+      "[START_DIR]/cache/work:/SRC",
+      "--volume",
+      "[START_DIR]/[SWARM_OUT_DIR]:/OUT",
+      "gcr.io/skia-public/gold-karma-chrome-tests:68.0.3440.106_v6",
+      "/SRC/skia/infra/canvaskit/test_canvaskit.sh",
+      "--builder",
+      "Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-CanvasKit",
+      "--git_hash",
+      "abc123",
+      "--buildbucket_build_id",
+      "",
+      "--bot_id",
+      "",
+      "--task_id",
+      "",
+      "--browser",
+      "Chrome",
+      "--config",
+      "Debug",
+      "--source_type",
+      "canvaskit",
+      "--issue",
+      "1234",
+      "--patchset",
+      "7"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+    },
+    "name": "Test CanvasKit with Docker"
+  },
+  {
+    "name": "$result",
+    "recipe_result": null,
+    "status_code": 0
+  }
+]
\ No newline at end of file
diff --git a/infra/bots/recipes/test_canvaskit.py b/infra/bots/recipes/test_canvaskit.py
new file mode 100644
index 0000000..6392f93
--- /dev/null
+++ b/infra/bots/recipes/test_canvaskit.py
@@ -0,0 +1,141 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Recipe which runs the Canvaskit tests using docker
+
+DEPS = [
+  'checkout',
+  'infra',
+  'recipe_engine/file',
+  'recipe_engine/path',
+  'recipe_engine/properties',
+  'recipe_engine/python',
+  'recipe_engine/step',
+  'run',
+  'vars',
+]
+
+
+DOCKER_IMAGE = 'gcr.io/skia-public/gold-karma-chrome-tests:68.0.3440.106_v6'
+INNER_KARMA_SCRIPT = '/SRC/skia/infra/canvaskit/test_canvaskit.sh'
+
+
+def RunSteps(api):
+  api.vars.setup()
+  checkout_root = api.checkout.default_checkout_root
+  out_dir = api.vars.swarming_out_dir
+  api.checkout.bot_update(checkout_root=checkout_root)
+
+  # Make sure this exists, otherwise Docker will make it with root permissions.
+  api.file.ensure_directory('mkdirs out_dir', out_dir, mode=0777)
+
+  # The karma script is configured to look in ./canvaskit/bin/ for
+  # the test files to load, so we must copy them there (see Set up for docker).
+  copy_dest = checkout_root.join('skia', 'experimental', 'canvaskit',
+                                 'canvaskit', 'bin')
+
+  base_dir = api.vars.build_dir
+  bundle_name = 'canvaskit.wasm'
+
+  api.python.inline(
+      name='Set up for docker',
+      program='''import errno
+import os
+import shutil
+import sys
+
+copy_dest = sys.argv[1]
+base_dir = sys.argv[2]
+bundle_name = sys.argv[3]
+out_dir = sys.argv[4]
+
+# Clean out old binaries (if any)
+try:
+  shutil.rmtree(copy_dest)
+except OSError as e:
+  if e.errno != errno.ENOENT:
+    raise
+
+# Make folder
+try:
+  os.makedirs(copy_dest)
+except OSError as e:
+  if e.errno != errno.EEXIST:
+    raise
+
+# Copy binaries (canvaskit.js and canvaskit.wasm) to where the karma tests
+# expect them ($SKIA_ROOT/experimental/canvaskit/canvaskit/bin/)
+dest = os.path.join(copy_dest, 'canvaskit.js')
+shutil.copyfile(os.path.join(base_dir, 'canvaskit.js'), dest)
+os.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.
+
+if bundle_name:
+  dest = os.path.join(copy_dest, bundle_name)
+  shutil.copyfile(os.path.join(base_dir, bundle_name), dest)
+  os.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.
+
+# Prepare output folder, api.file.ensure_directory doesn't touch
+# the permissions of the out directory if it already exists.
+os.chmod(out_dir, 0o777) # important, otherwise non-privileged docker can't write.
+''',
+      args=[copy_dest, base_dir, bundle_name, out_dir],
+      infra_step=True)
+
+
+  cmd = ['docker', 'run', '--shm-size=2gb', '--rm',
+         '--volume', '%s:/SRC' % checkout_root,
+         '--volume', '%s:/OUT' % out_dir]
+
+  cmd.extend([
+      DOCKER_IMAGE,             INNER_KARMA_SCRIPT,
+      '--builder',              api.vars.builder_name,
+      '--git_hash',             api.properties['revision'],
+      '--buildbucket_build_id', api.properties.get('buildbucket_build_id',
+                                                  ''),
+      '--bot_id',               api.vars.swarming_bot_id,
+      '--task_id',              api.vars.swarming_task_id,
+      '--browser',              'Chrome',
+      '--config',               api.vars.configuration,
+      '--source_type',          'canvaskit',
+      ])
+
+  if api.vars.is_trybot:
+    cmd.extend([
+      '--issue',         api.vars.issue,
+      '--patchset',      api.vars.patchset,
+    ])
+
+  api.run(
+    api.step,
+    'Test CanvasKit with Docker',
+    cmd=cmd)
+
+
+def GenTests(api):
+  yield (
+      api.test('Test-Debian9-EMCC-GCE-GPU-WEBGL1-wasm-Debug-All-CanvasKit') +
+      api.properties(buildername=('Test-Debian9-EMCC-GCE-GPU-WEBGL1'
+                                  '-wasm-Debug-All-CanvasKit'),
+                     repository='https://skia.googlesource.com/skia.git',
+                     revision='abc123',
+                     path_config='kitchen',
+                     swarm_out_dir='[SWARM_OUT_DIR]')
+  )
+
+  yield (
+      api.test('canvaskit_trybot') +
+      api.properties(buildername=('Test-Debian9-EMCC-GCE-CPU-AVX2'
+                                  '-wasm-Debug-All-CanvasKit'),
+                     repository='https://skia.googlesource.com/skia.git',
+                     revision='abc123',
+                     path_config='kitchen',
+                     swarm_out_dir='[SWARM_OUT_DIR]',
+                     patch_ref='89/456789/12',
+                     patch_repo='https://skia.googlesource.com/skia.git',
+                     patch_storage='gerrit',
+                     patch_set=7,
+                     patch_issue=1234,
+                     gerrit_project='skia',
+                     gerrit_url='https://skia-review.googlesource.com/')
+  )
diff --git a/infra/bots/recipes/test_pathkit.expected/Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Debug-All-PathKit.json b/infra/bots/recipes/test_pathkit.expected/Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Debug-All-PathKit.json
index 9616168..cce3441 100644
--- a/infra/bots/recipes/test_pathkit.expected/Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Debug-All-PathKit.json
+++ b/infra/bots/recipes/test_pathkit.expected/Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Debug-All-PathKit.json
@@ -112,7 +112,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport os\nimport shutil\nimport sys\n\ncopy_dest = sys.argv[1]\nbase_dir = sys.argv[2]\nbundle_name = sys.argv[3]\nout_dir = sys.argv[4]\n\n# Clean out old binaries (if any)\ntry:\n  shutil.rmtree(copy_dest)\nexcept OSError as e:\n  if e.errno != errno.ENOENT:\n    raise\n\n# Make folder\ntry:\n  os.makedirs(copy_dest)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\n# Copy binaries (pathkit.js and pathkit.wasm) to where the karma tests\n# expect them ($SKIA_ROOT/modules/pathkit/npm-wasm/test/)\ndest = os.path.join(copy_dest, 'pathkit.js')\nshutil.copyfile(os.path.join(base_dir, 'pathkit.js'), dest)\nos.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.\n\nif bundle_name:\n  dest = os.path.join(copy_dest, bundle_name)\n  shutil.copyfile(os.path.join(base_dir, bundle_name), dest)\n  os.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.\n\n# Prepare output folder, api.file.ensure_directory doesn't touch\n# the permissions of the out directory if it already exists.\nos.chmod(out_dir, 0o777) # important, otherwise non-privileged docker can't write.\n",
+      "import errno\nimport os\nimport shutil\nimport sys\n\ncopy_dest = sys.argv[1]\nbase_dir = sys.argv[2]\nbundle_name = sys.argv[3]\nout_dir = sys.argv[4]\n\n# Clean out old binaries (if any)\ntry:\n  shutil.rmtree(copy_dest)\nexcept OSError as e:\n  if e.errno != errno.ENOENT:\n    raise\n\n# Make folder\ntry:\n  os.makedirs(copy_dest)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\n# Copy binaries (pathkit.js and pathkit.wasm) to where the karma tests\n# expect them ($SKIA_ROOT/modules/pathkit/npm-wasm/bin/test/)\ndest = os.path.join(copy_dest, 'pathkit.js')\nshutil.copyfile(os.path.join(base_dir, 'pathkit.js'), dest)\nos.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.\n\nif bundle_name:\n  dest = os.path.join(copy_dest, bundle_name)\n  shutil.copyfile(os.path.join(base_dir, bundle_name), dest)\n  os.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.\n\n# Prepare output folder, api.file.ensure_directory doesn't touch\n# the permissions of the out directory if it already exists.\nos.chmod(out_dir, 0o777) # important, otherwise non-privileged docker can't write.\n",
       "[START_DIR]/cache/work/skia/modules/pathkit/npm-asmjs/bin/test",
       "[START_DIR]/build",
       "",
@@ -146,7 +146,7 @@
       "@@@STEP_LOG_LINE@python.inline@    raise@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@# Copy binaries (pathkit.js and pathkit.wasm) to where the karma tests@@@",
-      "@@@STEP_LOG_LINE@python.inline@# expect them ($SKIA_ROOT/modules/pathkit/npm-wasm/test/)@@@",
+      "@@@STEP_LOG_LINE@python.inline@# expect them ($SKIA_ROOT/modules/pathkit/npm-wasm/bin/test/)@@@",
       "@@@STEP_LOG_LINE@python.inline@dest = os.path.join(copy_dest, 'pathkit.js')@@@",
       "@@@STEP_LOG_LINE@python.inline@shutil.copyfile(os.path.join(base_dir, 'pathkit.js'), dest)@@@",
       "@@@STEP_LOG_LINE@python.inline@os.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.@@@",
@@ -196,13 +196,13 @@
       "run",
       "--shm-size=2gb",
       "--rm",
-      "-v",
+      "--volume",
       "[START_DIR]/cache/work:/SRC",
-      "-v",
+      "--volume",
       "[START_DIR]/[SWARM_OUT_DIR]:/OUT",
-      "-e",
+      "--env",
       "ASM_JS=1",
-      "gcr.io/skia-public/gold-karma-chrome-tests:68.0.3440.106_v5",
+      "gcr.io/skia-public/gold-karma-chrome-tests:68.0.3440.106_v6",
       "/SRC/skia/infra/pathkit/test_pathkit.sh",
       "--builder",
       "Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Debug-All-PathKit",
@@ -218,6 +218,8 @@
       "Chrome",
       "--config",
       "Debug",
+      "--source_type",
+      "pathkit",
       "--compiled_language",
       "asmjs"
     ],
diff --git a/infra/bots/recipes/test_pathkit.expected/Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Release-All-PathKit.json b/infra/bots/recipes/test_pathkit.expected/Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Release-All-PathKit.json
index 6683e7a..d13a691 100644
--- a/infra/bots/recipes/test_pathkit.expected/Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Release-All-PathKit.json
+++ b/infra/bots/recipes/test_pathkit.expected/Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Release-All-PathKit.json
@@ -112,7 +112,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport os\nimport shutil\nimport sys\n\ncopy_dest = sys.argv[1]\nbase_dir = sys.argv[2]\nbundle_name = sys.argv[3]\nout_dir = sys.argv[4]\n\n# Clean out old binaries (if any)\ntry:\n  shutil.rmtree(copy_dest)\nexcept OSError as e:\n  if e.errno != errno.ENOENT:\n    raise\n\n# Make folder\ntry:\n  os.makedirs(copy_dest)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\n# Copy binaries (pathkit.js and pathkit.wasm) to where the karma tests\n# expect them ($SKIA_ROOT/modules/pathkit/npm-wasm/test/)\ndest = os.path.join(copy_dest, 'pathkit.js')\nshutil.copyfile(os.path.join(base_dir, 'pathkit.js'), dest)\nos.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.\n\nif bundle_name:\n  dest = os.path.join(copy_dest, bundle_name)\n  shutil.copyfile(os.path.join(base_dir, bundle_name), dest)\n  os.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.\n\n# Prepare output folder, api.file.ensure_directory doesn't touch\n# the permissions of the out directory if it already exists.\nos.chmod(out_dir, 0o777) # important, otherwise non-privileged docker can't write.\n",
+      "import errno\nimport os\nimport shutil\nimport sys\n\ncopy_dest = sys.argv[1]\nbase_dir = sys.argv[2]\nbundle_name = sys.argv[3]\nout_dir = sys.argv[4]\n\n# Clean out old binaries (if any)\ntry:\n  shutil.rmtree(copy_dest)\nexcept OSError as e:\n  if e.errno != errno.ENOENT:\n    raise\n\n# Make folder\ntry:\n  os.makedirs(copy_dest)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\n# Copy binaries (pathkit.js and pathkit.wasm) to where the karma tests\n# expect them ($SKIA_ROOT/modules/pathkit/npm-wasm/bin/test/)\ndest = os.path.join(copy_dest, 'pathkit.js')\nshutil.copyfile(os.path.join(base_dir, 'pathkit.js'), dest)\nos.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.\n\nif bundle_name:\n  dest = os.path.join(copy_dest, bundle_name)\n  shutil.copyfile(os.path.join(base_dir, bundle_name), dest)\n  os.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.\n\n# Prepare output folder, api.file.ensure_directory doesn't touch\n# the permissions of the out directory if it already exists.\nos.chmod(out_dir, 0o777) # important, otherwise non-privileged docker can't write.\n",
       "[START_DIR]/cache/work/skia/modules/pathkit/npm-asmjs/bin/test",
       "[START_DIR]/build",
       "pathkit.js.mem",
@@ -146,7 +146,7 @@
       "@@@STEP_LOG_LINE@python.inline@    raise@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@# Copy binaries (pathkit.js and pathkit.wasm) to where the karma tests@@@",
-      "@@@STEP_LOG_LINE@python.inline@# expect them ($SKIA_ROOT/modules/pathkit/npm-wasm/test/)@@@",
+      "@@@STEP_LOG_LINE@python.inline@# expect them ($SKIA_ROOT/modules/pathkit/npm-wasm/bin/test/)@@@",
       "@@@STEP_LOG_LINE@python.inline@dest = os.path.join(copy_dest, 'pathkit.js')@@@",
       "@@@STEP_LOG_LINE@python.inline@shutil.copyfile(os.path.join(base_dir, 'pathkit.js'), dest)@@@",
       "@@@STEP_LOG_LINE@python.inline@os.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.@@@",
@@ -196,13 +196,13 @@
       "run",
       "--shm-size=2gb",
       "--rm",
-      "-v",
+      "--volume",
       "[START_DIR]/cache/work:/SRC",
-      "-v",
+      "--volume",
       "[START_DIR]/[SWARM_OUT_DIR]:/OUT",
-      "-e",
+      "--env",
       "ASM_JS=1",
-      "gcr.io/skia-public/gold-karma-chrome-tests:68.0.3440.106_v5",
+      "gcr.io/skia-public/gold-karma-chrome-tests:68.0.3440.106_v6",
       "/SRC/skia/infra/pathkit/test_pathkit.sh",
       "--builder",
       "Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Release-All-PathKit",
@@ -218,6 +218,8 @@
       "Chrome",
       "--config",
       "Release",
+      "--source_type",
+      "pathkit",
       "--compiled_language",
       "asmjs"
     ],
diff --git a/infra/bots/recipes/test_pathkit.expected/Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-PathKit.json b/infra/bots/recipes/test_pathkit.expected/Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-PathKit.json
index 66856ee..dd0c494 100644
--- a/infra/bots/recipes/test_pathkit.expected/Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-PathKit.json
+++ b/infra/bots/recipes/test_pathkit.expected/Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-PathKit.json
@@ -112,7 +112,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport os\nimport shutil\nimport sys\n\ncopy_dest = sys.argv[1]\nbase_dir = sys.argv[2]\nbundle_name = sys.argv[3]\nout_dir = sys.argv[4]\n\n# Clean out old binaries (if any)\ntry:\n  shutil.rmtree(copy_dest)\nexcept OSError as e:\n  if e.errno != errno.ENOENT:\n    raise\n\n# Make folder\ntry:\n  os.makedirs(copy_dest)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\n# Copy binaries (pathkit.js and pathkit.wasm) to where the karma tests\n# expect them ($SKIA_ROOT/modules/pathkit/npm-wasm/test/)\ndest = os.path.join(copy_dest, 'pathkit.js')\nshutil.copyfile(os.path.join(base_dir, 'pathkit.js'), dest)\nos.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.\n\nif bundle_name:\n  dest = os.path.join(copy_dest, bundle_name)\n  shutil.copyfile(os.path.join(base_dir, bundle_name), dest)\n  os.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.\n\n# Prepare output folder, api.file.ensure_directory doesn't touch\n# the permissions of the out directory if it already exists.\nos.chmod(out_dir, 0o777) # important, otherwise non-privileged docker can't write.\n",
+      "import errno\nimport os\nimport shutil\nimport sys\n\ncopy_dest = sys.argv[1]\nbase_dir = sys.argv[2]\nbundle_name = sys.argv[3]\nout_dir = sys.argv[4]\n\n# Clean out old binaries (if any)\ntry:\n  shutil.rmtree(copy_dest)\nexcept OSError as e:\n  if e.errno != errno.ENOENT:\n    raise\n\n# Make folder\ntry:\n  os.makedirs(copy_dest)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\n# Copy binaries (pathkit.js and pathkit.wasm) to where the karma tests\n# expect them ($SKIA_ROOT/modules/pathkit/npm-wasm/bin/test/)\ndest = os.path.join(copy_dest, 'pathkit.js')\nshutil.copyfile(os.path.join(base_dir, 'pathkit.js'), dest)\nos.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.\n\nif bundle_name:\n  dest = os.path.join(copy_dest, bundle_name)\n  shutil.copyfile(os.path.join(base_dir, bundle_name), dest)\n  os.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.\n\n# Prepare output folder, api.file.ensure_directory doesn't touch\n# the permissions of the out directory if it already exists.\nos.chmod(out_dir, 0o777) # important, otherwise non-privileged docker can't write.\n",
       "[START_DIR]/cache/work/skia/modules/pathkit/npm-wasm/bin/test",
       "[START_DIR]/build",
       "pathkit.wasm",
@@ -146,7 +146,7 @@
       "@@@STEP_LOG_LINE@python.inline@    raise@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@# Copy binaries (pathkit.js and pathkit.wasm) to where the karma tests@@@",
-      "@@@STEP_LOG_LINE@python.inline@# expect them ($SKIA_ROOT/modules/pathkit/npm-wasm/test/)@@@",
+      "@@@STEP_LOG_LINE@python.inline@# expect them ($SKIA_ROOT/modules/pathkit/npm-wasm/bin/test/)@@@",
       "@@@STEP_LOG_LINE@python.inline@dest = os.path.join(copy_dest, 'pathkit.js')@@@",
       "@@@STEP_LOG_LINE@python.inline@shutil.copyfile(os.path.join(base_dir, 'pathkit.js'), dest)@@@",
       "@@@STEP_LOG_LINE@python.inline@os.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.@@@",
@@ -196,11 +196,11 @@
       "run",
       "--shm-size=2gb",
       "--rm",
-      "-v",
+      "--volume",
       "[START_DIR]/cache/work:/SRC",
-      "-v",
+      "--volume",
       "[START_DIR]/[SWARM_OUT_DIR]:/OUT",
-      "gcr.io/skia-public/gold-karma-chrome-tests:68.0.3440.106_v5",
+      "gcr.io/skia-public/gold-karma-chrome-tests:68.0.3440.106_v6",
       "/SRC/skia/infra/pathkit/test_pathkit.sh",
       "--builder",
       "Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-PathKit",
@@ -215,7 +215,9 @@
       "--browser",
       "Chrome",
       "--config",
-      "Debug"
+      "Debug",
+      "--source_type",
+      "pathkit"
     ],
     "env": {
       "CHROME_HEADLESS": "1",
diff --git a/infra/bots/recipes/test_pathkit.expected/pathkit_trybot.json b/infra/bots/recipes/test_pathkit.expected/pathkit_trybot.json
index 7fdea5f..a1c1f6c 100644
--- a/infra/bots/recipes/test_pathkit.expected/pathkit_trybot.json
+++ b/infra/bots/recipes/test_pathkit.expected/pathkit_trybot.json
@@ -114,7 +114,7 @@
     "cmd": [
       "python",
       "-u",
-      "import errno\nimport os\nimport shutil\nimport sys\n\ncopy_dest = sys.argv[1]\nbase_dir = sys.argv[2]\nbundle_name = sys.argv[3]\nout_dir = sys.argv[4]\n\n# Clean out old binaries (if any)\ntry:\n  shutil.rmtree(copy_dest)\nexcept OSError as e:\n  if e.errno != errno.ENOENT:\n    raise\n\n# Make folder\ntry:\n  os.makedirs(copy_dest)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\n# Copy binaries (pathkit.js and pathkit.wasm) to where the karma tests\n# expect them ($SKIA_ROOT/modules/pathkit/npm-wasm/test/)\ndest = os.path.join(copy_dest, 'pathkit.js')\nshutil.copyfile(os.path.join(base_dir, 'pathkit.js'), dest)\nos.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.\n\nif bundle_name:\n  dest = os.path.join(copy_dest, bundle_name)\n  shutil.copyfile(os.path.join(base_dir, bundle_name), dest)\n  os.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.\n\n# Prepare output folder, api.file.ensure_directory doesn't touch\n# the permissions of the out directory if it already exists.\nos.chmod(out_dir, 0o777) # important, otherwise non-privileged docker can't write.\n",
+      "import errno\nimport os\nimport shutil\nimport sys\n\ncopy_dest = sys.argv[1]\nbase_dir = sys.argv[2]\nbundle_name = sys.argv[3]\nout_dir = sys.argv[4]\n\n# Clean out old binaries (if any)\ntry:\n  shutil.rmtree(copy_dest)\nexcept OSError as e:\n  if e.errno != errno.ENOENT:\n    raise\n\n# Make folder\ntry:\n  os.makedirs(copy_dest)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\n# Copy binaries (pathkit.js and pathkit.wasm) to where the karma tests\n# expect them ($SKIA_ROOT/modules/pathkit/npm-wasm/bin/test/)\ndest = os.path.join(copy_dest, 'pathkit.js')\nshutil.copyfile(os.path.join(base_dir, 'pathkit.js'), dest)\nos.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.\n\nif bundle_name:\n  dest = os.path.join(copy_dest, bundle_name)\n  shutil.copyfile(os.path.join(base_dir, bundle_name), dest)\n  os.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.\n\n# Prepare output folder, api.file.ensure_directory doesn't touch\n# the permissions of the out directory if it already exists.\nos.chmod(out_dir, 0o777) # important, otherwise non-privileged docker can't write.\n",
       "[START_DIR]/cache/work/skia/modules/pathkit/npm-wasm/bin/test",
       "[START_DIR]/build",
       "pathkit.wasm",
@@ -148,7 +148,7 @@
       "@@@STEP_LOG_LINE@python.inline@    raise@@@",
       "@@@STEP_LOG_LINE@python.inline@@@@",
       "@@@STEP_LOG_LINE@python.inline@# Copy binaries (pathkit.js and pathkit.wasm) to where the karma tests@@@",
-      "@@@STEP_LOG_LINE@python.inline@# expect them ($SKIA_ROOT/modules/pathkit/npm-wasm/test/)@@@",
+      "@@@STEP_LOG_LINE@python.inline@# expect them ($SKIA_ROOT/modules/pathkit/npm-wasm/bin/test/)@@@",
       "@@@STEP_LOG_LINE@python.inline@dest = os.path.join(copy_dest, 'pathkit.js')@@@",
       "@@@STEP_LOG_LINE@python.inline@shutil.copyfile(os.path.join(base_dir, 'pathkit.js'), dest)@@@",
       "@@@STEP_LOG_LINE@python.inline@os.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.@@@",
@@ -198,11 +198,11 @@
       "run",
       "--shm-size=2gb",
       "--rm",
-      "-v",
+      "--volume",
       "[START_DIR]/cache/work:/SRC",
-      "-v",
+      "--volume",
       "[START_DIR]/[SWARM_OUT_DIR]:/OUT",
-      "gcr.io/skia-public/gold-karma-chrome-tests:68.0.3440.106_v5",
+      "gcr.io/skia-public/gold-karma-chrome-tests:68.0.3440.106_v6",
       "/SRC/skia/infra/pathkit/test_pathkit.sh",
       "--builder",
       "Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-PathKit",
@@ -218,6 +218,8 @@
       "Chrome",
       "--config",
       "Debug",
+      "--source_type",
+      "pathkit",
       "--issue",
       "1234",
       "--patchset",
diff --git a/infra/bots/recipes/test_pathkit.py b/infra/bots/recipes/test_pathkit.py
index 6bfd687..2d3ba87 100644
--- a/infra/bots/recipes/test_pathkit.py
+++ b/infra/bots/recipes/test_pathkit.py
@@ -17,7 +17,7 @@
 ]
 
 
-DOCKER_IMAGE = 'gcr.io/skia-public/gold-karma-chrome-tests:68.0.3440.106_v5'
+DOCKER_IMAGE = 'gcr.io/skia-public/gold-karma-chrome-tests:68.0.3440.106_v6'
 INNER_KARMA_SCRIPT = '/SRC/skia/infra/pathkit/test_pathkit.sh'
 
 
@@ -33,10 +33,10 @@
   # The karma script is configured to look in ./npm-(asmjs|wasm)/bin/test/ for
   # the test files to load, so we must copy them there (see Set up for docker).
   copy_dest = checkout_root.join('skia', 'modules', 'pathkit',
-                        'npm-wasm', 'bin', 'test')
+                                 'npm-wasm', 'bin', 'test')
   if 'asmjs' in api.vars.builder_name:
     copy_dest = checkout_root.join('skia', 'modules', 'pathkit',
-                        'npm-asmjs', 'bin', 'test')
+                                   'npm-asmjs', 'bin', 'test')
 
   base_dir = api.vars.build_dir
   bundle_name = 'pathkit.wasm'
@@ -75,7 +75,7 @@
     raise
 
 # Copy binaries (pathkit.js and pathkit.wasm) to where the karma tests
-# expect them ($SKIA_ROOT/modules/pathkit/npm-wasm/test/)
+# expect them ($SKIA_ROOT/modules/pathkit/npm-wasm/bin/test/)
 dest = os.path.join(copy_dest, 'pathkit.js')
 shutil.copyfile(os.path.join(base_dir, 'pathkit.js'), dest)
 os.chmod(dest, 0o644) # important, otherwise non-privileged docker can't read.
@@ -93,24 +93,25 @@
       infra_step=True)
 
 
-
   cmd = ['docker', 'run', '--shm-size=2gb', '--rm',
-         '-v', '%s:/SRC' % checkout_root, '-v', '%s:/OUT' % out_dir]
+         '--volume', '%s:/SRC' % checkout_root,
+         '--volume', '%s:/OUT' % out_dir]
 
   if 'asmjs' in api.vars.builder_name:
-    cmd.extend(['-e', 'ASM_JS=1'])  # -e sets environment variables
+    cmd.extend(['--env', 'ASM_JS=1'])
 
   cmd.extend([
-         DOCKER_IMAGE,             INNER_KARMA_SCRIPT,
-         '--builder',              api.vars.builder_name,
-         '--git_hash',             api.properties['revision'],
-         '--buildbucket_build_id', api.properties.get('buildbucket_build_id',
-                                                      ''),
-         '--bot_id',               api.vars.swarming_bot_id,
-         '--task_id',              api.vars.swarming_task_id,
-         '--browser',              'Chrome',
-         '--config',               api.vars.configuration,
-         ])
+      DOCKER_IMAGE,             INNER_KARMA_SCRIPT,
+      '--builder',              api.vars.builder_name,
+      '--git_hash',             api.properties['revision'],
+      '--buildbucket_build_id', api.properties.get('buildbucket_build_id',
+                                                  ''),
+      '--bot_id',               api.vars.swarming_bot_id,
+      '--task_id',              api.vars.swarming_task_id,
+      '--browser',              'Chrome',
+      '--config',               api.vars.configuration,
+      '--source_type',          'pathkit',
+      ])
 
   if 'asmjs' in api.vars.builder_name:
     cmd.extend(['--compiled_language', 'asmjs']) # the default is wasm
diff --git a/infra/bots/tasks.json b/infra/bots/tasks.json
index 7a6405c..2e522db 100755
--- a/infra/bots/tasks.json
+++ b/infra/bots/tasks.json
@@ -297,6 +297,11 @@
         "Build-Debian9-EMCC-wasm-Debug-CanvasKit"
       ]
     },
+    "Build-Debian9-EMCC-wasm-Debug-CanvasKit_CPU": {
+      "tasks": [
+        "Build-Debian9-EMCC-wasm-Debug-CanvasKit_CPU"
+      ]
+    },
     "Build-Debian9-EMCC-wasm-Debug-PathKit": {
       "tasks": [
         "Build-Debian9-EMCC-wasm-Debug-PathKit"
@@ -307,6 +312,11 @@
         "Build-Debian9-EMCC-wasm-Release-CanvasKit"
       ]
     },
+    "Build-Debian9-EMCC-wasm-Release-CanvasKit_CPU": {
+      "tasks": [
+        "Build-Debian9-EMCC-wasm-Release-CanvasKit_CPU"
+      ]
+    },
     "Build-Debian9-EMCC-wasm-Release-PathKit": {
       "tasks": [
         "Build-Debian9-EMCC-wasm-Release-PathKit"
@@ -575,6 +585,11 @@
         "Upload-BuildStats-Debian9-EMCC-wasm-Release-CanvasKit"
       ]
     },
+    "BuildStats-Debian9-EMCC-wasm-Release-CanvasKit_CPU": {
+      "tasks": [
+        "Upload-BuildStats-Debian9-EMCC-wasm-Release-CanvasKit_CPU"
+      ]
+    },
     "BuildStats-Debian9-EMCC-wasm-Release-PathKit": {
       "tasks": [
         "Upload-BuildStats-Debian9-EMCC-wasm-Release-PathKit"
@@ -2210,16 +2225,36 @@
         "Upload-Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Release-All-PathKit"
       ]
     },
+    "Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-CanvasKit": {
+      "tasks": [
+        "Upload-Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-CanvasKit"
+      ]
+    },
     "Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-PathKit": {
       "tasks": [
         "Upload-Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-PathKit"
       ]
     },
+    "Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-CanvasKit": {
+      "tasks": [
+        "Upload-Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-CanvasKit"
+      ]
+    },
     "Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-PathKit": {
       "tasks": [
         "Upload-Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-PathKit"
       ]
     },
+    "Test-Debian9-EMCC-GCE-GPU-AVX2-wasm-Debug-All-CanvasKit": {
+      "tasks": [
+        "Upload-Test-Debian9-EMCC-GCE-GPU-AVX2-wasm-Debug-All-CanvasKit"
+      ]
+    },
+    "Test-Debian9-EMCC-GCE-GPU-AVX2-wasm-Release-All-CanvasKit": {
+      "tasks": [
+        "Upload-Test-Debian9-EMCC-GCE-GPU-AVX2-wasm-Release-All-CanvasKit"
+      ]
+    },
     "Test-Debian9-GCC-GCE-CPU-AVX2-x86-Debug-All": {
       "tasks": [
         "Upload-Test-Debian9-GCC-GCE-CPU-AVX2-x86-Debug-All"
@@ -10221,7 +10256,8 @@
         "gpu:none",
         "machine_type:n1-standard-16",
         "os:Debian-9.4",
-        "pool:Skia"
+        "pool:Skia",
+        "docker_installed:true"
       ],
       "env_prefixes": {
         "PATH": [
@@ -10349,7 +10385,8 @@
         "gpu:none",
         "machine_type:n1-standard-16",
         "os:Debian-9.4",
-        "pool:Skia"
+        "pool:Skia",
+        "docker_installed:true"
       ],
       "env_prefixes": {
         "PATH": [
@@ -10477,7 +10514,137 @@
         "gpu:none",
         "machine_type:n1-standard-16",
         "os:Debian-9.4",
-        "pool:Skia"
+        "pool:Skia",
+        "docker_installed:true"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 1,
+      "outputs": [
+        "build"
+      ],
+      "service_account": "skia-external-compile-tasks@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Build-Debian9-EMCC-wasm-Debug-CanvasKit_CPU": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        },
+        {
+          "name": "git",
+          "path": "cache/git"
+        },
+        {
+          "name": "git_cache",
+          "path": "cache/git_cache"
+        },
+        {
+          "name": "work",
+          "path": "cache/work"
+        },
+        {
+          "name": "docker",
+          "path": "cache/docker"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "infra/git/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "version:2.17.1.chromium15"
+        },
+        {
+          "name": "infra/tools/git/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:0ae21738597e5601ba90372315145fec18582fc4"
+        },
+        {
+          "name": "infra/tools/luci/git-credential-luci/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "compile",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-EMCC-wasm-Debug-CanvasKit_CPU\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-standard-16",
+        "os:Debian-9.4",
+        "pool:Skia",
+        "docker_installed:true"
       ],
       "env_prefixes": {
         "PATH": [
@@ -10605,7 +10772,8 @@
         "gpu:none",
         "machine_type:n1-standard-16",
         "os:Debian-9.4",
-        "pool:Skia"
+        "pool:Skia",
+        "docker_installed:true"
       ],
       "env_prefixes": {
         "PATH": [
@@ -10733,7 +10901,137 @@
         "gpu:none",
         "machine_type:n1-standard-16",
         "os:Debian-9.4",
-        "pool:Skia"
+        "pool:Skia",
+        "docker_installed:true"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 1,
+      "outputs": [
+        "build"
+      ],
+      "service_account": "skia-external-compile-tasks@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Build-Debian9-EMCC-wasm-Release-CanvasKit_CPU": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        },
+        {
+          "name": "git",
+          "path": "cache/git"
+        },
+        {
+          "name": "git_cache",
+          "path": "cache/git_cache"
+        },
+        {
+          "name": "work",
+          "path": "cache/work"
+        },
+        {
+          "name": "docker",
+          "path": "cache/docker"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "infra/git/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "version:2.17.1.chromium15"
+        },
+        {
+          "name": "infra/tools/git/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:0ae21738597e5601ba90372315145fec18582fc4"
+        },
+        {
+          "name": "infra/tools/luci/git-credential-luci/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "compile",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-EMCC-wasm-Release-CanvasKit_CPU\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-standard-16",
+        "os:Debian-9.4",
+        "pool:Skia",
+        "docker_installed:true"
       ],
       "env_prefixes": {
         "PATH": [
@@ -10861,7 +11159,8 @@
         "gpu:none",
         "machine_type:n1-standard-16",
         "os:Debian-9.4",
-        "pool:Skia"
+        "pool:Skia",
+        "docker_installed:true"
       ],
       "env_prefixes": {
         "PATH": [
@@ -17486,7 +17785,8 @@
         "gpu:none",
         "machine_type:n1-standard-16",
         "os:Debian-9.4",
-        "pool:Skia"
+        "pool:Skia",
+        "docker_installed:true"
       ],
       "env_prefixes": {
         "PATH": [
@@ -17587,7 +17887,110 @@
         "gpu:none",
         "machine_type:n1-standard-16",
         "os:Debian-9.4",
-        "pool:Skia"
+        "pool:Skia",
+        "docker_installed:true"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "outputs": [
+        "perf"
+      ]
+    },
+    "BuildStats-Debian9-EMCC-wasm-Release-CanvasKit_CPU": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "skia/bots/bloaty",
+          "path": "bloaty",
+          "version": "version:0"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "compute_buildstats",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"BuildStats-Debian9-EMCC-wasm-Release-CanvasKit_CPU\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-EMCC-wasm-Release-CanvasKit_CPU"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-standard-16",
+        "os:Debian-9.4",
+        "pool:Skia",
+        "docker_installed:true"
       ],
       "env_prefixes": {
         "PATH": [
@@ -17688,7 +18091,8 @@
         "gpu:none",
         "machine_type:n1-standard-16",
         "os:Debian-9.4",
-        "pool:Skia"
+        "pool:Skia",
+        "docker_installed:true"
       ],
       "env_prefixes": {
         "PATH": [
@@ -52257,6 +52661,103 @@
         "test"
       ]
     },
+    "Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-CanvasKit": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "test_canvaskit",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-CanvasKit\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-EMCC-wasm-Debug-CanvasKit_CPU"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "machine_type:n1-standard-16",
+        "os:Debian-9.4",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 1,
+      "outputs": [
+        "test"
+      ]
+    },
     "Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-PathKit": {
       "caches": [
         {
@@ -52354,6 +52855,103 @@
         "test"
       ]
     },
+    "Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-CanvasKit": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "test_canvaskit",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-CanvasKit\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-EMCC-wasm-Release-CanvasKit_CPU"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "machine_type:n1-standard-16",
+        "os:Debian-9.4",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 1,
+      "outputs": [
+        "test"
+      ]
+    },
     "Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-PathKit": {
       "caches": [
         {
@@ -52451,6 +53049,204 @@
         "test"
       ]
     },
+    "Test-Debian9-EMCC-GCE-GPU-AVX2-wasm-Debug-All-CanvasKit": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "test_canvaskit",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-EMCC-GCE-GPU-AVX2-wasm-Debug-All-CanvasKit\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-EMCC-wasm-Debug-CanvasKit"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-standard-16",
+        "os:Debian-9.4",
+        "pool:Skia",
+        "docker_installed:true"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 1,
+      "outputs": [
+        "test"
+      ]
+    },
+    "Test-Debian9-EMCC-GCE-GPU-AVX2-wasm-Release-All-CanvasKit": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "test_canvaskit",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-EMCC-GCE-GPU-AVX2-wasm-Release-All-CanvasKit\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-EMCC-wasm-Release-CanvasKit"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-standard-16",
+        "os:Debian-9.4",
+        "pool:Skia",
+        "docker_installed:true"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 1,
+      "outputs": [
+        "test"
+      ]
+    },
     "Test-Debian9-GCC-GCE-CPU-AVX2-x86-Debug-All": {
       "caches": [
         {
@@ -69251,6 +70047,105 @@
       "isolate": "swarm_recipe.isolate",
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
+    "Upload-BuildStats-Debian9-EMCC-wasm-Release-CanvasKit_CPU": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "infra/gsutil",
+          "path": "cipd_bin_packages",
+          "version": "version:4.28"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "upload_buildstats_results",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"BuildStats-Debian9-EMCC-wasm-Release-CanvasKit_CPU\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "BuildStats-Debian9-EMCC-wasm-Release-CanvasKit_CPU"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-9.4",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
+    },
     "Upload-BuildStats-Debian9-EMCC-wasm-Release-PathKit": {
       "caches": [
         {
@@ -89399,6 +90294,105 @@
       "isolate": "swarm_recipe.isolate",
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
+    "Upload-Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-CanvasKit": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "infra/gsutil",
+          "path": "cipd_bin_packages",
+          "version": "version:4.28"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "upload_dm_results",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-CanvasKit\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-CanvasKit"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-9.4",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
+    },
     "Upload-Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-PathKit": {
       "caches": [
         {
@@ -89498,6 +90492,105 @@
       "isolate": "swarm_recipe.isolate",
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
+    "Upload-Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-CanvasKit": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "infra/gsutil",
+          "path": "cipd_bin_packages",
+          "version": "version:4.28"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "upload_dm_results",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-CanvasKit\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-CanvasKit"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-9.4",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
+    },
     "Upload-Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-PathKit": {
       "caches": [
         {
@@ -89597,6 +90690,204 @@
       "isolate": "swarm_recipe.isolate",
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
+    "Upload-Test-Debian9-EMCC-GCE-GPU-AVX2-wasm-Debug-All-CanvasKit": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "infra/gsutil",
+          "path": "cipd_bin_packages",
+          "version": "version:4.28"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "upload_dm_results",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-EMCC-GCE-GPU-AVX2-wasm-Debug-All-CanvasKit\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Test-Debian9-EMCC-GCE-GPU-AVX2-wasm-Debug-All-CanvasKit"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-9.4",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Upload-Test-Debian9-EMCC-GCE-GPU-AVX2-wasm-Release-All-CanvasKit": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "infra/gsutil",
+          "path": "cipd_bin_packages",
+          "version": "version:4.28"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "upload_dm_results",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Debian9-EMCC-GCE-GPU-AVX2-wasm-Release-All-CanvasKit\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Test-Debian9-EMCC-GCE-GPU-AVX2-wasm-Release-All-CanvasKit"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-9.4",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
+    },
     "Upload-Test-Debian9-GCC-GCE-CPU-AVX2-x86-Debug-All": {
       "caches": [
         {
diff --git a/infra/canvaskit/test_canvaskit.sh b/infra/canvaskit/test_canvaskit.sh
new file mode 100755
index 0000000..ec6ae22
--- /dev/null
+++ b/infra/canvaskit/test_canvaskit.sh
@@ -0,0 +1,30 @@
+#!/bin/bash
+# Copyright 2018 Google LLC
+#
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# This assumes it is being run inside a docker container of gold-karma-chrome-tests
+# and a Skia checkout has been mounted at /SRC and the output directory
+# is mounted at /OUT
+
+# For example:
+# docker run -v $SKIA_ROOT:/SRC -v /tmp/dockerout:/OUT gcr.io/skia-public/gold-karma-chrome-tests:68.0.3440.106_v6 /SRC/infra/canvaskit/test_canvaskit.sh
+
+set -ex
+
+#BASE_DIR is the dir this script is in ($SKIA_ROOT/infra/canvaskit)
+BASE_DIR=`cd $(dirname ${BASH_SOURCE[0]}) && pwd`
+CANVASKIT_DIR=$BASE_DIR/../../experimental/canvaskit
+
+# Start the aggregator in the background
+/opt/gold-aggregator $@ &
+# Run the tests
+npx karma start $CANVASKIT_DIR/karma.conf.js --single-run
+# Tell the aggregator to dump the json
+# This curl command gets the HTTP code and stores it into $CODE
+CODE=`curl -s -o /dev/null -I -w "%{http_code}" -X POST localhost:8081/dump_json`
+if [ $CODE -ne 200 ]; then
+    # If we don't get 200 back, something is wrong with writing to disk, so exit with error
+    exit 1
+fi
diff --git a/infra/pathkit/Makefile b/infra/pathkit/Makefile
index eea36a0..2a0c260 100644
--- a/infra/pathkit/Makefile
+++ b/infra/pathkit/Makefile
@@ -16,7 +16,7 @@
 
 # Can check CHROME_VERSION with
 # docker run karma-chrome-tests /usr/bin/google-chrome-stable --version
-CHROME_VERSION=68.0.3440.106_v5
+CHROME_VERSION=68.0.3440.106_v6
 
 publish_gold_karma_chrome_tests: gold_docker_image
 	docker tag gold-karma-chrome-tests gcr.io/skia-public/gold-karma-chrome-tests:${CHROME_VERSION}
diff --git a/infra/pathkit/gold/pathkit_gold_aggregator.go b/infra/pathkit/gold/wasm_gold_aggregator.go
similarity index 96%
rename from infra/pathkit/gold/pathkit_gold_aggregator.go
rename to infra/pathkit/gold/wasm_gold_aggregator.go
index ad58c30..7f1e883 100644
--- a/infra/pathkit/gold/pathkit_gold_aggregator.go
+++ b/infra/pathkit/gold/wasm_gold_aggregator.go
@@ -46,6 +46,7 @@
 	issue            = flag.Int64("issue", 0, "issue (if tryjob)")
 	patchset         = flag.Int64("patchset", 0, "patchset (if tryjob)")
 	taskId           = flag.String("task_id", "", "swarming task id")
+	sourceType       = flag.String("source_type", "pathkit", "Gold Source type, like pathkit,canvaskit")
 )
 
 // Received from the JS side.
@@ -67,16 +68,20 @@
 func main() {
 	flag.Parse()
 
+	cpuGPU := "CPU"
+	if strings.Index(*builder, "-GPU-") != -1 {
+		cpuGPU = "GPU"
+	}
 	defaultKeys = map[string]string{
 		"arch":              "WASM",
 		"browser":           *browser,
 		"compiled_language": *compiledLanguage,
 		"compiler":          "emsdk",
 		"configuration":     *config,
-		"cpu_or_gpu":        "CPU",
+		"cpu_or_gpu":        cpuGPU,
 		"cpu_or_gpu_value":  "Browser",
 		"os":                *hostOS,
-		"source_type":       "pathkit",
+		"source_type":       *sourceType,
 	}
 
 	results = []*jsonio.Result{}
diff --git a/infra/pathkit/perf/pathkit_perf_aggregator.go b/infra/pathkit/perf/wasm_perf_aggregator.go
similarity index 96%
rename from infra/pathkit/perf/pathkit_perf_aggregator.go
rename to infra/pathkit/perf/wasm_perf_aggregator.go
index 4793ab5..4a0cd48 100644
--- a/infra/pathkit/perf/pathkit_perf_aggregator.go
+++ b/infra/pathkit/perf/wasm_perf_aggregator.go
@@ -22,6 +22,7 @@
 	"os"
 	"path"
 	"strconv"
+	"strings"
 
 	"github.com/google/uuid"
 	"go.skia.org/infra/perf/go/ingestcommon"
@@ -49,6 +50,7 @@
 	patch_storage    = flag.String("patch_storage", "", "patch storage (if tryjob)")
 	patchset         = flag.Int64("patchset", 0, "patchset (if tryjob)")
 	taskId           = flag.String("task_id", "", "swarming task id")
+	sourceType       = flag.String("source_type", "pathkit", "Gold Source type, like pathkit,canvaskit")
 )
 
 // Received from the JS side.
@@ -81,16 +83,20 @@
 func main() {
 	flag.Parse()
 
+	cpuGPU := "CPU"
+	if strings.Index(*builder, "-GPU-") != -1 {
+		cpuGPU = "GPU"
+	}
 	defaultKeys = map[string]string{
 		"arch":              "WASM",
 		"browser":           *browser,
 		"compiled_language": *compiledLanguage,
 		"compiler":          "emsdk",
 		"configuration":     *config,
-		"cpu_or_gpu":        "CPU",
+		"cpu_or_gpu":        cpuGPU,
 		"cpu_or_gpu_value":  "Browser",
 		"os":                *hostOS,
-		"source_type":       "pathkit",
+		"source_type":       *sourceType,
 	}
 
 	results = make(map[string][]reportBody)
diff --git a/modules/pathkit/package.json b/modules/pathkit/package.json
index 0f35a49..fff168f 100644
--- a/modules/pathkit/package.json
+++ b/modules/pathkit/package.json
@@ -9,12 +9,12 @@
     "pathkit-wasm": "^0.4.0"
   },
   "devDependencies": {
-    "is-docker": "^1.1.0",
+    "is-docker": "~1.1.0",
     "jasmine-core": "^3.1.0",
-    "karma": "^2.0.5",
-    "karma-chrome-launcher": "^2.2.0",
-    "karma-jasmine": "^1.1.2",
-    "requirejs": "^2.3.5"
+    "karma": "~3.0.0",
+    "karma-chrome-launcher": "~2.2.0",
+    "karma-jasmine": "~1.1.2",
+    "requirejs": "~2.3.5"
   },
   "scripts": {
     "test": "echo \"Error: no test specified\" && exit 1"
diff --git a/src/gpu/GrPathRendering_none.cpp b/src/gpu/GrPathRendering_none.cpp
index f289395..bbd4610 100644
--- a/src/gpu/GrPathRendering_none.cpp
+++ b/src/gpu/GrPathRendering_none.cpp
@@ -19,7 +19,6 @@
 
 GrPathRenderer* GrStencilAndCoverPathRenderer::Create(GrResourceProvider* resourceProvider,
                                                       const GrCaps& caps) {
-    SkDEBUGFAIL("NVPR disabled");
     return nullptr;
 }