[dart-sdk][webview] Initial CL adding Fuchsia webview support
Added a new 'fuchsia_webview_flutter' packages that contains the initial
scaffolding required to integrate fuchsia.web.* together with upstream
fulltter_webview plugin.
The only highlevel Webview API currently implemented is 'loadUrl'. The rest
of the APIs will be implemented in subsequent cls.
This package also contains an example Mod under:
fuchsia_webview_flutter/examples/
Change-Id: I9a8b8a63506e4a53117afefa749629620fd97771
diff --git a/packages/tests/BUILD.gn b/packages/tests/BUILD.gn
index 924f956..57f7ef0 100644
--- a/packages/tests/BUILD.gn
+++ b/packages/tests/BUILD.gn
@@ -28,6 +28,7 @@
"//topaz/public/dart/fuchsia_modular_flutter:fuchsia_modular_flutter_unittests($host_toolchain)",
"//topaz/public/dart/fuchsia_scenic_flutter:fuchsia_scenic_flutter_unittests($host_toolchain)",
"//topaz/public/dart/fuchsia_services:fuchsia_services_package_unittests($host_toolchain)",
+ "//topaz/public/dart/fuchsia_webview_flutter:fuchsia_webview_flutter_unittests($host_toolchain)",
"//topaz/public/dart/sledge:dart_sledge_tests($host_toolchain)",
"//topaz/public/dart/story_shell_labs:layout_unittests($host_toolchain)",
"//topaz/public/dart/widgets:dart_widget_tests($host_toolchain)",
@@ -58,6 +59,7 @@
testonly = true
public_deps = [
"//garnet/packages/tests:run_test_component",
+ "//topaz/bin/fidlgen_dart:fidlgen_dart_backend_ir_test($host_toolchain)",
"//topaz/packages/examples:tests",
"//topaz/packages/tests:bindings_tests",
"//topaz/packages/tests:crasher_dart",
@@ -67,8 +69,8 @@
"//topaz/packages/tests:dart_target_unittests",
"//topaz/packages/tests:dart_unittests",
"//topaz/packages/tests:fidl_changes",
- "//topaz/packages/tests:fidl_compatibility_test_topaz",
"//topaz/packages/tests:fidl_compatibility_test_server_dart",
+ "//topaz/packages/tests:fidl_compatibility_test_topaz",
"//topaz/packages/tests:fidl_dangerous_identifiers",
"//topaz/packages/tests:flutter_runner_tests",
"//topaz/packages/tests:flutter_screencap_test",
@@ -77,7 +79,6 @@
"//topaz/packages/tests:scenic",
"//topaz/packages/tests:sledge",
"//topaz/packages/tests:web_runner_tests",
- "//topaz/bin/fidlgen_dart:fidlgen_dart_backend_ir_test($host_toolchain)",
]
}
@@ -209,5 +210,5 @@
testonly = true
public_deps = [
"//topaz/shell/ermine/test/end_to_end:test",
- ]
+ ]
}
diff --git a/public/dart/fuchsia_webview_flutter/BUILD.gn b/public/dart/fuchsia_webview_flutter/BUILD.gn
new file mode 100644
index 0000000..dab8b2b
--- /dev/null
+++ b/public/dart/fuchsia_webview_flutter/BUILD.gn
@@ -0,0 +1,46 @@
+# Copyright 2019 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/dart/dart_library.gni")
+import("//topaz/runtime/dart/flutter_test.gni")
+
+dart_library("fuchsia_webview_flutter") {
+ package_name = "fuchsia_webview_flutter"
+
+ sdk_category = "partner"
+
+ sources = [
+ "src/fuchsia_web_services.dart",
+ "src/fuchsia_webview.dart",
+ "src/fuchsia_webview_platform_controller.dart",
+ "webview.dart",
+ ]
+
+ deps = [
+ "//sdk/fidl/fuchsia.sys",
+ "//sdk/fidl/fuchsia.ui.views",
+ "//sdk/fidl/fuchsia.web",
+ "//third_party/dart-pkg/pub/webview_flutter",
+ "//topaz/public/dart/fuchsia_logger",
+ "//topaz/public/dart/fuchsia_scenic",
+ "//topaz/public/dart/fuchsia_scenic_flutter",
+ "//zircon/public/fidl/fuchsia-io",
+ "//zircon/public/fidl/fuchsia-mem",
+ ]
+}
+
+# fx run-host-tests fuchsia_webview_flutter_unittests
+flutter_test("fuchsia_webview_flutter_unittests") {
+ sources = [
+ "fuchsia_webview_test.dart",
+ ]
+
+ deps = [
+ ":fuchsia_webview_flutter",
+ "//third_party/dart-pkg/pub/mockito",
+ "//third_party/dart-pkg/pub/test",
+ "//topaz/public/dart/fuchsia_webview_flutter",
+ "//topaz/public/lib/testing/flutter",
+ ]
+}
diff --git a/public/dart/fuchsia_webview_flutter/OWNERS b/public/dart/fuchsia_webview_flutter/OWNERS
new file mode 100644
index 0000000..8416e0c
--- /dev/null
+++ b/public/dart/fuchsia_webview_flutter/OWNERS
@@ -0,0 +1 @@
+nkorsote@google.com
diff --git a/public/dart/fuchsia_webview_flutter/analysis_options.yaml b/public/dart/fuchsia_webview_flutter/analysis_options.yaml
new file mode 100644
index 0000000..bebf512
--- /dev/null
+++ b/public/dart/fuchsia_webview_flutter/analysis_options.yaml
@@ -0,0 +1,5 @@
+# Copyright 2019 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+include: ../../analysis_options.yaml
diff --git a/public/dart/fuchsia_webview_flutter/examples/webview_mod/BUILD.gn b/public/dart/fuchsia_webview_flutter/examples/webview_mod/BUILD.gn
new file mode 100644
index 0000000..fdf597d
--- /dev/null
+++ b/public/dart/fuchsia_webview_flutter/examples/webview_mod/BUILD.gn
@@ -0,0 +1,25 @@
+# Copyright 2019 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/fidl/fidl.gni")
+import("//topaz/runtime/flutter_runner/flutter_app.gni")
+
+flutter_app("webview_mod") {
+ main_dart = "lib/main.dart"
+
+ fuchsia_package_name = "webview_mod"
+
+ meta = [
+ {
+ path = rebase_path("meta/webview_mod.cmx")
+ dest = "webview_mod.cmx"
+ },
+ ]
+
+ deps = [
+ "//topaz/public/dart/fuchsia_logger",
+ "//topaz/public/dart/fuchsia_modular",
+ "//topaz/public/dart/fuchsia_webview_flutter",
+ ]
+}
diff --git a/public/dart/fuchsia_webview_flutter/examples/webview_mod/OWNERS b/public/dart/fuchsia_webview_flutter/examples/webview_mod/OWNERS
new file mode 100644
index 0000000..9246b56
--- /dev/null
+++ b/public/dart/fuchsia_webview_flutter/examples/webview_mod/OWNERS
@@ -0,0 +1,3 @@
+miguelfrde@google.com
+nkorsote@google.com
+*
diff --git a/public/dart/fuchsia_webview_flutter/examples/webview_mod/README.md b/public/dart/fuchsia_webview_flutter/examples/webview_mod/README.md
new file mode 100644
index 0000000..dd57e04
--- /dev/null
+++ b/public/dart/fuchsia_webview_flutter/examples/webview_mod/README.md
@@ -0,0 +1,3 @@
+# Webview mod
+
+This mod showcases how to use Fuchsia webview functionality.
diff --git a/public/dart/fuchsia_webview_flutter/examples/webview_mod/analysis_options.yaml b/public/dart/fuchsia_webview_flutter/examples/webview_mod/analysis_options.yaml
new file mode 100644
index 0000000..d2a931f
--- /dev/null
+++ b/public/dart/fuchsia_webview_flutter/examples/webview_mod/analysis_options.yaml
@@ -0,0 +1,5 @@
+# Copyright 2019 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+include: ../../../../../tools/analysis_options.yaml
diff --git a/public/dart/fuchsia_webview_flutter/examples/webview_mod/lib/app.dart b/public/dart/fuchsia_webview_flutter/examples/webview_mod/lib/app.dart
new file mode 100644
index 0000000..ad20222
--- /dev/null
+++ b/public/dart/fuchsia_webview_flutter/examples/webview_mod/lib/app.dart
@@ -0,0 +1,122 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:convert';
+
+import 'package:flutter/material.dart';
+import 'package:fuchsia_modular/entity.dart';
+import 'package:webview_flutter/webview_flutter.dart';
+
+class App extends StatefulWidget {
+ final Stream<Entity> entityStream;
+ const App({
+ @required this.entityStream,
+ Key key,
+ }) : assert(entityStream != null),
+ super(key: key);
+ @override
+ State<App> createState() => AppState(entityStream);
+}
+
+StreamSubscription<String> _entityStreamSubscriber;
+
+class AppState extends State<App> {
+ final TextEditingController _textEditingController;
+ WebViewController _webViewController;
+ Stream<Entity> _entityStream;
+
+ AppState(this._entityStream)
+ : _textEditingController = TextEditingController();
+
+ @override
+ void initState() {
+ super.initState();
+ // initialize url from a passed intent param if one was present
+ _entityStream.listen((entity) {
+ _entityStreamSubscriber =
+ entity.watch().map(utf8.decode).listen((String url) {
+ _textEditingController.text = url;
+ });
+ });
+ }
+
+ @override
+ void dispose() {
+ _entityStreamSubscriber.cancel();
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return MaterialApp(
+ title: 'Webview Mod',
+ home: Scaffold(
+ backgroundColor: Colors.white,
+ body: Column(
+ children: <Widget>[
+ Row(
+ children: <Widget>[
+ Expanded(
+ child: TextField(
+ controller: _textEditingController,
+ ),
+ ),
+ _buildEnterBtn(),
+ Padding(padding: EdgeInsets.all(4.0)),
+ _buildClearBtn(),
+ ],
+ ),
+ Expanded(
+ child: _buildWebview(),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+
+ Widget _buildEnterBtn() {
+ return RaisedButton(
+ child: const Text('Enter'),
+ color: Theme.of(context).accentColor,
+ elevation: 4.0,
+ splashColor: Colors.blueGrey,
+ onPressed: () {
+ _loadUrl(_textEditingController.text);
+ },
+ );
+ }
+
+ Widget _buildClearBtn() {
+ return RaisedButton(
+ child: const Text('Clear'),
+ color: Theme.of(context).accentColor,
+ elevation: 4.0,
+ splashColor: Colors.blueGrey,
+ onPressed: _textEditingController.clear,
+ );
+ }
+
+ Widget _buildWebview() {
+ return Container(
+ child: WebView(
+ onWebViewCreated: (WebViewController controller) {
+ _webViewController = controller;
+ },
+ ),
+ );
+ }
+
+ void _loadUrl(String url) {
+ if (url == null || url.isEmpty) {
+ return;
+ }
+ Uri uri = Uri.parse(url);
+ if (!uri.hasScheme) {
+ uri = uri.replace(scheme: 'https');
+ }
+ _webViewController?.loadUrl(uri.toString());
+ }
+}
diff --git a/public/dart/fuchsia_webview_flutter/examples/webview_mod/lib/main.dart b/public/dart/fuchsia_webview_flutter/examples/webview_mod/lib/main.dart
new file mode 100644
index 0000000..cf9438d
--- /dev/null
+++ b/public/dart/fuchsia_webview_flutter/examples/webview_mod/lib/main.dart
@@ -0,0 +1,39 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:flutter/material.dart';
+import 'package:fuchsia_logger/logger.dart';
+import 'package:fuchsia_modular/entity.dart';
+import 'package:fuchsia_modular/lifecycle.dart';
+import 'package:fuchsia_modular/module.dart' as modular;
+import 'package:webview_flutter/webview_flutter.dart';
+import 'package:fuchsia_webview_flutter/webview.dart';
+
+import 'app.dart';
+
+class RootIntentHandler extends modular.IntentHandler {
+ final _entityStreamController = () {
+ final controller = StreamController<Entity>.broadcast();
+ Lifecycle().addTerminateListener(controller.close);
+ return controller;
+ }();
+
+ Stream<Entity> get entityStream => _entityStreamController.stream;
+
+ @override
+ void handleIntent(modular.Intent intent) async {
+ // parse the Intent Entity if one was provided
+ _entityStreamController.add(intent.getEntity(name: 'url', type: 'string'));
+ }
+}
+
+void main() {
+ WebView.platform = FuchsiaWebView();
+ setupLogger(name: 'Webview Mod');
+ final intentHandler = RootIntentHandler();
+ modular.Module().registerIntentHandler(intentHandler);
+ runApp(App(entityStream: intentHandler.entityStream));
+}
diff --git a/public/dart/fuchsia_webview_flutter/examples/webview_mod/meta/webview_mod.cmx b/public/dart/fuchsia_webview_flutter/examples/webview_mod/meta/webview_mod.cmx
new file mode 100644
index 0000000..e7f8a25
--- /dev/null
+++ b/public/dart/fuchsia_webview_flutter/examples/webview_mod/meta/webview_mod.cmx
@@ -0,0 +1,47 @@
+{
+ "facets": {
+ "fuchsia.modular": {
+ "@version": 2,
+ "binary": "webview_mod",
+ "intent_filters": [
+ {
+ "action": "com.fuchsia.navigate_to",
+ "parameters": [
+ {
+ "name": "url",
+ "type": "string"
+ }
+ ]
+ }
+ ]
+ }
+ },
+ "program": {
+ "data": "data/webview_mod"
+ },
+ "sandbox": {
+ "services": [
+ "fuchsia.cobalt.LoggerFactory",
+ "fuchsia.fonts.Provider",
+ "fuchsia.logger.LogSink",
+ "fuchsia.modular.Clipboard",
+ "fuchsia.modular.ComponentContext",
+ "fuchsia.modular.ContextWriter",
+ "fuchsia.modular.ModuleContext",
+ "fuchsia.net.SocketProvider",
+ "fuchsia.netstack.Netstack",
+ "fuchsia.process.Launcher",
+ "fuchsia.sys.Environment",
+ "fuchsia.sys.Launcher",
+ "fuchsia.sysmem.Allocator",
+ "fuchsia.ui.input.ImeService",
+ "fuchsia.ui.input.ImeVisibilityService",
+ "fuchsia.ui.policy.Presenter",
+ "fuchsia.ui.scenic.Scenic",
+ "fuchsia.web.ContextProvider"
+ ],
+ "system": [
+ "data/modules"
+ ]
+ }
+}
diff --git a/public/dart/fuchsia_webview_flutter/examples/webview_mod/pubspec.yaml b/public/dart/fuchsia_webview_flutter/examples/webview_mod/pubspec.yaml
new file mode 100644
index 0000000..2fd0fc9
--- /dev/null
+++ b/public/dart/fuchsia_webview_flutter/examples/webview_mod/pubspec.yaml
@@ -0,0 +1,9 @@
+# Copyright 2018 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+name: webview_mod
+
+flutter:
+ uses-material-design: true
+
diff --git a/public/dart/fuchsia_webview_flutter/lib/src/fuchsia_web_services.dart b/public/dart/fuchsia_webview_flutter/lib/src/fuchsia_web_services.dart
new file mode 100644
index 0000000..842bba29
--- /dev/null
+++ b/public/dart/fuchsia_webview_flutter/lib/src/fuchsia_web_services.dart
@@ -0,0 +1,156 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:convert' show utf8;
+import 'dart:io';
+
+import 'package:fidl/fidl.dart';
+import 'package:fidl_fuchsia_io/fidl_async.dart' as fidl_io;
+import 'package:fidl_fuchsia_mem/fidl_async.dart' as fidl_mem;
+import 'package:fidl_fuchsia_web/fidl_async.dart' as fidl_web;
+import 'package:fuchsia_logger/logger.dart';
+import 'package:fuchsia_scenic_flutter/child_view_connection.dart';
+import 'package:fuchsia_services/services.dart';
+import 'package:zircon/zircon.dart';
+import 'package:fuchsia_scenic/views.dart';
+
+/// Helper class to help connect and interface with 'fuchsia.web.*' services.
+class FuchsiaWebServices {
+ final fidl_web.ContextProviderProxy _contextProviderProxy =
+ fidl_web.ContextProviderProxy();
+ final fidl_web.ContextProxy _contextProxy = fidl_web.ContextProxy();
+ final fidl_web.FrameProxy _frameProxy = fidl_web.FrameProxy();
+ final fidl_web.NavigationControllerProxy _navigationControllerProxy =
+ fidl_web.NavigationControllerProxy();
+
+ final fidl_web.NavigationEventListenerBinding
+ _navigationEventObserverBinding =
+ fidl_web.NavigationEventListenerBinding();
+
+ ChildViewConnection _childViewConnection;
+
+ /// Constructs [FuchsiaWebServices] and connects to various 'fuchsia.web.*`
+ /// services.
+ FuchsiaWebServices({
+ fidl_web.ConsoleLogLevel javascriptLogLevel = fidl_web.ConsoleLogLevel.none,
+ }) {
+ StartupContext.fromStartupInfo()
+ .incoming
+ .connectToService(_contextProviderProxy);
+
+ // TODO(nkorsote): [service_directory] is effectively the sandbox inside
+ // which the created Context will run. If you give it a direct handle to
+ // component's /svc directory then it'll have access to everything the
+ // component can access. Alternatively, refactor this to use an Outgoing
+ // Directory.
+ if (!Directory('/svc').existsSync()) {
+ log.shout('no /svc directory');
+ return;
+ }
+ final channel = Channel.fromFile('/svc');
+ final fidl_web.CreateContextParams contextParams =
+ fidl_web.CreateContextParams(
+ serviceDirectory: InterfaceHandle<fidl_io.Directory>(channel));
+
+ _contextProviderProxy.create(contextParams, _contextProxy.ctrl.request());
+ _contextProxy.createFrame(_frameProxy.ctrl.request());
+
+ // Create token pair and pass one end to the webview and the other to child
+ // view connection which will be used to construct the child view widget
+ // that the webview will live in.
+ final tokenPair = ViewTokenPair();
+ _frameProxy.createView(tokenPair.viewToken);
+ _childViewConnection = ChildViewConnection(tokenPair.viewHolderToken);
+ _frameProxy
+ ..getNavigationController(_navigationControllerProxy.ctrl.request())
+ ..setJavaScriptLogLevel(javascriptLogLevel);
+ }
+
+ /// Returns a connection to a child view.
+ ///
+ /// It can be used to construct a [ChildView] widget that will display the
+ /// view's contents.
+ ChildViewConnection get childViewConnection => _childViewConnection;
+
+ /// Returns [fidl_web.NavigationControllerProxy]
+ fidl_web.NavigationControllerProxy get navigationController =>
+ _navigationControllerProxy;
+
+ /// Preforms the all the necessary cleanup.
+ void dispose() {
+ _navigationControllerProxy.ctrl.close();
+ _frameProxy.ctrl.close();
+ _contextProxy.ctrl.close();
+ _contextProviderProxy.ctrl.close();
+ }
+
+ /// Executes a UTF-8 encoded [script] for every subsequent page load where the
+ /// frame's URL has an origin reflected in [origins]. The script is executed
+ /// early, prior to the execution of the document's scripts.
+ ///
+ /// Scripts are identified by a identifier [id]. Any script previously
+ /// injected using the same [id] will be replaced.
+ ///
+ /// The order in which multiple bindings are executed is the same as the order
+ /// in which the bindings were added. If a script is added which clobbers an
+ /// existing script of the same [id], the previous script's precedence in the
+ /// injection order will be preserved.
+ ///
+ /// At least one [origins] entry must be specified. If a wildcard "*" is
+ /// specified in [origins], then the script will be evaluated for all
+ /// documents.
+ ///
+ /// If an error occurred, the FrameError will be set to one of these values:
+ /// - BUFFER_NOT_UTF8: [script] is not UTF-8 encoded.
+ /// - INVALID_ORIGIN: [origins] is an empty vector.
+ Future<void> injectJavascript(int id, List<String> origins, String script) {
+ final vmo = SizedVmo.fromUint8List(utf8.encode(script));
+ final buffer = fidl_mem.Buffer(vmo: vmo, size: vmo.size);
+ // TODO(nkosote): add catchError and decorate the error based on the error
+ // code
+ return _frameProxy.addBeforeLoadJavaScript(id, origins, buffer);
+ }
+
+ /// Posts a message to the [fidl_web.Frame]'s onMessage handler.
+ ///
+ /// [targetOrigin] restricts message delivery to the specified origin. If
+ /// [targetOrigin] is "*", then the message will be sent to the document
+ /// regardless of its origin.
+ ///
+ /// If an error occurred, the FrameError will be set to one of these values:
+ /// - INTERNAL_ERROR: The WebEngine failed to create a message pipe.
+ /// - BUFFER_NOT_UTF8: The script in [message]'s [fidl_web.WebMessage.data]
+ /// property is not UTF-8 encoded.
+ /// - INVALID_ORIGIN: origins is an empty vector.
+ /// - NO_DATA_IN_MESSAGE: The [fidl_web.WebMessage.data] property is missing
+ /// in [message].
+ Future<void> postMessage(
+ String targetOrigin,
+ String message, {
+ InterfaceRequest<fidl_web.MessagePort> outgoingMessagePortRequest,
+ }) {
+ final vmo = SizedVmo.fromUint8List(utf8.encode(message));
+ var msg = fidl_web.WebMessage(
+ data: fidl_mem.Buffer(vmo: vmo, size: vmo.size),
+ outgoingTransfer: outgoingMessagePortRequest != null
+ ? [
+ fidl_web.OutgoingTransferable.withMessagePort(
+ outgoingMessagePortRequest)
+ ]
+ : null,
+ );
+ // TODO(nkosote): add catchError and decorate the error based on the error
+ // code
+ return _frameProxy.postMessage(targetOrigin, msg);
+ }
+
+ /// Sets the listener for handling page navigation events.
+ ///
+ /// The [observer] to use. Unregisters any existing listener if null.
+ void setNavigationEventListener(fidl_web.NavigationEventListener observer) {
+ _frameProxy.setNavigationEventListener(
+ _navigationEventObserverBinding.wrap(observer));
+ }
+}
diff --git a/public/dart/fuchsia_webview_flutter/lib/src/fuchsia_webview.dart b/public/dart/fuchsia_webview_flutter/lib/src/fuchsia_webview.dart
new file mode 100644
index 0000000..57bd1df
--- /dev/null
+++ b/public/dart/fuchsia_webview_flutter/lib/src/fuchsia_webview.dart
@@ -0,0 +1,51 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/gestures.dart';
+import 'package:flutter/widgets.dart';
+import 'package:fuchsia_scenic_flutter/child_view.dart' show ChildView;
+import 'package:webview_flutter/platform_interface.dart';
+
+import 'fuchsia_web_services.dart';
+import 'fuchsia_webview_platform_controller.dart';
+
+/// Builds an Fuchsia webview.
+class FuchsiaWebView implements WebViewPlatform {
+ /// The fuchsia implementation of [WebViewPlatformController]
+ FuchsiaWebServices fuchsiaWebServices;
+
+ /// This constructor should only be used to inject a platform controller for
+ /// testing.
+ ///
+ /// TODO(nkorsote): hide this implementation detail
+ @visibleForTesting
+ FuchsiaWebView({this.fuchsiaWebServices});
+
+ @override
+ Widget build({
+ @required WebViewPlatformCallbacksHandler webViewPlatformCallbacksHandler,
+ BuildContext context,
+ CreationParams creationParams,
+ WebViewPlatformCreatedCallback onWebViewPlatformCreated,
+ Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers,
+ }) {
+ assert(webViewPlatformCallbacksHandler != null);
+
+ // TODO(nkorsote): figure what actual ID should be used instead of -1. Also,
+ // consider extracting to a constant.
+ final controller = FuchsiaWebViewPlatformController(
+ -1, webViewPlatformCallbacksHandler, fuchsiaWebServices);
+
+ onWebViewPlatformCreated(controller);
+ return ChildView(
+ connection: controller.fuchsiaWebServices.childViewConnection);
+ }
+
+ @override
+ Future<bool> clearCookies() =>
+ FuchsiaWebViewPlatformController.clearCookies();
+}
diff --git a/public/dart/fuchsia_webview_flutter/lib/src/fuchsia_webview_platform_controller.dart b/public/dart/fuchsia_webview_flutter/lib/src/fuchsia_webview_platform_controller.dart
new file mode 100644
index 0000000..07e3807
--- /dev/null
+++ b/public/dart/fuchsia_webview_flutter/lib/src/fuchsia_webview_platform_controller.dart
@@ -0,0 +1,142 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:convert' show utf8;
+
+import 'package:fidl_fuchsia_web/fidl_async.dart' as fidl_web;
+import 'package:fidl_fuchsia_net_http/fidl_async.dart' as fidl_net;
+import 'package:webview_flutter/platform_interface.dart';
+
+import 'fuchsia_web_services.dart';
+
+/// Fuchsia [WebViewPlatformController] implementation that serves as the entry
+/// point for all [fuchsia_webview_flutter/webview.dart]'s apis
+class FuchsiaWebViewPlatformController implements WebViewPlatformController {
+ /// Helper class to interact with fuchsia web services
+ FuchsiaWebServices _fuchsiaWebServices;
+
+ final WebViewPlatformCallbacksHandler _platformCallbacksHandler;
+
+ /// Initializes [FuchsiaWebViewPlatformController]
+ FuchsiaWebViewPlatformController(
+ int id, this._platformCallbacksHandler, this._fuchsiaWebServices)
+ : assert(_platformCallbacksHandler != null) {
+ // TODO(nkorsote): remove this prints with an actual impl. The prints are
+ // here to satisfy our strict dart linter for now.
+ print('id: $id');
+ print('_platformCallbacksHandler: $_platformCallbacksHandler');
+ print('fuchsiaWebServices: $fuchsiaWebServices');
+ }
+
+ /// Returns [FuchsiaWebServices]
+ FuchsiaWebServices get fuchsiaWebServices {
+ return _fuchsiaWebServices ??= FuchsiaWebServices();
+ }
+
+ @override
+ Future<void> addJavascriptChannels(Set<String> javascriptChannelNames) {
+ throw UnimplementedError(
+ 'FuchsiaWebView addJavascriptChannels is not implemented on the current platform');
+ }
+
+ @override
+ Future<bool> canGoBack() {
+ throw UnimplementedError(
+ 'FuchsiaWebView canGoBack is not implemented on the current platform');
+ }
+
+ @override
+ Future<bool> canGoForward() {
+ throw UnimplementedError(
+ 'FuchsiaWebView canGoForward is not implemented on the current platform');
+ }
+
+ @override
+ Future<void> clearCache() {
+ throw UnimplementedError(
+ 'FuchsiaWebView clearCache is not implemented on the current platform');
+ }
+
+ @override
+ Future<String> currentUrl() async {
+ final navigationState =
+ await fuchsiaWebServices.navigationController.getVisibleEntry();
+ return navigationState.url;
+ }
+
+ @override
+ Future<String> evaluateJavascript(String javascriptString) {
+ throw UnimplementedError(
+ 'FuchsiaWebView evaluateJavascript is not implemented on the current platform');
+ }
+
+ @override
+ Future<void> goBack() {
+ throw UnimplementedError(
+ 'FuchsiaWebView goBack is not implemented on the current platform');
+ }
+
+ @override
+ Future<void> goForward() {
+ throw UnimplementedError(
+ 'FuchsiaWebView goForward is not implemented on the current platform');
+ }
+
+ @override
+ Future<void> loadUrl(
+ String url,
+ Map<String, String> headers,
+ ) async {
+ assert(url != null);
+
+ final headersList = <fidl_net.Header>[];
+ if (headers != null) {
+ headers.forEach((k, v) {
+ headersList
+ .add(fidl_net.Header(name: utf8.encode(k), value: utf8.encode(v)));
+ });
+ }
+
+ return fuchsiaWebServices.navigationController.loadUrl(
+ url,
+ fidl_web.LoadUrlParams(
+ type: fidl_web.LoadUrlReason.typed,
+ headers: headersList,
+ ));
+ }
+
+ @override
+ Future<void> reload() {
+ throw UnimplementedError(
+ 'FuchsiaWebView reload is not implemented on the current platform');
+ }
+
+ @override
+ Future<void> removeJavascriptChannels(Set<String> javascriptChannelNames) {
+ throw UnimplementedError(
+ 'FuchsiaWebView removeJavascriptChannels is not implemented on the current platform');
+ }
+
+ @override
+ Future<void> updateSettings(WebSettings settings) {
+ throw UnimplementedError(
+ 'FuchsiaWebView updateSettings is not implemented on the current platform');
+ }
+
+ /// Clears all cookies for all [WebView] instances.
+ ///
+ /// Returns true if cookies were present before clearing, else false.
+ static Future<bool> clearCookies() {
+ throw UnimplementedError(
+ 'FuchsiaWebView clearCookies is not implemented on the current platform');
+ }
+
+ // TODO(nkorsote): implement this method
+ // static Map<String, dynamic> creationParamsToMap(
+ // CreationParams creationParams) {
+ // throw UnimplementedError(
+ // 'FuchsiaWebView creationParamsToMap is not implemented on the current platform');
+ // }
+}
diff --git a/public/dart/fuchsia_webview_flutter/lib/webview.dart b/public/dart/fuchsia_webview_flutter/lib/webview.dart
new file mode 100644
index 0000000..e07b6c2
--- /dev/null
+++ b/public/dart/fuchsia_webview_flutter/lib/webview.dart
@@ -0,0 +1,5 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+export 'src/fuchsia_webview.dart';
diff --git a/public/dart/fuchsia_webview_flutter/pubspec.yaml b/public/dart/fuchsia_webview_flutter/pubspec.yaml
new file mode 100644
index 0000000..2e71bbe
--- /dev/null
+++ b/public/dart/fuchsia_webview_flutter/pubspec.yaml
@@ -0,0 +1,7 @@
+# Copyright 2019 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+name: fuchsia_webview_flutter
+environment:
+ sdk: '>=2.0.0 <3.0.0'
diff --git a/public/dart/fuchsia_webview_flutter/test/fuchsia_webview_test.dart b/public/dart/fuchsia_webview_flutter/test/fuchsia_webview_test.dart
new file mode 100644
index 0000000..ba1d558
--- /dev/null
+++ b/public/dart/fuchsia_webview_flutter/test/fuchsia_webview_test.dart
@@ -0,0 +1,75 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:convert' show utf8;
+
+import 'package:fidl_fuchsia_net_http/fidl_async.dart' as fidl_net;
+import 'package:fidl_fuchsia_web/fidl_async.dart' as fidl_web;
+import 'package:flutter_test/flutter_test.dart';
+import 'package:fuchsia_webview_flutter/src/fuchsia_web_services.dart';
+import 'package:fuchsia_webview_flutter/src/fuchsia_webview_platform_controller.dart';
+import 'package:fuchsia_webview_flutter/webview.dart';
+import 'package:mockito/mockito.dart';
+import 'package:webview_flutter/platform_interface.dart';
+import 'package:webview_flutter/webview_flutter.dart';
+
+// ignore_for_file: implementation_imports
+
+class MockFuchsiaWebServices extends Mock implements FuchsiaWebServices {}
+
+class MockWebViewPlatformCallbacksHandler extends Mock
+ implements WebViewPlatformCallbacksHandler {}
+
+class MockFuchsiaWebViewPlatformController extends Mock
+ implements FuchsiaWebViewPlatformController {}
+
+class MockNavigationControllerProxy extends Mock
+ implements fidl_web.NavigationControllerProxy {}
+
+void main() {
+ FuchsiaWebServices mockWebServices = MockFuchsiaWebServices();
+ fidl_web.NavigationControllerProxy mockNavigationController =
+ MockNavigationControllerProxy();
+
+ group('Custom platform implementation', () {
+ setUpAll(() {
+ when(mockWebServices.navigationController)
+ .thenReturn(mockNavigationController);
+ WebView.platform = FuchsiaWebView(fuchsiaWebServices: mockWebServices);
+ });
+
+ tearDownAll(() {
+ WebView.platform = null;
+ });
+
+ testWidgets('Create WebView', (WidgetTester tester) async {
+ await tester.pumpWidget(const WebView());
+ });
+
+ testWidgets('loadUrl', (WidgetTester tester) async {
+ WebViewController controller;
+ await tester.pumpWidget(
+ WebView(
+ onWebViewCreated: (WebViewController webViewController) {
+ controller = webViewController;
+ },
+ ),
+ );
+
+ final headers = <String, String>{'header': 'value'};
+ String url = 'https://google.com';
+ await controller.loadUrl(url, headers: headers);
+
+ verify(mockNavigationController.loadUrl(
+ url,
+ fidl_web.LoadUrlParams(
+ type: fidl_web.LoadUrlReason.typed,
+ headers: [
+ fidl_net.Header(
+ name: utf8.encode('header'), value: utf8.encode('value'))
+ ],
+ )));
+ });
+ });
+}