Add timezone support (#20493)
This commit is contained in:
parent
468af978a3
commit
3fbb2f0cd9
|
|
@ -9,9 +9,9 @@
|
||||||
-->
|
-->
|
||||||
<Dependencies>
|
<Dependencies>
|
||||||
<ProductDependencies>
|
<ProductDependencies>
|
||||||
<Dependency Name="Microsoft.AspNetCore.Components.WebAssembly.Runtime" Version="3.2.0-preview5.20210.3">
|
<Dependency Name="Microsoft.AspNetCore.Components.WebAssembly.Runtime" Version="3.2.0-preview5.20213.2">
|
||||||
<Uri>https://github.com/dotnet/blazor</Uri>
|
<Uri>https://github.com/dotnet/blazor</Uri>
|
||||||
<Sha>f429c0dd1a5dc84a70c66091460747d52475874f</Sha>
|
<Sha>e3ae74171b19c8dbd70e94cf8ac3024a1125479f</Sha>
|
||||||
</Dependency>
|
</Dependency>
|
||||||
<Dependency Name="System.Net.Http.Json" Version="3.2.0-preview5.20210.3">
|
<Dependency Name="System.Net.Http.Json" Version="3.2.0-preview5.20210.3">
|
||||||
<Uri>https://github.com/dotnet/corefx</Uri>
|
<Uri>https://github.com/dotnet/corefx</Uri>
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,7 @@
|
||||||
<!-- Only listed explicitly to workaround https://github.com/dotnet/cli/issues/10528 -->
|
<!-- Only listed explicitly to workaround https://github.com/dotnet/cli/issues/10528 -->
|
||||||
<MicrosoftNETCorePlatformsPackageVersion>3.1.0</MicrosoftNETCorePlatformsPackageVersion>
|
<MicrosoftNETCorePlatformsPackageVersion>3.1.0</MicrosoftNETCorePlatformsPackageVersion>
|
||||||
<!-- Packages from aspnet/Blazor -->
|
<!-- Packages from aspnet/Blazor -->
|
||||||
<MicrosoftAspNetCoreComponentsWebAssemblyRuntimePackageVersion>3.2.0-preview5.20210.3</MicrosoftAspNetCoreComponentsWebAssemblyRuntimePackageVersion>
|
<MicrosoftAspNetCoreComponentsWebAssemblyRuntimePackageVersion>3.2.0-preview5.20213.2</MicrosoftAspNetCoreComponentsWebAssemblyRuntimePackageVersion>
|
||||||
<!-- Packages from aspnet/Extensions -->
|
<!-- Packages from aspnet/Extensions -->
|
||||||
<InternalAspNetCoreAnalyzersPackageVersion>3.1.3-servicing.20128.2</InternalAspNetCoreAnalyzersPackageVersion>
|
<InternalAspNetCoreAnalyzersPackageVersion>3.1.3-servicing.20128.2</InternalAspNetCoreAnalyzersPackageVersion>
|
||||||
<MicrosoftAspNetCoreAnalyzerTestingPackageVersion>3.1.3-servicing.20128.2</MicrosoftAspNetCoreAnalyzerTestingPackageVersion>
|
<MicrosoftAspNetCoreAnalyzerTestingPackageVersion>3.1.3-servicing.20128.2</MicrosoftAspNetCoreAnalyzerTestingPackageVersion>
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,47 @@
|
||||||
|
const uint64HighPartShift = Math.pow(2, 32);
|
||||||
|
const maxSafeNumberHighPart = Math.pow(2, 21) - 1; // The high-order int32 from Number.MAX_SAFE_INTEGER
|
||||||
|
|
||||||
|
export function readInt32LE(buffer: Uint8Array, position: number): any {
|
||||||
|
return (buffer[position])
|
||||||
|
| (buffer[position + 1] << 8)
|
||||||
|
| (buffer[position + 2] << 16)
|
||||||
|
| (buffer[position + 3] << 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function readUint32LE(buffer: Uint8Array, position: number): any {
|
||||||
|
return (buffer[position])
|
||||||
|
+ (buffer[position + 1] << 8)
|
||||||
|
+ (buffer[position + 2] << 16)
|
||||||
|
+ ((buffer[position + 3] << 24) >>> 0); // The >>> 0 coerces the value to unsigned
|
||||||
|
}
|
||||||
|
|
||||||
|
export function readUint64LE(buffer: Uint8Array, position: number): any {
|
||||||
|
// This cannot be done using bit-shift operators in JavaScript, because
|
||||||
|
// those all implicitly convert to int32
|
||||||
|
const highPart = readUint32LE(buffer, position + 4);
|
||||||
|
if (highPart > maxSafeNumberHighPart) {
|
||||||
|
throw new Error(`Cannot read uint64 with high order part ${highPart}, because the result would exceed Number.MAX_SAFE_INTEGER.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (highPart * uint64HighPartShift) + readUint32LE(buffer, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function readLEB128(buffer: Uint8Array, position: number) {
|
||||||
|
let result = 0;
|
||||||
|
let shift = 0;
|
||||||
|
for (let index = 0; index < 4; index++) {
|
||||||
|
const byte = buffer[position + index];
|
||||||
|
result |= (byte & 127) << shift;
|
||||||
|
if (byte < 128) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
shift += 7;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function numLEB128Bytes(value: number) {
|
||||||
|
return value < 128 ? 1
|
||||||
|
: value < 16384 ? 2
|
||||||
|
: value < 2097152 ? 3 : 4;
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ import { attachDebuggerHotkey, hasDebuggingEnabled } from './MonoDebugger';
|
||||||
import { showErrorNotification } from '../../BootErrors';
|
import { showErrorNotification } from '../../BootErrors';
|
||||||
import { WebAssemblyResourceLoader, LoadingResource } from '../WebAssemblyResourceLoader';
|
import { WebAssemblyResourceLoader, LoadingResource } from '../WebAssemblyResourceLoader';
|
||||||
import { Platform, System_Array, Pointer, System_Object, System_String } from '../Platform';
|
import { Platform, System_Array, Pointer, System_Object, System_String } from '../Platform';
|
||||||
|
import { loadTimezoneData } from './TimezoneDataFile';
|
||||||
|
|
||||||
let mono_string_get_utf8: (managedString: System_String) => Pointer;
|
let mono_string_get_utf8: (managedString: System_String) => Pointer;
|
||||||
let mono_wasm_add_assembly: (name: string, heapAddress: number, length: number) => void;
|
let mono_wasm_add_assembly: (name: string, heapAddress: number, length: number) => void;
|
||||||
|
|
@ -161,7 +162,7 @@ function addGlobalModuleScriptTagsToDocument(callback: () => void) {
|
||||||
|
|
||||||
function createEmscriptenModuleInstance(resourceLoader: WebAssemblyResourceLoader, onReady: () => void, onError: (reason?: any) => void) {
|
function createEmscriptenModuleInstance(resourceLoader: WebAssemblyResourceLoader, onReady: () => void, onError: (reason?: any) => void) {
|
||||||
const resources = resourceLoader.bootConfig.resources;
|
const resources = resourceLoader.bootConfig.resources;
|
||||||
const module = {} as typeof Module;
|
const module = (window['Module'] || { }) as typeof Module;
|
||||||
const suppressMessages = ['DEBUGGING ENABLED'];
|
const suppressMessages = ['DEBUGGING ENABLED'];
|
||||||
|
|
||||||
module.print = line => (suppressMessages.indexOf(line) < 0 && console.log(line));
|
module.print = line => (suppressMessages.indexOf(line) < 0 && console.log(line));
|
||||||
|
|
@ -170,8 +171,8 @@ function createEmscriptenModuleInstance(resourceLoader: WebAssemblyResourceLoade
|
||||||
console.error(line);
|
console.error(line);
|
||||||
showErrorNotification();
|
showErrorNotification();
|
||||||
};
|
};
|
||||||
module.preRun = [];
|
module.preRun = module.preRun || [];
|
||||||
module.postRun = [];
|
module.postRun = module.postRun || [];
|
||||||
(module as any).preloadPlugins = [];
|
(module as any).preloadPlugins = [];
|
||||||
|
|
||||||
// Begin loading the .dll/.pdb/.wasm files, but don't block here. Let other loading processes run in parallel.
|
// Begin loading the .dll/.pdb/.wasm files, but don't block here. Let other loading processes run in parallel.
|
||||||
|
|
@ -183,6 +184,15 @@ function createEmscriptenModuleInstance(resourceLoader: WebAssemblyResourceLoade
|
||||||
/* url */ `_framework/wasm/${dotnetWasmResourceName}`,
|
/* url */ `_framework/wasm/${dotnetWasmResourceName}`,
|
||||||
/* hash */ resourceLoader.bootConfig.resources.runtime[dotnetWasmResourceName]);
|
/* hash */ resourceLoader.bootConfig.resources.runtime[dotnetWasmResourceName]);
|
||||||
|
|
||||||
|
const dotnetTimeZoneResourceName = 'dotnet.timezones.dat';
|
||||||
|
let timeZoneResource: LoadingResource | undefined;
|
||||||
|
if (resourceLoader.bootConfig.resources.runtime.hasOwnProperty(dotnetTimeZoneResourceName)) {
|
||||||
|
timeZoneResource = resourceLoader.loadResource(
|
||||||
|
dotnetTimeZoneResourceName,
|
||||||
|
`_framework/wasm/${dotnetTimeZoneResourceName}`,
|
||||||
|
resourceLoader.bootConfig.resources.runtime[dotnetTimeZoneResourceName]);
|
||||||
|
}
|
||||||
|
|
||||||
// Override the mechanism for fetching the main wasm file so we can connect it to our cache
|
// Override the mechanism for fetching the main wasm file so we can connect it to our cache
|
||||||
module.instantiateWasm = (imports, successCallback): WebAssembly.Exports => {
|
module.instantiateWasm = (imports, successCallback): WebAssembly.Exports => {
|
||||||
(async () => {
|
(async () => {
|
||||||
|
|
@ -206,6 +216,10 @@ function createEmscriptenModuleInstance(resourceLoader: WebAssemblyResourceLoade
|
||||||
|
|
||||||
MONO.loaded_files = [];
|
MONO.loaded_files = [];
|
||||||
|
|
||||||
|
if (timeZoneResource) {
|
||||||
|
loadTimezone(timeZoneResource);
|
||||||
|
}
|
||||||
|
|
||||||
// Fetch the assemblies and PDBs in the background, telling Mono to wait until they are loaded
|
// Fetch the assemblies and PDBs in the background, telling Mono to wait until they are loaded
|
||||||
// Mono requires the assembly filenames to have a '.dll' extension, so supply such names regardless
|
// Mono requires the assembly filenames to have a '.dll' extension, so supply such names regardless
|
||||||
// of the extensions in the URLs. This allows loading assemblies with arbitrary filenames.
|
// of the extensions in the URLs. This allows loading assemblies with arbitrary filenames.
|
||||||
|
|
@ -339,6 +353,17 @@ function attachInteropInvoker(): void {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadTimezone(timeZoneResource: LoadingResource) : Promise<void> {
|
||||||
|
const runDependencyId = `blazor:timezonedata`;
|
||||||
|
Module.addRunDependency(runDependencyId);
|
||||||
|
|
||||||
|
const request = await timeZoneResource.response;
|
||||||
|
const arrayBuffer = await request.arrayBuffer();
|
||||||
|
loadTimezoneData(arrayBuffer)
|
||||||
|
|
||||||
|
Module.removeRunDependency(runDependencyId);
|
||||||
|
}
|
||||||
|
|
||||||
async function compileWasmModule(wasmResource: LoadingResource, imports: any): Promise<WebAssembly.Instance> {
|
async function compileWasmModule(wasmResource: LoadingResource, imports: any): Promise<WebAssembly.Instance> {
|
||||||
// This is the same logic as used in emscripten's generated js. We can't use emscripten's js because
|
// This is the same logic as used in emscripten's generated js. We can't use emscripten's js because
|
||||||
// it doesn't provide any method for supplying a custom response provider, and we want to integrate
|
// it doesn't provide any method for supplying a custom response provider, and we want to integrate
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
import { readInt32LE } from "../../BinaryDecoder";
|
||||||
|
import { decodeUtf8 } from "../../Utf8Decoder";
|
||||||
|
|
||||||
|
export async function loadTimezoneData(arrayBuffer: ArrayBuffer) : Promise<void> {
|
||||||
|
let remainingData = new Uint8Array(arrayBuffer);
|
||||||
|
|
||||||
|
// The timezone file is generated by https://github.com/dotnet/blazor/tree/master/src/TimeZoneData.
|
||||||
|
// The file format of the TZ file look like so
|
||||||
|
//
|
||||||
|
// [4 - byte length of manifest]
|
||||||
|
// [json manifest]
|
||||||
|
// [data bytes]
|
||||||
|
//
|
||||||
|
// The json manifest is an array that looks like so:
|
||||||
|
//
|
||||||
|
// [...["America/Fort_Nelson",2249],["America/Glace_Bay",2206]..]
|
||||||
|
//
|
||||||
|
// where the first token in each array is the relative path of the file on disk, and the second is the
|
||||||
|
// length of the file. The starting offset of a file can be calculated using the lengths of all files
|
||||||
|
// that appear prior to it.
|
||||||
|
const manifestSize = readInt32LE(remainingData, 0);
|
||||||
|
remainingData = remainingData.slice(4);
|
||||||
|
const manifestContent = decodeUtf8(remainingData.slice(0, manifestSize));
|
||||||
|
const manifest = JSON.parse(manifestContent) as ManifestEntry[];
|
||||||
|
remainingData = remainingData.slice(manifestSize);
|
||||||
|
|
||||||
|
// Create the folder structure
|
||||||
|
// /zoneinfo
|
||||||
|
// /zoneinfo/Africa
|
||||||
|
// /zoneinfo/Asia
|
||||||
|
// ..
|
||||||
|
Module['FS_createPath']('/', 'zoneinfo', true, true);
|
||||||
|
new Set(manifest.map(m => m[0].split('/')![0])).forEach(folder =>
|
||||||
|
Module['FS_createPath']('/zoneinfo', folder, true, true));
|
||||||
|
|
||||||
|
for (const [name, length] of manifest) {
|
||||||
|
const bytes = new Uint8Array(remainingData.slice(0, length));
|
||||||
|
Module['FS_createDataFile'](`/zoneinfo/${name}`, null, bytes, true, true, true);
|
||||||
|
remainingData = remainingData.slice(length);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type ManifestEntry = [string, number];
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { RenderBatch, ArrayRange, RenderTreeDiff, ArrayValues, RenderTreeEdit, EditType, FrameType, RenderTreeFrame, RenderTreeDiffReader, RenderTreeFrameReader, RenderTreeEditReader, ArrayRangeReader, ArrayBuilderSegmentReader, ArrayBuilderSegment } from './RenderBatch';
|
import { RenderBatch, ArrayRange, RenderTreeDiff, ArrayValues, RenderTreeEdit, EditType, FrameType, RenderTreeFrame, RenderTreeDiffReader, RenderTreeFrameReader, RenderTreeEditReader, ArrayRangeReader, ArrayBuilderSegmentReader, ArrayBuilderSegment } from './RenderBatch';
|
||||||
import { decodeUtf8 } from './Utf8Decoder';
|
import { decodeUtf8 } from '../../Utf8Decoder';
|
||||||
|
import { readInt32LE, readUint64LE, readLEB128, numLEB128Bytes } from '../../BinaryDecoder';
|
||||||
|
|
||||||
const updatedComponentsEntryLength = 4; // Each is a single int32 giving the location of the data
|
const updatedComponentsEntryLength = 4; // Each is a single int32 giving the location of the data
|
||||||
const referenceFramesEntryLength = 20; // 1 int for frame type, then 16 bytes for type-specific data
|
const referenceFramesEntryLength = 20; // 1 int for frame type, then 16 bytes for type-specific data
|
||||||
|
|
@ -7,8 +8,6 @@ const disposedComponentIdsEntryLength = 4; // Each is an int32 giving the ID
|
||||||
const disposedEventHandlerIdsEntryLength = 8; // Each is an int64 giving the ID
|
const disposedEventHandlerIdsEntryLength = 8; // Each is an int64 giving the ID
|
||||||
const editsEntryLength = 16; // 4 ints
|
const editsEntryLength = 16; // 4 ints
|
||||||
const stringTableEntryLength = 4; // Each is an int32 giving the string data location, or -1 for null
|
const stringTableEntryLength = 4; // Each is an int32 giving the string data location, or -1 for null
|
||||||
const uint64HighPartShift = Math.pow(2, 32);
|
|
||||||
const maxSafeNumberHighPart = Math.pow(2, 21) - 1; // The high-order int32 from Number.MAX_SAFE_INTEGER
|
|
||||||
|
|
||||||
export class OutOfProcessRenderBatch implements RenderBatch {
|
export class OutOfProcessRenderBatch implements RenderBatch {
|
||||||
constructor(private batchData: Uint8Array) {
|
constructor(private batchData: Uint8Array) {
|
||||||
|
|
@ -230,47 +229,4 @@ class OutOfProcessArrayBuilderSegmentReader implements ArrayBuilderSegmentReader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function readInt32LE(buffer: Uint8Array, position: number): any {
|
|
||||||
return (buffer[position])
|
|
||||||
| (buffer[position + 1] << 8)
|
|
||||||
| (buffer[position + 2] << 16)
|
|
||||||
| (buffer[position + 3] << 24);
|
|
||||||
}
|
|
||||||
|
|
||||||
function readUint32LE(buffer: Uint8Array, position: number): any {
|
|
||||||
return (buffer[position])
|
|
||||||
+ (buffer[position + 1] << 8)
|
|
||||||
+ (buffer[position + 2] << 16)
|
|
||||||
+ ((buffer[position + 3] << 24) >>> 0); // The >>> 0 coerces the value to unsigned
|
|
||||||
}
|
|
||||||
|
|
||||||
function readUint64LE(buffer: Uint8Array, position: number): any {
|
|
||||||
// This cannot be done using bit-shift operators in JavaScript, because
|
|
||||||
// those all implicitly convert to int32
|
|
||||||
const highPart = readUint32LE(buffer, position + 4);
|
|
||||||
if (highPart > maxSafeNumberHighPart) {
|
|
||||||
throw new Error(`Cannot read uint64 with high order part ${highPart}, because the result would exceed Number.MAX_SAFE_INTEGER.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (highPart * uint64HighPartShift) + readUint32LE(buffer, position);
|
|
||||||
}
|
|
||||||
|
|
||||||
function readLEB128(buffer: Uint8Array, position: number) {
|
|
||||||
let result = 0;
|
|
||||||
let shift = 0;
|
|
||||||
for (let index = 0; index < 4; index++) {
|
|
||||||
const byte = buffer[position + index];
|
|
||||||
result |= (byte & 127) << shift;
|
|
||||||
if (byte < 128) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
shift += 7;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function numLEB128Bytes(value: number) {
|
|
||||||
return value < 128 ? 1
|
|
||||||
: value < 16384 ? 2
|
|
||||||
: value < 2097152 ? 3 : 4;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,11 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<_BlazorJSFile Include="$(_BlazorJSPath)" />
|
<_BlazorJSFile Include="$(_BlazorJSPath)" />
|
||||||
<_BlazorJSFile Include="$(_BlazorJSMapPath)" Condition="Exists('$(_BlazorJSMapPath)')" />
|
<_BlazorJSFile Include="$(_BlazorJSMapPath)" Condition="Exists('$(_BlazorJSMapPath)')" />
|
||||||
<_DotNetWasmRuntimeFile Include="$(ComponentsWebAssemblyRuntimePath)*" />
|
|
||||||
|
<_DotNetWasmRuntimeFile Include="$(ComponentsWebAssemblyRuntimePath)*"/>
|
||||||
|
<_DotNetWasmRuntimeFile
|
||||||
|
Remove="%(Identity)"
|
||||||
|
Condition="'$(BlazorEnableTimeZoneSupport)' == 'false' AND '%(FileName)%(Extension)' == 'dotnet.timezones.dat'" />
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
ReferenceCopyLocalPaths includes all files that are part of the build out with CopyLocalLockFileAssemblies on.
|
ReferenceCopyLocalPaths includes all files that are part of the build out with CopyLocalLockFileAssemblies on.
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,14 @@
|
||||||
<configuration>
|
<configuration>
|
||||||
<system.webServer>
|
<system.webServer>
|
||||||
<staticContent>
|
<staticContent>
|
||||||
|
<remove fileExtension=".dat" />
|
||||||
<remove fileExtension=".dll" />
|
<remove fileExtension=".dll" />
|
||||||
<remove fileExtension=".json" />
|
<remove fileExtension=".json" />
|
||||||
<remove fileExtension=".wasm" />
|
<remove fileExtension=".wasm" />
|
||||||
<remove fileExtension=".woff" />
|
<remove fileExtension=".woff" />
|
||||||
<remove fileExtension=".woff2" />
|
<remove fileExtension=".woff2" />
|
||||||
<mimeMap fileExtension=".dll" mimeType="application/octet-stream" />
|
<mimeMap fileExtension=".dll" mimeType="application/octet-stream" />
|
||||||
|
<mimeMap fileExtension=".dat" mimeType="application/octet-stream" />
|
||||||
<mimeMap fileExtension=".json" mimeType="application/json" />
|
<mimeMap fileExtension=".json" mimeType="application/json" />
|
||||||
<mimeMap fileExtension=".wasm" mimeType="application/wasm" />
|
<mimeMap fileExtension=".wasm" mimeType="application/wasm" />
|
||||||
<mimeMap fileExtension=".woff" mimeType="application/font-woff" />
|
<mimeMap fileExtension=".woff" mimeType="application/font-woff" />
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Build
|
||||||
|
|
||||||
var buildOutputDirectory = project.BuildOutputDirectory;
|
var buildOutputDirectory = project.BuildOutputDirectory;
|
||||||
|
|
||||||
var extensions = new[] { ".dll", ".js", ".pdb", ".wasm", ".map", ".json" };
|
var extensions = new[] { ".dll", ".js", ".pdb", ".wasm", ".map", ".json", ".dat" };
|
||||||
// Act
|
// Act
|
||||||
var compressedFilesPath = Path.Combine(
|
var compressedFilesPath = Path.Combine(
|
||||||
project.DirectoryPath,
|
project.DirectoryPath,
|
||||||
|
|
@ -218,7 +218,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Build
|
||||||
|
|
||||||
var buildOutputDirectory = project.BuildOutputDirectory;
|
var buildOutputDirectory = project.BuildOutputDirectory;
|
||||||
|
|
||||||
var extensions = new[] { ".dll", ".js", ".pdb", ".wasm", ".map", ".json" };
|
var extensions = new[] { ".dll", ".js", ".pdb", ".wasm", ".map", ".json", ".dat" };
|
||||||
// Act
|
// Act
|
||||||
var compressedFilesPath = Path.Combine(
|
var compressedFilesPath = Path.Combine(
|
||||||
project.DirectoryPath,
|
project.DirectoryPath,
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Build
|
||||||
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "blazor.webassembly.js");
|
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "blazor.webassembly.js");
|
||||||
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "wasm", "dotnet.wasm");
|
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "wasm", "dotnet.wasm");
|
||||||
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "wasm", DotNetJsFileName);
|
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "wasm", DotNetJsFileName);
|
||||||
|
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "wasm", "dotnet.timezones.dat");
|
||||||
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "standalone.dll");
|
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "standalone.dll");
|
||||||
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "RazorClassLibrary.dll");
|
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "RazorClassLibrary.dll");
|
||||||
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output.
|
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output.
|
||||||
|
|
@ -53,6 +54,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Build
|
||||||
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "blazor.webassembly.js");
|
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "blazor.webassembly.js");
|
||||||
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "wasm", "dotnet.wasm");
|
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "wasm", "dotnet.wasm");
|
||||||
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "wasm", DotNetJsFileName);
|
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "wasm", DotNetJsFileName);
|
||||||
|
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "wasm", "dotnet.timezones.dat");
|
||||||
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "standalone.dll");
|
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "standalone.dll");
|
||||||
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "RazorClassLibrary.dll");
|
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "RazorClassLibrary.dll");
|
||||||
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output.
|
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output.
|
||||||
|
|
@ -81,6 +83,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Build
|
||||||
var runtime = bootJsonData.resources.runtime.Keys;
|
var runtime = bootJsonData.resources.runtime.Keys;
|
||||||
Assert.Contains(DotNetJsFileName, runtime);
|
Assert.Contains(DotNetJsFileName, runtime);
|
||||||
Assert.Contains("dotnet.wasm", runtime);
|
Assert.Contains("dotnet.wasm", runtime);
|
||||||
|
Assert.Contains("dotnet.timezones.dat", runtime);
|
||||||
|
|
||||||
var assemblies = bootJsonData.resources.assembly.Keys;
|
var assemblies = bootJsonData.resources.assembly.Keys;
|
||||||
Assert.Contains("standalone.dll", assemblies);
|
Assert.Contains("standalone.dll", assemblies);
|
||||||
|
|
@ -112,6 +115,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Build
|
||||||
var runtime = bootJsonData.resources.runtime.Keys;
|
var runtime = bootJsonData.resources.runtime.Keys;
|
||||||
Assert.Contains(DotNetJsFileName, runtime);
|
Assert.Contains(DotNetJsFileName, runtime);
|
||||||
Assert.Contains("dotnet.wasm", runtime);
|
Assert.Contains("dotnet.wasm", runtime);
|
||||||
|
Assert.Contains("dotnet.timezones.dat", runtime);
|
||||||
|
|
||||||
var assemblies = bootJsonData.resources.assembly.Keys;
|
var assemblies = bootJsonData.resources.assembly.Keys;
|
||||||
Assert.Contains("standalone.dll", assemblies);
|
Assert.Contains("standalone.dll", assemblies);
|
||||||
|
|
@ -122,6 +126,35 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Build
|
||||||
Assert.Null(bootJsonData.resources.satelliteResources);
|
Assert.Null(bootJsonData.resources.satelliteResources);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Build_WithBlazorEnableTimeZoneSupportDisabled_DoesNotCopyTimeZoneInfo()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary" });
|
||||||
|
project.Configuration = "Release";
|
||||||
|
project.AddProjectFileContent(
|
||||||
|
@"
|
||||||
|
<PropertyGroup>
|
||||||
|
<BlazorEnableTimeZoneSupport>false</BlazorEnableTimeZoneSupport>
|
||||||
|
</PropertyGroup>");
|
||||||
|
|
||||||
|
var result = await MSBuildProcessManager.DotnetMSBuild(project);
|
||||||
|
|
||||||
|
Assert.BuildPassed(result);
|
||||||
|
|
||||||
|
var buildOutputDirectory = project.BuildOutputDirectory;
|
||||||
|
|
||||||
|
var bootJsonPath = Path.Combine(buildOutputDirectory, "wwwroot", "_framework", "blazor.boot.json");
|
||||||
|
var bootJsonData = ReadBootJsonData(result, bootJsonPath);
|
||||||
|
|
||||||
|
var runtime = bootJsonData.resources.runtime.Keys;
|
||||||
|
Assert.Contains("dotnet.wasm", runtime);
|
||||||
|
Assert.DoesNotContain("dotnet.timezones.dat", runtime);
|
||||||
|
|
||||||
|
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "wasm", "dotnet.wasm");
|
||||||
|
Assert.FileDoesNotExist(result, buildOutputDirectory, "wwwroot", "_framework", "wasm", "dotnet.timezones.dat");
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Build_Hosted_Works()
|
public async Task Build_Hosted_Works()
|
||||||
{
|
{
|
||||||
|
|
@ -163,6 +196,8 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Build
|
||||||
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "blazor.webassembly.js");
|
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "blazor.webassembly.js");
|
||||||
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "wasm", "dotnet.wasm");
|
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "wasm", "dotnet.wasm");
|
||||||
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "wasm", DotNetJsFileName);
|
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "wasm", DotNetJsFileName);
|
||||||
|
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "wasm", "dotnet.timezones.dat");
|
||||||
|
|
||||||
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "standalone.dll");
|
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "standalone.dll");
|
||||||
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output.
|
Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output.
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,7 @@ namespace Microsoft.AspNetCore.Builder
|
||||||
// release builds unless BlazorEnableDebugging is explicitly set to true.
|
// release builds unless BlazorEnableDebugging is explicitly set to true.
|
||||||
AddMapping(contentTypeProvider, ".pdb", MediaTypeNames.Application.Octet);
|
AddMapping(contentTypeProvider, ".pdb", MediaTypeNames.Application.Octet);
|
||||||
AddMapping(contentTypeProvider, ".br", MediaTypeNames.Application.Octet);
|
AddMapping(contentTypeProvider, ".br", MediaTypeNames.Application.Octet);
|
||||||
|
AddMapping(contentTypeProvider, ".dat", MediaTypeNames.Application.Octet);
|
||||||
|
|
||||||
options.ContentTypeProvider = contentTypeProvider;
|
options.ContentTypeProvider = contentTypeProvider;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,6 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override TimeSpan UtcOffset => TimeZoneInfo.Local.BaseUtcOffset;
|
|
||||||
|
|
||||||
protected override void InitializeAsyncCore()
|
protected override void InitializeAsyncCore()
|
||||||
{
|
{
|
||||||
Navigate(ServerPathBase);
|
Navigate(ServerPathBase);
|
||||||
|
|
|
||||||
|
|
@ -22,13 +22,6 @@ namespace Microsoft.AspNetCore.Components.E2ETests.Tests
|
||||||
|
|
||||||
protected abstract void SetCulture(string culture);
|
protected abstract void SetCulture(string culture);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Blazor Server and these tests will use the application's UtcOffset when calculating DateTimeOffset when
|
|
||||||
/// an offset is not explicitly specified. Blazor WASM always calculates DateTimeOffsets as Utc.
|
|
||||||
/// We'll use <see cref="UtcOffset"/> to express this difference in calculating expected values in these tests.
|
|
||||||
/// </summary>
|
|
||||||
protected abstract TimeSpan UtcOffset { get; }
|
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("en-US")]
|
[InlineData("en-US")]
|
||||||
[InlineData("fr-FR")]
|
[InlineData("fr-FR")]
|
||||||
|
|
@ -69,11 +62,11 @@ namespace Microsoft.AspNetCore.Components.E2ETests.Tests
|
||||||
// datetimeoffset
|
// datetimeoffset
|
||||||
input = Browser.FindElement(By.Id("input_type_text_datetimeoffset"));
|
input = Browser.FindElement(By.Id("input_type_text_datetimeoffset"));
|
||||||
display = Browser.FindElement(By.Id("input_type_text_datetimeoffset_value"));
|
display = Browser.FindElement(By.Id("input_type_text_datetimeoffset_value"));
|
||||||
Browser.Equal(new DateTimeOffset(new DateTime(1985, 3, 4), UtcOffset).ToString(cultureInfo), () => display.Text);
|
Browser.Equal(new DateTimeOffset(new DateTime(1985, 3, 4)).ToString(cultureInfo), () => display.Text);
|
||||||
|
|
||||||
input.ReplaceText(new DateTimeOffset(new DateTime(2000, 1, 2), UtcOffset).ToString(cultureInfo));
|
input.ReplaceText(new DateTimeOffset(new DateTime(2000, 1, 2)).ToString(cultureInfo));
|
||||||
input.SendKeys("\t");
|
input.SendKeys("\t");
|
||||||
Browser.Equal(new DateTimeOffset(new DateTime(2000, 1, 2), UtcOffset).ToString(cultureInfo), () => display.Text);
|
Browser.Equal(new DateTimeOffset(new DateTime(2000, 1, 2)).ToString(cultureInfo), () => display.Text);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The logic is different for verifying culture-invariant fields. The problem is that the logic for what
|
// The logic is different for verifying culture-invariant fields. The problem is that the logic for what
|
||||||
|
|
@ -140,13 +133,13 @@ namespace Microsoft.AspNetCore.Components.E2ETests.Tests
|
||||||
input = Browser.FindElement(By.Id("input_type_date_datetimeoffset"));
|
input = Browser.FindElement(By.Id("input_type_date_datetimeoffset"));
|
||||||
display = Browser.FindElement(By.Id("input_type_date_datetimeoffset_value"));
|
display = Browser.FindElement(By.Id("input_type_date_datetimeoffset_value"));
|
||||||
extraInput = Browser.FindElement(By.Id("input_type_date_datetimeoffset_extrainput"));
|
extraInput = Browser.FindElement(By.Id("input_type_date_datetimeoffset_extrainput"));
|
||||||
Browser.Equal(new DateTimeOffset(new DateTime(1985, 3, 4), UtcOffset).ToString(cultureInfo), () => display.Text);
|
Browser.Equal(new DateTimeOffset(new DateTime(1985, 3, 4)).ToString(cultureInfo), () => display.Text);
|
||||||
Browser.Equal(new DateTimeOffset(new DateTime(1985, 3, 4), UtcOffset).ToString("yyyy-MM-dd", CultureInfo.InvariantCulture), () => input.GetAttribute("value"));
|
Browser.Equal(new DateTimeOffset(new DateTime(1985, 3, 4)).ToString("yyyy-MM-dd", CultureInfo.InvariantCulture), () => input.GetAttribute("value"));
|
||||||
|
|
||||||
extraInput.ReplaceText(new DateTimeOffset(new DateTime(2000, 1, 2), UtcOffset).ToString(cultureInfo));
|
extraInput.ReplaceText(new DateTimeOffset(new DateTime(2000, 1, 2)).ToString(cultureInfo));
|
||||||
extraInput.SendKeys("\t");
|
extraInput.SendKeys("\t");
|
||||||
Browser.Equal(new DateTimeOffset(new DateTime(2000, 1, 2), UtcOffset).ToString(cultureInfo), () => display.Text);
|
Browser.Equal(new DateTimeOffset(new DateTime(2000, 1, 2)).ToString(cultureInfo), () => display.Text);
|
||||||
Browser.Equal(new DateTimeOffset(new DateTime(2000, 1, 2), UtcOffset).ToString("yyyy-MM-dd", CultureInfo.InvariantCulture), () => input.GetAttribute("value"));
|
Browser.Equal(new DateTimeOffset(new DateTime(2000, 1, 2)).ToString("yyyy-MM-dd", CultureInfo.InvariantCulture), () => input.GetAttribute("value"));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
|
@ -209,13 +202,13 @@ namespace Microsoft.AspNetCore.Components.E2ETests.Tests
|
||||||
input = Browser.FindElement(By.Id("inputdate_datetimeoffset"));
|
input = Browser.FindElement(By.Id("inputdate_datetimeoffset"));
|
||||||
display = Browser.FindElement(By.Id("inputdate_datetimeoffset_value"));
|
display = Browser.FindElement(By.Id("inputdate_datetimeoffset_value"));
|
||||||
extraInput = Browser.FindElement(By.Id("inputdate_datetimeoffset_extrainput"));
|
extraInput = Browser.FindElement(By.Id("inputdate_datetimeoffset_extrainput"));
|
||||||
Browser.Equal(new DateTimeOffset(new DateTime(1985, 3, 4), UtcOffset).ToString(cultureInfo), () => display.Text);
|
Browser.Equal(new DateTimeOffset(new DateTime(1985, 3, 4)).ToString(cultureInfo), () => display.Text);
|
||||||
Browser.Equal(new DateTimeOffset(new DateTime(1985, 3, 4), UtcOffset).ToString("yyyy-MM-dd", CultureInfo.InvariantCulture), () => input.GetAttribute("value"));
|
Browser.Equal(new DateTimeOffset(new DateTime(1985, 3, 4)).ToString("yyyy-MM-dd", CultureInfo.InvariantCulture), () => input.GetAttribute("value"));
|
||||||
|
|
||||||
extraInput.ReplaceText(new DateTimeOffset(new DateTime(2000, 1, 2), UtcOffset).ToString(cultureInfo));
|
extraInput.ReplaceText(new DateTimeOffset(new DateTime(2000, 1, 2)).ToString(cultureInfo));
|
||||||
extraInput.SendKeys("\t");
|
extraInput.SendKeys("\t");
|
||||||
Browser.Equal(new DateTimeOffset(new DateTime(2000, 1, 2), UtcOffset).ToString(cultureInfo), () => display.Text);
|
Browser.Equal(new DateTimeOffset(new DateTime(2000, 1, 2)).ToString(cultureInfo), () => display.Text);
|
||||||
Browser.Equal(new DateTimeOffset(new DateTime(2000, 1, 2), UtcOffset).ToString("yyyy-MM-dd", CultureInfo.InvariantCulture), () => input.GetAttribute("value"));
|
Browser.Equal(new DateTimeOffset(new DateTime(2000, 1, 2)).ToString("yyyy-MM-dd", CultureInfo.InvariantCulture), () => input.GetAttribute("value"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,6 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override TimeSpan UtcOffset => TimeSpan.Zero;
|
|
||||||
|
|
||||||
protected override void SetCulture(string culture)
|
protected override void SetCulture(string culture)
|
||||||
{
|
{
|
||||||
Navigate($"{ServerPathBase}/?culture={culture}", noReload: false);
|
Navigate($"{ServerPathBase}/?culture={culture}", noReload: false);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue