Chromium Code Reviews| 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) { |
| 188 mStarted = true; | 170 mStarted = true; |
|
sgurun-gerrit only
2017/02/10 22:02:48
Another bug is that the user can call setMessageCa
sgurun-gerrit only
2017/02/10 23:14:31
I mean not early out but throw.
sgurun-gerrit only
2017/02/10 23:14:31
i.e. Throw not early out.
darin (slow to review)
2017/02/14 19:50:05
Got it. I can add such a check.
| |
| 189 synchronized (mLock) { | 171 synchronized (mLock) { |
| 190 mMessageCallback = messageCallback; | 172 mMessageCallback = messageCallback; |
| 191 if (handler != null) { | 173 if (handler != null) { |
| 192 mHandler = new MessageHandler(handler.getLooper()); | 174 mHandler = new MessageHandler(handler.getLooper()); |
| 193 } | 175 } |
| 194 } | 176 } |
| 195 releaseMessages(); | 177 nativeStartReceivingMessages(mNativeAppWebMessagePort); |
| 196 } | 178 } |
| 197 | 179 |
| 198 // Only called on IO thread. | 180 // Called on a background thread. |
| 199 public void onReceivedMessage(String message, AppWebMessagePort[] sentPorts) { | 181 @CalledByNative |
| 182 private void onMessagesAvailable() { | |
| 200 synchronized (mLock) { | 183 synchronized (mLock) { |
| 201 PostMessageFromWeb m = new PostMessageFromWeb(this, message, sentPor ts); | |
| 202 Handler handler = mHandler != null ? mHandler : sDefaultHandler; | 184 Handler handler = mHandler != null ? mHandler : sDefaultHandler; |
| 203 Message msg = handler.obtainMessage(POST_MESSAGE, m); | 185 Message msg = handler.obtainMessage(MESSAGES_AVAILABLE, this); |
| 204 handler.sendMessage(msg); | 186 handler.sendMessage(msg); |
| 205 } | 187 } |
| 206 } | 188 } |
| 207 | 189 |
| 208 private void releaseMessages() { | 190 // This method is called by nativeDispatchNextMessage while mLock is held. |
| 209 if (mReleasedMessages || !isReady() || mMessageCallback == null) { | 191 @CalledByNative |
| 192 private void onReceivedMessage(String message, AppWebMessagePort[] ports) { | |
| 193 if (mMessageCallback == null) { | |
| 194 Log.w(TAG, "No handler set for port [" + mNativeAppWebMessagePort | |
| 195 + "], dropping message " + message); | |
| 210 return; | 196 return; |
| 211 } | 197 } |
| 212 mReleasedMessages = true; | 198 mMessageCallback.onMessage(message, ports); |
| 213 mMessagePortService.releaseMessages(mPortId); | |
| 214 } | 199 } |
| 215 | 200 |
| 216 // This method may be called on a different thread than UI thread. | 201 // This method may be called on either the UI thread or a background thread. |
| 217 public void onMessage(String message, AppWebMessagePort[] ports) { | 202 private void dispatchReceivedMessages() { |
| 218 synchronized (mLock) { | 203 // Dispatch all of the available messages unless interrupted by close(). |
| 219 if (isClosed()) { | 204 // NOTE: nativeDispatchNextMessage returns true and calls onReceivedMess age |
| 220 Log.w(TAG, "Port [" + mPortId + "] received message in closed st ate"); | 205 // if a message is available else it returns false. |
| 221 return; | 206 while (true) { |
| 207 synchronized (mLock) { | |
| 208 if (!(isReady() && nativeDispatchNextMessage(mNativeAppWebMessag ePort))) { | |
|
Yusuf
2017/02/10 21:43:50
Something that just caught my eye, not excatly sur
darin (slow to review)
2017/02/14 19:50:05
isReady() checks mNativeAppWebMessagePort. I will
| |
| 209 break; | |
| 210 } | |
| 222 } | 211 } |
| 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 } | 212 } |
| 230 } | 213 } |
| 231 | 214 |
| 232 @Override | 215 @Override |
| 233 public void postMessage(String message, MessagePort[] sentPorts) throws Ille galStateException { | 216 public void postMessage(String message, MessagePort[] sentPorts) throws Ille galStateException { |
| 234 if (isClosed() || isTransferred()) { | 217 if (isClosed() || isTransferred()) { |
| 235 throw new IllegalStateException("Port is already closed or transferr ed"); | 218 throw new IllegalStateException("Port is already closed or transferr ed"); |
| 236 } | 219 } |
| 237 AppWebMessagePort[] ports = null; | 220 AppWebMessagePort[] ports = null; |
| 238 if (sentPorts != null) { | 221 if (sentPorts != null) { |
| 239 for (MessagePort port : sentPorts) { | 222 for (MessagePort port : sentPorts) { |
| 240 if (port.equals(this)) { | 223 if (port.equals(this)) { |
| 241 throw new IllegalStateException("Source port cannot be trans ferred"); | 224 throw new IllegalStateException("Source port cannot be trans ferred"); |
| 242 } | 225 } |
| 226 if (port.isClosed() || port.isTransferred()) { | |
| 227 throw new IllegalStateException("Port is already closed or t ransferred"); | |
| 228 } | |
| 229 if (port.isStarted()) { | |
| 230 throw new IllegalStateException("Port is already started"); | |
| 231 } | |
| 243 } | 232 } |
| 244 ports = Arrays.copyOf(sentPorts, sentPorts.length, AppWebMessagePort [].class); | 233 ports = Arrays.copyOf(sentPorts, sentPorts.length, AppWebMessagePort [].class); |
| 245 } | 234 } |
| 246 mStarted = true; | 235 mStarted = true; |
| 247 mPostMessageSender.postMessage(null, message, null, ports); | 236 nativePostMessage(mNativeAppWebMessagePort, message, ports); |
| 248 } | 237 } |
| 249 | 238 |
| 250 // Implements PostMessageSender.PostMessageSenderDelegate interface method. | 239 private static native void nativeInitializeAppWebMessagePortPair(AppWebMessa gePort[] ports); |
| 251 @Override | |
| 252 public boolean isPostMessageSenderReady() { | |
| 253 return isReady(); | |
| 254 } | |
| 255 | 240 |
| 256 // Implements PostMessageSender.PostMessageSenderDelegate interface method. | 241 private native void nativeCloseMessagePort(long nativeAppWebMessagePort); |
| 257 @Override | 242 private native void nativePostMessage(long nativeAppWebMessagePort, String m essage, |
| 258 public void onPostMessageQueueEmpty() { | 243 AppWebMessagePort[] ports); |
| 259 if (isClosed()) { | 244 private native boolean nativeDispatchNextMessage(long nativeAppWebMessagePor t); |
| 260 cleanup(); | 245 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 } | 246 } |
| OLD | NEW |