Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable evaluating javascript in the firefox devtools console #23296

Merged
merged 1 commit into from May 9, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions components/devtools/Cargo.toml
Expand Up @@ -24,3 +24,4 @@ msg = {path = "../msg"}
serde = "1.0"
serde_json = "1.0"
time = "0.1"
uuid = {version = "0.7", features = ["v4"]}
191 changes: 124 additions & 67 deletions components/devtools/actors/console.rs
Expand Up @@ -19,6 +19,7 @@ use msg::constellation_msg::PipelineId;
use serde_json::{self, Map, Number, Value};
use std::cell::RefCell;
use std::net::TcpStream;
use uuid::Uuid;

trait EncodableConsoleMessage {
fn encode(&self) -> serde_json::Result<String>;
Expand Down Expand Up @@ -72,10 +73,29 @@ struct EvaluateJSReply {
result: Value,
timestamp: u64,
exception: Value,
exceptionMessage: String,
exceptionMessage: Value,
helperResult: Value,
}

#[derive(Serialize)]
struct EvaluateJSEvent {
from: String,
r#type: String,
input: String,
result: Value,
timestamp: u64,
resultID: String,
exception: Value,
exceptionMessage: Value,
helperResult: Value,
}

#[derive(Serialize)]
struct EvaluateJSAsyncReply {
from: String,
resultID: String,
}

#[derive(Serialize)]
struct SetPreferencesReply {
from: String,
Expand All @@ -89,6 +109,86 @@ pub struct ConsoleActor {
pub streams: RefCell<Vec<TcpStream>>,
}

impl ConsoleActor {
fn evaluateJS(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is moved as-is from the "evaluateJS" handler so that "evaluateJS" and "evaluateJSAsync" can share it

&self,
registry: &ActorRegistry,
msg: &Map<String, Value>,
) -> Result<EvaluateJSReply, ()> {
let input = msg.get("text").unwrap().as_str().unwrap().to_owned();
let (chan, port) = ipc::channel().unwrap();
self.script_chan
.send(DevtoolScriptControlMsg::EvaluateJS(
self.pipeline,
input.clone(),
chan,
))
.unwrap();

//TODO: extract conversion into protocol module or some other useful place
let result = match port.recv().map_err(|_| ())? {
VoidValue => {
let mut m = Map::new();
m.insert("type".to_owned(), Value::String("undefined".to_owned()));
Value::Object(m)
},
NullValue => {
let mut m = Map::new();
m.insert("type".to_owned(), Value::String("null".to_owned()));
Value::Object(m)
},
BooleanValue(val) => Value::Bool(val),
NumberValue(val) => {
if val.is_nan() {
let mut m = Map::new();
m.insert("type".to_owned(), Value::String("NaN".to_owned()));
Value::Object(m)
} else if val.is_infinite() {
let mut m = Map::new();
if val < 0. {
m.insert("type".to_owned(), Value::String("-Infinity".to_owned()));
} else {
m.insert("type".to_owned(), Value::String("Infinity".to_owned()));
}
Value::Object(m)
} else if val == 0. && val.is_sign_negative() {
let mut m = Map::new();
m.insert("type".to_owned(), Value::String("-0".to_owned()));
Value::Object(m)
} else {
Value::Number(Number::from_f64(val).unwrap())
}
},
StringValue(s) => Value::String(s),
ActorValue { class, uuid } => {
//TODO: make initial ActorValue message include these properties?
let mut m = Map::new();
let actor = ObjectActor::new(registry, uuid);

m.insert("type".to_owned(), Value::String("object".to_owned()));
m.insert("class".to_owned(), Value::String(class));
m.insert("actor".to_owned(), Value::String(actor));
m.insert("extensible".to_owned(), Value::Bool(true));
m.insert("frozen".to_owned(), Value::Bool(false));
m.insert("sealed".to_owned(), Value::Bool(false));
Value::Object(m)
},
};

//TODO: catch and return exception values from JS evaluation
let reply = EvaluateJSReply {
from: self.name(),
input: input,
result: result,
timestamp: 0,
exception: Value::Null,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exceptions must be null if they are not present. We were sending an empty object over the wire, and devtools was interpreting it as an error.

exceptionMessage: Value::Null,
helperResult: Value::Null,
};
std::result::Result::Ok(reply)
}
}

impl Actor for ConsoleActor {
fn name(&self) -> String {
self.name.clone()
Expand Down Expand Up @@ -191,76 +291,33 @@ impl Actor for ConsoleActor {
},

"evaluateJS" => {
let input = msg.get("text").unwrap().as_str().unwrap().to_owned();
let (chan, port) = ipc::channel().unwrap();
self.script_chan
.send(DevtoolScriptControlMsg::EvaluateJS(
self.pipeline,
input.clone(),
chan,
))
.unwrap();

//TODO: extract conversion into protocol module or some other useful place
let result = match port.recv().map_err(|_| ())? {
VoidValue => {
let mut m = Map::new();
m.insert("type".to_owned(), Value::String("undefined".to_owned()));
Value::Object(m)
},
NullValue => {
let mut m = Map::new();
m.insert("type".to_owned(), Value::String("null".to_owned()));
Value::Object(m)
},
BooleanValue(val) => Value::Bool(val),
NumberValue(val) => {
if val.is_nan() {
let mut m = Map::new();
m.insert("type".to_owned(), Value::String("NaN".to_owned()));
Value::Object(m)
} else if val.is_infinite() {
let mut m = Map::new();
if val < 0. {
m.insert("type".to_owned(), Value::String("-Infinity".to_owned()));
} else {
m.insert("type".to_owned(), Value::String("Infinity".to_owned()));
}
Value::Object(m)
} else if val == 0. && val.is_sign_negative() {
let mut m = Map::new();
m.insert("type".to_owned(), Value::String("-0".to_owned()));
Value::Object(m)
} else {
Value::Number(Number::from_f64(val).unwrap())
}
},
StringValue(s) => Value::String(s),
ActorValue { class, uuid } => {
//TODO: make initial ActorValue message include these properties?
let mut m = Map::new();
let actor = ObjectActor::new(registry, uuid);
let msg = self.evaluateJS(&registry, &msg);
stream.write_json_packet(&msg);
ActorMessageStatus::Processed
},

m.insert("type".to_owned(), Value::String("object".to_owned()));
m.insert("class".to_owned(), Value::String(class));
m.insert("actor".to_owned(), Value::String(actor));
m.insert("extensible".to_owned(), Value::Bool(true));
m.insert("frozen".to_owned(), Value::Bool(false));
m.insert("sealed".to_owned(), Value::Bool(false));
Value::Object(m)
},
"evaluateJSAsync" => {
let resultID = Uuid::new_v4().to_string();
let early_reply = EvaluateJSAsyncReply {
from: self.name(),
resultID: resultID.clone(),
};

//TODO: catch and return exception values from JS evaluation
let msg = EvaluateJSReply {
// Emit an eager reply so that the client starts listening
// for an async event with the resultID
stream.write_json_packet(&early_reply);
let reply = self.evaluateJS(&registry, &msg).unwrap();
let msg = EvaluateJSEvent {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hope there is a better way to do this..

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could have an intermediate type with From impls but I don't think it's a big deal.

from: self.name(),
input: input,
result: result,
timestamp: 0,
exception: Value::Object(Map::new()),
exceptionMessage: "".to_owned(),
helperResult: Value::Object(Map::new()),
r#type: "evaluationResult".to_owned(),
input: reply.input,
result: reply.result,
timestamp: reply.timestamp,
resultID: resultID,
exception: reply.exception,
exceptionMessage: reply.exceptionMessage,
helperResult: reply.helperResult,
};
// Send the data from evaluateJS along with a resultID
stream.write_json_packet(&msg);
ActorMessageStatus::Processed
},
Expand Down