Unverified Commit 2bbd0046 authored by Angjie Li's avatar Angjie Li Committed by GitHub

Support --web-renderer options which would allow user to specify which...

Support --web-renderer options which would allow user to specify which rendering backend to use. (#68848)
parent ed5d8718
......@@ -50,10 +50,14 @@ enum Artifact {
webPrecompiledSdkSourcemaps,
webPrecompiledCanvaskitSdk,
webPrecompiledCanvaskitSdkSourcemaps,
webPrecompiledCanvaskitAndHtmlSdk,
webPrecompiledCanvaskitAndHtmlSdkSourcemaps,
webPrecompiledSoundSdk,
webPrecompiledSoundSdkSourcemaps,
webPrecompiledCanvaskitSoundSdk,
webPrecompiledCanvaskitSoundSdkSourcemaps,
webPrecompiledCanvaskitAndHtmlSoundSdk,
webPrecompiledCanvaskitAndHtmlSoundSdkSourcemaps,
iosDeploy,
idevicesyslog,
......@@ -158,13 +162,17 @@ String _artifactToFileName(Artifact artifact, [ TargetPlatform platform, BuildMo
return 'libraries.json';
case Artifact.webPrecompiledSdk:
case Artifact.webPrecompiledCanvaskitSdk:
case Artifact.webPrecompiledCanvaskitAndHtmlSdk:
case Artifact.webPrecompiledSoundSdk:
case Artifact.webPrecompiledCanvaskitSoundSdk:
case Artifact.webPrecompiledCanvaskitAndHtmlSoundSdk:
return 'dart_sdk.js';
case Artifact.webPrecompiledSdkSourcemaps:
case Artifact.webPrecompiledCanvaskitSdkSourcemaps:
case Artifact.webPrecompiledCanvaskitAndHtmlSdkSourcemaps:
case Artifact.webPrecompiledSoundSdkSourcemaps:
case Artifact.webPrecompiledCanvaskitSoundSdkSourcemaps:
case Artifact.webPrecompiledCanvaskitAndHtmlSoundSdkSourcemaps:
return 'dart_sdk.js.map';
}
assert(false, 'Invalid artifact $artifact.');
......@@ -415,6 +423,10 @@ class CachedArtifacts implements Artifacts {
return _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit', _artifactToFileName(artifact, platform, mode));
case Artifact.webPrecompiledCanvaskitSdkSourcemaps:
return _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit', _artifactToFileName(artifact, platform, mode));
case Artifact.webPrecompiledCanvaskitAndHtmlSdk:
return _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit-html', _artifactToFileName(artifact, platform, mode));
case Artifact.webPrecompiledCanvaskitAndHtmlSdkSourcemaps:
return _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit-html', _artifactToFileName(artifact, platform, mode));
case Artifact.webPrecompiledSoundSdk:
return _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-sound', _artifactToFileName(artifact, platform, mode));
case Artifact.webPrecompiledSoundSdkSourcemaps:
......@@ -423,6 +435,10 @@ class CachedArtifacts implements Artifacts {
return _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit-sound', _artifactToFileName(artifact, platform, mode));
case Artifact.webPrecompiledCanvaskitSoundSdkSourcemaps:
return _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit-sound', _artifactToFileName(artifact, platform, mode));
case Artifact.webPrecompiledCanvaskitAndHtmlSoundSdk:
return _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit-html-sound', _artifactToFileName(artifact, platform, mode));
case Artifact.webPrecompiledCanvaskitAndHtmlSoundSdkSourcemaps:
return _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit-html-sound', _artifactToFileName(artifact, platform, mode));
default:
assert(false, 'Artifact $artifact not available for platform $platform.');
return null;
......@@ -610,6 +626,10 @@ class LocalEngineArtifacts implements Artifacts {
return _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit', artifactFileName);
case Artifact.webPrecompiledCanvaskitSdkSourcemaps:
return _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit', artifactFileName);
case Artifact.webPrecompiledCanvaskitAndHtmlSdk:
return _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit-html', artifactFileName);
case Artifact.webPrecompiledCanvaskitAndHtmlSdkSourcemaps:
return _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit-html', artifactFileName);
case Artifact.webPrecompiledSoundSdk:
return _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-sound', artifactFileName);
case Artifact.webPrecompiledSoundSdkSourcemaps:
......@@ -618,6 +638,10 @@ class LocalEngineArtifacts implements Artifacts {
return _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit-sound', artifactFileName);
case Artifact.webPrecompiledCanvaskitSoundSdkSourcemaps:
return _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit-sound', artifactFileName);
case Artifact.webPrecompiledCanvaskitAndHtmlSoundSdk:
return _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit-html-sound', artifactFileName);
case Artifact.webPrecompiledCanvaskitAndHtmlSoundSdkSourcemaps:
return _fileSystem.path.join(_getFlutterWebSdkPath(), 'kernel', 'amd-canvaskit-html-sound', artifactFileName);
}
assert(false, 'Invalid artifact $artifact.');
return null;
......
......@@ -23,6 +23,7 @@ class BuildWebCommand extends BuildSubCommand {
usesPubOption();
addBuildModeFlags(excludeDebug: true);
usesDartDefineOption();
usesWebRendererOption();
addEnableExperimentation(hide: !verboseHelp);
addNullSafetyModeOptions(hide: !verboseHelp);
argParser.addFlag('csp',
......@@ -88,7 +89,7 @@ class BuildWebCommand extends BuildSubCommand {
buildInfo,
boolArg('csp'),
stringArg('pwa-strategy'),
boolArg('source-maps')
boolArg('source-maps'),
);
return FlutterCommandResult.success();
}
......
......@@ -29,6 +29,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment
addBuildModeFlags(defaultToRelease: false, verboseHelp: verboseHelp);
usesDartDefineOption();
usesFlavorOption();
usesWebRendererOption();
argParser
..addFlag('trace-startup',
negatable: false,
......
......@@ -37,6 +37,16 @@ import '../project.dart';
import '../web/bootstrap.dart';
import '../web/chrome.dart';
/// Web rendering backend mode.
enum WebRendererMode {
/// Auto detects which rendering backend to use.
autoDetect,
/// Always uses canvaskit.
canvaskit,
/// Always uses html.
html,
}
typedef DwdsLauncher = Future<Dwds> Function(
{@required AssetReader assetReader,
@required Stream<BuildResult> buildResults,
......@@ -528,8 +538,8 @@ class WebAssetServer implements AssetReader {
return modules;
}
/// Whether to use the canvaskit SDK for rendering.
bool canvasKitRendering = false;
/// Determines what rendering backed to use.
WebRendererMode webRenderer = WebRendererMode.html;
shelf.Response _serveIndex() {
final Map<String, String> headers = <String, String>{
......@@ -554,33 +564,9 @@ class WebAssetServer implements AssetReader {
// Return the actual file objects so that local engine changes are automatically picked up.
switch (path) {
case 'dart_sdk.js':
if (_buildInfo.nullSafetyMode == NullSafetyMode.unsound) {
return globals.fs.file(canvasKitRendering
? globals.artifacts
.getArtifactPath(Artifact.webPrecompiledCanvaskitSdk)
: globals.artifacts.getArtifactPath(Artifact.webPrecompiledSdk));
} else {
return globals.fs.file(canvasKitRendering
? globals.artifacts
.getArtifactPath(Artifact.webPrecompiledCanvaskitSoundSdk)
: globals.artifacts
.getArtifactPath(Artifact.webPrecompiledSoundSdk));
}
break;
return _resolveDartSdkJsFile;
case 'dart_sdk.js.map':
if (_buildInfo.nullSafetyMode == NullSafetyMode.unsound) {
return globals.fs.file(canvasKitRendering
? globals.artifacts.getArtifactPath(
Artifact.webPrecompiledCanvaskitSdkSourcemaps)
: globals.artifacts
.getArtifactPath(Artifact.webPrecompiledSdkSourcemaps));
} else {
return globals.fs.file(canvasKitRendering
? globals.artifacts.getArtifactPath(
Artifact.webPrecompiledCanvaskitSoundSdkSourcemaps)
: globals.artifacts
.getArtifactPath(Artifact.webPrecompiledSoundSdkSourcemaps));
}
return _resolveDartSdkJsMapFile;
}
// This is the special generated entrypoint.
if (path == 'web_entrypoint.dart') {
......@@ -631,6 +617,54 @@ class WebAssetServer implements AssetReader {
return webSdkFile;
}
static const Map<WebRendererMode, Map<NullSafetyMode, Artifact>> _dartSdkJsArtifactMap =
<WebRendererMode, Map<NullSafetyMode, Artifact>> {
WebRendererMode.autoDetect: <NullSafetyMode, Artifact> {
NullSafetyMode.sound: Artifact.webPrecompiledCanvaskitAndHtmlSoundSdk,
NullSafetyMode.unsound: Artifact.webPrecompiledCanvaskitAndHtmlSdk,
NullSafetyMode.autodetect: Artifact.webPrecompiledCanvaskitAndHtmlSoundSdk,
},
WebRendererMode.canvaskit: <NullSafetyMode, Artifact> {
NullSafetyMode.sound: Artifact.webPrecompiledCanvaskitSoundSdk,
NullSafetyMode.unsound: Artifact.webPrecompiledCanvaskitSdk,
NullSafetyMode.autodetect: Artifact.webPrecompiledCanvaskitSoundSdk,
},
WebRendererMode.html: <NullSafetyMode, Artifact> {
NullSafetyMode.sound: Artifact.webPrecompiledSoundSdk,
NullSafetyMode.unsound: Artifact.webPrecompiledSdk,
NullSafetyMode.autodetect: Artifact.webPrecompiledSoundSdk,
},
};
static const Map<WebRendererMode, Map<NullSafetyMode, Artifact>> _dartSdkJsMapArtifactMap =
<WebRendererMode, Map<NullSafetyMode, Artifact>> {
WebRendererMode.autoDetect: <NullSafetyMode, Artifact> {
NullSafetyMode.sound: Artifact.webPrecompiledCanvaskitAndHtmlSoundSdkSourcemaps,
NullSafetyMode.unsound: Artifact.webPrecompiledCanvaskitAndHtmlSdkSourcemaps,
NullSafetyMode.autodetect: Artifact.webPrecompiledCanvaskitAndHtmlSoundSdkSourcemaps,
},
WebRendererMode.canvaskit: <NullSafetyMode, Artifact> {
NullSafetyMode.sound: Artifact.webPrecompiledCanvaskitSoundSdkSourcemaps,
NullSafetyMode.unsound: Artifact.webPrecompiledCanvaskitSdkSourcemaps,
NullSafetyMode.autodetect: Artifact.webPrecompiledCanvaskitSoundSdkSourcemaps,
},
WebRendererMode.html: <NullSafetyMode, Artifact> {
NullSafetyMode.sound: Artifact.webPrecompiledSoundSdkSourcemaps,
NullSafetyMode.unsound: Artifact.webPrecompiledSdkSourcemaps,
NullSafetyMode.autodetect: Artifact.webPrecompiledSoundSdkSourcemaps,
},
};
File get _resolveDartSdkJsFile =>
globals.fs.file(globals.artifacts.getArtifactPath(
_dartSdkJsArtifactMap[webRenderer][_buildInfo.nullSafetyMode]
));
File get _resolveDartSdkJsMapFile =>
globals.fs.file(globals.artifacts.getArtifactPath(
_dartSdkJsMapArtifactMap[webRenderer][_buildInfo.nullSafetyMode]
));
@override
Future<String> dartSourceContents(String serverPath) async {
final File result = _resolveDartFile(serverPath);
......@@ -776,9 +810,11 @@ class WebDevFS implements DevFS {
expressionCompiler,
testMode: testMode,
);
if (buildInfo.dartDefines.contains('FLUTTER_WEB_USE_SKIA=true')) {
webAssetServer.canvasKitRendering = true;
}
if (buildInfo.dartDefines.contains('FLUTTER_WEB_AUTO_DETECT=true')) {
webAssetServer.webRenderer = WebRendererMode.autoDetect;
} else if (buildInfo.dartDefines.contains('FLUTTER_WEB_USE_SKIA=true')) {
webAssetServer.webRenderer = WebRendererMode.canvaskit;
}
if (hostname == 'any') {
_baseUri = Uri.http('localhost:$port', '');
} else {
......
......@@ -848,14 +848,6 @@ class _ResidentWebRunner extends ResidentWebRunner {
return 0;
}
@override
Future<bool> toggleCanvaskit() async {
final WebDevFS webDevFS = device.devFS as WebDevFS;
webDevFS.webAssetServer.canvasKitRendering = !webDevFS.webAssetServer.canvasKitRendering;
await _wipConnection?.sendCommand('Page.reload');
return webDevFS.webAssetServer.canvasKitRendering;
}
@override
Future<void> exitApp() async {
await device.exitApps();
......
......@@ -896,14 +896,6 @@ abstract class ResidentRunner {
globals.logger.printTrace('complete');
}
/// Toggle whether canvaskit is being used for rendering, returning the new
/// state.
///
/// Only supported on the web.
Future<bool> toggleCanvaskit() {
throw Exception('Canvaskit not supported by this runner.');
}
/// Write the SkSL shaders to a zip file in build directory.
///
/// Returns the name of the file, or `null` on failures.
......
......@@ -129,6 +129,22 @@ abstract class FlutterCommand extends Command<void> {
/// The flag name for whether or not to use ipv6.
static const String ipv6Flag = 'ipv6';
/// The map used to convert web-renderer option to a List of dart-defines.
static const Map<String, Iterable<String>> _webRendererDartDefines =
<String, Iterable<String>> {
'auto': <String>[
'FLUTTER_WEB_AUTO_DETECT=true',
],
'canvaskit': <String>[
'FLUTTER_WEB_AUTO_DETECT=false',
'FLUTTER_WEB_USE_SKIA=true'
],
'html': <String>[
'FLUTTER_WEB_AUTO_DETECT=false',
'FLUTTER_WEB_USE_SKIA=false'
],
};
@override
ArgParser get argParser => _argParser;
final ArgParser _argParser = ArgParser(
......@@ -425,6 +441,18 @@ abstract class FlutterCommand extends Command<void> {
);
}
void usesWebRendererOption() {
argParser.addOption('web-renderer',
defaultsTo: 'html',
allowed: <String>['auto', 'canvaskit', 'html'],
help: 'Which rendering backend to use for Flutter for Web.'
'auto - Use the HTML renderer on mobile devices,'
' and CanvasKit on desktop devices.'
'canvaskit - Always use the CanvasKit renderer.'
'html - Default. Always use the HTML renderer.',
);
}
void usesDeviceUserOption() {
argParser.addOption(FlutterOptions.kDeviceUser,
help: 'Identifier number for a user or work profile on Android only. Run "adb shell pm list users" for available identifiers.',
......@@ -810,6 +838,14 @@ abstract class FlutterCommand extends Command<void> {
? stringArg(FlutterOptions.kPerformanceMeasurementFile)
: null;
final List<String> dartDefines = argParser.options.containsKey(FlutterOptions.kDartDefinesOption)
? stringsArg(FlutterOptions.kDartDefinesOption)
: <String>[];
if (argParser.options.containsKey('web-renderer') && argResults.wasParsed('web-renderer')) {
_updateDartDefines(dartDefines, stringArg('web-renderer'));
}
return BuildInfo(buildMode,
argParser.options.containsKey('flavor')
? stringArg('flavor')
......@@ -834,9 +870,7 @@ abstract class FlutterCommand extends Command<void> {
treeShakeIcons: treeShakeIcons,
splitDebugInfoPath: splitDebugInfoPath,
dartObfuscation: dartObfuscation,
dartDefines: argParser.options.containsKey(FlutterOptions.kDartDefinesOption)
? stringsArg(FlutterOptions.kDartDefinesOption)
: const <String>[],
dartDefines: dartDefines,
bundleSkSLPath: bundleSkSLPath,
dartExperiments: experiments,
performanceMeasurementFile: performanceMeasurementFile,
......@@ -909,6 +943,15 @@ abstract class FlutterCommand extends Command<void> {
}
}
/// Updates dart-defines based on [webRenderer].
void _updateDartDefines(List<String> dartDefines, String webRenderer) {
if (dartDefines.any((String d) => d.startsWith('FLUTTER_WEB_USE_SKIA='))) {
throwToolExit('Only one of "--web-renderer" and '
'"--dart-defines=FLUTTER_WEB_USE_SKIA" may be specified.');
}
dartDefines.addAll(_webRendererDartDefines[webRenderer]);
}
void _registerSignalHandlers(String commandPath, DateTime startTime) {
final SignalHandler handler = (io.ProcessSignal s) {
Cache.releaseLock();
......
......@@ -1483,13 +1483,6 @@ void main() {
));
}));
testUsingContext('ResidentRunner does support CanvasKit', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
expect(residentRunner.toggleCanvaskit,
throwsA(isA<Exception>()));
}));
testUsingContext('ResidentRunner handles writeSkSL returning no data', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
......
......@@ -753,25 +753,6 @@ void main() {
Platform: () => FakePlatform(operatingSystem: 'linux', environment: <String, String>{}),
});
testUsingContext('web resident runner can toggle CanvasKit', () async {
final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice);
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
final WebAssetServer webAssetServer = WebAssetServer(null, null, null, null, null, null);
when(mockWebDevFS.webAssetServer).thenReturn(webAssetServer);
expect(webAssetServer.canvasKitRendering, false);
final bool toggleResult = await residentWebRunner.toggleCanvaskit();
expect(webAssetServer.canvasKitRendering, true);
expect(toggleResult, true);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
Pub: () => MockPub(),
Platform: () => FakePlatform(operatingSystem: 'linux', environment: <String, String>{}),
});
testUsingContext('Exits when initial compile fails', () async {
final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice);
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
......
......@@ -688,8 +688,8 @@ void main() {
expect(await webDevFS.webAssetServer.dartSourceContents('dart_sdk.js'), 'BELLOW');
// Toggle CanvasKit
expect(webDevFS.webAssetServer.canvasKitRendering, false);
webDevFS.webAssetServer.canvasKitRendering = true;
expect(webDevFS.webAssetServer.webRenderer, WebRendererMode.html);
webDevFS.webAssetServer.webRenderer = WebRendererMode.canvaskit;
expect(await webDevFS.webAssetServer.dartSourceContents('dart_sdk.js'), 'OL');
expect(await webDevFS.webAssetServer.dartSourceContents('dart_sdk.js.map'), 'CHUM');
......@@ -804,7 +804,7 @@ void main() {
expect(await webDevFS.webAssetServer.dartSourceContents('dart_sdk.js'), 'BELLOW');
// Toggle CanvasKit
webDevFS.webAssetServer.canvasKitRendering = true;
webDevFS.webAssetServer.webRenderer = WebRendererMode.canvaskit;
expect(await webDevFS.webAssetServer.dartSourceContents('dart_sdk.js'), 'OL');
expect(await webDevFS.webAssetServer.dartSourceContents('dart_sdk.js.map'), 'CHUM');
......@@ -909,7 +909,58 @@ void main() {
await webDevFS.create();
expect(webDevFS.webAssetServer.canvasKitRendering, true);
expect(webDevFS.webAssetServer.webRenderer, WebRendererMode.canvaskit);
await webDevFS.destroy();
}));
test('Can start web server with auto detect enabled', () => testbed.run(() async {
globals.fs.file('.packages').writeAsStringSync('\n');
final File outputFile = globals.fs.file(globals.fs.path.join('lib', 'main.dart'))
..createSync(recursive: true);
outputFile.parent.childFile('a.sources').writeAsStringSync('');
outputFile.parent.childFile('a.json').writeAsStringSync('{}');
outputFile.parent.childFile('a.map').writeAsStringSync('{}');
outputFile.parent.childFile('.packages').writeAsStringSync('\n');
final ResidentCompiler residentCompiler = MockResidentCompiler();
when(residentCompiler.recompile(
any,
any,
outputPath: anyNamed('outputPath'),
packageConfig: anyNamed('packageConfig'),
)).thenAnswer((Invocation invocation) async {
return const CompilerOutput('a', 0, <Uri>[]);
});
final WebDevFS webDevFS = WebDevFS(
hostname: 'localhost',
port: 0,
packagesFilePath: '.packages',
urlTunneller: null,
useSseForDebugProxy: true,
useSseForDebugBackend: true,
nullAssertions: true,
buildInfo: const BuildInfo(
BuildMode.debug,
'',
treeShakeIcons: false,
dartDefines: <String>[
'FLUTTER_WEB_AUTO_DETECT=true',
]
),
enableDwds: false,
entrypoint: Uri.base,
testMode: true,
expressionCompiler: null,
chromiumLauncher: null,
);
webDevFS.requireJS.createSync(recursive: true);
webDevFS.stackTraceMapper.createSync(recursive: true);
await webDevFS.create();
expect(webDevFS.webAssetServer.webRenderer, WebRendererMode.autoDetect);
await webDevFS.destroy();
}));
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment