Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(169)

Side by Side Diff: lib/src/runner/browser/static/host.dart

Issue 959383004: Add a host script to run in a browser and start iframes for suites. (Closed) Base URL: git@github.com:dart-lang/unittest@master
Patch Set: Code review changes Created 5 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | lib/src/runner/browser/static/host.dart.js » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
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
3 // BSD-style license that can be found in the LICENSE file.
4
5 library unittest.runner.browser.host;
6
7 import 'dart:async';
8 import 'dart:convert';
9 import 'dart:html';
10
11 import 'package:stack_trace/stack_trace.dart';
12 import 'package:unittest/src/util/multi_channel.dart';
13 import 'package:unittest/src/util/stream_channel.dart';
14
15 // TODO(nweiz): test this once we can run browser tests.
16 /// Code that runs in the browser and loads test suites at the server's behest.
17 ///
18 /// One instance of this runs for each browser. When the server tells it to load
19 /// a test, it starts an iframe pointing at that test's code; from then on, it
20 /// just relays messages between the two.
21 ///
22 /// The browser uses two layers of [MultiChannel]s when communicating with the
23 /// server:
24 ///
25 /// server
26 /// │
27 /// (WebSocket)
28 /// │
29 /// ┏━ host.html ━━━━━━━━┿━━━━━━━━━━━━━━━━━┓
30 /// ┃ │ ┃
31 /// ┃ ┌──────┬───MultiChannel─────┐ ┃
32 /// ┃ │ │ │ │ │ ┃
33 /// ┃ host suite suite suite suite ┃
34 /// ┃ │ │ │ │ ┃
35 /// ┗━━━━━━━━━━━┿━━━━━━┿━━━━━━┿━━━━━━┿━━━━━┛
36 /// │ │ │ │
37 /// │ ... ... ...
38 /// │
39 /// (postMessage)
40 /// │
41 /// ┏━ suite.html (in iframe) ┿━━━━━━━━━━━━━━━━━━━━━━━━━━┓
42 /// ┃ │ ┃
43 /// ┃ ┌──────────MultiChannel┬─────────┐ ┃
44 /// ┃ │ │ │ │ │ ┃
45 /// ┃ IframeListener test test test running test ┃
46 /// ┃ ┃
47 /// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
48 ///
49 /// The host (this code) has a [MultiChannel] that splits the WebSocket
50 /// connection with the server. One connection is used for the host itself to
51 /// receive messages like "load a suite at this URL", and the rest are connected
52 /// to each test suite's iframe via `postMessage`.
53 ///
54 /// Each iframe then has its own [MultiChannel] which takes its `postMessage`
55 /// connection and splits it again. One connection is used for the
56 /// [IframeListener], which sends messages like "here are all the tests in this
57 /// suite". The rest are used for each test, receiving messages like "start
58 /// running". A new connection is also created whenever a test begins running to
59 /// send status messages about its progress.
60 ///
61 /// It's of particular note that the suite's [MultiChannel] connection uses the
62 /// host's purely as a transport layer; neither is aware that the other is also
63 /// using [MultiChannel]. This is necessary, since the host doesn't share memory
64 /// with the suites and thus can't share its [MultiChannel] with them, but it
65 /// does mean that the server needs to be sure to nest its [MultiChannel]s at
66 /// the same place the client does.
67 void main() {
68 runZoned(() {
69 var serverChannel = _connectToServer();
70 serverChannel.stream.listen((message) {
71 assert(message['command'] == 'loadSuite');
72 var suiteChannel = serverChannel.virtualChannel(message['channel']);
73 var iframeChannel = _connectToIframe(message['url']);
74 suiteChannel.pipe(iframeChannel);
75 });
76 }, onError: (error, stackTrace) {
77 print("$error\n${new Trace.from(stackTrace).terse}");
78 });
79 }
80
81 /// Creates a [MultiChannel] connection to the server, using a [WebSocket] as
82 /// the underlying protocol.
83 MultiChannel _connectToServer() {
84 // The `managerUrl` query parameter contains the WebSocket URL of the remote
85 // [BrowserManager] with which this communicates.
86 var currentUrl = Uri.parse(window.location.href);
87 var webSocketUrl = currentUrl
88 .resolve(currentUrl.queryParameters['managerUrl'])
89 .replace(scheme: 'ws');
90 var webSocket = new WebSocket(webSocketUrl.toString());
91
92 var inputController = new StreamController(sync: true);
93 webSocket.onMessage.listen(
94 (message) => inputController.add(JSON.decode(message.data)));
95
96 var outputController = new StreamController(sync: true);
97 outputController.stream.listen(
98 (message) => webSocket.send(JSON.encode(message)));
99
100 return new MultiChannel(inputController.stream, outputController.sink);
101 }
102
103 /// Creates an iframe with `src` [url] and establishes a connection to it using
104 /// `postMessage`.
105 StreamChannel _connectToIframe(String url) {
106 var iframe = new IFrameElement();
107 iframe.src = url;
108 document.body.children.add(iframe);
109
110 var inputController = new StreamController(sync: true);
111 var outputController = new StreamController(sync: true);
112 iframe.onLoad.first.then((_) {
113 // TODO(nweiz): use MessageChannel once Firefox supports it
114 // (http://caniuse.com/#search=MessageChannel).
115
116 // Send an initial command to give the iframe something to reply to.
117 iframe.contentWindow.postMessage(
118 {"command": "connect"},
119 window.location.origin);
120
121 window.onMessage.listen((message) {
122 // A message on the Window can theoretically come from any website. It's
123 // very unlikely that a malicious site would care about hacking someone's
124 // unit tests, let alone be able to find the unittest server while it's
125 // running, but it's good practice to check the origin anyway.
126 if (message.origin != window.location.origin) return;
127
128 // TODO(nweiz): Stop manually checking href here once issue 22554 is
129 // fixed.
130 if (message.data["href"] != iframe.src) return;
131
132 message.stopPropagation();
133 inputController.add(message.data["data"]);
134 });
135
136 outputController.stream.listen((message) =>
137 iframe.contentWindow.postMessage(message, window.location.origin));
138 });
139
140 return new StreamChannel(inputController.stream, outputController.sink);
141 }
OLDNEW
« no previous file with comments | « no previous file | lib/src/runner/browser/static/host.dart.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698