blob: 44f7ae753fd7c468523b641709e70ff589762b54 [file] [log] [blame]
Devlin Cronin0b875672017-10-06 00:49:211// Copyright 2017 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#include "extensions/renderer/messaging_util.h"
6
7#include <string>
8
9#include "base/logging.h"
10#include "extensions/common/api/messaging/message.h"
11#include "gin/converter.h"
Devlin Cronin37198932017-11-07 20:15:2312#include "gin/dictionary.h"
Devlin Cronin0b875672017-10-06 00:49:2113#include "third_party/WebKit/public/web/WebUserGestureIndicator.h"
14
15namespace extensions {
16namespace messaging_util {
17
Devlin Cronin37198932017-11-07 20:15:2318const char kSendMessageChannel[] = "chrome.runtime.sendMessage";
19const char kSendRequestChannel[] = "chrome.extension.sendRequest";
20
21const int kNoFrameId = -1;
22
Devlin Cronin0b875672017-10-06 00:49:2123std::unique_ptr<Message> MessageFromV8(v8::Local<v8::Context> context,
24 v8::Local<v8::Value> value) {
25 DCHECK(!value.IsEmpty());
26 v8::Isolate* isolate = context->GetIsolate();
27 v8::Context::Scope context_scope(context);
28
29 // TODO(devlin): For some reason, we don't use the signature for
30 // Port.postMessage when evaluating the parameters. We probably should, but
31 // we don't know how many extensions that may break. It would be good to
32 // investigate, and, ideally, use the signature.
33
34 if (value->IsUndefined()) {
35 // JSON.stringify won't serialized undefined (it returns undefined), but it
36 // will serialized null. We've always converted undefined to null in JS
37 // bindings, so preserve this behavior for now.
38 value = v8::Null(isolate);
39 }
40
41 bool success = false;
42 v8::Local<v8::String> stringified;
43 {
44 v8::TryCatch try_catch(isolate);
45 success = v8::JSON::Stringify(context, value).ToLocal(&stringified);
46 }
47
48 std::string message;
49 if (success) {
50 message = gin::V8ToString(stringified);
51 // JSON.stringify can fail to produce a string value in one of two ways: it
52 // can throw an exception (as with unserializable objects), or it can return
53 // `undefined` (as with e.g. passing a function). If JSON.stringify returns
54 // `undefined`, the v8 API then coerces it to the string value "undefined".
55 // Check for this, and consider it a failure (since we didn't properly
56 // serialize a value).
57 success = message != "undefined";
58 }
59
60 if (!success)
61 return nullptr;
62
63 return std::make_unique<Message>(
64 message, blink::WebUserGestureIndicator::IsProcessingUserGesture());
65}
66
67v8::Local<v8::Value> MessageToV8(v8::Local<v8::Context> context,
68 const Message& message) {
69 v8::Isolate* isolate = context->GetIsolate();
70 v8::Context::Scope context_scope(context);
71
72 v8::Local<v8::String> v8_message_string =
73 gin::StringToV8(isolate, message.data);
74 v8::Local<v8::Value> parsed_message;
75 v8::TryCatch try_catch(isolate);
76 if (!v8::JSON::Parse(context, v8_message_string).ToLocal(&parsed_message)) {
77 NOTREACHED();
78 return v8::Local<v8::Value>();
79 }
80 return parsed_message;
81}
82
Devlin Cronin37198932017-11-07 20:15:2383int ExtractIntegerId(v8::Local<v8::Value> value) {
84 // Account for -0, which is a valid integer, but is stored as a number in v8.
85 DCHECK(value->IsNumber() &&
86 (value->IsInt32() || value.As<v8::Number>()->Value() == 0.0));
87 return value->Int32Value();
88}
89
90ParseOptionsResult ParseMessageOptions(v8::Local<v8::Context> context,
91 v8::Local<v8::Object> v8_options,
92 int flags,
93 MessageOptions* options_out,
94 std::string* error_out) {
95 DCHECK(!v8_options.IsEmpty());
96 DCHECK(!v8_options->IsNull());
97
98 v8::Isolate* isolate = context->GetIsolate();
99
100 MessageOptions options;
101
102 // Theoretically, our argument matching code already checked the types of
103 // the properties on v8_connect_options. However, since we don't make an
104 // independent copy, it's possible that author script has super sneaky
105 // getters/setters that change the result each time the property is
106 // queried. Make no assumptions.
107 gin::Dictionary options_dict(isolate, v8_options);
108 if ((flags & PARSE_CHANNEL_NAME) != 0) {
109 v8::Local<v8::Value> v8_channel_name;
110 if (!options_dict.Get("name", &v8_channel_name))
111 return THROWN;
112
113 if (!v8_channel_name->IsUndefined()) {
114 if (!v8_channel_name->IsString()) {
115 *error_out = "connectInfo.name must be a string.";
116 return TYPE_ERROR;
117 }
118 options.channel_name = gin::V8ToString(v8_channel_name);
119 }
120 }
121
122 if ((flags & PARSE_INCLUDE_TLS_CHANNEL_ID) != 0) {
123 v8::Local<v8::Value> v8_include_tls_channel_id;
124 if (!options_dict.Get("includeTlsChannelId", &v8_include_tls_channel_id))
125 return THROWN;
126
127 if (!v8_include_tls_channel_id->IsUndefined()) {
128 if (!v8_include_tls_channel_id->IsBoolean()) {
129 *error_out = "connectInfo.includeTlsChannelId must be a boolean.";
130 return TYPE_ERROR;
131 }
132 options.include_tls_channel_id =
133 v8_include_tls_channel_id->BooleanValue();
134 }
135 }
136
137 if ((flags & PARSE_FRAME_ID) != 0) {
138 v8::Local<v8::Value> v8_frame_id;
139 if (!options_dict.Get("frameId", &v8_frame_id))
140 return THROWN;
141
142 if (!v8_frame_id->IsUndefined()) {
143 if (!v8_frame_id->IsInt32() &&
144 (!v8_frame_id->IsNumber() ||
145 v8_frame_id.As<v8::Number>()->Value() != 0.0)) {
146 *error_out = "connectInfo.frameId must be an integer.";
147 return TYPE_ERROR;
148 }
149 options.frame_id = v8_frame_id->Int32Value();
150 }
151 }
152
153 *options_out = std::move(options);
154 return SUCCESS;
155}
156
Devlin Cronin0b875672017-10-06 00:49:21157} // namespace messaging_util
158} // namespace extensions