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