Chromium Code Reviews| Index: chrome/browser/renderer_host/render_widget_host.cc |
| =================================================================== |
| --- chrome/browser/renderer_host/render_widget_host.cc (revision 29767) |
| +++ chrome/browser/renderer_host/render_widget_host.cc (working copy) |
| @@ -71,7 +71,10 @@ |
| view_being_painted_(false), |
| text_direction_updated_(false), |
| text_direction_(WebKit::WebTextDirectionLeftToRight), |
| - text_direction_canceled_(false) { |
| + text_direction_canceled_(false), |
| + pending_key_events_(false), |
| + suppress_next_char_events_(false), |
| + death_flag_(NULL) { |
| if (routing_id_ == MSG_ROUTING_NONE) |
| routing_id_ = process_->GetNextRoutingID(); |
| @@ -82,6 +85,10 @@ |
| } |
| RenderWidgetHost::~RenderWidgetHost() { |
| + // Force the method that destroys this object to exit immediately. |
| + if (death_flag_) |
|
darin (slow to review)
2009/11/24 07:47:36
this seems like a very ugly hack for the fact that
James Su
2009/11/24 09:10:56
I don't like it either, but I don't know how to fi
|
| + *death_flag_ = true; |
| + |
| // Clear our current or cached backing store if either remains. |
| BackingStoreManager::RemoveBackingStore(this); |
| @@ -395,22 +402,95 @@ |
| if (!process_->HasConnection()) |
| return; |
| + // To help understand following logic, please refer to the comments |
| + // explaining |pending_key_events_| and |suppress_next_char_events_| |
| + // members. And the comments in OnMsgInputEventAck() method, which contains |
| + // the bottom half of the logic. |
| + // |
| + // There are to different situations for us to handle key events: |
|
darin (slow to review)
2009/11/24 07:47:36
nit: "There are two..."
James Su
2009/11/24 09:10:56
This is fixed in CL 400012.
|
| + // 1. After sending a key event to the renderer, we receive its ACK message |
| + // from the renderer before sending the next key event. |
| + // In this case, there is always no more than 1 key event in |key_queue_|, |
| + // and we send the key event one by one in this method, except that a |
| + // sequence of Char events may be suppressed directly if the preceding |
| + // RawKeyDown event was handled as an accelerator key in the browser. |
| + // |
| + // 2. We get the next key event before receving the previous one's ACK |
| + // message from the renderer. |
| + // In this case, when we get a key event, the previous key event is still in |
| + // |keq_queue_| waiting for being handled in OnMsgInputEventAck() method. |
| + // Then we need handle following cases differently: |
| + // 1) If we get a Char event, then the previous key event in |key_queue_| |
| + // waiting for ACK must be a RawKeyDown event. Then we need keep this Char |
| + // event in |key_queue_| rather than sending it immediately, because we |
| + // can't determine if we should send it to the renderer or just suppress |
| + // it, until we receive the preceding RawKeyDown event's ACK message. |
| + // We increase the count of |pending_key_events_| to help remember this |
| + // Char event is not sent to the renderer yet. |
| + // 2) If there is already one or more key event pending in |key_queue_| |
| + // (|pending_key_events_| > 0), we can not send a new key event |
| + // immediately no matter what type it is. Otherwise the key events will be |
| + // in wrong order. In this case we just keep them in |key_queue_| and |
| + // increase |pending_key_events_| to remember them. |
| + // |
| + // Then all pending key events in |key_queue_| will be handled properly in |
| + // OnMsgInputEventAck() method. Please refer to that method for details. |
| + |
| // Tab switching/closing accelerators aren't sent to the renderer to avoid a |
| // hung/malicious renderer from interfering. |
| if (!ShouldSendToRenderer(key_event)) { |
| UnhandledKeyboardEvent(key_event); |
| + if (key_event.type == WebKeyboardEvent::RawKeyDown) |
| + suppress_next_char_events_ = true; |
| return; |
| } |
| + bool is_char = (key_event.type == WebKeyboardEvent::Char); |
| + |
| + // Handle the first situation mentioned above. |
| + if (suppress_next_char_events_) { |
|
darin (slow to review)
2009/11/24 07:51:45
couldn't we implement this discarding of events in
James Su
2009/11/24 09:10:56
Good suggestion. I'll look into this approach to s
|
| + // If preceding RawKeyDown event was handled by the browser, then we need |
| + // suppress all Char events generated by it. Please note that, one |
| + // RawKeyDown event may generate multiple Char events, so we can't reset |
| + // |suppress_next_char_events_| until we get a KeyUp or a RawKeyDown. |
| + if (is_char) |
| + return; |
| + // We get a KeyUp or a RawKeyDown event. |
| + suppress_next_char_events_ = false; |
| + } |
| + |
| // Put all WebKeyboardEvent objects in a queue since we can't trust the |
| // renderer and we need to give something to the UnhandledInputEvent |
| // handler. |
| - key_queue_.push(key_event); |
| + key_queue_.push_back(key_event); |
| HISTOGRAM_COUNTS_100("Renderer.KeyboardQueueSize", key_queue_.size()); |
| + |
| + // Handle the second situation mentioned above. |
| + size_t key_queue_size = key_queue_.size(); |
| + if ((is_char && key_queue_size > 1) || pending_key_events_) { |
| + // The event is not send to the renderer, increase |pending_key_events_| |
| + // to remember it. |
| + ++pending_key_events_; |
| + |
| + // Some sanity checks. |
| + // At least one key event in the front of |key_queue_| has been sent to |
| + // the renderer, otherwise we won't be able to get any ACK back from the |
| + // renderer. |
| + DCHECK(key_queue_size > pending_key_events_); |
| + // Char events must be preceded by RawKeyDown events. |
| + // TODO(suzhe): verify it on all platforms. |
| + DCHECK(key_queue_[key_queue_size - pending_key_events_ - 1].type == |
| + WebKeyboardEvent::RawKeyDown); |
| + // The first pending key event must be a Char event. Other key events must |
| + // already be sent to the renderer at this point. |
| + DCHECK(key_queue_[key_queue_size - pending_key_events_].type == |
| + WebKeyboardEvent::Char); |
| + } else { |
| + // Fall into the first situation, so we can send the event immediately. |
| + // Only forward the non-native portions of our event. |
| + ForwardInputEvent(key_event, sizeof(WebKeyboardEvent)); |
| + } |
| } |
| - |
| - // Only forward the non-native portions of our event. |
| - ForwardInputEvent(key_event, sizeof(WebKeyboardEvent)); |
| } |
| void RenderWidgetHost::ForwardInputEvent(const WebInputEvent& input_event, |
| @@ -721,6 +801,10 @@ |
| } |
| void RenderWidgetHost::OnMsgInputEventAck(const IPC::Message& message) { |
| + // Track if |this| is destroyed or not. |
| + bool is_dead = false; |
| + death_flag_ = &is_dead; |
| + |
| // Log the time delta for processing an input event. |
| TimeDelta delta = TimeTicks::Now() - input_event_start_time_; |
| UMA_HISTOGRAM_TIMES("MPArch.RWH_InputEventDelta", delta); |
| @@ -751,23 +835,86 @@ |
| LOG(ERROR) << "We seem to have a different key type sent from " |
| << "the renderer. (" << key_queue_.front().type << " vs. " |
| << type << "). Ignoring event."; |
| + |
| + // Something must be wrong. |key_queue_| must be cleared here to make sure |
| + // the feedback loop for sending upcoming keyboard events can be resumed |
| + // correctly. |
| + key_queue_.clear(); |
| + pending_key_events_ = 0; |
| + suppress_next_char_events_ = false; |
| } else { |
| bool processed = false; |
| if (!message.ReadBool(&iter, &processed)) |
| process()->ReceivedBadMessage(message.type()); |
| NativeWebKeyboardEvent front_item = key_queue_.front(); |
| - key_queue_.pop(); |
| + key_queue_.pop_front(); |
| + bool processed_by_browser = false; |
| if (!processed) { |
| - UnhandledKeyboardEvent(front_item); |
| + processed_by_browser = UnhandledKeyboardEvent(front_item); |
| // WARNING: This RenderWidgetHost can be deallocated at this point |
| // (i.e. in the case of Ctrl+W, where the call to |
| // UnhandledKeyboardEvent destroys this RenderWidgetHost). |
| } |
| + |
| + // This RenderWidgetHost was already deallocated, we can't do anything |
| + // from now on, including resetting |death_flag_|. So just return. |
| + if (is_dead) |
| + return; |
| + |
| + // Suppress the following Char events if the RawKeyDown event was handled |
| + // by the browser rather than the renderer. |
| + if (front_item.type == WebKeyboardEvent::RawKeyDown) |
| + suppress_next_char_events_ = processed_by_browser; |
| + |
| + // If more than one key events in |key_queue_| were already sent to the |
| + // renderer but haven't got ACK messages yet, we must wait for ACK |
| + // messages of these key events before sending more key events to the |
| + // renderer. |
| + if (pending_key_events_ && key_queue_.size() == pending_key_events_) { |
| + size_t i = 0; |
| + if (suppress_next_char_events_) { |
| + // Suppress the sequence of pending Char events if preceding |
| + // RawKeyDown event was handled by the browser. |
| + while (pending_key_events_ && |
| + key_queue_[0].type == WebKeyboardEvent::Char) { |
| + --pending_key_events_; |
| + key_queue_.pop_front(); |
| + } |
| + } else { |
| + // Otherwise, send these pending Char events to the renderer. |
| + // Note: we can't remove them from |key_queue_|, as we still need to |
| + // wait for their ACK messages from the renderer. |
| + while (pending_key_events_ && |
| + key_queue_[i].type == WebKeyboardEvent::Char) { |
| + --pending_key_events_; |
| + ForwardInputEvent(key_queue_[i++], sizeof(WebKeyboardEvent)); |
| + } |
| + } |
| + |
| + // Reset |suppress_next_char_events_| if there is still one or more |
| + // pending KeyUp or RawKeyDown events in the queue. |
| + // We can't reset it if there is no pending event anymore, because we |
| + // don't know if the following event is a Char event or not. |
| + if (pending_key_events_) |
| + suppress_next_char_events_ = false; |
| + |
| + // We can safely send following pending KeyUp and RawKeyDown events to |
| + // the renderer, until we meet another Char event. |
| + while (pending_key_events_ && |
| + key_queue_[i].type != WebKeyboardEvent::Char) { |
| + --pending_key_events_; |
| + ForwardInputEvent(key_queue_[i++], sizeof(WebKeyboardEvent)); |
| + } |
| + } |
| } |
| } |
| + |
| + // Reset |death_flag_| to NULL, otherwise it'll point to an invalid memory |
| + // address after returning from this method. |
| + death_flag_ = NULL; |
| } |
| void RenderWidgetHost::OnMsgFocus() { |