migrate to null safety (#67)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index e05301b..20d83d7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,8 @@
 # webkit_inspection_protocol.dart
 
+## 1.0.0
+- Migrate to null safety.
+
 ## 0.7.5
 - Allow the latest `logging` package.
 
diff --git a/example/multiplex_impl.dart b/example/multiplex_impl.dart
index c2e5d5a..a2e9911 100644
--- a/example/multiplex_impl.dart
+++ b/example/multiplex_impl.dart
@@ -21,7 +21,7 @@
 class Server {
   static final _log = new Logger('Server');
 
-  Future<HttpServer> _server;
+  Future<HttpServer>? _server;
   final ChromeConnection chrome;
   final int port;
   final bool modelDom;
@@ -29,7 +29,7 @@
   final _connections = <String, Future<WipConnection>>{};
   final _modelDoms = <String, WipDomModel>{};
 
-  Server(this.port, this.chrome, {this.modelDom}) {
+  Server(this.port, this.chrome, {this.modelDom = false}) {
     _server = io.serve(_handler, InternetAddress.anyIPv4, port);
   }
 
@@ -81,7 +81,7 @@
         ..write('/devtools/page/')
         ..write(tab.id)
         ..write('">');
-      if (tab.title != null && tab.title.isNotEmpty) {
+      if (tab.title != null && tab.title!.isNotEmpty) {
         html.write(tab.title);
       } else {
         html.write(tab.url);
@@ -128,10 +128,10 @@
 
     return ws.webSocketHandler((WebSocketChannel webSocket) async {
       var debugger = await _connections.putIfAbsent(path[2], () async {
-        var tab = await chrome.getTab((tab) => tab.id == path[2]);
+        var tab = (await chrome.getTab((tab) => tab.id == path[2]))!;
         return WipConnection.connect(tab.webSocketDebuggerUrl);
       });
-      WipDomModel dom;
+      WipDomModel? dom;
       if (modelDom) {
         dom = await _modelDoms.putIfAbsent(path[2], () {
           return new WipDomModel(debugger.dom);
@@ -149,12 +149,12 @@
 
   Future close() async {
     if (_server != null) {
-      await (await _server).close(force: true);
+      await (await _server!).close(force: true);
       _server = null;
     }
   }
 
-  Object _jsonEncode(Object obj) {
+  Object? _jsonEncode(Object? obj) {
     if (obj is ChromeTab) {
       var json = <String, dynamic>{
         'description': obj.description,
diff --git a/lib/dom_model.dart b/lib/dom_model.dart
index 72e70da..ccfc032 100644
--- a/lib/dom_model.dart
+++ b/lib/dom_model.dart
@@ -31,50 +31,42 @@
   final WipDom _dom;
 
   final Map<int, _Node> _nodeCache = {};
-  Future<_Node> _root;
+  Future<_Node>? _root;
 
-  Stream<AttributeModifiedEvent> onAttributeModified;
-  Stream<AttributeRemovedEvent> onAttributeRemoved;
-  Stream<CharacterDataModifiedEvent> onCharacterDataModified;
-  Stream<ChildNodeCountUpdatedEvent> onChildNodeCountUpdated;
-  Stream<ChildNodeInsertedEvent> onChildNodeInserted;
-  Stream<ChildNodeRemovedEvent> onChildNodeRemoved;
-  Stream<DocumentUpdatedEvent> onDocumentUpdated;
-  Stream<SetChildNodesEvent> onSetChildNodes;
+  late final Stream<AttributeModifiedEvent> onAttributeModified =
+      StreamTransformer.fromHandlers(handleData: _onAttributeModified)
+          .bind(_dom.onAttributeModified);
+  late final Stream<AttributeRemovedEvent> onAttributeRemoved =
+      StreamTransformer.fromHandlers(handleData: _onAttributeRemoved)
+          .bind(_dom.onAttributeRemoved);
+  late final Stream<CharacterDataModifiedEvent> onCharacterDataModified =
+      StreamTransformer.fromHandlers(handleData: _onCharacterDataModified)
+          .bind(_dom.onCharacterDataModified);
+  late final Stream<ChildNodeCountUpdatedEvent> onChildNodeCountUpdated =
+      StreamTransformer.fromHandlers(handleData: _onChildNodeCountUpdated)
+          .bind(_dom.onChildNodeCountUpdated);
+  late final Stream<ChildNodeInsertedEvent> onChildNodeInserted =
+      StreamTransformer.fromHandlers(handleData: _onChildNodeInserted)
+          .bind(_dom.onChildNodeInserted);
+  late final Stream<ChildNodeRemovedEvent> onChildNodeRemoved =
+      StreamTransformer.fromHandlers(handleData: _onChildNodeRemoved)
+          .bind(_dom.onChildNodeRemoved);
+  late final Stream<DocumentUpdatedEvent> onDocumentUpdated =
+      StreamTransformer.fromHandlers(handleData: _onDocumentUpdated)
+          .bind(_dom.onDocumentUpdated);
+  late final Stream<SetChildNodesEvent> onSetChildNodes =
+      StreamTransformer.fromHandlers(handleData: _onSetChildNodes)
+          .bind(_dom.onSetChildNodes);
 
   WipDomModel(this._dom) {
-    onAttributeModified =
-        new StreamTransformer.fromHandlers(handleData: _onAttributeModified)
-            .bind(_dom.onAttributeModified)
-              ..listen(_logEvent);
-    onAttributeRemoved =
-        new StreamTransformer.fromHandlers(handleData: _onAttributeRemoved)
-            .bind(_dom.onAttributeRemoved)
-              ..listen(_logEvent);
-    onCharacterDataModified =
-        new StreamTransformer.fromHandlers(handleData: _onCharacterDataModified)
-            .bind(_dom.onCharacterDataModified)
-              ..listen(_logEvent);
-    onChildNodeCountUpdated =
-        new StreamTransformer.fromHandlers(handleData: _onChildNodeCountUpdated)
-            .bind(_dom.onChildNodeCountUpdated)
-              ..listen(_logEvent);
-    onChildNodeInserted =
-        new StreamTransformer.fromHandlers(handleData: _onChildNodeInserted)
-            .bind(_dom.onChildNodeInserted)
-              ..listen(_logEvent);
-    onChildNodeRemoved =
-        new StreamTransformer.fromHandlers(handleData: _onChildNodeRemoved)
-            .bind(_dom.onChildNodeRemoved)
-              ..listen(_logEvent);
-    onDocumentUpdated =
-        new StreamTransformer.fromHandlers(handleData: _onDocumentUpdated)
-            .bind(_dom.onDocumentUpdated)
-              ..listen(_logEvent);
-    onSetChildNodes =
-        new StreamTransformer.fromHandlers(handleData: _onSetChildNodes)
-            .bind(_dom.onSetChildNodes)
-              ..listen(_logEvent);
+    onAttributeModified.listen(_logEvent);
+    onAttributeRemoved.listen(_logEvent);
+    onCharacterDataModified.listen(_logEvent);
+    onChildNodeCountUpdated.listen(_logEvent);
+    onChildNodeInserted.listen(_logEvent);
+    onChildNodeRemoved.listen(_logEvent);
+    onDocumentUpdated.listen(_logEvent);
+    onSetChildNodes.listen(_logEvent);
   }
 
   void _logEvent(WipEvent event) {
@@ -84,14 +76,14 @@
   void _onAttributeModified(
       AttributeModifiedEvent event, EventSink<AttributeModifiedEvent> sink) {
     var node = _getOrCreateNode(event.nodeId);
-    node._attributes[event.name] = event.value;
+    node._attributes![event.name] = event.value;
     sink.add(event);
   }
 
   void _onAttributeRemoved(
       AttributeRemovedEvent event, EventSink<AttributeRemovedEvent> sink) {
     var node = _getOrCreateNode(event.nodeId);
-    node._attributes.remove(event.name);
+    node._attributes!.remove(event.name);
     sink.add(event);
   }
 
@@ -112,14 +104,11 @@
   void _onChildNodeInserted(
       ChildNodeInsertedEvent event, EventSink<ChildNodeInsertedEvent> sink) {
     var parent = _getOrCreateNode(event.parentNodeId);
-    int index = 0;
-    if (event.previousNodeId != null) {
-      index =
-          parent._children.indexOf(_getOrCreateNode(event.previousNodeId)) + 1;
-    }
+    int index =
+        parent._children!.indexOf(_getOrCreateNode(event.previousNodeId)) + 1;
     var node = _getOrCreateNodeFromNode(event.node);
-    parent._children.insert(index, node);
-    parent._childNodeCount = parent._children.length;
+    parent._children!.insert(index, node);
+    parent._childNodeCount = parent._children!.length;
     sink.add(event);
   }
 
@@ -127,8 +116,8 @@
       ChildNodeRemovedEvent event, EventSink<ChildNodeRemovedEvent> sink) {
     var parent = _getOrCreateNode(event.parentNodeId);
     var node = _nodeCache.remove(event.nodeId);
-    parent._children.remove(node);
-    parent._childNodeCount = parent._children.length;
+    parent._children!.remove(node);
+    parent._childNodeCount = parent._children!.length;
     sink.add(event);
   }
 
@@ -144,7 +133,7 @@
     var parent = _getOrCreateNode(event.nodeId);
     parent._children =
         event.nodes.map(_getOrCreateNodeFromNode).toList(growable: true);
-    parent._childNodeCount = parent._children.length;
+    parent._childNodeCount = parent._children!.length;
     sink.add(event);
   }
 
@@ -164,7 +153,7 @@
     if (_root == null) {
       _root = _dom.getDocument().then((n) => _getOrCreateNodeFromNode(n));
     }
-    return _root;
+    return _root!;
   }
 
   _Node _getOrCreateNode(int nodeId) =>
@@ -174,15 +163,15 @@
     try {
       var node = _getOrCreateNode(src.nodeId);
       if (src.attributes != null) {
-        node._attributes = new Map.from(src.attributes);
+        node._attributes = Map.of(src.attributes!);
       }
       if (src.children != null) {
         node._children =
-            src.children.map(_getOrCreateNodeFromNode).toList(growable: true);
+            src.children!.map(_getOrCreateNodeFromNode).toList(growable: true);
       }
-      node._childNodeCount = src.childNodeCount;
+      node._childNodeCount = src.childNodeCount ?? 0;
       if (src.contentDocument != null) {
-        node._contentDocument = _getOrCreateNodeFromNode(src.contentDocument);
+        node._contentDocument = _getOrCreateNodeFromNode(src.contentDocument!);
       }
       node._documentUrl = src.documentUrl;
       node._internalSubset = src.internalSubset;
@@ -207,85 +196,85 @@
 }
 
 class _Node implements Node {
-  Map<String, String> _attributes;
+  Map<String, String>? _attributes;
 
   @override
-  Map<String, String> get attributes =>
-      _attributes != null ? new UnmodifiableMapView(_attributes) : null;
+  Map<String, String>? get attributes =>
+      _attributes != null ? new UnmodifiableMapView(_attributes!) : null;
 
-  int _childNodeCount;
+  int? _childNodeCount;
 
   @override
-  int get childNodeCount => _childNodeCount;
+  int? get childNodeCount => _childNodeCount;
 
-  List<_Node> _children;
+  List<_Node>? _children;
 
   @override
-  List<Node> get children =>
-      _children != null ? new UnmodifiableListView(_children) : null;
+  List<Node>? get children =>
+      _children != null ? new UnmodifiableListView(_children!) : null;
 
-  _Node _contentDocument;
+  _Node? _contentDocument;
 
   @override
-  Node get contentDocument => _contentDocument;
+  Node? get contentDocument => _contentDocument;
 
-  String _documentUrl;
+  String? _documentUrl;
 
   @override
-  String get documentUrl => _documentUrl;
+  String? get documentUrl => _documentUrl;
 
-  String _internalSubset;
+  String? _internalSubset;
 
   @override
-  String get internalSubset => _internalSubset;
+  String? get internalSubset => _internalSubset;
 
-  String _localName;
+  String? _localName;
 
   @override
-  String get localName => _localName;
+  String get localName => _localName!;
 
-  String _name;
+  String? _name;
 
   @override
-  String get name => _name;
+  String? get name => _name;
 
   @override
   final int nodeId;
 
-  String _nodeName;
+  String? _nodeName;
 
   @override
-  String get nodeName => _nodeName;
+  String get nodeName => _nodeName!;
 
-  int _nodeType;
+  int? _nodeType;
 
   @override
-  int get nodeType => _nodeType;
+  int get nodeType => _nodeType!;
 
-  String _nodeValue;
+  String? _nodeValue;
 
   @override
-  String get nodeValue => _nodeValue;
+  String get nodeValue => _nodeValue!;
 
-  String _publicId;
+  String? _publicId;
 
   @override
-  String get publicId => _publicId;
+  String? get publicId => _publicId;
 
-  String _systemId;
+  String? _systemId;
 
   @override
-  String get systemId => _systemId;
+  String? get systemId => _systemId;
 
-  String _value;
+  String? _value;
 
   @override
-  String get value => _value;
+  String? get value => _value;
 
-  String _xmlVersion;
+  String? _xmlVersion;
 
   @override
-  String get xmlVersion => _xmlVersion;
+  String? get xmlVersion => _xmlVersion;
 
   _Node(this.nodeId);
 
@@ -300,44 +289,42 @@
       'nodeValue': nodeValue
     };
     if (visited.add(nodeId)) {
-      if (attributes != null && attributes.isNotEmpty) {
-        map['attributes'] = flattenAttributesMap(attributes);
+      if (attributes != null && attributes!.isNotEmpty) {
+        map['attributes'] = flattenAttributesMap(attributes!);
       }
       if (childNodeCount != null) {
-        map['childNodeCount'] = childNodeCount;
+        map['childNodeCount'] = childNodeCount!;
       }
-      if (_children != null && _children.isNotEmpty) {
+      if (_children != null && _children!.isNotEmpty) {
         var newChildren = [];
-        _children.forEach((child) {
-          if (child != null) {
-            newChildren.add(child._toJsonInternal(visited));
-          }
+        _children!.forEach((child) {
+          newChildren.add(child._toJsonInternal(visited));
         });
         map['children'] = newChildren;
       }
       if (_contentDocument != null) {
-        map['contentDocument'] = _contentDocument._toJsonInternal(visited);
+        map['contentDocument'] = _contentDocument!._toJsonInternal(visited);
       }
       if (documentUrl != null) {
-        map['documentUrl'] = documentUrl;
+        map['documentUrl'] = documentUrl!;
       }
       if (internalSubset != null) {
-        map['internalSubset'] = internalSubset;
+        map['internalSubset'] = internalSubset!;
       }
       if (name != null) {
-        map['name'] = name;
+        map['name'] = name!;
       }
       if (publicId != null) {
-        map['publicId'] = publicId;
+        map['publicId'] = publicId!;
       }
       if (systemId != null) {
-        map['systemId'] = systemId;
+        map['systemId'] = systemId!;
       }
       if (value != null) {
-        map['value'] = value;
+        map['value'] = value!;
       }
       if (xmlVersion != null) {
-        map['xmlVersion'] = xmlVersion;
+        map['xmlVersion'] = xmlVersion!;
       }
     }
     return map;
@@ -347,9 +334,7 @@
 List<String> flattenAttributesMap(Map<String, String> attributes) {
   var result = <String>[];
   attributes.forEach((k, v) {
-    if (k != null) {
-      result..add(k)..add(v);
-    }
+    result..add(k)..add(v);
   });
   return result;
 }
diff --git a/lib/forwarder.dart b/lib/forwarder.dart
index 85f9f47..bd29583 100644
--- a/lib/forwarder.dart
+++ b/lib/forwarder.dart
@@ -21,7 +21,7 @@
   final Stream<String> _in;
   final StreamSink _out;
   final WipConnection _debugger;
-  final WipDom domModel;
+  final WipDom? domModel;
 
   /// If false, no Debugger.paused events will be forwarded back to the client.
   /// This gets automatically set to true if a breakpoint is set by the client.
@@ -33,7 +33,7 @@
       new StreamController.broadcast();
 
   factory WipForwarder(WipConnection debugger, Stream<String> stream,
-      {StreamSink sink, WipDom domModel}) {
+      {StreamSink? sink, WipDom? domModel}) {
     if (sink == null) {
       sink = stream as StreamSink;
     }
@@ -63,12 +63,12 @@
       if (domModel != null) {
         switch (method) {
           case 'DOM.getDocument':
-            response['result'] = {'root': (await domModel.getDocument())};
+            response['result'] = {'root': (await domModel!.getDocument())};
             processed = true;
             break;
           case 'DOM.getAttributes':
             var attributes = flattenAttributesMap(
-                await domModel.getAttributes(params['nodeId'] as int));
+                await domModel!.getAttributes(params['nodeId'] as int));
             response['result'] = {'attributes': attributes};
             processed = true;
             break;
diff --git a/lib/src/console.dart b/lib/src/console.dart
index e1500d7..689736a 100644
--- a/lib/src/console.dart
+++ b/lib/src/console.dart
@@ -27,17 +27,17 @@
 class ConsoleMessageEvent extends WipEvent {
   ConsoleMessageEvent(Map<String, dynamic> json) : super(json);
 
-  Map get _message => params['message'] as Map;
+  Map get _message => params!['message'] as Map;
 
   String get text => _message['text'] as String;
 
   String get level => _message['level'] as String;
 
-  String get url => _message['url'] as String;
+  String? get url => _message['url'] as String?;
 
   Iterable<WipConsoleCallFrame> getStackTrace() {
     if (_message.containsKey('stackTrace')) {
-      return (params['stackTrace'] as List).map((frame) =>
+      return (params!['stackTrace'] as List).map((frame) =>
           new WipConsoleCallFrame.fromMap(frame as Map<String, dynamic>));
     } else {
       return [];
diff --git a/lib/src/debugger.dart b/lib/src/debugger.dart
index a98f2c9..577cb40 100644
--- a/lib/src/debugger.dart
+++ b/lib/src/debugger.dart
@@ -25,19 +25,19 @@
   Future<String> getScriptSource(String scriptId) async {
     return (await sendCommand('Debugger.getScriptSource',
             params: {'scriptId': scriptId}))
-        .result['scriptSource'] as String;
+        .result!['scriptSource'] as String;
   }
 
   Future<WipResponse> pause() => sendCommand('Debugger.pause');
 
   Future<WipResponse> resume() => sendCommand('Debugger.resume');
 
-  Future<WipResponse> stepInto({Map<String, dynamic> params}) =>
+  Future<WipResponse> stepInto({Map<String, dynamic>? params}) =>
       sendCommand('Debugger.stepInto', params: params);
 
   Future<WipResponse> stepOut() => sendCommand('Debugger.stepOut');
 
-  Future<WipResponse> stepOver({Map<String, dynamic> params}) =>
+  Future<WipResponse> stepOver({Map<String, dynamic>? params}) =>
       sendCommand('Debugger.stepOver', params: params);
 
   Future<WipResponse> setPauseOnExceptions(PauseState state) {
@@ -53,7 +53,7 @@
   ///    evaluates to true.
   Future<SetBreakpointResponse> setBreakpoint(
     WipLocation location, {
-    String condition,
+    String? condition,
   }) async {
     Map<String, dynamic> params = {
       'location': location.toJsonMap(),
@@ -65,9 +65,9 @@
     final WipResponse response =
         await sendCommand('Debugger.setBreakpoint', params: params);
 
-    if (response.result.containsKey('exceptionDetails')) {
+    if (response.result!.containsKey('exceptionDetails')) {
       throw new ExceptionDetails(
-          response.result['exceptionDetails'] as Map<String, dynamic>);
+          response.result!['exceptionDetails'] as Map<String, dynamic>);
     } else {
       return new SetBreakpointResponse(response.json);
     }
@@ -88,7 +88,7 @@
   Future<RemoteObject> evaluateOnCallFrame(
     String callFrameId,
     String expression, {
-    bool returnByValue,
+    bool? returnByValue,
   }) async {
     Map<String, dynamic> params = {
       'callFrameId': callFrameId,
@@ -101,12 +101,12 @@
     final WipResponse response =
         await sendCommand('Debugger.evaluateOnCallFrame', params: params);
 
-    if (response.result.containsKey('exceptionDetails')) {
+    if (response.result!.containsKey('exceptionDetails')) {
       throw new ExceptionDetails(
-          response.result['exceptionDetails'] as Map<String, dynamic>);
+          response.result!['exceptionDetails'] as Map<String, dynamic>);
     } else {
       return new RemoteObject(
-          response.result['result'] as Map<String, dynamic>);
+          response.result!['result'] as Map<String, dynamic>);
     }
   }
 
@@ -120,8 +120,8 @@
   ///   (non-nested) function as start.
   Future<List<WipBreakLocation>> getPossibleBreakpoints(
     WipLocation start, {
-    WipLocation end,
-    bool restrictToFunction,
+    WipLocation? end,
+    bool? restrictToFunction,
   }) async {
     Map<String, dynamic> params = {
       'start': start.toJsonMap(),
@@ -136,11 +136,11 @@
     final WipResponse response =
         await sendCommand('Debugger.getPossibleBreakpoints', params: params);
 
-    if (response.result.containsKey('exceptionDetails')) {
+    if (response.result!.containsKey('exceptionDetails')) {
       throw new ExceptionDetails(
-          response.result['exceptionDetails'] as Map<String, dynamic>);
+          response.result!['exceptionDetails'] as Map<String, dynamic>);
     } else {
-      List locations = response.result['locations'];
+      List locations = response.result!['locations'];
       return List.from(locations.map((map) => WipBreakLocation(map)));
     }
   }
@@ -190,7 +190,7 @@
 class ScriptParsedEvent extends WipEvent {
   ScriptParsedEvent(Map<String, dynamic> json) : super(json);
 
-  WipScript get script => new WipScript(params);
+  WipScript get script => new WipScript(params!);
 
   String toString() => script.toString();
 }
@@ -209,7 +209,7 @@
   DebuggerPausedEvent(Map<String, dynamic> json) : super(json);
 
   /// Call stack the virtual machine stopped on.
-  List<WipCallFrame> getCallFrames() => (params['callFrames'] as List)
+  List<WipCallFrame> getCallFrames() => (params!['callFrames'] as List)
       .map((frame) => new WipCallFrame(frame as Map<String, dynamic>))
       .toList();
 
@@ -217,21 +217,21 @@
   ///
   /// Allowed Values: ambiguous, assert, debugCommand, DOM, EventListener,
   /// exception, instrumentation, OOM, other, promiseRejection, XHR.
-  String get reason => params['reason'] as String;
+  String get reason => params!['reason'] as String;
 
   /// Object containing break-specific auxiliary properties.
-  Object get data => params['data'];
+  Object? get data => params!['data'];
 
   /// Hit breakpoints IDs (optional).
-  List<String> get hitBreakpoints {
-    if (params['hitBreakpoints'] == null) return null;
-    return (params['hitBreakpoints'] as List).cast<String>();
+  List<String>? get hitBreakpoints {
+    if (params!['hitBreakpoints'] == null) return null;
+    return (params!['hitBreakpoints'] as List).cast<String>();
   }
 
   /// Async stack trace, if any.
-  StackTrace get asyncStackTrace => params['asyncStackTrace'] == null
+  StackTrace? get asyncStackTrace => params!['asyncStackTrace'] == null
       ? null
-      : StackTrace(params['asyncStackTrace']);
+      : StackTrace(params!['asyncStackTrace']);
 
   String toString() => 'paused: ${reason}';
 }
@@ -270,7 +270,7 @@
   /// The value being returned, if the function is at return point.
   ///
   /// (optional)
-  RemoteObject get returnValue {
+  RemoteObject? get returnValue {
     return json.containsKey('returnValue')
         ? new RemoteObject(json['returnValue'] as Map<String, dynamic>)
         : null;
@@ -284,7 +284,7 @@
 
   WipLocation(this.json);
 
-  WipLocation.fromValues(String scriptId, int lineNumber, {int columnNumber})
+  WipLocation.fromValues(String scriptId, int lineNumber, {int? columnNumber})
       : json = {} {
     json['scriptId'] = scriptId;
     json['lineNumber'] = lineNumber;
@@ -297,7 +297,7 @@
 
   int get lineNumber => json['lineNumber'];
 
-  int get columnNumber => json['columnNumber'];
+  int? get columnNumber => json['columnNumber'];
 
   Map<String, dynamic> toJsonMap() {
     return json;
@@ -323,9 +323,9 @@
 
   int get endColumn => json['endColumn'] as int;
 
-  bool get isContentScript => json['isContentScript'] as bool;
+  bool? get isContentScript => json['isContentScript'] as bool?;
 
-  String get sourceMapURL => json['sourceMapURL'] as String;
+  String? get sourceMapURL => json['sourceMapURL'] as String?;
 
   String toString() => '[script ${scriptId}: ${url}]';
 }
@@ -339,7 +339,7 @@
   String get scope => json['type'] as String;
 
   /// Name of the scope, null if unnamed closure or global scope
-  String get name => json['name'] as String;
+  String? get name => json['name'] as String?;
 
   /// Object representing the scope. For global and with scopes it represents
   /// the actual object; for the rest of the scopes, it is artificial transient
@@ -352,7 +352,7 @@
   WipBreakLocation(Map<String, dynamic> json) : super(json);
 
   WipBreakLocation.fromValues(String scriptId, int lineNumber,
-      {int columnNumber, String type})
+      {int? columnNumber, String? type})
       : super.fromValues(scriptId, lineNumber, columnNumber: columnNumber) {
     if (type != null) {
       json['type'] = type;
@@ -360,14 +360,14 @@
   }
 
   /// Allowed Values: `debuggerStatement`, `call`, `return`.
-  String get type => json['type'];
+  String? get type => json['type'] as String?;
 }
 
 /// The response from [WipDebugger.setBreakpoint].
 class SetBreakpointResponse extends WipResponse {
   SetBreakpointResponse(Map<String, dynamic> json) : super(json);
 
-  String get breakpointId => result['breakpointId'];
+  String get breakpointId => result!['breakpointId'];
 
-  WipLocation get actualLocation => WipLocation(result['actualLocation']);
+  WipLocation get actualLocation => WipLocation(result!['actualLocation']);
 }
diff --git a/lib/src/dom.dart b/lib/src/dom.dart
index d1fd1e5..be0d978 100644
--- a/lib/src/dom.dart
+++ b/lib/src/dom.dart
@@ -14,25 +14,25 @@
   Future<Map<String, String>> getAttributes(int nodeId) async {
     WipResponse resp =
         await sendCommand('DOM.getAttributes', params: {'nodeId': nodeId});
-    return _attributeListToMap((resp.result['attributes'] as List).cast());
+    return _attributeListToMap((resp.result!['attributes'] as List).cast());
   }
 
   Future<Node> getDocument() async =>
-      new Node((await sendCommand('DOM.getDocument')).result['root']
+      new Node((await sendCommand('DOM.getDocument')).result!['root']
           as Map<String, dynamic>);
 
   Future<String> getOuterHtml(int nodeId) async =>
       (await sendCommand('DOM.getOuterHTML', params: {'nodeId': nodeId}))
-          .result['root'] as String;
+          .result!['root'] as String;
 
-  Future hideHighlight() => sendCommand('DOM.hideHighlight');
+  Future<void> hideHighlight() => sendCommand('DOM.hideHighlight');
 
-  Future highlightNode(int nodeId,
-      {Rgba borderColor,
-      Rgba contentColor,
-      Rgba marginColor,
-      Rgba paddingColor,
-      bool showInfo}) {
+  Future<void> highlightNode(int nodeId,
+      {Rgba? borderColor,
+      Rgba? contentColor,
+      Rgba? marginColor,
+      Rgba? paddingColor,
+      bool? showInfo}) {
     var params = <String, dynamic>{'nodeId': nodeId, 'highlightConfig': {}};
 
     if (borderColor != null) {
@@ -58,8 +58,8 @@
     return sendCommand('DOM.highlightNode', params: params);
   }
 
-  Future highlightRect(int x, int y, int width, int height,
-      {Rgba color, Rgba outlineColor}) {
+  Future<void> highlightRect(int x, int y, int width, int height,
+      {Rgba? color, Rgba? outlineColor}) {
     var params = <String, dynamic>{
       'x': x,
       'y': y,
@@ -79,7 +79,7 @@
   }
 
   Future<int> moveTo(int nodeId, int targetNodeId,
-      {int insertBeforeNodeId}) async {
+      {int? insertBeforeNodeId}) async {
     var params = {'nodeId': nodeId, 'targetNodeId': targetNodeId};
 
     if (insertBeforeNodeId != null) {
@@ -87,52 +87,52 @@
     }
 
     var resp = await sendCommand('DOM.moveTo', params: params);
-    return resp.result['nodeId'] as int;
+    return resp.result!['nodeId'] as int;
   }
 
   Future<int> querySelector(int nodeId, String selector) async {
     var resp = await sendCommand('DOM.querySelector',
         params: {'nodeId': nodeId, 'selector': selector});
-    return resp.result['nodeId'] as int;
+    return resp.result!['nodeId'] as int;
   }
 
   Future<List<int>> querySelectorAll(int nodeId, String selector) async {
     var resp = await sendCommand('DOM.querySelectorAll',
         params: {'nodeId': nodeId, 'selector': selector});
-    return (resp.result['nodeIds'] as List).cast();
+    return (resp.result!['nodeIds'] as List).cast();
   }
 
-  Future removeAttribute(int nodeId, String name) =>
+  Future<void> removeAttribute(int nodeId, String name) =>
       sendCommand('DOM.removeAttribute',
           params: {'nodeId': nodeId, 'name': name});
 
-  Future removeNode(int nodeId) =>
+  Future<void> removeNode(int nodeId) =>
       sendCommand('DOM.removeNode', params: {'nodeId': nodeId});
 
-  Future requestChildNodes(int nodeId) =>
+  Future<void> requestChildNodes(int nodeId) =>
       sendCommand('DOM.requestChildNodes', params: {'nodeId': nodeId});
 
   Future<int> requestNode(String objectId) async {
     var resp =
         await sendCommand('DOM.requestNode', params: {'objectId': objectId});
-    return resp.result['nodeId'] as int;
+    return resp.result!['nodeId'] as int;
   }
 
-  Future<RemoteObject> resolveNode(int nodeId, {String objectGroup}) async {
+  Future<RemoteObject> resolveNode(int nodeId, {String? objectGroup}) async {
     var params = <String, dynamic>{'nodeId': nodeId};
     if (objectGroup != null) {
       params['objectGroup'] = objectGroup;
     }
 
     var resp = await sendCommand('DOM.resolveNode', params: params);
-    return new RemoteObject(resp.result['object'] as Map<String, dynamic>);
+    return new RemoteObject(resp.result!['object'] as Map<String, dynamic>);
   }
 
-  Future setAttributeValue(int nodeId, String name, String value) =>
+  Future<void> setAttributeValue(int nodeId, String name, String value) =>
       sendCommand('DOM.setAttributeValue',
           params: {'nodeId': nodeId, 'name': name, 'value': value});
 
-  Future setAttributesAsText(int nodeId, String text, {String name}) {
+  Future<void> setAttributesAsText(int nodeId, String text, {String? name}) {
     var params = {'nodeId': nodeId, 'text': text};
     if (name != null) {
       params['name'] = name;
@@ -143,14 +143,14 @@
   Future<int> setNodeName(int nodeId, String name) async {
     var resp = await sendCommand('DOM.setNodeName',
         params: {'nodeId': nodeId, 'name': name});
-    return resp.result['nodeId'] as int;
+    return resp.result!['nodeId'] as int;
   }
 
-  Future setNodeValue(int nodeId, String value) =>
+  Future<void> setNodeValue(int nodeId, String value) =>
       sendCommand('DOM.setNodeValue',
           params: {'nodeId': nodeId, 'value': value});
 
-  Future setOuterHtml(int nodeId, String outerHtml) =>
+  Future<void> setOuterHtml(int nodeId, String outerHtml) =>
       sendCommand('DOM.setOuterHTML',
           params: {'nodeId': nodeId, 'outerHtml': outerHtml});
 
@@ -190,59 +190,53 @@
 class AttributeModifiedEvent extends WipEvent {
   AttributeModifiedEvent(Map<String, dynamic> json) : super(json);
 
-  int get nodeId => params['nodeId'] as int;
+  int get nodeId => params!['nodeId'] as int;
 
-  String get name => params['name'] as String;
+  String get name => params!['name'] as String;
 
-  String get value => params['value'] as String;
+  String get value => params!['value'] as String;
 }
 
 class AttributeRemovedEvent extends WipEvent {
   AttributeRemovedEvent(Map<String, dynamic> json) : super(json);
 
-  int get nodeId => params['nodeId'] as int;
+  int get nodeId => params!['nodeId'] as int;
 
-  String get name => params['name'] as String;
+  String get name => params!['name'] as String;
 }
 
 class CharacterDataModifiedEvent extends WipEvent {
   CharacterDataModifiedEvent(Map<String, dynamic> json) : super(json);
 
-  int get nodeId => params['nodeId'] as int;
+  int get nodeId => params!['nodeId'] as int;
 
-  String get characterData => params['characterData'] as String;
+  String get characterData => params!['characterData'] as String;
 }
 
 class ChildNodeCountUpdatedEvent extends WipEvent {
   ChildNodeCountUpdatedEvent(Map<String, dynamic> json) : super(json);
 
-  int get nodeId => params['nodeId'] as int;
+  int get nodeId => params!['nodeId'] as int;
 
-  int get childNodeCount => params['childNodeCount'] as int;
+  int get childNodeCount => params!['childNodeCount'] as int;
 }
 
 class ChildNodeInsertedEvent extends WipEvent {
   ChildNodeInsertedEvent(Map<String, dynamic> json) : super(json);
 
-  int get parentNodeId => params['parentNodeId'] as int;
+  int get parentNodeId => params!['parentNodeId'] as int;
 
-  int get previousNodeId => params['previousNodeId'] as int;
-  Node _node;
+  int get previousNodeId => params!['previousNodeId'] as int;
 
-  Node get node {
-    if (_node == null) {
-      _node = new Node(params['node'] as Map<String, dynamic>);
-    }
-    return _node;
-  }
+  late final node = Node(params!['node'] as Map<String, dynamic>);
 }
 
 class ChildNodeRemovedEvent extends WipEvent {
   ChildNodeRemovedEvent(Map<String, dynamic> json) : super(json);
 
-  int get parentNodeId => params['parentNodeId'] as int;
+  int get parentNodeId => params!['parentNodeId'] as int;
 
-  int get nodeId => params['nodeId'] as int;
+  int get nodeId => params!['nodeId'] as int;
 }
 
 class DocumentUpdatedEvent extends WipEvent {
@@ -252,10 +246,10 @@
 class SetChildNodesEvent extends WipEvent {
   SetChildNodesEvent(Map<String, dynamic> json) : super(json);
 
-  int get nodeId => params['parentId'] as int;
+  int get nodeId => params!['parentId'] as int;
 
   Iterable<Node> get nodes sync* {
-    for (Map node in params['nodes']) {
+    for (Map node in params!['nodes']) {
       yield new Node(node as Map<String, dynamic>);
     }
   }
@@ -271,41 +265,31 @@
 
   Node(this._map);
 
-  Map<String, String> _attributes;
+  late final Map<String, String>? attributes = _map.containsKey('attributes')
+      ? _attributeListToMap((_map['attributes'] as List).cast())
+      : null;
 
-  Map<String, String> get attributes {
-    if (_attributes == null && _map.containsKey('attributes')) {
-      _attributes = _attributeListToMap((_map['attributes'] as List).cast());
-    }
-    return _attributes;
-  }
+  int? get childNodeCount => _map['childNodeCount'] as int?;
 
-  int get childNodeCount => _map['childNodeCount'] as int;
+  late final List<Node>? children = _map.containsKey('children')
+      ? UnmodifiableListView((_map['children'] as List)
+          .map((c) => new Node(c as Map<String, dynamic>)))
+      : null;
 
-  List<Node> _children;
-
-  List<Node> get children {
-    if (_children == null && _map.containsKey('children')) {
-      _children = new UnmodifiableListView((_map['children'] as List)
-          .map((c) => new Node(c as Map<String, dynamic>)));
-    }
-    return _children;
-  }
-
-  Node get contentDocument {
+  Node? get contentDocument {
     if (_map.containsKey('contentDocument')) {
       return new Node(_map['contentDocument'] as Map<String, dynamic>);
     }
     return null;
   }
 
-  String get documentUrl => _map['documentURL'] as String;
+  String? get documentUrl => _map['documentURL'] as String?;
 
-  String get internalSubset => _map['internalSubset'] as String;
+  String? get internalSubset => _map['internalSubset'] as String?;
 
   String get localName => _map['localName'] as String;
 
-  String get name => _map['name'] as String;
+  String? get name => _map['name'] as String?;
 
   int get nodeId => _map['nodeId'] as int;
 
@@ -315,29 +299,29 @@
 
   String get nodeValue => _map['nodeValue'] as String;
 
-  String get publicId => _map['publicId'] as String;
+  String? get publicId => _map['publicId'] as String?;
 
-  String get systemId => _map['systemId'] as String;
+  String? get systemId => _map['systemId'] as String?;
 
-  String get value => _map['value'] as String;
+  String? get value => _map['value'] as String?;
 
-  String get xmlVersion => _map['xmlVersion'] as String;
+  String? get xmlVersion => _map['xmlVersion'] as String?;
 
   String toString() => '$nodeName: $nodeId $attributes';
 }
 
 class Rgba {
-  final int a;
+  final int? a;
   final int b;
   final int r;
   final int g;
 
   Rgba(this.r, this.g, this.b, [this.a]);
 
-  Map toJson() {
+  Map<String, int> toJson() {
     var json = {'r': r, 'g': g, 'b': b};
     if (a != null) {
-      json['a'] = a;
+      json['a'] = a!;
     }
     return json;
   }
diff --git a/lib/src/log.dart b/lib/src/log.dart
index 6dc5b46..97ec139 100644
--- a/lib/src/log.dart
+++ b/lib/src/log.dart
@@ -19,7 +19,7 @@
 class LogEntry extends WipEvent {
   LogEntry(Map<String, dynamic> json) : super(json);
 
-  Map<String, dynamic> get _entry => params['entry'] as Map<String, dynamic>;
+  Map<String, dynamic> get _entry => params!['entry'] as Map<String, dynamic>;
 
   /// Log entry source. Allowed values: xml, javascript, network, storage,
   /// appcache, rendering, security, deprecation, worker, violation,
@@ -34,7 +34,7 @@
 
   /// URL of the resource if known.
   @optional
-  String get url => _entry['url'] as String;
+  String? get url => _entry['url'] as String?;
 
   /// Timestamp when this entry was added.
   num get timestamp => _entry['timestamp'] as num;
diff --git a/lib/src/page.dart b/lib/src/page.dart
index e8bf1e5..5b125d0 100644
--- a/lib/src/page.dart
+++ b/lib/src/page.dart
@@ -16,7 +16,7 @@
       sendCommand('Page.navigate', params: {'url': url});
 
   Future<WipResponse> reload(
-      {bool ignoreCache, String scriptToEvaluateOnLoad}) {
+      {bool? ignoreCache, String? scriptToEvaluateOnLoad}) {
     var params = <String, dynamic>{};
     if (ignoreCache != null) {
       params['ignoreCache'] = ignoreCache;
diff --git a/lib/src/runtime.dart b/lib/src/runtime.dart
index da09256..8a66086 100644
--- a/lib/src/runtime.dart
+++ b/lib/src/runtime.dart
@@ -28,9 +28,9 @@
   ///     return once awaited promise is resolved.
   Future<RemoteObject> evaluate(
     String expression, {
-    bool returnByValue,
-    int contextId,
-    bool awaitPromise,
+    bool? returnByValue,
+    int? contextId,
+    bool? awaitPromise,
   }) async {
     Map<String, dynamic> params = {
       'expression': expression,
@@ -48,12 +48,12 @@
     final WipResponse response =
         await sendCommand('Runtime.evaluate', params: params);
 
-    if (response.result.containsKey('exceptionDetails')) {
+    if (response.result!.containsKey('exceptionDetails')) {
       throw new ExceptionDetails(
-          response.result['exceptionDetails'] as Map<String, dynamic>);
+          response.result!['exceptionDetails'] as Map<String, dynamic>);
     } else {
       return new RemoteObject(
-          response.result['result'] as Map<String, dynamic>);
+          response.result!['result'] as Map<String, dynamic>);
     }
   }
 
@@ -64,10 +64,10 @@
   /// object (int, String, double, bool).
   Future<RemoteObject> callFunctionOn(
     String functionDeclaration, {
-    String objectId,
-    List<dynamic> arguments,
-    bool returnByValue,
-    int executionContextId,
+    String? objectId,
+    List<dynamic>? arguments,
+    bool? returnByValue,
+    int? executionContextId,
   }) async {
     Map<String, dynamic> params = {
       'functionDeclaration': functionDeclaration,
@@ -95,12 +95,12 @@
     final WipResponse response =
         await sendCommand('Runtime.callFunctionOn', params: params);
 
-    if (response.result.containsKey('exceptionDetails')) {
+    if (response.result!.containsKey('exceptionDetails')) {
       throw new ExceptionDetails(
-          response.result['exceptionDetails'] as Map<String, dynamic>);
+          response.result!['exceptionDetails'] as Map<String, dynamic>);
     } else {
       return new RemoteObject(
-          response.result['result'] as Map<String, dynamic>);
+          response.result!['result'] as Map<String, dynamic>);
     }
   }
 
@@ -109,13 +109,13 @@
   @experimental
   Future<HeapUsage> getHeapUsage() async {
     final WipResponse response = await sendCommand('Runtime.getHeapUsage');
-    return HeapUsage(response.result);
+    return HeapUsage(response.result!);
   }
 
   /// Returns the isolate id.
   @experimental
   Future<String> getIsolateId() async {
-    return (await sendCommand('Runtime.getIsolateId')).result['id'] as String;
+    return (await sendCommand('Runtime.getIsolateId')).result!['id'] as String;
   }
 
   /// Returns properties of a given object. Object group of the result is
@@ -127,7 +127,7 @@
   /// itself, not to its prototype chain.
   Future<List<PropertyDescriptor>> getProperties(
     RemoteObject object, {
-    bool ownProperties,
+    bool? ownProperties,
   }) async {
     Map<String, dynamic> params = {
       'objectId': object.objectId,
@@ -139,11 +139,11 @@
     final WipResponse response =
         await sendCommand('Runtime.getProperties', params: params);
 
-    if (response.result.containsKey('exceptionDetails')) {
+    if (response.result!.containsKey('exceptionDetails')) {
       throw new ExceptionDetails(
-          response.result['exceptionDetails'] as Map<String, dynamic>);
+          response.result!['exceptionDetails'] as Map<String, dynamic>);
     } else {
-      List locations = response.result['result'];
+      List locations = response.result!['result'];
       return List.from(locations.map((map) => PropertyDescriptor(map)));
     }
   }
@@ -161,12 +161,12 @@
       eventStream(
           'Runtime.executionContextCreated',
           (WipEvent event) =>
-              new ExecutionContextDescription(event.params['context']));
+              new ExecutionContextDescription(event.params!['context']));
 
   /// Issued when execution context is destroyed.
   Stream<String> get onExecutionContextDestroyed => eventStream(
       'Runtime.executionContextDestroyed',
-      (WipEvent event) => event.params['executionContextId']);
+      (WipEvent event) => event.params!['executionContextId']);
 
   /// Issued when all executionContexts were cleared in browser.
   Stream get onExecutionContextsCleared => eventStream(
@@ -180,13 +180,13 @@
   /// Type of the call. Allowed values: log, debug, info, error, warning, dir,
   /// dirxml, table, trace, clear, startGroup, startGroupCollapsed, endGroup,
   /// assert, profile, profileEnd.
-  String get type => params['type'] as String;
+  String get type => params!['type'] as String;
 
   /// Call timestamp.
-  num get timestamp => params['timestamp'] as num;
+  num get timestamp => params!['timestamp'] as num;
 
   /// Call arguments.
-  List<RemoteObject> get args => (params['args'] as List)
+  List<RemoteObject> get args => (params!['args'] as List)
       .map((m) => new RemoteObject(m as Map<String, dynamic>))
       .toList();
 }
@@ -212,10 +212,10 @@
   ExceptionThrownEvent(Map<String, dynamic> json) : super(json);
 
   /// Timestamp of the exception.
-  int get timestamp => params['timestamp'] as int;
+  int get timestamp => params!['timestamp'] as int;
 
   ExceptionDetails get exceptionDetails =>
-      new ExceptionDetails(params['exceptionDetails'] as Map<String, dynamic>);
+      new ExceptionDetails(params!['exceptionDetails'] as Map<String, dynamic>);
 }
 
 class ExceptionDetails implements Exception {
@@ -243,17 +243,17 @@
 
   /// Script ID of the exception location.
   @optional
-  String get scriptId => json['scriptId'] as String;
+  String? get scriptId => json['scriptId'] as String?;
 
   /// JavaScript stack trace if available.
   @optional
-  StackTrace get stackTrace => json['stackTrace'] == null
+  StackTrace? get stackTrace => json['stackTrace'] == null
       ? null
       : new StackTrace(json['stackTrace'] as Map<String, dynamic>);
 
   /// Exception object if available.
   @optional
-  RemoteObject get exception => json['exception'] == null
+  RemoteObject? get exception => json['exception'] == null
       ? null
       : new RemoteObject(json['exception'] as Map<String, dynamic>);
 
@@ -278,7 +278,7 @@
   /// Asynchronous JavaScript stack trace that preceded this stack, if
   /// available.
   @optional
-  StackTrace get parent {
+  StackTrace? get parent {
     return json['parent'] == null ? null : StackTrace(json['parent']);
   }
 
@@ -341,22 +341,22 @@
   /// Allowed Values: array, null, node, regexp, date, map, set, weakmap,
   /// weakset, iterator, generator, error, proxy, promise, typedarray,
   /// arraybuffer, dataview, i32, i64, f32, f64, v128, anyref.
-  String get subtype => json['subtype'] as String;
+  String? get subtype => json['subtype'] as String?;
 
   /// Object class (constructor) name.
   ///
   /// Specified for object type values only.
-  String get className => json['className'] as String;
+  String? get className => json['className'] as String?;
 
   /// Remote object value in case of primitive values or JSON values (if it was
   /// requested). (optional)
-  Object get value => json['value'];
+  Object? get value => json['value'];
 
   /// String representation of the object. (optional)
-  String get description => json['description'] as String;
+  String? get description => json['description'] as String?;
 
   /// Unique object identifier (for non-primitive values). (optional)
-  String get objectId => json['objectId'] as String;
+  String? get objectId => json['objectId'] as String?;
 
   @override
   String toString() => '$type $value';
@@ -389,24 +389,24 @@
   String get name => json['name'];
 
   /// The value associated with the property.
-  RemoteObject get value =>
+  RemoteObject? get value =>
       json['value'] != null ? RemoteObject(json['value']) : null;
 
   /// True if the value associated with the property may be changed (data
   /// descriptors only).
-  bool get writable => json['writable'];
+  bool? get writable => json['writable'] as bool?;
 
   /// True if the type of this property descriptor may be changed and if the
   /// property may be deleted from the corresponding object.
-  bool get configurable => json['configurable'];
+  bool get configurable => json['configurable'] as bool;
 
   /// True if this property shows up during enumeration of the properties on the
   /// corresponding object.
-  bool get enumerable => json['enumerable'];
+  bool get enumerable => json['enumerable'] as bool;
 
   /// True if the result was thrown during the evaluation.
-  bool get wasThrown => json['wasThrown'];
+  bool? get wasThrown => json['wasThrown'] as bool?;
 
   /// True if the property is owned for the object.
-  bool get isOwn => json['isOwn'];
+  bool? get isOwn => json['isOwn'] as bool?;
 }
diff --git a/lib/src/target.dart b/lib/src/target.dart
index 5b51382..41a04c1 100644
--- a/lib/src/target.dart
+++ b/lib/src/target.dart
@@ -16,7 +16,7 @@
   Future<String> createTarget(String url) async {
     WipResponse response =
         await sendCommand('Target.createTarget', params: {'url': url});
-    return response.result['targetId'] as String;
+    return response.result!['targetId'] as String;
   }
 
   /// Activates (focuses) the target.
@@ -29,7 +29,7 @@
   Future<bool> closeTarget(String targetId) async {
     WipResponse response =
         await sendCommand('Target.closeTarget', params: {'targetId': targetId});
-    return response.result['success'] as bool;
+    return response.result!['success'] as bool;
   }
 
   /// Inject object to the target's main frame that provides a communication
@@ -43,7 +43,7 @@
   @experimental
   Future<WipResponse> exposeDevToolsProtocol(
     String targetId, {
-    String bindingName,
+    String? bindingName,
   }) {
     final Map<String, dynamic> params = {'targetId': targetId};
     if (bindingName != null) {
diff --git a/lib/webkit_inspection_protocol.dart b/lib/webkit_inspection_protocol.dart
index 1a0330e..a8f6b13 100644
--- a/lib/webkit_inspection_protocol.dart
+++ b/lib/webkit_inspection_protocol.dart
@@ -44,8 +44,8 @@
         (jsonDecode(respBody) as List).map((m) => new ChromeTab(m as Map)));
   }
 
-  Future<ChromeTab> getTab(bool accept(ChromeTab tab),
-      {Duration retryFor}) async {
+  Future<ChromeTab?> getTab(bool accept(ChromeTab tab),
+      {Duration? retryFor}) async {
     var start = new DateTime.now();
     var end = start;
     if (retryFor != null) {
@@ -84,16 +84,16 @@
 
   ChromeTab(this._map);
 
-  String get description => _map['description'] as String;
+  String? get description => _map['description'] as String?;
 
-  String get devtoolsFrontendUrl => _map['devtoolsFrontendUrl'] as String;
+  String? get devtoolsFrontendUrl => _map['devtoolsFrontendUrl'] as String?;
 
-  String get faviconUrl => _map['faviconUrl'] as String;
+  String? get faviconUrl => _map['faviconUrl'] as String?;
 
   /// Ex. `E1999E8A-EE27-0450-9900-5BFF4C69CA83`.
   String get id => _map['id'] as String;
 
-  String get title => _map['title'] as String;
+  String? get title => _map['title'] as String?;
 
   /// Ex. `background_page`, `page`.
   String get type => _map['type'] as String;
@@ -124,33 +124,20 @@
 
   int _nextId = 0;
 
-  WipConsole _console; // ignore: deprecated_member_use
   @Deprecated('This domain is deprecated - use Runtime or Log instead')
-  WipConsole get console => _console;
+  late final WipConsole console = WipConsole(this);
 
-  WipDebugger _debugger;
+  late final WipDebugger debugger = WipDebugger(this);
 
-  WipDebugger get debugger => _debugger;
+  late final WipDom dom = WipDom(this);
 
-  WipDom _dom;
+  late final WipPage page = WipPage(this);
 
-  WipDom get dom => _dom;
+  late final WipTarget target = WipTarget(this);
 
-  WipPage _page;
+  late final WipLog log = WipLog(this);
 
-  WipPage get page => _page;
-
-  WipTarget _target;
-
-  WipTarget get target => _target;
-
-  WipLog _log;
-
-  WipLog get log => _log;
-
-  WipRuntime _runtime;
-
-  WipRuntime get runtime => _runtime;
+  late final WipRuntime runtime = WipRuntime(this);
 
   final StreamController<String> _onSend =
       StreamController.broadcast(sync: true);
@@ -169,14 +156,6 @@
   }
 
   WipConnection._(this.url, this._ws) {
-    _console = new WipConsole(this); // ignore: deprecated_member_use
-    _debugger = new WipDebugger(this);
-    _dom = new WipDom(this);
-    _page = new WipPage(this);
-    _target = new WipTarget(this);
-    _log = new WipLog(this);
-    _runtime = new WipRuntime(this);
-
     _ws.listen((data) {
       var json = jsonDecode(data as String) as Map<String, dynamic>;
       _onReceive.add(data);
@@ -198,7 +177,7 @@
   String toString() => url;
 
   Future<WipResponse> sendCommand(String method,
-      [Map<String, dynamic> params]) {
+      [Map<String, dynamic>? params]) {
     var completer = new Completer<WipResponse>();
     var json = {'id': _nextId++, 'method': method};
     if (params != null) {
@@ -242,11 +221,11 @@
   final Map<String, dynamic> json;
 
   final String method;
-  final Map<String, dynamic> params;
+  final Map<String, dynamic>? params;
 
   WipEvent(this.json)
       : method = json['method'] as String,
-        params = json['params'] as Map<String, dynamic>;
+        params = json['params'] as Map<String, dynamic>?;
 
   String toString() => 'WipEvent: $method($params)';
 }
@@ -255,15 +234,15 @@
   final Map<String, dynamic> json;
 
   final int id;
-  final dynamic error;
+  final Map<String, dynamic>? error;
 
   WipError(this.json)
       : id = json['id'] as int,
-        error = json['error'];
+        error = json['error'] as Map<String, dynamic>?;
 
-  int get code => error == null ? null : error['code'];
+  int? get code => error == null ? null : error!['code'];
 
-  String get message => error == null ? null : error['message'];
+  String? get message => error == null ? null : error!['message'];
 
   String toString() => 'WipError $code $message';
 }
@@ -272,11 +251,11 @@
   final Map<String, dynamic> json;
 
   final int id;
-  final Map<String, dynamic> result;
+  final Map<String, dynamic>? result;
 
   WipResponse(this.json)
       : id = json['id'] as int,
-        result = json['result'] as Map<String, dynamic>;
+        result = json['result'] as Map<String, dynamic>?;
 
   String toString() => 'WipResponse $id: $result';
 }
@@ -290,16 +269,13 @@
   final Map<String, Stream> _eventStreams = {};
 
   final WipConnection connection;
-  Stream<WipDomain> _onClosed;
 
-  Stream<WipDomain> get onClosed => _onClosed;
+  late final Stream<WipDomain> onClosed = StreamTransformer.fromHandlers(
+      handleData: (event, EventSink<WipDomain> sink) {
+    sink.add(this);
+  }).bind(connection.onClose);
 
-  WipDomain(WipConnection connection) : this.connection = connection {
-    this._onClosed = new StreamTransformer.fromHandlers(
-        handleData: (event, EventSink<WipDomain> sink) {
-      sink.add(this);
-    }).bind(connection.onClose);
-  }
+  WipDomain(WipConnection connection) : this.connection = connection;
 
   Stream<T> eventStream<T>(String method, WipEventTransformer<T> transformer) {
     return _eventStreams
@@ -318,7 +294,7 @@
 
   Future<WipResponse> sendCommand(
     String method, {
-    Map<String, dynamic> params,
+    Map<String, dynamic>? params,
   }) {
     return connection.sendCommand(method, params);
   }
diff --git a/pubspec.yaml b/pubspec.yaml
index f08319e..7da6c45 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,18 +1,23 @@
 name: webkit_inspection_protocol
-version: 0.7.5
+version: 1.0.0
 description: A client for the Chrome DevTools Protocol (previously called the Webkit Inspection Protocol).
 homepage: https://github.com/google/webkit_inspection_protocol.dart
 
 environment:
-  sdk: '>=2.0.0 <3.0.0'
+  sdk: '>=2.12.0-0 <3.0.0'
 
 dependencies:
-  logging: '>=0.11.0 <2.0.0'
+  logging: ^1.0.0
 
 dev_dependencies:
-  args: '>=0.13.0 <2.0.0'
-  shelf: ^0.7.0
-  shelf_static: ^0.2.0
-  shelf_web_socket: ^0.2.0
+  args: ^2.0.0
+  shelf: ^1.0.0
+  shelf_static: ^1.0.0
+  shelf_web_socket: ^1.0.0
   test: ^1.16.0
-  webdriver: ^2.0.0
+  webdriver: ^3.0.0
+
+dependency_overrides:
+  test: ^1.16.0
+  shelf_static: ^1.0.0
+  shelf_web_socket: ^1.0.0
diff --git a/test/console_test.dart b/test/console_test.dart
index 46c5abf..e1441bf 100644
--- a/test/console_test.dart
+++ b/test/console_test.dart
@@ -13,7 +13,7 @@
 
 void main() {
   group('WipConsole', () {
-    WipConsole console; // ignore: deprecated_member_use
+    WipConsole? console; // ignore: deprecated_member_use
     List<ConsoleMessageEvent> events = [];
     var subs = [];
 
@@ -35,11 +35,11 @@
       // ignore: deprecated_member_use
       console = (await wipConnection).console;
       events.clear();
-      subs.add(console.onMessage.listen(events.add));
+      subs.add(console!.onMessage.listen(events.add));
     });
 
     tearDown(() async {
-      await console.disable();
+      await console!.disable();
       console = null;
       await closeConnection();
       subs.forEach((s) => s.cancel());
@@ -47,14 +47,14 @@
     });
 
     test('receives new console messages', () async {
-      await console.enable();
+      await console!.enable();
       await navigateToPage('console_test.html');
       await checkMessages(4);
     });
 
     test('receives old console messages', () async {
       await navigateToPage('console_test.html');
-      await console.enable();
+      await console!.enable();
       await checkMessages(4);
     });
 
diff --git a/test/debugger_test.dart b/test/debugger_test.dart
index 8c2c265..9315cfc 100644
--- a/test/debugger_test.dart
+++ b/test/debugger_test.dart
@@ -13,7 +13,7 @@
 
 void main() {
   group('WipDebugger', () {
-    WipDebugger debugger;
+    WipDebugger? debugger;
     List<StreamSubscription> subs = [];
 
     setUp(() async {
@@ -21,7 +21,7 @@
     });
 
     tearDown(() async {
-      await debugger.disable();
+      await debugger!.disable();
       debugger = null;
 
       await closeConnection();
@@ -31,9 +31,9 @@
 
     test('gets script events', () async {
       final controller = StreamController<ScriptParsedEvent>();
-      subs.add(debugger.onScriptParsed.listen(controller.add));
+      subs.add(debugger!.onScriptParsed.listen(controller.add));
 
-      await debugger.enable();
+      await debugger!.enable();
       await navigateToPage('debugger_test.html');
 
       expect(controller.stream.first, isNotNull);
@@ -41,24 +41,24 @@
 
     test('getScriptSource', () async {
       final controller = StreamController<ScriptParsedEvent>();
-      subs.add(debugger.onScriptParsed.listen(controller.add));
+      subs.add(debugger!.onScriptParsed.listen(controller.add));
 
-      await debugger.enable();
+      await debugger!.enable();
       await navigateToPage('debugger_test.html');
 
       final event = await controller.stream
           .firstWhere((event) => event.script.url.endsWith('.html'));
       expect(event.script.scriptId, isNotEmpty);
 
-      final source = await debugger.getScriptSource(event.script.scriptId);
+      final source = await debugger!.getScriptSource(event.script.scriptId);
       expect(source, isNotEmpty);
     });
 
     test('getPossibleBreakpoints', () async {
       final controller = StreamController<ScriptParsedEvent>();
-      subs.add(debugger.onScriptParsed.listen(controller.add));
+      subs.add(debugger!.onScriptParsed.listen(controller.add));
 
-      await debugger.enable();
+      await debugger!.enable();
       await navigateToPage('debugger_test.html');
 
       final event = await controller.stream
@@ -67,7 +67,7 @@
 
       final script = event.script;
 
-      final result = await debugger
+      final result = await debugger!
           .getPossibleBreakpoints(WipLocation.fromValues(script.scriptId, 0));
       expect(result, isNotEmpty);
       expect(result.any((bp) => bp.lineNumber == 10), true);
@@ -75,9 +75,9 @@
 
     test('setBreakpoint / removeBreakpoint', () async {
       final controller = StreamController<ScriptParsedEvent>();
-      subs.add(debugger.onScriptParsed.listen(controller.add));
+      subs.add(debugger!.onScriptParsed.listen(controller.add));
 
-      await debugger.enable();
+      await debugger!.enable();
       await navigateToPage('debugger_test.html');
 
       final event = await controller.stream
@@ -86,11 +86,11 @@
 
       final script = event.script;
 
-      final bpResult = await debugger
+      final bpResult = await debugger!
           .setBreakpoint(WipLocation.fromValues(script.scriptId, 10));
       expect(bpResult.breakpointId, isNotEmpty);
 
-      final result = await debugger.removeBreakpoint(bpResult.breakpointId);
+      final result = await debugger!.removeBreakpoint(bpResult.breakpointId);
       expect(result.result, isEmpty);
     });
   });
diff --git a/test/dom_model_test.dart b/test/dom_model_test.dart
index 762d128..6b9f8d7 100644
--- a/test/dom_model_test.dart
+++ b/test/dom_model_test.dart
@@ -12,7 +12,7 @@
 
 void main() {
   group('WipDomModel', () {
-    WipDom dom;
+    WipDom? dom;
 
     setUp(() async {
       dom = new WipDomModel((await navigateToPage('dom_model_test.html')).dom);
@@ -24,46 +24,46 @@
     });
 
     test('maintains model across getDocument calls', () async {
-      var document1 = await dom.getDocument();
-      var document2 = await dom.getDocument();
+      var document1 = await dom!.getDocument();
+      var document2 = await dom!.getDocument();
       expect(document2.nodeId, document1.nodeId);
     });
 
     test('requestChildNodes updates children', () async {
-      Node htmlNode = (await dom.getDocument()).children[1];
-      for (var child in htmlNode.children) {
+      Node htmlNode = (await dom!.getDocument()).children![1];
+      for (var child in htmlNode.children!) {
         expect(child.children, isNull);
-        await dom.requestChildNodes(child.nodeId);
+        await dom!.requestChildNodes(child.nodeId);
       }
       // wait for children to be updated
-      for (var child in htmlNode.children) {
+      for (var child in htmlNode.children!) {
         expect(child.children, isNotNull);
       }
     });
 
     test('removing a node updates children', () async {
-      Node bodyNode = (await dom.getDocument()).children[1].children[1];
-      await dom.requestChildNodes(bodyNode.nodeId);
-      var childCount = bodyNode.childNodeCount;
-      await dom.removeNode(bodyNode.children.first.nodeId);
+      Node bodyNode = (await dom!.getDocument()).children![1].children![1];
+      await dom!.requestChildNodes(bodyNode.nodeId);
+      var childCount = bodyNode.childNodeCount!;
+      await dom!.removeNode(bodyNode.children!.first.nodeId);
 
       expect(bodyNode.children, hasLength(childCount - 1));
       expect(bodyNode.childNodeCount, childCount - 1);
     });
 
     test('Moving a node updates children', () async {
-      Node bodyNode = (await dom.getDocument()).children[1].children[1];
-      await dom.requestChildNodes(bodyNode.nodeId);
-      Node div1 = bodyNode.children[0];
-      Node div2 = bodyNode.children[1];
+      Node bodyNode = (await dom!.getDocument()).children![1].children![1];
+      await dom!.requestChildNodes(bodyNode.nodeId);
+      Node div1 = bodyNode.children![0];
+      Node div2 = bodyNode.children![1];
 
       expect(div1.childNodeCount, 1);
       expect(div2.childNodeCount, 0);
 
-      await dom.requestChildNodes(div1.nodeId);
-      await dom.requestChildNodes(div2.nodeId);
+      await dom!.requestChildNodes(div1.nodeId);
+      await dom!.requestChildNodes(div2.nodeId);
 
-      await dom.moveTo(div1.children.first.nodeId, div2.nodeId);
+      await dom!.moveTo(div1.children!.first.nodeId, div2.nodeId);
 
       expect(div1.childNodeCount, 0);
       expect(div2.childNodeCount, 1);
@@ -71,56 +71,56 @@
     }, skip: 'google/webkit_inspection_protocol.dart/issues/52');
 
     test('Setting node value updates value', () async {
-      Node bodyNode = (await dom.getDocument()).children[1].children[1];
-      await dom.requestChildNodes(bodyNode.nodeId);
+      Node bodyNode = (await dom!.getDocument()).children![1].children![1];
+      await dom!.requestChildNodes(bodyNode.nodeId);
 
-      Node div1 = bodyNode.children[0];
-      await dom.requestChildNodes(div1.nodeId);
+      Node div1 = bodyNode.children![0];
+      await dom!.requestChildNodes(div1.nodeId);
 
-      Node h1 = div1.children[0];
-      await dom.requestChildNodes(h1.nodeId);
+      Node h1 = div1.children![0];
+      await dom!.requestChildNodes(h1.nodeId);
 
-      Node text = h1.children[0];
+      Node text = h1.children![0];
 
       expect(text.nodeValue, 'test');
 
-      await dom.setNodeValue(text.nodeId, 'some new text');
+      await dom!.setNodeValue(text.nodeId, 'some new text');
 
       expect(text.nodeValue, 'some new text');
     });
 
     test('Adding attribute updates attributes', () async {
-      Node bodyNode = (await dom.getDocument()).children[1].children[1];
-      expect(bodyNode.attributes.containsKey('my-attr'), isFalse);
-      await dom.setAttributeValue(bodyNode.nodeId, 'my-attr', 'my-value');
-      expect(bodyNode.attributes['my-attr'], 'my-value');
+      Node bodyNode = (await dom!.getDocument()).children![1].children![1];
+      expect(bodyNode.attributes!.containsKey('my-attr'), isFalse);
+      await dom!.setAttributeValue(bodyNode.nodeId, 'my-attr', 'my-value');
+      expect(bodyNode.attributes!['my-attr'], 'my-value');
     });
 
     test('Changing attribute updates attributes', () async {
-      Node bodyNode = (await dom.getDocument()).children[1].children[1];
-      expect(bodyNode.attributes['test-attr'], 'test-attr-value');
-      await dom.setAttributeValue(bodyNode.nodeId, 'test-attr', 'my-value');
-      expect(bodyNode.attributes['test-attr'], 'my-value');
+      Node bodyNode = (await dom!.getDocument()).children![1].children![1];
+      expect(bodyNode.attributes!['test-attr'], 'test-attr-value');
+      await dom!.setAttributeValue(bodyNode.nodeId, 'test-attr', 'my-value');
+      expect(bodyNode.attributes!['test-attr'], 'my-value');
     });
 
     test('Removing attribute updates attributes', () async {
-      Node bodyNode = (await dom.getDocument()).children[1].children[1];
-      expect(bodyNode.attributes['test-attr'], 'test-attr-value');
-      await dom.removeAttribute(bodyNode.nodeId, 'test-attr');
-      expect(bodyNode.attributes.containsKey('test-attr'), isFalse);
+      Node bodyNode = (await dom!.getDocument()).children![1].children![1];
+      expect(bodyNode.attributes!['test-attr'], 'test-attr-value');
+      await dom!.removeAttribute(bodyNode.nodeId, 'test-attr');
+      expect(bodyNode.attributes!.containsKey('test-attr'), isFalse);
     });
 
     test('refreshing resets document', () async {
-      var document1 = await dom.getDocument();
+      var document1 = await dom!.getDocument();
       await navigateToPage('dom_model_test.html');
-      var document2 = await dom.getDocument();
+      var document2 = await dom!.getDocument();
       expect(document2.nodeId, isNot(document1.nodeId));
     });
 
     test('getting attributes works', () async {
-      Node bodyNode = (await dom.getDocument()).children[1].children[1];
+      Node bodyNode = (await dom!.getDocument()).children![1].children![1];
       var attributes = bodyNode.attributes;
-      var getAttributes = await dom.getAttributes(bodyNode.nodeId);
+      var getAttributes = await dom!.getAttributes(bodyNode.nodeId);
 
       expect(getAttributes, attributes);
       expect(bodyNode.attributes, attributes);
diff --git a/test/runtime_test.dart b/test/runtime_test.dart
index 4f4b78a..c318203 100644
--- a/test/runtime_test.dart
+++ b/test/runtime_test.dart
@@ -13,7 +13,7 @@
 
 void main() {
   group('WipRuntime', () {
-    WipRuntime runtime;
+    WipRuntime? runtime;
     List<StreamSubscription> subs = [];
 
     setUp(() async {
@@ -21,7 +21,7 @@
     });
 
     tearDown(() async {
-      await runtime.disable();
+      await runtime!.disable();
       runtime = null;
 
       await closeConnection();
@@ -30,37 +30,37 @@
     });
 
     test('getIsolateId', () async {
-      await runtime.enable();
+      await runtime!.enable();
       await navigateToPage('runtime_test.html');
 
-      expect(await runtime.getIsolateId(), isNotEmpty);
+      expect(await runtime!.getIsolateId(), isNotEmpty);
     });
 
     test('getHeapUsage', () async {
-      await runtime.enable();
+      await runtime!.enable();
       await navigateToPage('runtime_test.html');
 
-      HeapUsage usage = await runtime.getHeapUsage();
+      HeapUsage usage = await runtime!.getHeapUsage();
 
       expect(usage.usedSize, greaterThan(0));
       expect(usage.totalSize, greaterThan(0));
     });
 
     test('evaluate', () async {
-      await runtime.enable();
+      await runtime!.enable();
       await navigateToPage('runtime_test.html');
 
-      RemoteObject result = await runtime.evaluate('1+1');
+      RemoteObject result = await runtime!.evaluate('1+1');
       expect(result.type, 'number');
       expect(result.value, 2);
     });
 
     test('callFunctionOn', () async {
-      await runtime.enable();
+      await runtime!.enable();
       await navigateToPage('runtime_test.html');
 
-      RemoteObject console = await runtime.evaluate('console');
-      RemoteObject result = await runtime.callFunctionOn(
+      RemoteObject console = await runtime!.evaluate('console');
+      RemoteObject result = await runtime!.callFunctionOn(
         '''
         function(msg) {
           console.log(msg);
@@ -77,12 +77,12 @@
     });
 
     test('getProperties', () async {
-      await runtime.enable();
+      await runtime!.enable();
       await navigateToPage('runtime_test.html');
 
-      RemoteObject console = await runtime.evaluate('console');
+      RemoteObject console = await runtime!.evaluate('console');
 
-      List<PropertyDescriptor> properties = await runtime.getProperties(
+      List<PropertyDescriptor> properties = await runtime!.getProperties(
         console,
         ownProperties: true,
       );
diff --git a/test/test_setup.dart b/test/test_setup.dart
index 5a5cfbe..5a600b4 100644
--- a/test/test_setup.dart
+++ b/test/test_setup.dart
@@ -10,7 +10,7 @@
 import 'package:webdriver/io.dart';
 import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
 
-Future<WipConnection> _wipConnection;
+Future<WipConnection>? _wipConnection;
 
 /// Returns a (cached) debugger connection to the first regular tab of
 /// the browser with remote debugger running at 'localhost:9222',
@@ -19,17 +19,17 @@
     _wipConnection = () async {
       var debugPort = await _startWebDriver(await _startChromeDriver());
       var chrome = new ChromeConnection('localhost', debugPort);
-      var tab = await chrome
-          .getTab((tab) => !tab.isBackgroundPage && !tab.isChromeExtension);
+      var tab = (await chrome
+          .getTab((tab) => !tab.isBackgroundPage && !tab.isChromeExtension))!;
       var connection = await tab.connect();
       connection.onClose.listen((_) => _wipConnection = null);
       return connection;
     }();
   }
-  return _wipConnection;
+  return _wipConnection!;
 }
 
-Process _chromeDriver;
+Process? _chromeDriver;
 
 /// Starts ChromeDriver and returns the listening port.
 Future<int> _startChromeDriver() async {
@@ -44,7 +44,7 @@
         ['--port=$chromeDriverPort', '--url-base=wd/hub']);
     // On windows this takes a while to boot up, wait for the first line
     // of stdout as a signal that it is ready.
-    await _chromeDriver.stdout
+    await _chromeDriver!.stdout
         .transform(utf8.decoder)
         .transform(const LineSplitter())
         .first;
@@ -55,7 +55,7 @@
   return chromeDriverPort;
 }
 
-WebDriver _webDriver;
+WebDriver? _webDriver;
 
 /// Starts WebDriver and returns the listening debug port.
 Future<int> _startWebDriver(int chromeDriverPort) async {