OLD | NEW |
| (Empty) |
1 // Copyright 2015 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 package org.chromium.android_webview; | |
6 | |
7 import android.os.Handler; | |
8 import android.os.Looper; | |
9 import android.os.Message; | |
10 import android.util.Log; | |
11 | |
12 /** | |
13 * Represents the MessageChannel MessagePort object. Inspired from | |
14 * http://www.whatwg.org/specs/web-apps/current-work/multipage/web-messaging.htm
l#message-channels | |
15 * | |
16 * State management: | |
17 * | |
18 * Initially a message port will be in a pending state. It will be ready once it
is created in | |
19 * content/ (in IO thread) and a message port id is assigned. | |
20 * A pending message port cannnot be transferred, and cannot send or receive mes
sages. However, | |
21 * these details are hidden from the user. If a message port is in the pending s
tate: | |
22 * 1. Any messages posted in this port will be queued until the port is ready | |
23 * 2. Transferring the port using a message channel will cause the message (and
any subsequent | |
24 * messages sent) to be queued until the port is ready | |
25 * 3. Transferring the pending port via postMessageToFrame will cause the messag
e (and all | |
26 * subsequent messages posted via postMessageToFrame) to be queued until the
port is ready. | |
27 * | |
28 * A message port can be in transferred state while a transfer is pending or com
plete. An | |
29 * application cannot use a transferred port to post messages. If a transferred
port | |
30 * receives messages, they will be queued. This state is not visible to embedder
app. | |
31 * | |
32 * A message port should be closed by the app when it is not needed any more. Th
is will free | |
33 * any resources used by it. A closed port cannot receive/send messages and cann
ot be transferred. | |
34 * close() can be called multiple times. A transferred port cannot be closed by
the application, | |
35 * since the ownership is also transferred during the transfer. Closing a transf
erred port will | |
36 * throw an exception. | |
37 * | |
38 * The fact that messages can be handled on a separate thread means that thread | |
39 * synchronization is important. All methods are called on UI thread except as n
oted. | |
40 * | |
41 * Restrictions: | |
42 * The HTML5 message protocol is very flexible in transferring ports. However, t
his | |
43 * sometimes leads to surprising behavior. For example, in current version of ch
rome (m41) | |
44 * the code below | |
45 * 1. var c1 = new MessageChannel(); | |
46 * 2. var c2 = new MessageChannel(); | |
47 * 3. c1.port2.onmessage= function(e) { console.log("1"); } | |
48 * 4. c2.port2.onmessage = function(e) { | |
49 * 5. e.ports[0].onmessage = function(f) { | |
50 * 6. console.log("3"); | |
51 * 7. } | |
52 * 8. } | |
53 * 9. c1.port1.postMessage("test"); | |
54 * 10. c2.port1.postMessage("test2",[c1.port2]) | |
55 * | |
56 * prints 1 or 3 depending on whether or not line 10 is included in code. Furthe
r if | |
57 * it gets executed with a timeout, depending on timeout value, the printout val
ue | |
58 * changes. | |
59 * | |
60 * To prevent such problems, Android webview implementation limits the transfer
of ports | |
61 * as below: | |
62 * Webview puts a port to a "started" state if: | |
63 * 1. The port is ever used to post a message, or | |
64 * 2. The port was ever registered a handler to receive a message. | |
65 * A started port cannot be transferred. | |
66 * | |
67 * This restriction should not impact postmessage functionality in a big way, | |
68 * because an app can still create as many channels as it wants to and use it fo
r | |
69 * transferring data. As a return, it simplifies implementation and prevents har
d | |
70 * to debug, racy corner cases while receiving/sending data. | |
71 */ | |
72 public class AwMessagePort implements PostMessageSender.PostMessageSenderDelegat
e { | |
73 | |
74 /** | |
75 * The message callback for receiving messages. Called on UI thread or if | |
76 * provided, on the handler that is provided. | |
77 */ | |
78 public abstract static class MessageCallback { | |
79 public abstract void onMessage(String message, AwMessagePort[] sentPorts
); | |
80 } | |
81 | |
82 private static final String TAG = "MessagePort"; | |
83 private static final int PENDING = -1; | |
84 | |
85 // the what value for POST_MESSAGE | |
86 private static final int POST_MESSAGE = 1; | |
87 | |
88 private static class PostMessageFromWeb { | |
89 public AwMessagePort port; | |
90 public String message; | |
91 public AwMessagePort[] sentPorts; | |
92 | |
93 public PostMessageFromWeb(AwMessagePort port, String message, AwMessageP
ort[] sentPorts) { | |
94 this.port = port; | |
95 this.message = message; | |
96 this.sentPorts = sentPorts; | |
97 } | |
98 } | |
99 | |
100 // Implements the handler to handle messageport messages received from web. | |
101 // These messages are received on IO thread and normally handled in main | |
102 // thread however, alternatively application can pass a handler to execute t
hem. | |
103 private static class MessageHandler extends Handler { | |
104 public MessageHandler(Looper looper) { | |
105 super(looper); | |
106 } | |
107 @Override | |
108 public void handleMessage(Message msg) { | |
109 if (msg.what == POST_MESSAGE) { | |
110 PostMessageFromWeb m = (PostMessageFromWeb) msg.obj; | |
111 m.port.onMessage(m.message, m.sentPorts); | |
112 return; | |
113 } | |
114 throw new IllegalStateException("undefined message"); | |
115 } | |
116 } | |
117 // The default message handler | |
118 private static final MessageHandler sDefaultHandler = | |
119 new MessageHandler(Looper.getMainLooper()); | |
120 | |
121 private int mPortId = PENDING; | |
122 private MessageCallback mMessageCallback; | |
123 private AwMessagePortService mMessagePortService; | |
124 private boolean mClosed; | |
125 private boolean mTransferred; | |
126 private boolean mStarted; | |
127 private boolean mReleasedMessages; | |
128 private PostMessageSender mPostMessageSender; | |
129 private MessageHandler mHandler; | |
130 private final Object mLock = new Object(); | |
131 | |
132 public AwMessagePort(AwMessagePortService messagePortService) { | |
133 mMessagePortService = messagePortService; | |
134 mPostMessageSender = new PostMessageSender(this, mMessagePortService); | |
135 mMessagePortService.addObserver(mPostMessageSender); | |
136 } | |
137 | |
138 public boolean isReady() { | |
139 return mPortId != PENDING; | |
140 } | |
141 | |
142 public int portId() { | |
143 return mPortId; | |
144 } | |
145 | |
146 public void setPortId(int id) { | |
147 mPortId = id; | |
148 releaseMessages(); | |
149 } | |
150 | |
151 public void close() { | |
152 if (mTransferred) { | |
153 throw new IllegalStateException("Port is already transferred"); | |
154 } | |
155 synchronized (mLock) { | |
156 if (mClosed) return; | |
157 mClosed = true; | |
158 } | |
159 // If the port is already ready, and no messages are waiting in the | |
160 // queue to be transferred, onPostMessageQueueEmpty() callback is not | |
161 // received (it is received only after messages are purged). In this | |
162 // case do the cleanup here. | |
163 if (isReady() && mPostMessageSender.isMessageQueueEmpty()) { | |
164 cleanup(); | |
165 } | |
166 } | |
167 | |
168 public boolean isClosed() { | |
169 return mClosed; | |
170 } | |
171 | |
172 public boolean isTransferred() { | |
173 return mTransferred; | |
174 } | |
175 | |
176 public void setTransferred() { | |
177 mTransferred = true; | |
178 } | |
179 | |
180 public boolean isStarted() { | |
181 return mStarted; | |
182 } | |
183 | |
184 // Only called on UI thread | |
185 public void setMessageCallback(MessageCallback messageCallback, Handler hand
ler) { | |
186 mStarted = true; | |
187 synchronized (mLock) { | |
188 mMessageCallback = messageCallback; | |
189 if (handler != null) { | |
190 mHandler = new MessageHandler(handler.getLooper()); | |
191 } | |
192 } | |
193 releaseMessages(); | |
194 } | |
195 | |
196 // Only called on IO thread. | |
197 public void onReceivedMessage(String message, AwMessagePort[] sentPorts) { | |
198 synchronized (mLock) { | |
199 PostMessageFromWeb m = new PostMessageFromWeb(this, message, sentPor
ts); | |
200 Handler handler = mHandler != null ? mHandler : sDefaultHandler; | |
201 Message msg = handler.obtainMessage(POST_MESSAGE, m); | |
202 handler.sendMessage(msg); | |
203 } | |
204 } | |
205 | |
206 private void releaseMessages() { | |
207 if (mReleasedMessages || !isReady() || mMessageCallback == null) { | |
208 return; | |
209 } | |
210 mReleasedMessages = true; | |
211 mMessagePortService.releaseMessages(mPortId); | |
212 } | |
213 | |
214 // This method may be called on a different thread than UI thread. | |
215 public void onMessage(String message, AwMessagePort[] ports) { | |
216 synchronized (mLock) { | |
217 if (isClosed()) { | |
218 Log.w(TAG, "Port [" + mPortId + "] received message in closed st
ate"); | |
219 return; | |
220 } | |
221 if (mMessageCallback == null) { | |
222 Log.w(TAG, "No handler set for port [" + mPortId + "], dropping
message " | |
223 + message); | |
224 return; | |
225 } | |
226 mMessageCallback.onMessage(message, ports); | |
227 } | |
228 } | |
229 | |
230 public void postMessage(String message, AwMessagePort[] sentPorts) | |
231 throws IllegalStateException { | |
232 if (isClosed() || isTransferred()) { | |
233 throw new IllegalStateException("Port is already closed or transferr
ed"); | |
234 } | |
235 if (sentPorts != null) { | |
236 for (AwMessagePort port : sentPorts) { | |
237 if (port.equals(this)) { | |
238 throw new IllegalStateException("Source port cannot be trans
ferred"); | |
239 } | |
240 } | |
241 } | |
242 mStarted = true; | |
243 mPostMessageSender.postMessage(null, message, null, sentPorts); | |
244 } | |
245 | |
246 // Implements PostMessageSender.PostMessageSenderDelegate interface method. | |
247 @Override | |
248 public boolean isPostMessageSenderReady() { | |
249 return isReady(); | |
250 } | |
251 | |
252 // Implements PostMessageSender.PostMessageSenderDelegate interface method. | |
253 @Override | |
254 public void onPostMessageQueueEmpty() { | |
255 if (isClosed()) { | |
256 cleanup(); | |
257 } | |
258 } | |
259 | |
260 // Implements PostMessageSender.PostMessageSenderDelegate interface method. | |
261 @Override | |
262 public void postMessageToWeb(String frameName, String message, String target
Origin, | |
263 int[] sentPortIds) { | |
264 mMessagePortService.postMessage(mPortId, message, sentPortIds); | |
265 } | |
266 | |
267 private void cleanup() { | |
268 mMessagePortService.removeObserver(mPostMessageSender); | |
269 mPostMessageSender = null; | |
270 mMessagePortService.closePort(mPortId); | |
271 } | |
272 } | |
OLD | NEW |