| 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 |