diff --git a/src/engine/runtime.js b/src/engine/runtime.js index 8761c66b3..d3a1723a4 100644 --- a/src/engine/runtime.js +++ b/src/engine/runtime.js @@ -392,6 +392,13 @@ class Runtime extends EventEmitter { * @type {function} */ this.removeCloudVariable = this._initializeRemoveCloudVariable(newCloudDataManager); + + /** + * A string representing the origin of the current project from outside of the + * Scratch community, such as CSFirst. + * @type {?string} + */ + this.origin = null; } /** diff --git a/src/serialization/sb3.js b/src/serialization/sb3.js index bb82390a1..6989229c9 100644 --- a/src/serialization/sb3.js +++ b/src/serialization/sb3.js @@ -561,6 +561,9 @@ const serialize = function (runtime, targetId) { const meta = Object.create(null); meta.semver = '3.0.0'; meta.vm = vmPackage.version; + if (runtime.origin) { + meta.origin = runtime.origin; + } // Attach full user agent string to metadata if available meta.agent = 'none'; @@ -1235,6 +1238,13 @@ const deserialize = function (json, runtime, zip, isSingleSprite) { extensionURLs: new Map() }; + // Store the origin field (e.g. project originated at CSFirst) so that we can save it again. + if (json.meta && json.meta.origin) { + runtime.origin = json.meta.origin; + } else { + runtime.origin = null; + } + // First keep track of the current target order in the json, // then sort by the layer order property before parsing the targets // so that their corresponding render drawables can be created in diff --git a/test/fixtures/origin-absent.sb3 b/test/fixtures/origin-absent.sb3 new file mode 100644 index 000000000..c6c7755d8 Binary files /dev/null and b/test/fixtures/origin-absent.sb3 differ diff --git a/test/fixtures/origin.sb3 b/test/fixtures/origin.sb3 new file mode 100644 index 000000000..b8356a1f0 Binary files /dev/null and b/test/fixtures/origin.sb3 differ diff --git a/test/unit/serialization_sb3.js b/test/unit/serialization_sb3.js index baeb23543..efba61e92 100644 --- a/test/unit/serialization_sb3.js +++ b/test/unit/serialization_sb3.js @@ -11,6 +11,8 @@ const commentsSB3NoDupeIds = path.resolve(__dirname, '../fixtures/comments_no_du const variableReporterSB2ProjectPath = path.resolve(__dirname, '../fixtures/top-level-variable-reporter.sb2'); const topLevelReportersProjectPath = path.resolve(__dirname, '../fixtures/top-level-reporters.sb3'); const draggableSB3ProjectPath = path.resolve(__dirname, '../fixtures/draggable.sb3'); +const originSB3ProjectPath = path.resolve(__dirname, '../fixtures/origin.sb3'); +const originAbsentSB3ProjectPath = path.resolve(__dirname, '../fixtures/origin-absent.sb3'); const FakeRenderer = require('../fixtures/fake-renderer'); test('serialize', t => { @@ -324,3 +326,38 @@ test('(#1850) sprite draggability state read when loading SB3 file', t => { t.end(); }); }); + +test('load origin value from SB3 file json metadata', t => { + const vm = new VirtualMachine(); + vm.loadProject(readFileToBuffer(originSB3ProjectPath)) + .then(() => { + t.type(vm.runtime.origin, 'string'); + }) + .then(() => vm.loadProject(readFileToBuffer(originAbsentSB3ProjectPath))) + .then(() => { + // After loading a project with an origin, then loading one without an origin, + // origin value should no longer be set. + t.equal(vm.runtime.origin, null); + t.end(); + }); +}); + +test('serialize origin value if it is present', t => { + const vm = new VirtualMachine(); + vm.loadProject(readFileToBuffer(originSB3ProjectPath)) + .then(() => { + const result = sb3.serialize(vm.runtime); + t.type(result.meta.origin, 'string'); + t.end(); + }); +}); + +test('do not serialize origin value if it is not present', t => { + const vm = new VirtualMachine(); + vm.loadProject(readFileToBuffer(originAbsentSB3ProjectPath)) + .then(() => { + const result = sb3.serialize(vm.runtime); + t.equal(result.meta.origin, undefined); + t.end(); + }); +});