Clean up how JS functions are registered and located for invocation from .NET

This commit is contained in:
Steve Sanderson 2018-01-10 11:55:52 +00:00
parent 7bbf2b54aa
commit 0187384638
7 changed files with 48 additions and 29 deletions

View File

@ -1,13 +1,11 @@
import { platform } from './Environment'
import { registerFunction } from './RegisteredFunction';
// This file defines an export that, when the library is loaded in a browser via a
// <script> element, will be attached to the global namespace
const blazorInstance = {
platform: platform,
registerFunction: registerFunction
};
if (typeof window !== 'undefined') {
window['Blazor'] = blazorInstance;
// When the library is loaded in a browser via a <script> element, make the
// following APIs available in global scope for invocation from JS
window['Blazor'] = {
platform,
registerFunction,
};
}

View File

@ -0,0 +1,12 @@
import { invokeWithJsonMarshalling } from './RegisteredFunction';
import { attachComponentToElement, renderRenderTree } from './Rendering/Renderer';
/**
* The definitive list of internal functions invokable from .NET code.
* These function names are treated as 'reserved' and cannot be passed to registerFunction.
*/
export const internalRegisteredFunctions = {
attachComponentToElement,
invokeWithJsonMarshalling,
renderRenderTree,
};

View File

@ -1,5 +1,6 @@
import { MethodHandle, System_Object, System_String, System_Array, Pointer, Platform } from '../Platform';
import { getAssemblyNameFromUrl } from '../DotNet';
import { getRegisteredFunction } from '../../RegisteredFunction';
let assembly_load: (assemblyName: string) => number;
let find_class: (assemblyHandle: number, namespace: string, className: string) => number;
@ -120,6 +121,10 @@ export const monoPlatform: Platform = {
},
};
// Bypass normal type checking to add this extra function. It's only intended to be called from
// the JS code in Mono's driver.c. It's never intended to be called from TypeScript.
(monoPlatform as any).monoGetRegisteredFunction = getRegisteredFunction;
function addScriptTagsToDocument() {
// Load either the wasm or asm.js version of the Mono runtime
const browserSupportsNativeWebAssembly = typeof WebAssembly !== 'undefined' && WebAssembly.validate;

View File

@ -1,12 +1,14 @@
import { System_String } from './Platform/Platform';
import { platform } from './Environment';
import { internalRegisteredFunctions } from './InternalRegisteredFunction';
const registeredFunctions: { [identifier: string]: Function } = {};
// Code in Mono 'driver.c' looks for the registered functions here
window['__blazorRegisteredFunctions'] = registeredFunctions;
const registeredFunctions: { [identifier: string]: Function | undefined } = {};
export function registerFunction(identifier: string, implementation: Function) {
if (internalRegisteredFunctions.hasOwnProperty(identifier)) {
throw new Error(`The function identifier '${identifier}' is reserved and cannot be registered.`);
}
if (registeredFunctions.hasOwnProperty(identifier)) {
throw new Error(`A function with the identifier '${identifier}' has already been registered.`);
}
@ -14,13 +16,19 @@ export function registerFunction(identifier: string, implementation: Function) {
registeredFunctions[identifier] = implementation;
}
// Handle the JSON-marshalled RegisteredFunction.Invoke calls
registerFunction('__blazor_InvokeJson', (identifier: System_String, ...argsJson: System_String[]) => {
const identifierJsString = platform.toJavaScriptString(identifier);
if (!(registeredFunctions && registeredFunctions.hasOwnProperty(identifierJsString))) {
throw new Error(`Could not find registered function with name "${identifierJsString}".`);
export function getRegisteredFunction(identifier: string): Function {
// By prioritising the internal ones, we ensure you can't override them
const result = internalRegisteredFunctions[identifier] || registeredFunctions[identifier];
if (result) {
return result;
} else {
throw new Error(`Could not find registered function with name '${identifier}'.`);
}
const funcInstance = registeredFunctions[identifierJsString];
}
export function invokeWithJsonMarshalling(identifier: System_String, ...argsJson: System_String[]) {
const identifierJsString = platform.toJavaScriptString(identifier);
const funcInstance = getRegisteredFunction(identifierJsString);
const args = argsJson.map(json => JSON.parse(platform.toJavaScriptString(json)));
const result = funcInstance.apply(null, args);
if (result !== null && result !== undefined) {
@ -29,4 +37,4 @@ registerFunction('__blazor_InvokeJson', (identifier: System_String, ...argsJson:
} else {
return null;
}
});
}

View File

@ -1,5 +1,4 @@
import { registerFunction } from '../RegisteredFunction';
import { System_Object, System_String, System_Array, MethodHandle, Pointer } from '../Platform/Platform';
import { System_Object, System_String, System_Array, MethodHandle, Pointer } from '../Platform/Platform';
import { platform } from '../Environment';
import { getTreeNodePtr, renderTreeNode, NodeType, RenderTreeNodePointer } from './RenderTreeNode';
let raiseEventMethod: MethodHandle;
@ -14,10 +13,7 @@ type ComponentIdToParentElement = { [componentId: number]: Element };
type BrowserRendererRegistry = { [browserRendererId: number]: ComponentIdToParentElement };
const browserRenderers: BrowserRendererRegistry = {};
registerFunction('_blazorAttachComponentToElement', attachComponentToElement);
registerFunction('_blazorRender', renderRenderTree);
function attachComponentToElement(browserRendererId: number, elementSelector: System_String, componentId: number) {
export function attachComponentToElement(browserRendererId: number, elementSelector: System_String, componentId: number) {
const elementSelectorJs = platform.toJavaScriptString(elementSelector);
const element = document.querySelector(elementSelectorJs);
if (!element) {
@ -28,7 +24,7 @@ function attachComponentToElement(browserRendererId: number, elementSelector: Sy
browserRenderers[browserRendererId][componentId] = element;
}
function renderRenderTree(renderComponentArgs: Pointer) {
export function renderRenderTree(renderComponentArgs: Pointer) {
const browserRendererId = platform.readHeapInt32(renderComponentArgs, 0);
const browserRenderer = browserRenderers[browserRendererId];
if (!browserRenderer) {

View File

@ -24,7 +24,7 @@ namespace Microsoft.Blazor.Browser.Interop
// This is a low-perf convenience method that bypasses the need to deal with
// .NET memory and data structures on the JS side
var argsJson = args.Select(Json.Serialize);
var resultJson = InvokeUnmarshalled<string>("__blazor_InvokeJson",
var resultJson = InvokeUnmarshalled<string>("invokeWithJsonMarshalling",
argsJson.Prepend(identifier).ToArray());
return Json.Deserialize<TRes>(resultJson);
}

View File

@ -48,7 +48,7 @@ namespace Microsoft.Blazor.Browser.Rendering
{
var componentId = AssignComponentId(component);
RegisteredFunction.InvokeUnmarshalled<int, string, int, object>(
"_blazorAttachComponentToElement",
"attachComponentToElement",
_browserRendererId,
domElementSelector,
componentId);
@ -71,7 +71,7 @@ namespace Microsoft.Blazor.Browser.Rendering
ArraySegment<RenderTreeNode> renderTree)
{
RegisteredFunction.InvokeUnmarshalled<RenderComponentArgs, object>(
"_blazorRender",
"renderRenderTree",
new RenderComponentArgs
{
BrowserRendererId = _browserRendererId,