Initial debugger support
This commit is contained in:
parent
c881a63a78
commit
cafb56569d
|
|
@ -68,7 +68,7 @@
|
|||
<script type="blazor-boot"></script>
|
||||
<script src="loader.js"></script>
|
||||
<script>
|
||||
initMono(['/_framework/_bin/MonoSanityClient.dll'], function () {
|
||||
initMono(['_framework/_bin/MonoSanityClient.dll', '_framework/_bin/MonoSanityClient.pdb'], function () {
|
||||
var buttons = document.getElementsByTagName('button');
|
||||
for (var i = 0; i < buttons.length; i++) {
|
||||
buttons[i].disabled = false;
|
||||
|
|
|
|||
|
|
@ -30,8 +30,9 @@
|
|||
preloadAssemblies(loadAssemblyUrls);
|
||||
}],
|
||||
postRun: [function () {
|
||||
var load_runtime = Module.cwrap('mono_wasm_load_runtime', null, ['string']);
|
||||
load_runtime('appBinDir');
|
||||
var load_runtime = Module.cwrap('mono_wasm_load_runtime', null, ['string', 'number']);
|
||||
load_runtime('appBinDir', 1);
|
||||
MONO.mono_wasm_runtime_is_ready = true;
|
||||
onReadyCallback();
|
||||
}]
|
||||
};
|
||||
|
|
@ -85,13 +86,22 @@
|
|||
.concat(loadBclAssemblies.map(function (name) { return '_framework/_bin/' + name + '.dll'; }));
|
||||
|
||||
Module.FS_createPath('/', 'appBinDir', true, true);
|
||||
|
||||
MONO.loaded_files = []; // Used by debugger
|
||||
allAssemblyUrls.forEach(function (url) {
|
||||
FS.createPreloadedFile('appBinDir', getAssemblyNameFromUrl(url) + '.dll', url, true, false, null, function onError(err) {
|
||||
throw err;
|
||||
});
|
||||
FS.createPreloadedFile('appBinDir', getFileNameFromUrl(url), url, true, false,
|
||||
/* success */ function() { MONO.loaded_files.push(toAbsoluteUrl(url)); },
|
||||
/* failure */ function onError(err) { throw err; }
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
var anchorTagForAbsoluteUrlConversions = document.createElement('a');
|
||||
function toAbsoluteUrl(possiblyRelativeUrl) {
|
||||
anchorTagForAbsoluteUrlConversions.href = possiblyRelativeUrl;
|
||||
return anchorTagForAbsoluteUrlConversions.href;
|
||||
}
|
||||
|
||||
function asyncLoad(url, onload, onerror) {
|
||||
var xhr = new XMLHttpRequest;
|
||||
xhr.open('GET', url, /* async: */ true);
|
||||
|
|
@ -161,11 +171,11 @@
|
|||
document.body.appendChild(scriptElem);
|
||||
}
|
||||
|
||||
function getAssemblyNameFromUrl(url) {
|
||||
function getFileNameFromUrl(url) {
|
||||
var lastSegment = url.substring(url.lastIndexOf('/') + 1);
|
||||
var queryStringStartPos = lastSegment.indexOf('?');
|
||||
var filename = queryStringStartPos < 0 ? lastSegment : lastSegment.substring(0, queryStringStartPos);
|
||||
return filename.replace(/\.dll$/, '');
|
||||
return filename;
|
||||
}
|
||||
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Razor" TreatAsLocalProperty="BlazorLinkOnBuild">
|
||||
<Project Sdk="Microsoft.NET.Sdk.Razor" TreatAsLocalProperty="BlazorLinkOnBuild">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<BlazorLinkOnBuild>false</BlazorLinkOnBuild>
|
||||
|
||||
<!-- loader.js is hard-coded to assume it can load .pdbs regardless of Debug/Release configuration -->
|
||||
<BlazorEnableDebugging>true</BlazorEnableDebugging>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Local alternative to <PackageReference Include="Microsoft.AspNetCore.Blazor.Build" /> -->
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import '../../Microsoft.JSInterop/JavaScriptRuntime/src/Microsoft.JSInterop';
|
||||
import { platform } from './Environment';
|
||||
import { getAssemblyNameFromUrl } from './Platform/DotNet';
|
||||
import { getAssemblyNameFromUrl } from './Platform/Url';
|
||||
import './GlobalExports';
|
||||
|
||||
async function boot() {
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
export function getAssemblyNameFromUrl(url: string) {
|
||||
const lastSegment = url.substring(url.lastIndexOf('/') + 1);
|
||||
const queryStringStartPos = lastSegment.indexOf('?');
|
||||
const filename = queryStringStartPos < 0 ? lastSegment : lastSegment.substring(0, queryStringStartPos);
|
||||
return filename.replace(/\.dll$/, '');
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
import { getAssemblyNameFromUrl, getFileNameFromUrl } from '../Url';
|
||||
|
||||
const currentBrowserIsChrome = (window as any).chrome
|
||||
&& navigator.userAgent.indexOf('Edge') < 0; // Edge pretends to be Chrome
|
||||
|
||||
let hasReferencedPdbs = false;
|
||||
|
||||
export function hasDebuggingEnabled() {
|
||||
return hasReferencedPdbs && currentBrowserIsChrome;
|
||||
}
|
||||
|
||||
export function attachDebuggerHotkey(loadAssemblyUrls: string[]) {
|
||||
hasReferencedPdbs = loadAssemblyUrls
|
||||
.some(url => /\.pdb$/.test(getFileNameFromUrl(url)));
|
||||
|
||||
// Use the combination shift+alt+D because it isn't used by the major browsers
|
||||
// for anything else by default
|
||||
const altKeyName = navigator.platform.match(/^Mac/i) ? 'Cmd' : 'Alt';
|
||||
if (hasDebuggingEnabled()) {
|
||||
console.info(`Debugging hotkey: Shift+${altKeyName}+D (when application has focus)`);
|
||||
}
|
||||
|
||||
// Even if debugging isn't enabled, we register the hotkey so we can report why it's not enabled
|
||||
document.addEventListener('keydown', evt => {
|
||||
if (evt.shiftKey && (evt.metaKey || evt.altKey) && evt.code === 'KeyD') {
|
||||
if (!hasReferencedPdbs) {
|
||||
console.error('Cannot start debugging, because the application was not compiled with debugging enabled.');
|
||||
} else if (!currentBrowserIsChrome) {
|
||||
console.error('Currently, only Chrome is supported for debugging.');
|
||||
} else {
|
||||
launchDebugger();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function launchDebugger() {
|
||||
// The noopener flag is essential, because otherwise Chrome tracks the association with the
|
||||
// parent tab, and then when the parent tab pauses in the debugger, the child tab does so
|
||||
// too (even if it's since navigated to a different page). This means that the debugger
|
||||
// itself freezes, and not just the page being debugged.
|
||||
//
|
||||
// We have to construct a link element and simulate a click on it, because the more obvious
|
||||
// window.open(..., 'noopener') always opens a new window instead of a new tab.
|
||||
const link = document.createElement('a');
|
||||
link.href = `_framework/debug?url=${encodeURIComponent(location.href)}`;
|
||||
link.target = '_blank';
|
||||
link.rel = 'noopener noreferrer';
|
||||
link.click();
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import { MethodHandle, System_Object, System_String, System_Array, Pointer, Platform } from '../Platform';
|
||||
import { getAssemblyNameFromUrl } from '../DotNet';
|
||||
import { getAssemblyNameFromUrl, getFileNameFromUrl } from '../Url';
|
||||
import { attachDebuggerHotkey, hasDebuggingEnabled } from './MonoDebugger';
|
||||
|
||||
const assemblyHandleCache: { [assemblyName: string]: number } = {};
|
||||
const typeHandleCache: { [fullyQualifiedTypeName: string]: number } = {};
|
||||
|
|
@ -11,14 +12,16 @@ let find_method: (typeHandle: number, methodName: string, unknownArg: number) =>
|
|||
let invoke_method: (method: MethodHandle, target: System_Object, argsArrayPtr: number, exceptionFlagIntPtr: number) => System_Object;
|
||||
let mono_string_get_utf8: (managedString: System_String) => Mono.Utf8Ptr;
|
||||
let mono_string: (jsString: string) => System_String;
|
||||
const appBinDirName = 'appBinDir';
|
||||
|
||||
export const monoPlatform: Platform = {
|
||||
start: function start(loadAssemblyUrls: string[]) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
attachDebuggerHotkey(loadAssemblyUrls);
|
||||
|
||||
// mono.js assumes the existence of this
|
||||
window['Browser'] = {
|
||||
init: () => { },
|
||||
asyncLoad: asyncLoad
|
||||
init: () => { }
|
||||
};
|
||||
// Emscripten works by expecting the module config to be a global
|
||||
window['Module'] = createEmscriptenModuleInstance(loadAssemblyUrls, resolve, reject);
|
||||
|
|
@ -192,8 +195,9 @@ function createEmscriptenModuleInstance(loadAssemblyUrls: string[], onReady: ()
|
|||
const module = {} as typeof Module;
|
||||
const wasmBinaryFile = '_framework/wasm/mono.wasm';
|
||||
const asmjsCodeFile = '_framework/asmjs/mono.asm.js';
|
||||
const suppressMessages = ['DEBUGGING ENABLED'];
|
||||
|
||||
module.print = line => console.log(`WASM: ${line}`);
|
||||
module.print = line => (suppressMessages.indexOf(line) < 0 && console.log(`WASM: ${line}`));
|
||||
module.printErr = line => console.error(`WASM: ${line}`);
|
||||
module.preRun = [];
|
||||
module.postRun = [];
|
||||
|
|
@ -216,14 +220,39 @@ function createEmscriptenModuleInstance(loadAssemblyUrls: string[], onReady: ()
|
|||
mono_string_get_utf8 = Module.cwrap('mono_wasm_string_get_utf8', 'number', ['number']);
|
||||
mono_string = Module.cwrap('mono_wasm_string_from_js', 'number', ['string']);
|
||||
|
||||
Module.FS_createPath('/', 'appBinDir', true, true);
|
||||
loadAssemblyUrls.forEach(url =>
|
||||
FS.createPreloadedFile('appBinDir', `${getAssemblyNameFromUrl(url)}.dll`, url, true, false, undefined, onError));
|
||||
Module.FS_createPath('/', appBinDirName, true, true);
|
||||
MONO.loaded_files = [];
|
||||
|
||||
loadAssemblyUrls.forEach(url => {
|
||||
const filename = getFileNameFromUrl(url);
|
||||
const runDependencyId = `blazor:${filename}`;
|
||||
addRunDependency(runDependencyId);
|
||||
asyncLoad(url).then(
|
||||
data => {
|
||||
Module.FS_createDataFile(appBinDirName, filename, data, true, false, false);
|
||||
MONO.loaded_files.push(toAbsoluteUrl(url));
|
||||
removeRunDependency(runDependencyId);
|
||||
},
|
||||
errorInfo => {
|
||||
// If it's a 404 on a .pdb, we don't want to block the app from starting up.
|
||||
// We'll just skip that file and continue (though the 404 is logged in the console).
|
||||
// This happens if you build a Debug build but then run in Production environment.
|
||||
const isPdb404 = errorInfo instanceof XMLHttpRequest
|
||||
&& errorInfo.status === 404
|
||||
&& filename.match(/\.pdb$/);
|
||||
if (!isPdb404) {
|
||||
onError(errorInfo);
|
||||
}
|
||||
removeRunDependency(runDependencyId);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
module.postRun.push(() => {
|
||||
const load_runtime = Module.cwrap('mono_wasm_load_runtime', null, ['string']);
|
||||
load_runtime('appBinDir');
|
||||
const load_runtime = Module.cwrap('mono_wasm_load_runtime', null, ['string', 'number']);
|
||||
load_runtime(appBinDirName, hasDebuggingEnabled() ? 1 : 0);
|
||||
MONO.mono_wasm_runtime_is_ready = true;
|
||||
attachInteropInvoker();
|
||||
onReady();
|
||||
});
|
||||
|
|
@ -231,20 +260,28 @@ function createEmscriptenModuleInstance(loadAssemblyUrls: string[], onReady: ()
|
|||
return module;
|
||||
}
|
||||
|
||||
function asyncLoad(url, onload, onerror) {
|
||||
var xhr = new XMLHttpRequest;
|
||||
xhr.open('GET', url, /* async: */ true);
|
||||
xhr.responseType = 'arraybuffer';
|
||||
xhr.onload = function xhr_onload() {
|
||||
if (xhr.status == 200 || xhr.status == 0 && xhr.response) {
|
||||
var asm = new Uint8Array(xhr.response);
|
||||
onload(asm);
|
||||
} else {
|
||||
onerror(xhr);
|
||||
}
|
||||
};
|
||||
xhr.onerror = onerror;
|
||||
xhr.send(null);
|
||||
const anchorTagForAbsoluteUrlConversions = document.createElement('a');
|
||||
function toAbsoluteUrl(possiblyRelativeUrl: string) {
|
||||
anchorTagForAbsoluteUrlConversions.href = possiblyRelativeUrl;
|
||||
return anchorTagForAbsoluteUrlConversions.href;
|
||||
}
|
||||
|
||||
function asyncLoad(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var xhr = new XMLHttpRequest;
|
||||
xhr.open('GET', url, /* async: */ true);
|
||||
xhr.responseType = 'arraybuffer';
|
||||
xhr.onload = function xhr_onload() {
|
||||
if (xhr.status == 200 || xhr.status == 0 && xhr.response) {
|
||||
var asm = new Uint8Array(xhr.response);
|
||||
resolve(asm);
|
||||
} else {
|
||||
reject(xhr);
|
||||
}
|
||||
};
|
||||
xhr.onerror = reject;
|
||||
xhr.send(null);
|
||||
});
|
||||
}
|
||||
|
||||
function getArrayDataPointer<T>(array: System_Array<T>): number {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
declare namespace Module {
|
||||
declare namespace Module {
|
||||
function UTF8ToString(utf8: Mono.Utf8Ptr): string;
|
||||
var preloadPlugins: any[];
|
||||
|
||||
|
|
@ -11,7 +11,17 @@
|
|||
function FS_createDataFile(parent, name, data, canRead, canWrite, canOwn);
|
||||
}
|
||||
|
||||
// Emscripten declares these globals
|
||||
declare const addRunDependency: any;
|
||||
declare const removeRunDependency: any;
|
||||
|
||||
declare namespace Mono {
|
||||
interface Utf8Ptr { Utf8Ptr__DO_NOT_IMPLEMENT: any }
|
||||
interface StackSaveHandle { StackSaveHandle__DO_NOT_IMPLEMENT: any }
|
||||
}
|
||||
|
||||
// Mono uses this global to hang various debugging-related items on
|
||||
declare namespace MONO {
|
||||
var loaded_files: string[];
|
||||
var mono_wasm_runtime_is_ready: boolean;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
export function getFileNameFromUrl(url: string) {
|
||||
// This could also be called "get last path segment from URL", but the primary
|
||||
// use case is to extract things that look like filenames
|
||||
const lastSegment = url.substring(url.lastIndexOf('/') + 1);
|
||||
const queryStringStartPos = lastSegment.indexOf('?');
|
||||
return queryStringStartPos < 0 ? lastSegment : lastSegment.substring(0, queryStringStartPos);
|
||||
}
|
||||
|
||||
export function getAssemblyNameFromUrl(url: string) {
|
||||
return getFileNameFromUrl(url).replace(/\.dll$/, '');
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
|
|
@ -45,7 +46,14 @@ namespace Microsoft.AspNetCore.Blazor.Build
|
|||
assemblyResolutionContext.ResolveAssemblies();
|
||||
|
||||
var paths = assemblyResolutionContext.Results.Select(r => r.Path);
|
||||
return paths;
|
||||
return paths.Concat(FindPdbs(paths));
|
||||
}
|
||||
|
||||
private static IEnumerable<string> FindPdbs(IEnumerable<string> dllPaths)
|
||||
{
|
||||
return dllPaths
|
||||
.Select(path => Path.ChangeExtension(path, "pdb"))
|
||||
.Where(path => File.Exists(path));
|
||||
}
|
||||
|
||||
public class AssemblyResolutionContext
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
<Project>
|
||||
<Project>
|
||||
<Import Project="Blazor.MonoRuntime.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<DefaultWebContentItemExcludes>$(DefaultWebContentItemExcludes);wwwroot\**</DefaultWebContentItemExcludes>
|
||||
|
||||
<!-- By default, enable auto rebuilds for debug builds. Note that the server will not enable it in production environments regardless. -->
|
||||
<BlazorRebuildOnFileChange Condition="'$(Configuration)' == 'Debug'">true</BlazorRebuildOnFileChange>
|
||||
|
||||
<BlazorRebuildOnFileChange Condition="'$(Configuration)' == 'Debug' AND '$(BlazorRebuildOnFileChange)' == ''">true</BlazorRebuildOnFileChange>
|
||||
|
||||
<!-- By default, enable debugging for debug builds. -->
|
||||
<BlazorEnableDebugging Condition="'$(Configuration)' == 'Debug' AND '$(BlazorEnableDebugging)' == ''">true</BlazorEnableDebugging>
|
||||
|
||||
<!-- When using IISExpress with a standalone app, there's no point restarting IISExpress after build. It slows things unnecessarily and breaks in-flight HTTP requests. -->
|
||||
<NoRestartServerOnBuild>true</NoRestartServerOnBuild>
|
||||
</PropertyGroup>
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
<WriteLinesToFile File="$(BlazorMetadataFilePath)" Lines="$(MSBuildProjectFullPath)" Overwrite="true" Encoding="Unicode"/>
|
||||
<WriteLinesToFile File="$(BlazorMetadataFilePath)" Lines="$(OutDir)$(AssemblyName).dll" Overwrite="false" Encoding="Unicode"/>
|
||||
<WriteLinesToFile File="$(BlazorMetadataFilePath)" Condition="'$(BlazorRebuildOnFileChange)'=='true'" Lines="autorebuild:true" Overwrite="false" Encoding="Unicode"/>
|
||||
<WriteLinesToFile File="$(BlazorMetadataFilePath)" Condition="'$(BlazorEnableDebugging)'=='true'" Lines="debug:true" Overwrite="false" Encoding="Unicode"/>
|
||||
<ItemGroup>
|
||||
<ContentWithTargetPath Include="$(BlazorMetadataFilePath)" TargetPath="$(BlazorMetadataFileName)" CopyToOutputDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
<PropertyGroup Label="Blazor build outputs">
|
||||
<MonoLinkerI18NAssemblies>none</MonoLinkerI18NAssemblies> <!-- See Mono linker docs - allows comma-separated values from: none,all,cjk,mideast,other,rare,west -->
|
||||
<AdditionalMonoLinkerOptions>-c link -u link -t --verbose </AdditionalMonoLinkerOptions>
|
||||
<AdditionalMonoLinkerOptions>-c link -u link -b true -t --verbose </AdditionalMonoLinkerOptions>
|
||||
<BaseBlazorDistPath>dist/</BaseBlazorDistPath>
|
||||
<BaseBlazorPackageContentOutputPath>$(BaseBlazorDistPath)_content/</BaseBlazorPackageContentOutputPath>
|
||||
<BaseBlazorRuntimeOutputPath>$(BaseBlazorDistPath)_framework/</BaseBlazorRuntimeOutputPath>
|
||||
|
|
|
|||
|
|
@ -272,6 +272,7 @@
|
|||
<_BlazorCommonInput Include="@(IntermediateAssembly)" />
|
||||
<_BlazorCommonInput Include="@(_BlazorDependencyInput)" />
|
||||
<_BlazorCommonInput Include="$(_BlazorShouldLinkApplicationAssemblies)" />
|
||||
<_BlazorCommonInput Include="$(BlazorEnableDebugging)" />
|
||||
<_BlazorLinkingOption Condition="'$(_BlazorShouldLinkApplicationAssemblies)' == ''" Include="false" />
|
||||
<_BlazorLinkingOption Condition="'$(_BlazorShouldLinkApplicationAssemblies)' != ''" Include="true" />
|
||||
</ItemGroup>
|
||||
|
|
@ -340,11 +341,15 @@
|
|||
</ReadLinesFromFile>
|
||||
|
||||
<ItemGroup>
|
||||
<BlazorItemOutput Include="@(_OptimizedFiles)">
|
||||
<BlazorItemOutput Include="@(_OptimizedFiles->WithMetadataValue('Extension','.dll'))">
|
||||
<TargetOutputPath>$(BlazorRuntimeBinOutputPath)%(FileName)%(Extension)</TargetOutputPath>
|
||||
<Type>Assembly</Type>
|
||||
<PrimaryOutput Condition="'%(FileName)' == @(IntermediateAssembly->'%(FileName)')">true</PrimaryOutput>
|
||||
</BlazorItemOutput>
|
||||
<BlazorItemOutput Include="@(_OptimizedFiles->WithMetadataValue('Extension','.pdb'))">
|
||||
<TargetOutputPath>$(BlazorRuntimeBinOutputPath)%(FileName)%(Extension)</TargetOutputPath>
|
||||
<Type>Pdb</Type>
|
||||
</BlazorItemOutput>
|
||||
<FileWrites Include="@(BlazorItemOutput->WithMetadataValue('Type','Assembly')->'%(TargetOutputPath)')" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
@ -443,6 +448,7 @@
|
|||
<!-- Collect the contents of /obj/<<configuration>>/<<targetframework>>/blazor/blazor/linker/ -->
|
||||
<ItemGroup>
|
||||
<_BlazorLinkerOutput Include="$(BlazorIntermediateLinkerOutputPath)*.dll" />
|
||||
<_BlazorLinkerOutput Include="$(BlazorIntermediateLinkerOutputPath)*.pdb" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
|
|
@ -497,14 +503,17 @@
|
|||
-->
|
||||
|
||||
<ItemGroup>
|
||||
<BlazorItemOutput Include="@(_IntermediateResolvedRuntimeDependencies)">
|
||||
<BlazorItemOutput Include="@(_IntermediateResolvedRuntimeDependencies->WithMetadataValue('Extension','.dll'))">
|
||||
<TargetOutputPath>$(BlazorRuntimeBinOutputPath)%(FileName)%(Extension)</TargetOutputPath>
|
||||
<Type>Assembly</Type>
|
||||
<PrimaryOutput Condition="'%(FileName)' == @(IntermediateAssembly->'%(FileName)')">true</PrimaryOutput>
|
||||
</BlazorItemOutput>
|
||||
<BlazorItemOutput Include="@(_IntermediateResolvedRuntimeDependencies->WithMetadataValue('Extension','.pdb'))">
|
||||
<TargetOutputPath>$(BlazorRuntimeBinOutputPath)%(FileName)%(Extension)</TargetOutputPath>
|
||||
<Type>Pdb</Type>
|
||||
</BlazorItemOutput>
|
||||
<FileWrites Include="@(BlazorItemOutput->WithMetadataValue('Type','Assembly')->'%(TargetOutputPath)')" />
|
||||
</ItemGroup>
|
||||
|
||||
</Target>
|
||||
|
||||
<Target
|
||||
|
|
@ -583,8 +592,11 @@
|
|||
<Target Name="_ResolveBlazorIndexHtmlInputs">
|
||||
<ItemGroup>
|
||||
<BlazorIndexHtmlInput Include="$(BlazorIndexHtml)" />
|
||||
<BlazorIndexHtmlInput Include="$(Configuration)" />
|
||||
<BlazorIndexHtmlInput Include="@(BlazorItemOutput->WithMetadataValue('Type','Assembly')->'%(FullPath)')" />
|
||||
<BlazorIndexHtmlInput Include="@(BlazorItemOutput->WithMetadataValue('Type','Pdb')->'%(FullPath)')" />
|
||||
<BlazorIndexHtmlInput Include="@(_BlazorLinkingOption)" />
|
||||
<BlazorIndexHtmlInput Include="$(BlazorEnableDebugging)" />
|
||||
</ItemGroup>
|
||||
|
||||
<WriteLinesToFile
|
||||
|
|
@ -607,6 +619,7 @@
|
|||
<ItemGroup>
|
||||
<_UnlinkedAppReferencesPaths Include="@(_BlazorDependencyInput)" />
|
||||
<_AppReferences Include="@(BlazorItemOutput->WithMetadataValue('Type','Assembly')->WithMetadataValue('PrimaryOutput','')->'%(FileName)%(Extension)')" />
|
||||
<_AppReferences Include="@(BlazorItemOutput->WithMetadataValue('Type','Pdb')->'%(FileName)%(Extension)')" Condition="'$(BlazorEnableDebugging)' == 'true'" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<_LinkerEnabledFlag Condition="'$(_BlazorShouldLinkApplicationAssemblies)' != ''">--linker-enabled</_LinkerEnabledFlag>
|
||||
|
|
|
|||
|
|
@ -8,20 +8,6 @@
|
|||
<!-- Preserve all methods on WasmRuntime, because these are called by JS-side code
|
||||
to implement timers. Fixes https://github.com/aspnet/Blazor/issues/239 -->
|
||||
<type fullname="System.Threading.WasmRuntime" />
|
||||
|
||||
<!-- Mono redirects certain string constructor calls to CreateString internally, so preserve them -->
|
||||
<type fullname="System.String">
|
||||
<method signature="System.String CreateString(System.Char,System.Int32)" />
|
||||
<method signature="System.String CreateString(System.Char[])" />
|
||||
<method signature="System.String CreateString(System.Char[],System.Int32,System.Int32)" />
|
||||
<method signature="System.String CreateString(System.Char*)" />
|
||||
<method signature="System.String CreateString(System.Char*,System.Int32,System.Int32)" />
|
||||
<method signature="System.String CreateString(System.Byte*)" />
|
||||
<method signature="System.String CreateString(System.Byte*,System.Int32,System.Int32)" />
|
||||
<method signature="System.String CreateString(System.Byte*,System.Int32,System.Int32,System.Text.Encoding)" />
|
||||
<method signature="System.String CreateString(System.SByte*)" />
|
||||
<method signature="System.String CreateString(System.SByte*,System.Int32,System.Int32,System.Text.Encoding)" />
|
||||
</type>
|
||||
</assembly>
|
||||
|
||||
</linker>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Blazor.Server;
|
||||
|
|
@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Builder
|
|||
// way to remove middleware once it's registered.
|
||||
private static List<object> _uncollectableWatchers = new List<object>();
|
||||
|
||||
public static void UseHostedAutoRebuild(this IApplicationBuilder appBuilder, BlazorConfig config, string hostAppContentRootPath)
|
||||
public static void UseHostedAutoRebuild(this IApplicationBuilder app, BlazorConfig config, string hostAppContentRootPath)
|
||||
{
|
||||
var isFirstFileWrite = true;
|
||||
WatchFileSystem(config, () =>
|
||||
|
|
@ -51,7 +51,7 @@ namespace Microsoft.AspNetCore.Builder
|
|||
catch (Exception ex)
|
||||
{
|
||||
// If we don't have permission to write these files, autorebuild will not be enabled
|
||||
var loggerFactory = appBuilder.ApplicationServices.GetRequiredService<ILoggerFactory>();
|
||||
var loggerFactory = app.ApplicationServices.GetRequiredService<ILoggerFactory>();
|
||||
var logger = loggerFactory.CreateLogger(typeof (AutoRebuildExtensions));
|
||||
logger?.LogWarning(ex,
|
||||
"Cannot autorebuild because there was an error when writing to a file in '{0}'.",
|
||||
|
|
@ -63,7 +63,7 @@ namespace Microsoft.AspNetCore.Builder
|
|||
});
|
||||
}
|
||||
|
||||
public static void UseDevServerAutoRebuild(this IApplicationBuilder appBuilder, BlazorConfig config)
|
||||
public static void UseDevServerAutoRebuild(this IApplicationBuilder app, BlazorConfig config)
|
||||
{
|
||||
// Currently this only supports VS for Windows. Later on we can add
|
||||
// an IRebuildService implementation for VS for Mac, etc.
|
||||
|
|
@ -87,7 +87,7 @@ namespace Microsoft.AspNetCore.Builder
|
|||
buildToken = new RebuildToken(DateTime.Now);
|
||||
});
|
||||
|
||||
appBuilder.Use(async (context, next) =>
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
|
@ -23,12 +23,12 @@ namespace Microsoft.AspNetCore.Builder
|
|||
/// Configures the middleware pipeline to work with Blazor.
|
||||
/// </summary>
|
||||
/// <typeparam name="TProgram">Any type from the client app project. This is used to identify the client app assembly.</typeparam>
|
||||
/// <param name="applicationBuilder"></param>
|
||||
/// <param name="app"></param>
|
||||
public static void UseBlazor<TProgram>(
|
||||
this IApplicationBuilder applicationBuilder)
|
||||
this IApplicationBuilder app)
|
||||
{
|
||||
var clientAssemblyInServerBinDir = typeof(TProgram).Assembly;
|
||||
applicationBuilder.UseBlazor(new BlazorOptions
|
||||
app.UseBlazor(new BlazorOptions
|
||||
{
|
||||
ClientAssemblyPath = clientAssemblyInServerBinDir.Location,
|
||||
});
|
||||
|
|
@ -37,21 +37,21 @@ namespace Microsoft.AspNetCore.Builder
|
|||
/// <summary>
|
||||
/// Configures the middleware pipeline to work with Blazor.
|
||||
/// </summary>
|
||||
/// <param name="applicationBuilder"></param>
|
||||
/// <param name="app"></param>
|
||||
/// <param name="options"></param>
|
||||
public static void UseBlazor(
|
||||
this IApplicationBuilder applicationBuilder,
|
||||
this IApplicationBuilder app,
|
||||
BlazorOptions options)
|
||||
{
|
||||
// TODO: Make the .blazor.config file contents sane
|
||||
// Currently the items in it are bizarre and don't relate to their purpose,
|
||||
// hence all the path manipulation here. We shouldn't be hardcoding 'dist' here either.
|
||||
var env = (IHostingEnvironment)applicationBuilder.ApplicationServices.GetService(typeof(IHostingEnvironment));
|
||||
var env = (IHostingEnvironment)app.ApplicationServices.GetService(typeof(IHostingEnvironment));
|
||||
var config = BlazorConfig.Read(options.ClientAssemblyPath);
|
||||
var distDirStaticFiles = new StaticFileOptions
|
||||
{
|
||||
FileProvider = new PhysicalFileProvider(config.DistPath),
|
||||
ContentTypeProvider = CreateContentTypeProvider(),
|
||||
ContentTypeProvider = CreateContentTypeProvider(config.EnableDebugging),
|
||||
OnPrepareResponse = SetCacheHeaders
|
||||
};
|
||||
|
||||
|
|
@ -59,16 +59,16 @@ namespace Microsoft.AspNetCore.Builder
|
|||
{
|
||||
if (env.ApplicationName.Equals(DevServerApplicationName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
applicationBuilder.UseDevServerAutoRebuild(config);
|
||||
app.UseDevServerAutoRebuild(config);
|
||||
}
|
||||
else
|
||||
{
|
||||
applicationBuilder.UseHostedAutoRebuild(config, env.ContentRootPath);
|
||||
app.UseHostedAutoRebuild(config, env.ContentRootPath);
|
||||
}
|
||||
}
|
||||
|
||||
// First, match the request against files in the client app dist directory
|
||||
applicationBuilder.UseStaticFiles(distDirStaticFiles);
|
||||
app.UseStaticFiles(distDirStaticFiles);
|
||||
|
||||
// Next, match the request against static files in wwwroot
|
||||
if (!string.IsNullOrEmpty(config.WebRootPath))
|
||||
|
|
@ -77,16 +77,22 @@ namespace Microsoft.AspNetCore.Builder
|
|||
// (and don't require them to be copied into dist).
|
||||
// TODO: When publishing is implemented, have config.WebRootPath set
|
||||
// to null so that it only serves files that were copied to dist
|
||||
applicationBuilder.UseStaticFiles(new StaticFileOptions
|
||||
app.UseStaticFiles(new StaticFileOptions
|
||||
{
|
||||
FileProvider = new PhysicalFileProvider(config.WebRootPath),
|
||||
OnPrepareResponse = SetCacheHeaders
|
||||
});
|
||||
}
|
||||
|
||||
// Accept debugger connections
|
||||
if (config.EnableDebugging)
|
||||
{
|
||||
app.UseMonoDebugProxy();
|
||||
}
|
||||
|
||||
// Finally, use SPA fallback routing (serve default page for anything else,
|
||||
// excluding /_framework/*)
|
||||
applicationBuilder.MapWhen(IsNotFrameworkDir, childAppBuilder =>
|
||||
app.MapWhen(IsNotFrameworkDir, childAppBuilder =>
|
||||
{
|
||||
childAppBuilder.UseSpa(spa =>
|
||||
{
|
||||
|
|
@ -116,12 +122,18 @@ namespace Microsoft.AspNetCore.Builder
|
|||
private static bool IsNotFrameworkDir(HttpContext context)
|
||||
=> !context.Request.Path.StartsWithSegments("/_framework");
|
||||
|
||||
private static IContentTypeProvider CreateContentTypeProvider()
|
||||
private static IContentTypeProvider CreateContentTypeProvider(bool enableDebugging)
|
||||
{
|
||||
var result = new FileExtensionContentTypeProvider();
|
||||
result.Mappings.Add(".dll", MediaTypeNames.Application.Octet);
|
||||
result.Mappings.Add(".mem", MediaTypeNames.Application.Octet);
|
||||
result.Mappings.Add(".wasm", WasmMediaTypeNames.Application.Wasm);
|
||||
|
||||
if (enableDebugging)
|
||||
{
|
||||
result.Mappings.Add(".pdb", MediaTypeNames.Application.Octet);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
|
@ -15,6 +15,7 @@ namespace Microsoft.AspNetCore.Blazor.Server
|
|||
public string DistPath
|
||||
=> Path.Combine(Path.GetDirectoryName(SourceOutputAssemblyPath), "dist");
|
||||
public bool EnableAutoRebuilding { get; }
|
||||
public bool EnableDebugging { get; }
|
||||
|
||||
public static BlazorConfig Read(string assemblyPath)
|
||||
=> new BlazorConfig(assemblyPath);
|
||||
|
|
@ -44,6 +45,7 @@ namespace Microsoft.AspNetCore.Blazor.Server
|
|||
}
|
||||
|
||||
EnableAutoRebuilding = configLines.Contains("autorebuild:true", StringComparer.Ordinal);
|
||||
EnableDebugging = configLines.Contains("debug:true", StringComparer.Ordinal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,178 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Newtonsoft.Json;
|
||||
using WsProxy;
|
||||
|
||||
namespace Microsoft.AspNetCore.Builder
|
||||
{
|
||||
internal static class MonoDebugProxyAppBuilderExtensions
|
||||
{
|
||||
public static void UseMonoDebugProxy(this IApplicationBuilder app)
|
||||
{
|
||||
app.UseWebSockets();
|
||||
|
||||
app.Use((context, next) =>
|
||||
{
|
||||
var requestPath = context.Request.Path;
|
||||
if (!requestPath.StartsWithSegments("/_framework/debug"))
|
||||
{
|
||||
return next();
|
||||
}
|
||||
|
||||
if (requestPath.Equals("/_framework/debug/ws-proxy", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return DebugWebSocketProxyRequest(context);
|
||||
}
|
||||
|
||||
if (requestPath.Equals("/_framework/debug", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return DebugHome(context);
|
||||
}
|
||||
|
||||
context.Response.StatusCode = (int)HttpStatusCode.NotFound;
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
}
|
||||
|
||||
private static async Task DebugWebSocketProxyRequest(HttpContext context)
|
||||
{
|
||||
if (!context.WebSockets.IsWebSocketRequest)
|
||||
{
|
||||
context.Response.StatusCode = 400;
|
||||
return;
|
||||
}
|
||||
|
||||
var browserUri = new Uri(context.Request.Query["browser"]);
|
||||
await new MonoProxy().Run(context, browserUri);
|
||||
}
|
||||
|
||||
private static async Task DebugHome(HttpContext context)
|
||||
{
|
||||
context.Response.ContentType = "text/html";
|
||||
|
||||
var request = context.Request;
|
||||
var appRootUrl = $"{request.Scheme}://{request.Host}{request.PathBase}/";
|
||||
var targetTabUrl = request.Query["url"];
|
||||
if (string.IsNullOrEmpty(targetTabUrl))
|
||||
{
|
||||
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
|
||||
await context.Response.WriteAsync("No value specified for 'url'");
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Allow overriding port (but not hostname, as we're connecting to the
|
||||
// local browser, not to the webserver serving the app)
|
||||
var debuggerHost = "http://localhost:9222";
|
||||
var debuggerTabsListUrl = $"{debuggerHost}/json";
|
||||
IEnumerable<BrowserTab> availableTabs;
|
||||
|
||||
try
|
||||
{
|
||||
availableTabs = await GetOpenedBrowserTabs(debuggerHost);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
await context.Response.WriteAsync($@"
|
||||
<h1>Unable to find debuggable browser tab</h1>
|
||||
<p>
|
||||
Could not get a list of browser tabs from <code>{debuggerTabsListUrl}</code>.
|
||||
Ensure Chrome is running with debugging enabled.
|
||||
</p>
|
||||
<h2>Resolution</h2>
|
||||
{GetLaunchChromeInstructions(appRootUrl)}
|
||||
<p>... then use that new tab for debugging.</p>
|
||||
<h2>Underlying exception:</h2>
|
||||
<pre>{ex}</pre>
|
||||
");
|
||||
return;
|
||||
}
|
||||
|
||||
var matchingTabs = availableTabs
|
||||
.Where(t => t.Url.Equals(targetTabUrl, StringComparison.Ordinal))
|
||||
.ToList();
|
||||
if (matchingTabs.Count == 0)
|
||||
{
|
||||
await context.Response.WriteAsync($@"
|
||||
<h1>Unable to find debuggable browser tab</h1>
|
||||
<p>
|
||||
The response from <code>{debuggerTabsListUrl}</code> does not include
|
||||
any entry for <code>{targetTabUrl}</code>.
|
||||
</p>");
|
||||
return;
|
||||
}
|
||||
else if (matchingTabs.Count > 1)
|
||||
{
|
||||
// TODO: Automatically disambiguate by adding a GUID to the page title
|
||||
// when you press the debugger hotkey, include it in the querystring passed
|
||||
// here, then remove it once the debugger connects.
|
||||
await context.Response.WriteAsync($@"
|
||||
<h1>Multiple matching tabs are open</h1>
|
||||
<p>
|
||||
There is more than one browser tab at <code>{targetTabUrl}</code>.
|
||||
Close the ones you do not wish to debug, then refresh this page.
|
||||
</p>");
|
||||
return;
|
||||
}
|
||||
|
||||
// Now we know uniquely which tab to debug, construct the URL to the debug
|
||||
// page and redirect there
|
||||
var tabToDebug = matchingTabs.Single();
|
||||
var underlyingV8Endpoint = tabToDebug.WebSocketDebuggerUrl;
|
||||
var proxyEndpoint = $"{request.Host}{request.PathBase}/_framework/debug/ws-proxy?browser={WebUtility.UrlEncode(underlyingV8Endpoint)}";
|
||||
var devToolsUrlAbsolute = new Uri(debuggerHost + tabToDebug.DevtoolsFrontendUrl);
|
||||
var devToolsUrlWithProxy = $"{devToolsUrlAbsolute.Scheme}://{devToolsUrlAbsolute.Authority}{devToolsUrlAbsolute.AbsolutePath}?ws={proxyEndpoint}";
|
||||
context.Response.Redirect(devToolsUrlWithProxy);
|
||||
}
|
||||
|
||||
private static string GetLaunchChromeInstructions(string appRootUrl)
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
return $@"<p>Close this browser, then press Win+R and enter the following:</p>
|
||||
<p><strong><code>""%programfiles(x86)%\Google\Chrome\Application\chrome.exe"" --remote-debugging-port=9222 {appRootUrl}</code></strong></p>";
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
return $@"<p>Close this browser, then in a terminal window execute the following:</p>
|
||||
<p><strong><code>google-chrome --remote-debugging-port=9222 {appRootUrl}</code></strong></p>";
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
{
|
||||
return $@"<p>Close this browser, then in a terminal window execute the following:</p>
|
||||
<p><strong><code>open /Applications/Google\ Chrome.app --remote-debugging-port=9222 {appRootUrl}</code></strong></p>";
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Unknown OS platform");
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<IEnumerable<BrowserTab>> GetOpenedBrowserTabs(string debuggerHost)
|
||||
{
|
||||
using (var httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(5) })
|
||||
{
|
||||
var jsonResponse = await httpClient.GetStringAsync($"{debuggerHost}/json");
|
||||
return JsonConvert.DeserializeObject<BrowserTab[]>(jsonResponse);
|
||||
}
|
||||
}
|
||||
|
||||
class BrowserTab
|
||||
{
|
||||
public string Type { get; set; }
|
||||
public string Url { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string DevtoolsFrontendUrl { get; set; }
|
||||
public string WebSocketDebuggerUrl { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -44,11 +44,12 @@ namespace WsProxy
|
|||
}
|
||||
}
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
/*
|
||||
public void Configure (IApplicationBuilder app, IHostingEnvironment env)
|
||||
{
|
||||
//loggerFactory.AddConsole();
|
||||
//loggerFactory.AddDebug();
|
||||
//app.UseDeveloperExceptionPage ();
|
||||
app.UseDeveloperExceptionPage ();
|
||||
|
||||
app.UseWebSockets (); app.UseRouter (router => {
|
||||
router.MapGet ("devtools/page/{pageId}", async context => {
|
||||
|
|
@ -66,5 +67,6 @@ namespace WsProxy
|
|||
});
|
||||
});
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -263,9 +263,9 @@ namespace WsProxy {
|
|||
Send (this.ide, o, token);
|
||||
}
|
||||
|
||||
public async Task Run (HttpContext context)
|
||||
public async Task Run (HttpContext context, Uri browserUri)
|
||||
{
|
||||
var browserUri = GetBrowserUri (context.Request.Path.ToString ());
|
||||
//var browserUri = GetBrowserUri (context.Request.Path.ToString ());
|
||||
Debug ("wsproxy start");
|
||||
using (this.ide = await context.WebSockets.AcceptWebSocketAsync ()) {
|
||||
Debug ("ide connected");
|
||||
|
|
|
|||
|
|
@ -58,15 +58,20 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
|||
uncalled implementation code from mscorlib.dll anyway.
|
||||
*/
|
||||
"Microsoft.AspNetCore.Blazor.Browser.dll",
|
||||
"Microsoft.AspNetCore.Blazor.Browser.pdb",
|
||||
"Microsoft.AspNetCore.Blazor.dll",
|
||||
"Microsoft.AspNetCore.Blazor.pdb",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions.dll",
|
||||
"Microsoft.Extensions.DependencyInjection.dll",
|
||||
"Microsoft.JSInterop.dll",
|
||||
"Microsoft.JSInterop.pdb",
|
||||
"Mono.Security.dll",
|
||||
"Mono.WebAssembly.Interop.dll",
|
||||
"Mono.WebAssembly.Interop.pdb",
|
||||
"mscorlib.dll",
|
||||
"netstandard.dll",
|
||||
"StandaloneApp.dll",
|
||||
"StandaloneApp.pdb",
|
||||
"System.dll",
|
||||
"System.Collections.Concurrent.dll",
|
||||
"System.Collections.dll",
|
||||
|
|
|
|||
Loading…
Reference in New Issue