Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/engine/mutation-adapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ const mutatorTagToObject = function (dom) {
for (const prop in dom.attribs) {
if (prop === 'xmlns') continue;
obj[prop] = decodeHtml(dom.attribs[prop]);
if (prop === 'blockinfo') {
obj.blockInfo = JSON.parse(obj.blockinfo);
delete obj.blockinfo;
}
}
for (let i = 0; i < dom.children.length; i++) {
obj.children.push(
Expand Down
37 changes: 25 additions & 12 deletions src/extension-support/extension-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -390,27 +390,40 @@ class ExtensionManager {
log.warn(`Ignoring opcode "${blockInfo.opcode}" for button with text: ${blockInfo.text}`);
}
break;
default:
default: {
if (!blockInfo.opcode) {
throw new Error('Missing opcode for block');
}

blockInfo.func = blockInfo.func ? this._sanitizeID(blockInfo.func) : blockInfo.opcode;
const funcName = blockInfo.func ? this._sanitizeID(blockInfo.func) : blockInfo.opcode;

// Avoid promise overhead if possible
if (dispatch._isRemoteService(serviceName)) {
blockInfo.func = dispatch.call.bind(dispatch, serviceName, blockInfo.func);
} else {
const getBlockInfo = blockInfo.isDynamic ?
args => args && args.mutation && args.mutation.blockInfo :
() => blockInfo;
const callBlockFunc = (() => {
if (dispatch._isRemoteService(serviceName)) {
return (args, util, realBlockInfo) =>
dispatch.call(serviceName, funcName, args, util, realBlockInfo);
}

// avoid promise latency if we can call direct
const serviceObject = dispatch.services[serviceName];
const func = serviceObject[blockInfo.func];
if (func) {
blockInfo.func = func.bind(serviceObject);
} else {
throw new Error(`Could not find extension block function called ${blockInfo.func}`);
if (!serviceObject[funcName]) {
// The function might show up later as a dynamic property of the service object
log.warn(`Could not find extension block function called ${funcName}`);
}
}
return (args, util, realBlockInfo) =>
serviceObject[funcName](args, util, realBlockInfo);
})();

blockInfo.func = (args, util) => {
const realBlockInfo = getBlockInfo(args);
// TODO: filter args using the keys of realBlockInfo.arguments? maybe only if sandboxed?
return callBlockFunc(args, util, realBlockInfo);
};
break;
}
}

return blockInfo;
}
Expand Down
33 changes: 26 additions & 7 deletions test/integration/internal-extension.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
const test = require('tap').test;
const Worker = require('tiny-worker');

const BlockType = require('../../src/extension-support/block-type');

const dispatch = require('../../src/dispatch/central-dispatch');
const VirtualMachine = require('../../src/virtual-machine');

Expand All @@ -12,7 +14,9 @@ dispatch.workerClass = Worker;

class TestInternalExtension {
constructor () {
this.status = {};
this.status = {
goLog: []
};
this.status.constructorCalled = true;
}

Expand All @@ -23,7 +27,14 @@ class TestInternalExtension {
name: 'Test Internal Extension',
blocks: [
{
opcode: 'go'
isDynamic: true,
opcode: 'go',
text: 'thing 1'
},
{
isDynamic: true,
opcode: 'go',
text: 'thing 2'
}
],
menus: {
Expand All @@ -33,8 +44,8 @@ class TestInternalExtension {
};
}

go () {
this.status.goCalled = true;
go (args, util, blockInfo) {
this.status.goLog.push(blockInfo.text);
}

_buildAMenu () {
Expand All @@ -58,12 +69,20 @@ test('internal extension', t => {
vm.extensionManager._registerInternalExtension(extension);
t.ok(extension.status.getInfoCalled);

t.deepEqual(extension.status.goLog, []);

const func = vm.runtime.getOpcodeFunction('testInternalExtension_go');
t.type(func, 'function');
t.throws(func); // should fail to get dynamic blockInfo

t.deepEqual(extension.status.goLog, []);

// simulate `defineDynamicBlock`
const extensionInfo = extension.getInfo();
func({mutation: {blockInfo: extensionInfo.blocks[0]}});
func({mutation: {blockInfo: extensionInfo.blocks[1]}});

t.notOk(extension.status.goCalled);
func();
t.ok(extension.status.goCalled);
t.deepEqual(extension.status.goLog, ['thing 1', 'thing 2']);

// There should be 2 menus - one is an array, one is the function to call.
t.equal(vm.runtime._blockInfo[0].menus.length, 2);
Expand Down