diff --git a/core/block_svg.js b/core/block_svg.js
index 7f2c4776..cfb2775f 100644
--- a/core/block_svg.js
+++ b/core/block_svg.js
@@ -83,6 +83,11 @@ Blockly.BlockSvg = function(workspace, prototypeName, opt_id) {
   Blockly.Tooltip.bindMouseEvents(this.svgPath_);
   Blockly.BlockSvg.superClass_.constructor.call(this,
       workspace, prototypeName, opt_id);
+
+  // Expose this block's ID on its top-level SVG group.
+  if (this.svgGroup_.dataset) {
+    this.svgGroup_.dataset.id = this.id;
+  }
 };
 goog.inherits(Blockly.BlockSvg, Blockly.Block);
 
diff --git a/core/bubble.js b/core/bubble.js
index dd63d664..91215542 100644
--- a/core/bubble.js
+++ b/core/bubble.js
@@ -29,7 +29,6 @@ goog.provide('Blockly.Bubble');
 goog.require('Blockly.Touch');
 goog.require('Blockly.Workspace');
 goog.require('goog.dom');
-goog.require('goog.math');
 goog.require('goog.math.Coordinate');
 goog.require('goog.userAgent');
 
@@ -56,7 +55,7 @@ Blockly.Bubble = function(workspace, content, shape, anchorXY,
   if (this.workspace_.RTL) {
     angle = -angle;
   }
-  this.arrow_radians_ = goog.math.toRadians(angle);
+  this.arrow_radians_ = Blockly.utils.toRadians(angle);
 
   var canvas = workspace.getBubbleCanvas();
   canvas.appendChild(this.createDom_(content, !!(bubbleWidth && bubbleHeight)));
diff --git a/core/bubble_dragger.js b/core/bubble_dragger.js
index 4f085ed9..2366581e 100644
--- a/core/bubble_dragger.js
+++ b/core/bubble_dragger.js
@@ -159,7 +159,7 @@ Blockly.BubbleDragger.prototype.maybeDeleteBubble_ = function() {
 
   if (this.wouldDeleteBubble_) {
     if (trashcan) {
-      goog.Timer.callOnce(trashcan.close, 100, trashcan);
+      setTimeout(trashcan.close.bind(trashcan), 100);
     }
     // Fire a move event, so we know where to go back to for an undo.
     this.fireMoveEvent_();
diff --git a/core/comment.js b/core/comment.js
index fc631d47..d8ca4fce 100644
--- a/core/comment.js
+++ b/core/comment.js
@@ -199,6 +199,8 @@ Blockly.Comment.prototype.setVisible = function(visible) {
         /** @type {!Blockly.WorkspaceSvg} */ (this.block_.workspace),
         this.createEditor_(), this.block_.svgPath_,
         this.iconXY_, this.width_, this.height_);
+    // Expose this comment's block's ID on its top-level SVG group.
+    this.bubble_.setSvgId(this.block_.id);
     this.bubble_.registerResizeEvent(this.resizeBubble_.bind(this));
     this.updateColour();
   } else {
diff --git a/core/inject.js b/core/inject.js
index 446349a3..7b0dbddc 100644
--- a/core/inject.js
+++ b/core/inject.js
@@ -281,7 +281,7 @@ Blockly.createMainWorkspace_ = function(svg, options, blockDragSurface, workspac
   if (!options.hasCategories && options.languageTree) {
     // Add flyout as an <svg> that is a sibling of the workspace svg.
     var flyout = mainWorkspace.addFlyout_('svg');
-    Blockly.utils.insertAfter_(flyout, svg);
+    Blockly.utils.insertAfter(flyout, svg);
   }
 
   // A null translation will also apply the correct initial scale.
diff --git a/core/scrollbar.js b/core/scrollbar.js
index 450f12c0..c5431291 100644
--- a/core/scrollbar.js
+++ b/core/scrollbar.js
@@ -55,7 +55,7 @@ Blockly.ScrollbarPair = function(workspace) {
         'class': 'blocklyScrollbarBackground'
       },
       null);
-  Blockly.utils.insertAfter_(this.corner_, workspace.getBubbleCanvas());
+  Blockly.utils.insertAfter(this.corner_, workspace.getBubbleCanvas());
 };
 
 /**
@@ -625,7 +625,7 @@ Blockly.Scrollbar.prototype.createDom_ = function(opt_class) {
         'ry': radius
       },
       this.svgGroup_);
-  Blockly.utils.insertAfter_(this.outerSvg_, this.workspace_.getParentSvg());
+  Blockly.utils.insertAfter(this.outerSvg_, this.workspace_.getParentSvg());
 };
 
 /**
diff --git a/core/touch.js b/core/touch.js
index 6de4dcf0..debad6b9 100644
--- a/core/touch.js
+++ b/core/touch.js
@@ -180,7 +180,7 @@ Blockly.Touch.checkTouchIdentifier = function(e) {
  * @param {!Event} e A touch event.
  */
 Blockly.Touch.setClientFromTouch = function(e) {
-  if (goog.string.startsWith(e.type, 'touch')) {
+  if (Blockly.utils.startsWith(e.type, 'touch')) {
     // Map the touch event's properties to the event.
     var touchPoint = e.changedTouches[0];
     e.clientX = touchPoint.clientX;
@@ -194,8 +194,8 @@ Blockly.Touch.setClientFromTouch = function(e) {
  * @return {boolean} true if it is a mouse or touch event; false otherwise.
  */
 Blockly.Touch.isMouseOrTouchEvent = function(e) {
-  return goog.string.startsWith(e.type, 'touch') ||
-      goog.string.startsWith(e.type, 'mouse');
+  return Blockly.utils.startsWith(e.type, 'touch') ||
+      Blockly.utils.startsWith(e.type, 'mouse');
 };
 
 /**
diff --git a/core/trashcan.js b/core/trashcan.js
index a9341311..4a39ce1f 100644
--- a/core/trashcan.js
+++ b/core/trashcan.js
@@ -27,7 +27,6 @@
 goog.provide('Blockly.Trashcan');
 
 goog.require('goog.dom');
-goog.require('goog.math');
 goog.require('goog.math.Rect');
 
 
@@ -309,13 +308,14 @@ Blockly.Trashcan.prototype.setOpen_ = function(state) {
  */
 Blockly.Trashcan.prototype.animateLid_ = function() {
   this.lidOpen_ += this.isOpen ? 0.2 : -0.2;
-  this.lidOpen_ = goog.math.clamp(this.lidOpen_, 0, 1);
+  this.lidOpen_ = Math.min(Math.max(this.lidOpen_, 0), 1);
   var lidAngle = this.lidOpen_ * 45;
   this.svgLid_.setAttribute('transform', 'rotate(' +
       (this.workspace_.RTL ? -lidAngle : lidAngle) + ',' +
       (this.workspace_.RTL ? 4 : this.WIDTH_ - 4) + ',' +
       (this.LID_HEIGHT_ - 2) + ')');
-  var opacity = goog.math.lerp(0.4, 0.8, this.lidOpen_);
+  // Linear interpolation between 0.4 and 0.8.
+  var opacity = 0.4 + this.lidOpen_ * (0.8 - 0.4);
   this.svgGroup_.style.opacity = opacity;
   if (this.lidOpen_ > 0 && this.lidOpen_ < 1) {
     this.lidTask_ = setTimeout(this.animateLid_.bind(this), 20);
diff --git a/core/utils.js b/core/utils.js
index 15a0125c..9cc9f2f8 100644
--- a/core/utils.js
+++ b/core/utils.js
@@ -867,9 +867,9 @@ Blockly.utils.is3dSupported = function() {
  * Contrast with node.insertBefore function.
  * @param {!Element} newNode New element to insert.
  * @param {!Element} refNode Existing element to precede new node.
- * @private
+ * @package
  */
-Blockly.utils.insertAfter_ = function(newNode, refNode) {
+Blockly.utils.insertAfter = function(newNode, refNode) {
   var siblingNode = refNode.nextSibling;
   var parentNode = refNode.parentNode;
   if (!parentNode) {
diff --git a/core/workspace_drag_surface_svg.js b/core/workspace_drag_surface_svg.js
index ad831e51..12e5e7e0 100644
--- a/core/workspace_drag_surface_svg.js
+++ b/core/workspace_drag_surface_svg.js
@@ -151,13 +151,13 @@ Blockly.WorkspaceDragSurfaceSvg.prototype.clearAndHide = function(newSurface) {
   // If there is a previous sibling, put the blockCanvas back right afterwards,
   // otherwise insert it as the first child node in newSurface.
   if (this.previousSibling_ != null) {
-    Blockly.utils.insertAfter_(blockCanvas, this.previousSibling_);
+    Blockly.utils.insertAfter(blockCanvas, this.previousSibling_);
   } else {
     newSurface.insertBefore(blockCanvas, newSurface.firstChild);
   }
 
   // Reattach the bubble canvas after the blockCanvas.
-  Blockly.utils.insertAfter_(bubbleCanvas, blockCanvas);
+  Blockly.utils.insertAfter(bubbleCanvas, blockCanvas);
   // Hide the drag surface.
   this.SVG_.style.display = 'none';
   goog.asserts.assert(