[simple_browser] Add support for popup windows.

Spawns new Simple Browser tabs for browser popups.

Test: Used Gaia login, Drive features which require popups.
Bug: 29938
Change-Id: I27092480b4ec125e9065ddc863e3b832dc2b06b9
diff --git a/bin/simple_browser/lib/main.dart b/bin/simple_browser/lib/main.dart
index c9b34d8..21ecc0f 100644
--- a/bin/simple_browser/lib/main.dart
+++ b/bin/simple_browser/lib/main.dart
@@ -36,8 +36,15 @@
 void main() {
   setupLogger(name: 'Browser');
   final _context = ChromiumWebView.createContext();
-  final tabsBloc = TabsBloc(
-    tabFactory: () => WebPageBloc(context: _context),
+
+  // Bind |tabsBloc| here so that it can be referenced in the TabsBloc
+  // constructor arguments.
+  TabsBloc<WebPageBloc> tabsBloc;
+  tabsBloc = TabsBloc(
+    tabFactory: () => WebPageBloc(
+        context: _context,
+        popupHandler: (tab) =>
+            tabsBloc.request.add(AddTabAction<WebPageBloc>(tab: tab))),
     disposeTab: (tab) {
       tab.dispose();
     },
diff --git a/bin/simple_browser/lib/src/blocs/tabs_bloc.dart b/bin/simple_browser/lib/src/blocs/tabs_bloc.dart
index f10d4ba..56ff52b 100644
--- a/bin/simple_browser/lib/src/blocs/tabs_bloc.dart
+++ b/bin/simple_browser/lib/src/blocs/tabs_bloc.dart
@@ -76,6 +76,12 @@
         _tabs.value = UnmodifiableListView<T>(_tabsList);
         disposeTab(tab);
         break;
+      case TabsActionType.addTab:
+        final AddTabAction<T> addTabAction = action;
+        _tabsList.add(addTabAction.tab);
+        _tabs.value = UnmodifiableListView<T>(_tabsList);
+        _currentTab.value = addTabAction.tab;
+        break;
     }
   }
 }
diff --git a/bin/simple_browser/lib/src/blocs/webpage_bloc.dart b/bin/simple_browser/lib/src/blocs/webpage_bloc.dart
index e2cf673..0859574 100644
--- a/bin/simple_browser/lib/src/blocs/webpage_bloc.dart
+++ b/bin/simple_browser/lib/src/blocs/webpage_bloc.dart
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 import 'dart:async';
+import 'package:fidl/fidl.dart' show InterfaceHandle;
 import 'package:flutter/foundation.dart';
 import 'package:fuchsia_logger/logger.dart';
 import 'package:fuchsia_scenic_flutter/child_view.dart'
@@ -33,6 +34,7 @@
 //   isLoadedState: bool indicating whether main document has fully loaded.
 class WebPageBloc extends web.NavigationEventListener {
   final ChromiumWebView _webView;
+  final _PopupListener _popupListener;
 
   ChildViewConnection get childViewConnection => _webView.childViewConnection;
 
@@ -65,8 +67,12 @@
   WebPageBloc({
     String homePage,
     web.ContextProxy context,
-  }) : _webView = ChromiumWebView.withContext(context: context) {
-    _webView.setNavigationEventListener(this);
+    void Function(WebPageBloc popup) popupHandler,
+  })  : _webView = ChromiumWebView.withContext(context: context),
+        _popupListener = _PopupListener(popupHandler) {
+    _webView
+      ..setNavigationEventListener(this)
+      ..setPopupFrameCreationListener(_popupListener);
 
     if (homePage != null) {
       _handleAction(NavigateToAction(url: homePage));
@@ -74,6 +80,18 @@
     _webPageActionController.stream.listen(_handleAction);
   }
 
+  WebPageBloc.withFrame({
+    @required web.FrameProxy frame,
+    void Function(WebPageBloc popup) popupHandler,
+  })  : _webView = ChromiumWebView.withFrame(frame: frame),
+        _popupListener = _PopupListener(popupHandler) {
+    _webView
+      ..setNavigationEventListener(this)
+      ..setPopupFrameCreationListener(_popupListener);
+
+    _webPageActionController.stream.listen(_handleAction);
+  }
+
   void dispose() {
     _webView.dispose();
     _webPageActionController.close();
@@ -132,3 +150,18 @@
     }
   }
 }
+
+class _PopupListener extends web.PopupFrameCreationListener {
+  final void Function(WebPageBloc popup) _handler;
+
+  _PopupListener(handler) : _handler = handler;
+
+  @override
+  Future<void> onPopupFrameCreated(
+    InterfaceHandle<web.Frame> frame,
+    web.PopupFrameCreationInfo info,
+  ) async {
+    _handler(WebPageBloc.withFrame(
+        frame: web.FrameProxy()..ctrl.bind(frame), popupHandler: _handler));
+  }
+}
diff --git a/bin/simple_browser/lib/src/models/tabs_action.dart b/bin/simple_browser/lib/src/models/tabs_action.dart
index f7123ba..cfc8ad2 100644
--- a/bin/simple_browser/lib/src/models/tabs_action.dart
+++ b/bin/simple_browser/lib/src/models/tabs_action.dart
@@ -11,7 +11,7 @@
 }
 
 // Operations allowed for tab management
-enum TabsActionType { newTab, removeTab, focusTab }
+enum TabsActionType { newTab, removeTab, focusTab, addTab }
 
 // Instructs to add a new tab to tab list.
 class NewTabAction<T> extends TabsAction<T> {
@@ -29,3 +29,9 @@
   final T tab;
   const FocusTabAction({@required this.tab}) : super(TabsActionType.focusTab);
 }
+
+// Instructs to add an existing tab to the tab list.
+class AddTabAction<T> extends TabsAction<T> {
+  final T tab;
+  const AddTabAction({@required this.tab}) : super(TabsActionType.addTab);
+}