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 library test.runner.browser.host; | 5 library test.runner.browser.host; |
6 | 6 |
7 import 'dart:async'; | 7 import 'dart:async'; |
8 import 'dart:convert'; | 8 import 'dart:convert'; |
9 import 'dart:html'; | 9 import 'dart:html'; |
10 import 'dart:js' as js; | 10 import 'dart:js' as js; |
11 | 11 |
12 import 'package:stack_trace/stack_trace.dart'; | 12 import 'package:stack_trace/stack_trace.dart'; |
13 import 'package:test/src/util/multi_channel.dart'; | 13 import 'package:test/src/util/multi_channel.dart'; |
14 import 'package:test/src/util/stream_channel.dart'; | 14 import 'package:test/src/util/stream_channel.dart'; |
15 | 15 |
| 16 /// The iframes created for each loaded test suite, indexed by the suite id. |
| 17 final _iframes = new Map<int, IFrameElement>(); |
| 18 |
16 // TODO(nweiz): test this once we can run browser tests. | 19 // TODO(nweiz): test this once we can run browser tests. |
17 /// Code that runs in the browser and loads test suites at the server's behest. | 20 /// Code that runs in the browser and loads test suites at the server's behest. |
18 /// | 21 /// |
19 /// One instance of this runs for each browser. When the server tells it to load | 22 /// One instance of this runs for each browser. When the server tells it to load |
20 /// a test, it starts an iframe pointing at that test's code; from then on, it | 23 /// a test, it starts an iframe pointing at that test's code; from then on, it |
21 /// just relays messages between the two. | 24 /// just relays messages between the two. |
22 /// | 25 /// |
23 /// The browser uses two layers of [MultiChannel]s when communicating with the | 26 /// The browser uses two layers of [MultiChannel]s when communicating with the |
24 /// server: | 27 /// server: |
25 /// | 28 /// |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
67 /// the same place the client does. | 70 /// the same place the client does. |
68 void main() { | 71 void main() { |
69 // This tells content_shell not to close immediately after the page has | 72 // This tells content_shell not to close immediately after the page has |
70 // rendered. | 73 // rendered. |
71 var testRunner = js.context['testRunner']; | 74 var testRunner = js.context['testRunner']; |
72 if (testRunner != null) testRunner.callMethod('waitUntilDone', []); | 75 if (testRunner != null) testRunner.callMethod('waitUntilDone', []); |
73 | 76 |
74 runZoned(() { | 77 runZoned(() { |
75 var serverChannel = _connectToServer(); | 78 var serverChannel = _connectToServer(); |
76 serverChannel.stream.listen((message) { | 79 serverChannel.stream.listen((message) { |
77 assert(message['command'] == 'loadSuite'); | 80 if (message['command'] == 'loadSuite') { |
78 var suiteChannel = serverChannel.virtualChannel(message['channel']); | 81 var suiteChannel = serverChannel.virtualChannel(message['channel']); |
79 var iframeChannel = _connectToIframe(message['url']); | 82 var iframeChannel = _connectToIframe(message['url'], message['id']); |
80 suiteChannel.pipe(iframeChannel); | 83 suiteChannel.pipe(iframeChannel); |
| 84 } else { |
| 85 assert(message['command'] == 'closeSuite'); |
| 86 _iframes[message['id']].remove(); |
| 87 } |
81 }); | 88 }); |
82 }, onError: (error, stackTrace) { | 89 }, onError: (error, stackTrace) { |
83 print("$error\n${new Trace.from(stackTrace).terse}"); | 90 print("$error\n${new Trace.from(stackTrace).terse}"); |
84 }); | 91 }); |
85 } | 92 } |
86 | 93 |
87 /// Creates a [MultiChannel] connection to the server, using a [WebSocket] as | 94 /// Creates a [MultiChannel] connection to the server, using a [WebSocket] as |
88 /// the underlying protocol. | 95 /// the underlying protocol. |
89 MultiChannel _connectToServer() { | 96 MultiChannel _connectToServer() { |
90 // The `managerUrl` query parameter contains the WebSocket URL of the remote | 97 // The `managerUrl` query parameter contains the WebSocket URL of the remote |
91 // [BrowserManager] with which this communicates. | 98 // [BrowserManager] with which this communicates. |
92 var currentUrl = Uri.parse(window.location.href); | 99 var currentUrl = Uri.parse(window.location.href); |
93 var webSocket = new WebSocket(currentUrl.queryParameters['managerUrl']); | 100 var webSocket = new WebSocket(currentUrl.queryParameters['managerUrl']); |
94 | 101 |
95 var inputController = new StreamController(sync: true); | 102 var inputController = new StreamController(sync: true); |
96 webSocket.onMessage.listen( | 103 webSocket.onMessage.listen( |
97 (message) => inputController.add(JSON.decode(message.data))); | 104 (message) => inputController.add(JSON.decode(message.data))); |
98 | 105 |
99 var outputController = new StreamController(sync: true); | 106 var outputController = new StreamController(sync: true); |
100 outputController.stream.listen( | 107 outputController.stream.listen( |
101 (message) => webSocket.send(JSON.encode(message))); | 108 (message) => webSocket.send(JSON.encode(message))); |
102 | 109 |
103 return new MultiChannel(inputController.stream, outputController.sink); | 110 return new MultiChannel(inputController.stream, outputController.sink); |
104 } | 111 } |
105 | 112 |
106 /// Creates an iframe with `src` [url] and establishes a connection to it using | 113 /// Creates an iframe with `src` [url] and establishes a connection to it using |
107 /// `postMessage`. | 114 /// `postMessage`. |
108 StreamChannel _connectToIframe(String url) { | 115 /// |
| 116 /// [id] identifies the suite loaded in this iframe. |
| 117 StreamChannel _connectToIframe(String url, int id) { |
109 var iframe = new IFrameElement(); | 118 var iframe = new IFrameElement(); |
| 119 _iframes[id] = iframe; |
110 iframe.src = url; | 120 iframe.src = url; |
111 document.body.children.add(iframe); | 121 document.body.children.add(iframe); |
112 | 122 |
113 var inputController = new StreamController(sync: true); | 123 var inputController = new StreamController(sync: true); |
114 var outputController = new StreamController(sync: true); | 124 var outputController = new StreamController(sync: true); |
115 | 125 |
116 // Use this to avoid sending a message to the iframe before it's sent a | 126 // Use this to avoid sending a message to the iframe before it's sent a |
117 // message to us. This ensures that no messages get dropped on the floor. | 127 // message to us. This ensures that no messages get dropped on the floor. |
118 var readyCompleter = new Completer(); | 128 var readyCompleter = new Completer(); |
119 | 129 |
(...skipping 15 matching lines...) Expand all Loading... |
135 if (!readyCompleter.isCompleted) readyCompleter.complete(); | 145 if (!readyCompleter.isCompleted) readyCompleter.complete(); |
136 }); | 146 }); |
137 | 147 |
138 outputController.stream.listen((message) async { | 148 outputController.stream.listen((message) async { |
139 await readyCompleter.future; | 149 await readyCompleter.future; |
140 iframe.contentWindow.postMessage(message, window.location.origin); | 150 iframe.contentWindow.postMessage(message, window.location.origin); |
141 }); | 151 }); |
142 | 152 |
143 return new StreamChannel(inputController.stream, outputController.sink); | 153 return new StreamChannel(inputController.stream, outputController.sink); |
144 } | 154 } |
OLD | NEW |