diff --git a/core/block_svg.js b/core/block_svg.js index 29d6d8db5..b3ebab4fc 100644 --- a/core/block_svg.js +++ b/core/block_svg.js @@ -927,11 +927,15 @@ Blockly.BlockSvg.prototype.dispose = function(healStack, animate) { /** * Encode a block for copying. - * @return {!Blockly.ICopyable.CopyData} Copy metadata. + * @return {?Blockly.ICopyable.CopyData} Copy metadata, or null if the block is + * an insertion marker. * @package */ Blockly.BlockSvg.prototype.toCopyData = function() { - var xml = Blockly.Xml.blockToDom(this, true); + if (this.isInsertionMarker_) { + return null; + } + var xml = /** @type {!Element} */ (Blockly.Xml.blockToDom(this, true)); // Copy only the selected block and internal blocks. Blockly.Xml.deleteNext(xml); // Encode start position in XML. diff --git a/core/blockly.js b/core/blockly.js index 997ce3710..1c7025c30 100644 --- a/core/blockly.js +++ b/core/blockly.js @@ -272,9 +272,11 @@ Blockly.onKeyDown = function(e) { */ Blockly.copy_ = function(toCopy) { var data = toCopy.toCopyData(); - Blockly.clipboardXml_ = data.xml; - Blockly.clipboardSource_ = data.source; - Blockly.clipboardTypeCounts_ = data.typeCounts; + if (data) { + Blockly.clipboardXml_ = data.xml; + Blockly.clipboardSource_ = data.source; + Blockly.clipboardTypeCounts_ = data.typeCounts; + } }; /** diff --git a/core/connection.js b/core/connection.js index 556fb69f9..6077d0cb2 100644 --- a/core/connection.js +++ b/core/connection.js @@ -115,7 +115,8 @@ Blockly.Connection.prototype.connect_ = function(childConnection) { // Displaced shadow blocks dissolve rather than reattaching or bumping. if (orphanBlock.isShadow()) { // Save the shadow block so that field values are preserved. - shadowDom = Blockly.Xml.blockToDom(orphanBlock); + // This cast assumes that a block can not be both a shadow block and an insertion marker. + shadowDom = /** @type {!Element} */ (Blockly.Xml.blockToDom(orphanBlock)); orphanBlock.dispose(false); orphanBlock = null; } else if (parentConnection.type == Blockly.INPUT_VALUE) { diff --git a/core/flyout_base.js b/core/flyout_base.js index 1a3c6f966..25333d41f 100644 --- a/core/flyout_base.js +++ b/core/flyout_base.js @@ -948,7 +948,8 @@ Blockly.Flyout.prototype.placeNewBlock_ = function(oldBlock) { } // Create the new block by cloning the block in the flyout (via XML). - var xml = Blockly.Xml.blockToDom(oldBlock, true); + // This cast assumes that the oldBlock can not be an insertion marker. + var xml = /** @type {!Element} */ (Blockly.Xml.blockToDom(oldBlock, true)); // The target workspace would normally resize during domToBlock, which will // lead to weird jumps. Save it for terminateDrag. targetWorkspace.setResizesEnabled(false); diff --git a/core/interfaces/i_copyable.js b/core/interfaces/i_copyable.js index a20565dc5..4aeac9776 100644 --- a/core/interfaces/i_copyable.js +++ b/core/interfaces/i_copyable.js @@ -25,7 +25,7 @@ Blockly.ICopyable = function() {}; /** * Encode for copying. - * @return {!Blockly.ICopyable.CopyData} Copy metadata. + * @return {?Blockly.ICopyable.CopyData} Copy metadata. */ Blockly.ICopyable.prototype.toCopyData; diff --git a/core/trashcan.js b/core/trashcan.js index 9502a1445..7820512c8 100644 --- a/core/trashcan.js +++ b/core/trashcan.js @@ -606,7 +606,8 @@ Blockly.Trashcan.prototype.onDelete_ = function(event) { if (this.workspace_.options.maxTrashcanContents <= 0) { return; } - if (event.type == Blockly.Events.BLOCK_DELETE && + // Must check that the tagName exists since oldXml can be a DocumentFragment. + if (event.type == Blockly.Events.BLOCK_DELETE && event.oldXml.tagName && event.oldXml.tagName.toLowerCase() != 'shadow') { var cleanedXML = this.cleanBlockXML_(event.oldXml); if (this.contents_.indexOf(cleanedXML) != -1) { diff --git a/core/workspace_svg.js b/core/workspace_svg.js index 84d697b00..feba580ae 100644 --- a/core/workspace_svg.js +++ b/core/workspace_svg.js @@ -1326,13 +1326,16 @@ Blockly.WorkspaceSvg.prototype.highlightBlock = function(id, opt_state) { /** * Paste the provided block onto the workspace. - * @param {!Element} xmlBlock XML block element. + * @param {!Element|!DocumentFragment} xmlBlock XML block element or an empty + * DocumentFragment if the block was an insertion marker. */ Blockly.WorkspaceSvg.prototype.paste = function(xmlBlock) { - if (!this.rendered || xmlBlock.getElementsByTagName('block').length >= + if (!this.rendered || !xmlBlock.tagName || xmlBlock.getElementsByTagName('block').length >= this.remainingCapacity()) { return; } + // The check above for tagName rules out the possibility of this being a DocumentFragment. + xmlBlock = /** @type {!Element} */ (xmlBlock); if (this.currentGesture_) { this.currentGesture_.cancel(); // Dragging while pasting? No. } diff --git a/core/xml.js b/core/xml.js index ae245b864..20c82015b 100644 --- a/core/xml.js +++ b/core/xml.js @@ -74,15 +74,15 @@ Blockly.Xml.variablesToDom = function(variableList) { * Encode a block subtree as XML with XY coordinates. * @param {!Blockly.Block} block The root block to encode. * @param {boolean=} opt_noId True if the encoder should skip the block ID. - * @return {!Element} Tree of XML elements. + * @return {!Element|!DocumentFragment} Tree of XML elements or an empty document + * fragment if the block was an insertion marker. */ Blockly.Xml.blockToDomWithXY = function(block, opt_noId) { if (block.isInsertionMarker()) { // Skip over insertion markers. block = block.getChildren(false)[0]; if (!block) { - // Disappears when appended. Cast to ANY b/c DocumentFragment -> Element - // is invalid. We have to cast to ANY in between. - return /** @type{?} */ (new DocumentFragment()); + // Disappears when appended. + return new DocumentFragment(); } } @@ -138,7 +138,8 @@ Blockly.Xml.allFieldsToDom_ = function(block, element) { * Encode a block subtree as XML. * @param {!Blockly.Block} block The root block to encode. * @param {boolean=} opt_noId True if the encoder should skip the block ID. - * @return {!Element} Tree of XML elements. + * @return {!Element|!DocumentFragment} Tree of XML elements or an empty document + * fragment if the block was an insertion marker. */ Blockly.Xml.blockToDom = function(block, opt_noId) { // Skip over insertion markers. @@ -147,9 +148,8 @@ Blockly.Xml.blockToDom = function(block, opt_noId) { if (child) { return Blockly.Xml.blockToDom(child); } else { - // Disappears when appended. Cast to ANY b/c DocumentFragment -> Element - // is invalid. We have to cast to ANY in between. - return /** @type{?} */ (new DocumentFragment()); + // Disappears when appended. + return new DocumentFragment(); } } @@ -808,7 +808,8 @@ Blockly.Xml.domToField_ = function(block, fieldName, xml) { /** * Remove any 'next' block (statements in a stack). - * @param {!Element} xmlBlock XML block element. + * @param {!Element|!DocumentFragment} xmlBlock XML block element or an empty + * DocumentFragment if the block was an insertion marker. */ Blockly.Xml.deleteNext = function(xmlBlock) { for (var i = 0, child; (child = xmlBlock.childNodes[i]); i++) {