OLD | NEW |
---|---|
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 import 'dart:async'; | 5 import 'dart:async'; |
6 import 'dart:convert'; | 6 import 'dart:convert'; |
7 import 'dart:html'; | 7 import 'dart:html'; |
8 import 'dart:js' as js; | 8 import 'dart:js' as js; |
9 | 9 |
10 import 'package:stack_trace/stack_trace.dart'; | 10 import 'package:stack_trace/stack_trace.dart'; |
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
81 } else if (message['command'] == 'displayPause') { | 81 } else if (message['command'] == 'displayPause') { |
82 document.body.classes.add('paused'); | 82 document.body.classes.add('paused'); |
83 } else if (message['command'] == 'resume') { | 83 } else if (message['command'] == 'resume') { |
84 document.body.classes.remove('paused'); | 84 document.body.classes.remove('paused'); |
85 } else { | 85 } else { |
86 assert(message['command'] == 'closeSuite'); | 86 assert(message['command'] == 'closeSuite'); |
87 _iframes[message['id']].remove(); | 87 _iframes[message['id']].remove(); |
88 } | 88 } |
89 }); | 89 }); |
90 | 90 |
91 // Send periodic pings to the test runner so it can know when the browser is | |
92 // paused for debugging. | |
93 new Timer.periodic(new Duration(seconds: 1), | |
kevmoo
2016/02/17 16:22:48
const Duration?
| |
94 (_) => serverChannel.sink.add({"command": "ping"})); | |
95 | |
91 var play = document.querySelector("#play"); | 96 var play = document.querySelector("#play"); |
92 play.onClick.listen((_) { | 97 play.onClick.listen((_) { |
93 document.body.classes.remove('paused'); | 98 document.body.classes.remove('paused'); |
94 serverChannel.sink.add({"command": "resume"}); | 99 serverChannel.sink.add({"command": "resume"}); |
95 }); | 100 }); |
96 }, onError: (error, stackTrace) { | 101 }, onError: (error, stackTrace) { |
97 print("$error\n${new Trace.from(stackTrace).terse}"); | 102 print("$error\n${new Trace.from(stackTrace).terse}"); |
98 }); | 103 }); |
99 } | 104 } |
100 | 105 |
101 /// Creates a [MultiChannel] connection to the server, using a [WebSocket] as | 106 /// Creates a [MultiChannel] connection to the server, using a [WebSocket] as |
102 /// the underlying protocol. | 107 /// the underlying protocol. |
103 MultiChannel _connectToServer() { | 108 MultiChannel _connectToServer() { |
104 // The `managerUrl` query parameter contains the WebSocket URL of the remote | 109 // The `managerUrl` query parameter contains the WebSocket URL of the remote |
105 // [BrowserManager] with which this communicates. | 110 // [BrowserManager] with which this communicates. |
106 var currentUrl = Uri.parse(window.location.href); | 111 var currentUrl = Uri.parse(window.location.href); |
107 var webSocket = new WebSocket(currentUrl.queryParameters['managerUrl']); | 112 var webSocket = new WebSocket(currentUrl.queryParameters['managerUrl']); |
108 | 113 |
109 var inputController = new StreamController(sync: true); | 114 var controller = new StreamChannelController(sync: true); |
110 webSocket.onMessage.listen((message) { | 115 webSocket.onMessage.listen((message) { |
111 inputController.add(JSON.decode(message.data)); | 116 controller.local.sink.add(JSON.decode(message.data)); |
112 }); | 117 }); |
113 | 118 |
114 var outputController = new StreamController(sync: true); | 119 controller.local.stream.listen( |
115 outputController.stream.listen( | |
116 (message) => webSocket.send(JSON.encode(message))); | 120 (message) => webSocket.send(JSON.encode(message))); |
117 | 121 |
118 return new MultiChannel( | 122 return new MultiChannel(controller.foreign); |
119 new StreamChannel(inputController.stream, outputController.sink)); | |
120 } | 123 } |
121 | 124 |
122 /// Creates an iframe with `src` [url] and establishes a connection to it using | 125 /// Creates an iframe with `src` [url] and establishes a connection to it using |
123 /// `postMessage`. | 126 /// `postMessage`. |
124 /// | 127 /// |
125 /// [id] identifies the suite loaded in this iframe. | 128 /// [id] identifies the suite loaded in this iframe. |
126 StreamChannel _connectToIframe(String url, int id) { | 129 StreamChannel _connectToIframe(String url, int id) { |
127 var iframe = new IFrameElement(); | 130 var iframe = new IFrameElement(); |
128 _iframes[id] = iframe; | 131 _iframes[id] = iframe; |
129 iframe.src = url; | 132 iframe.src = url; |
130 document.body.children.add(iframe); | 133 document.body.children.add(iframe); |
131 | 134 |
132 var inputController = new StreamController(sync: true); | 135 var controller = new StreamChannelController(sync: true); |
133 var outputController = new StreamController(sync: true); | |
134 | 136 |
135 // Use this to avoid sending a message to the iframe before it's sent a | 137 // Use this to avoid sending a message to the iframe before it's sent a |
136 // message to us. This ensures that no messages get dropped on the floor. | 138 // message to us. This ensures that no messages get dropped on the floor. |
137 var readyCompleter = new Completer(); | 139 var readyCompleter = new Completer(); |
138 | 140 |
139 // TODO(nweiz): use MessageChannel once Firefox supports it | 141 // TODO(nweiz): use MessageChannel once Firefox supports it |
140 // (http://caniuse.com/#search=MessageChannel). | 142 // (http://caniuse.com/#search=MessageChannel). |
141 window.onMessage.listen((message) { | 143 window.onMessage.listen((message) { |
142 // A message on the Window can theoretically come from any website. It's | 144 // A message on the Window can theoretically come from any website. It's |
143 // very unlikely that a malicious site would care about hacking someone's | 145 // very unlikely that a malicious site would care about hacking someone's |
144 // unit tests, let alone be able to find the test server while it's | 146 // unit tests, let alone be able to find the test server while it's |
145 // running, but it's good practice to check the origin anyway. | 147 // running, but it's good practice to check the origin anyway. |
146 if (message.origin != window.location.origin) return; | 148 if (message.origin != window.location.origin) return; |
147 | 149 |
148 // TODO(nweiz): Stop manually checking href here once issue 22554 is | 150 // TODO(nweiz): Stop manually checking href here once issue 22554 is |
149 // fixed. | 151 // fixed. |
150 if (message.data["href"] != iframe.src) return; | 152 if (message.data["href"] != iframe.src) return; |
151 | 153 |
152 message.stopPropagation(); | 154 message.stopPropagation(); |
153 inputController.add(message.data["data"]); | 155 |
154 if (!readyCompleter.isCompleted) readyCompleter.complete(); | 156 // This message indicates that the iframe is actively listening for events. |
157 if (message.data["ready"] == true) { | |
158 readyCompleter.complete(); | |
159 } else { | |
160 controller.local.sink.add(message.data["data"]); | |
161 } | |
155 }); | 162 }); |
156 | 163 |
157 outputController.stream.listen((message) async { | 164 controller.local.stream.listen((message) async { |
158 await readyCompleter.future; | 165 await readyCompleter.future; |
159 iframe.contentWindow.postMessage(message, window.location.origin); | 166 |
167 // JSON-encode the message to work around sdk#25636, which caused the | |
168 // structured clone algorithm to be broken with Window.postMessage in | |
169 // 1.14.{0,1,2}. Once we no longer care about these Dartiums, stop encoding. | |
170 iframe.contentWindow.postMessage( | |
171 JSON.encode(message), window.location.origin); | |
160 }); | 172 }); |
161 | 173 |
162 return new StreamChannel(inputController.stream, outputController.sink); | 174 return controller.foreign; |
163 } | 175 } |
OLD | NEW |