Skip to content

Commit

Permalink
fix: cereal backwards compatibility (google#5421)
Browse files Browse the repository at this point in the history
* fix: remove duplicate serialization hook implementations

* feat: add backwards compatibility to field serialization

* feat: add support for serializing old mutator hooks

* fix: build

* fix: refactor field changes into helpers

* fix: typo

* fix: removing xmlns

* tests: add tests for serialization and deserialization of mutator hooks

* fix: switch to early returns
  • Loading branch information
BeksOmega authored and alschmiedt committed Sep 20, 2021
1 parent 448c433 commit 96935c2
Show file tree
Hide file tree
Showing 15 changed files with 399 additions and 130 deletions.
53 changes: 53 additions & 0 deletions core/field.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,13 @@ const Tooltip = goog.require('Blockly.Tooltip');
const WidgetDiv = goog.require('Blockly.WidgetDiv');
/* eslint-disable-next-line no-unused-vars */
const WorkspaceSvg = goog.requireType('Blockly.WorkspaceSvg');
const Xml = goog.require('Blockly.Xml');
const dom = goog.require('Blockly.utils.dom');
const browserEvents = goog.require('Blockly.browserEvents');
const style = goog.require('Blockly.utils.style');
const userAgent = goog.require('Blockly.utils.userAgent');
const utils = goog.require('Blockly.utils');
const utilsXml = goog.require('Blockly.utils.xml');
/** @suppress {extraRequire} */
goog.require('Blockly.Events.BlockChange');
/** @suppress {extraRequire} */
Expand Down Expand Up @@ -439,6 +441,10 @@ Field.prototype.toXml = function(fieldElement) {
* @package
*/
Field.prototype.saveState = function() {
const legacyState = this.saveLegacyState(Field);
if (legacyState !== null) {
return legacyState;
}
return this.getValue();
};

Expand All @@ -449,9 +455,56 @@ Field.prototype.saveState = function() {
* @package
*/
Field.prototype.loadState = function(state) {
if (this.loadLegacyState(Field, state)) {
return;
}
this.setValue(state);
};

// eslint-disable-next-line valid-jsdoc
/**
* Returns a stringified version of the XML state, if it should be used.
* Otherwise this returns null, to signal the field should use its own
* serialization.
* @param {?} callingClass The class calling this method.
* Used to see if `this` has overridden any relevant hooks.
* @return {?string} The stringified version of the XML state, or null.
* @protected
*/
Field.prototype.saveLegacyState = function(callingClass) {
if (callingClass.prototype.saveState === this.saveState &&
callingClass.prototype.toXml !== this.toXml) {
const elem = utilsXml.createElement("field");
elem.setAttribute("name", this.name || '');
const text = Xml.domToText(this.toXml(elem));
return text.replace(
' xmlns="https://developers.google.com/blockly/xml"', '');
}
// Either they called this on purpose from their saveState, or they have
// no implementations of either hook. Just do our thing.
return null;
};

// eslint-disable-next-line valid-jsdoc
/**
* Loads the given state using either the old XML hoooks, if they should be
* used. Returns true to indicate loading has been handled, false otherwise.
* @param {?} callingClass The class calling this method.
* Used to see if `this` has overridden any relevant hooks.
* @param {*} state The state to apply to the field.
* @return {boolean} Whether the state was applied or not.
*/
Field.prototype.loadLegacyState = function(callingClass, state) {
if (callingClass.prototype.loadState === this.loadState &&
callingClass.prototype.fromXml !== this.fromXml) {
this.fromXml(Xml.textToDom(/** @type {string} */ (state)));
return true;
}
// Either they called this on purpose from their loadState, or they have
// no implementations of either hook. Just do our thing.
return false;
};

/**
* Dispose of all DOM objects and events belonging to this editable field.
* @package
Expand Down
20 changes: 0 additions & 20 deletions core/field_angle.js
Original file line number Diff line number Diff line change
Expand Up @@ -254,26 +254,6 @@ Blockly.FieldAngle.prototype.initView = function() {
this.textElement_.appendChild(this.symbol_);
};

/**
* Saves this field's value.
* @return {number} The angle value held by this field.
* @override
* @package
*/
Blockly.FieldAngle.prototype.saveState = function() {
return /** @type {number} */ (this.getValue());
};

/**
* Sets the field's value based on the given state.
* @param {*} state The state to apply to the angle field.
* @override
* @package
*/
Blockly.FieldAngle.prototype.loadState = function(state) {
this.setValue(state);
};

/**
* Updates the graph when the field rerenders.
* @protected
Expand Down
18 changes: 6 additions & 12 deletions core/field_checkbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,22 +104,16 @@ FieldCheckbox.prototype.configure_ = function(config) {

/**
* Saves this field's value.
* @return {boolean} The boolean value held by this field.
* @return {*} The boolean value held by this field.
* @override
* @package
*/
FieldCheckbox.prototype.saveState = function() {
return /** @type {boolean} */ (this.getValueBoolean());
};

/**
* Sets the field's value based on the given state.
* @param {*} state The state to apply to the checkbox field.
* @override
* @package
*/
FieldCheckbox.prototype.loadState = function(state) {
this.setValue(state);
const legacyState = this.saveLegacyState(FieldCheckbox);
if (legacyState !== null) {
return legacyState;
}
return this.getValueBoolean();
};

/**
Expand Down
20 changes: 0 additions & 20 deletions core/field_colour.js
Original file line number Diff line number Diff line change
Expand Up @@ -188,26 +188,6 @@ FieldColour.prototype.initView = function() {
}
};

/**
* Saves this field's value.
* @return {string} The colour value held by this field.
* @override
* @package
*/
FieldColour.prototype.saveState = function() {
return /** @type {string} */ (this.getValue());
};

/**
* Sets the field's value based on the given state.
* @param {*} state The state to apply to the colour field.
* @override
* @package
*/
FieldColour.prototype.loadState = function(state) {
this.setValue(state);
};

/**
* @override
*/
Expand Down
13 changes: 3 additions & 10 deletions core/field_dropdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -170,23 +170,16 @@ FieldDropdown.prototype.fromXml = function(fieldElement) {
this.setValue(fieldElement.textContent);
};

/**
* Saves this field's value.
* @return {*} The dropdown value held by this field.
* @override
* @package
*/
FieldDropdown.prototype.saveState = function() {
return this.getValue();
};

/**
* Sets the field's value based on the given state.
* @param {*} state The state to apply to the dropdown field.
* @override
* @package
*/
FieldDropdown.prototype.loadState = function(state) {
if (this.loadLegacyState(FieldDropdown, state)) {
return;
}
if (this.isOptionListDynamic()) {
this.getOptions(false);
}
Expand Down
20 changes: 0 additions & 20 deletions core/field_label_serializable.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,26 +68,6 @@ FieldLabelSerializable.prototype.EDITABLE = false;
*/
FieldLabelSerializable.prototype.SERIALIZABLE = true;

/**
* Saves this field's value.
* @return {string} The text value held by this field.
* @override
* @package
*/
FieldLabelSerializable.prototype.saveState = function() {
return /** @type {string} */ (this.getValue());
};

/**
* Sets the field's value based on the given state.
* @param {*} state The state to apply to the label field.
* @override
* @package
*/
FieldLabelSerializable.prototype.loadState = function(state) {
this.setValue(state);
};

fieldRegistry.register('field_label_serializable', FieldLabelSerializable);

exports = FieldLabelSerializable;
14 changes: 10 additions & 4 deletions core/field_multilineinput.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,21 +124,27 @@ FieldMultilineInput.prototype.fromXml = function(fieldElement) {

/**
* Saves this field's value.
* @return {string} The text value held by this field.
* @override
* @return {*} The state of this field.
* @package
*/
FieldMultilineInput.prototype.saveState = function() {
return /** @type {string} */ (this.getValue());
const legacyState = this.saveLegacyState(FieldMultilineInput);
if (legacyState !== null) {
return legacyState;
}
return this.getValue();
};

/**
* Sets the field's value based on the given state.
* @param {*} state The state to apply to the multiline input field.
* @param {*} state The state of the variable to assign to this variable field.
* @override
* @package
*/
FieldMultilineInput.prototype.loadState = function(state) {
if (this.loadLegacyState(Field, state)) {
return;
}
this.setValue(state);
};

Expand Down
20 changes: 0 additions & 20 deletions core/field_number.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,26 +118,6 @@ FieldNumber.prototype.configure_ = function(config) {
this.setPrecisionInternal_(config['precision']);
};

/**
* Saves this field's value.
* @return {number} The number value held by this field.
* @override
* @package
*/
FieldNumber.prototype.saveState = function() {
return /** @type {number} */ (this.getValue());
};

/**
* Sets the field's value based on the given state.
* @param {*} state The state to apply to the nuber field.
* @override
* @package
*/
FieldNumber.prototype.loadState = function(state) {
this.setValue(state);
};

/**
* Set the maximum, minimum and precision constraints on this field.
* Any of these properties may be undefined or NaN to be disabled.
Expand Down
20 changes: 0 additions & 20 deletions core/field_textinput.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,26 +182,6 @@ FieldTextInput.prototype.initView = function() {
this.createTextElement_();
};

/**
* Saves this field's value.
* @return {*} The text value held by this field.
* @override
* @package
*/
FieldTextInput.prototype.saveState = function() {
return this.getValue();
};

/**
* Sets the field's value based on the given state.
* @param {*} state The state to apply to the text input field.
* @override
* @package
*/
FieldTextInput.prototype.loadState = function(state) {
this.setValue(state);
};

/**
* Ensure that the input value casts to a valid string.
* @param {*=} opt_newValue The input value.
Expand Down
9 changes: 8 additions & 1 deletion core/field_variable.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,11 +199,15 @@ FieldVariable.prototype.toXml = function(fieldElement) {

/**
* Saves this field's value.
* @return {{id: string}} The ID of the variable referenced by this field.
* @return {*} The ID of the variable referenced by this field.
* @override
* @package
*/
FieldVariable.prototype.saveState = function() {
const legacyState = this.saveLegacyState(FieldVariable);
if (legacyState !== null) {
return legacyState;
}
// Make sure the variable is initialized.
this.initModel();
return {
Expand All @@ -218,6 +222,9 @@ FieldVariable.prototype.saveState = function() {
* @package
*/
FieldVariable.prototype.loadState = function(state) {
if (this.loadLegacyState(FieldVariable, state)) {
return;
}
// This is necessary so that blocks in the flyout can have custom var names.
const variable = Variables.getOrCreateVariablePackage(
this.sourceBlock_.workspace,
Expand Down
13 changes: 12 additions & 1 deletion core/serialization/blocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const {ISerializer} = goog.requireType('Blockly.serialization.ISerializer');
const Size = goog.require('Blockly.utils.Size');
// eslint-disable-next-line no-unused-vars
const Workspace = goog.requireType('Blockly.Workspace');
const Xml = goog.require('Blockly.Xml');
const inputTypes = goog.require('Blockly.inputTypes');
const priorities = goog.require('Blockly.serialization.priorities');
const serializationRegistry = goog.require('Blockly.serialization.registry');
Expand Down Expand Up @@ -162,6 +163,12 @@ const saveExtraState = function(block, state) {
if (extraState !== null) {
state['extraState'] = extraState;
}
} else if (block.mutationToDom) {
const extraState = block.mutationToDom();
if (extraState !== null) {
state['extraState'] = Xml.domToText(extraState).replace(
' xmlns="https://developers.google.com/blockly/xml"', '');
}
}
};

Expand Down Expand Up @@ -427,7 +434,11 @@ const loadExtraState = function(block, state) {
if (!state['extraState']) {
return;
}
block.loadExtraState(state['extraState']);
if (block.loadExtraState) {
block.loadExtraState(state['extraState']);
} else {
block.domToMutation(Xml.textToDom(state['extraState']));
}
};

/**
Expand Down
4 changes: 2 additions & 2 deletions tests/deps.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 96935c2

Please sign in to comment.