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 { platform } from './Environment'
import { registerFunction } from './RegisteredFunction'; 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') { 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 { MethodHandle, System_Object, System_String, System_Array, Pointer, Platform } from '../Platform';
import { getAssemblyNameFromUrl } from '../DotNet'; import { getAssemblyNameFromUrl } from '../DotNet';
import { getRegisteredFunction } from '../../RegisteredFunction';
let assembly_load: (assemblyName: string) => number; let assembly_load: (assemblyName: string) => number;
let find_class: (assemblyHandle: number, namespace: string, className: 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() { function addScriptTagsToDocument() {
// Load either the wasm or asm.js version of the Mono runtime // Load either the wasm or asm.js version of the Mono runtime
const browserSupportsNativeWebAssembly = typeof WebAssembly !== 'undefined' && WebAssembly.validate; const browserSupportsNativeWebAssembly = typeof WebAssembly !== 'undefined' && WebAssembly.validate;

View File

@ -1,12 +1,14 @@
import { System_String } from './Platform/Platform'; import { System_String } from './Platform/Platform';
import { platform } from './Environment'; import { platform } from './Environment';
import { internalRegisteredFunctions } from './InternalRegisteredFunction';
const registeredFunctions: { [identifier: string]: Function } = {}; const registeredFunctions: { [identifier: string]: Function | undefined } = {};
// Code in Mono 'driver.c' looks for the registered functions here
window['__blazorRegisteredFunctions'] = registeredFunctions;
export function registerFunction(identifier: string, implementation: Function) { 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)) { if (registeredFunctions.hasOwnProperty(identifier)) {
throw new Error(`A function with the identifier '${identifier}' has already been registered.`); 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; registeredFunctions[identifier] = implementation;
} }
// Handle the JSON-marshalled RegisteredFunction.Invoke calls export function getRegisteredFunction(identifier: string): Function {
registerFunction('__blazor_InvokeJson', (identifier: System_String, ...argsJson: System_String[]) => { // By prioritising the internal ones, we ensure you can't override them
const identifierJsString = platform.toJavaScriptString(identifier); const result = internalRegisteredFunctions[identifier] || registeredFunctions[identifier];
if (!(registeredFunctions && registeredFunctions.hasOwnProperty(identifierJsString))) { if (result) {
throw new Error(`Could not find registered function with name "${identifierJsString}".`); 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 args = argsJson.map(json => JSON.parse(platform.toJavaScriptString(json)));
const result = funcInstance.apply(null, args); const result = funcInstance.apply(null, args);
if (result !== null && result !== undefined) { if (result !== null && result !== undefined) {
@ -29,4 +37,4 @@ registerFunction('__blazor_InvokeJson', (identifier: System_String, ...argsJson:
} else { } else {
return null; 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 { platform } from '../Environment';
import { getTreeNodePtr, renderTreeNode, NodeType, RenderTreeNodePointer } from './RenderTreeNode'; import { getTreeNodePtr, renderTreeNode, NodeType, RenderTreeNodePointer } from './RenderTreeNode';
let raiseEventMethod: MethodHandle; let raiseEventMethod: MethodHandle;
@ -14,10 +13,7 @@ type ComponentIdToParentElement = { [componentId: number]: Element };
type BrowserRendererRegistry = { [browserRendererId: number]: ComponentIdToParentElement }; type BrowserRendererRegistry = { [browserRendererId: number]: ComponentIdToParentElement };
const browserRenderers: BrowserRendererRegistry = {}; const browserRenderers: BrowserRendererRegistry = {};
registerFunction('_blazorAttachComponentToElement', attachComponentToElement); export function attachComponentToElement(browserRendererId: number, elementSelector: System_String, componentId: number) {
registerFunction('_blazorRender', renderRenderTree);
function attachComponentToElement(browserRendererId: number, elementSelector: System_String, componentId: number) {
const elementSelectorJs = platform.toJavaScriptString(elementSelector); const elementSelectorJs = platform.toJavaScriptString(elementSelector);
const element = document.querySelector(elementSelectorJs); const element = document.querySelector(elementSelectorJs);
if (!element) { if (!element) {
@ -28,7 +24,7 @@ function attachComponentToElement(browserRendererId: number, elementSelector: Sy
browserRenderers[browserRendererId][componentId] = element; browserRenderers[browserRendererId][componentId] = element;
} }
function renderRenderTree(renderComponentArgs: Pointer) { export function renderRenderTree(renderComponentArgs: Pointer) {
const browserRendererId = platform.readHeapInt32(renderComponentArgs, 0); const browserRendererId = platform.readHeapInt32(renderComponentArgs, 0);
const browserRenderer = browserRenderers[browserRendererId]; const browserRenderer = browserRenderers[browserRendererId];
if (!browserRenderer) { 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 // This is a low-perf convenience method that bypasses the need to deal with
// .NET memory and data structures on the JS side // .NET memory and data structures on the JS side
var argsJson = args.Select(Json.Serialize); var argsJson = args.Select(Json.Serialize);
var resultJson = InvokeUnmarshalled<string>("__blazor_InvokeJson", var resultJson = InvokeUnmarshalled<string>("invokeWithJsonMarshalling",
argsJson.Prepend(identifier).ToArray()); argsJson.Prepend(identifier).ToArray());
return Json.Deserialize<TRes>(resultJson); return Json.Deserialize<TRes>(resultJson);
} }

View File

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