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

Implement XMLHttpRequest.send(Document) #10604

Closed
wants to merge 3 commits into from

Conversation

jaysonsantos
Copy link

If you guys need a rebase just tell me and I do it.
When serializing document I am using unwrap, should I change extracted return to be a result?
Fixes #9490


This change is Reviewable

@highfive
Copy link

Thanks for the pull request, and welcome! The Servo team is excited to review your changes, and you should hear from @wafflespeanut (or someone else) soon.

@highfive
Copy link

Heads up! This PR modifies the following files:

  • @KiChjang: components/script/dom/document.rs, components/script/dom/xmlhttprequest.rs, components/script/dom/webidls/XMLHttpRequest.webidl

@highfive highfive added the S-awaiting-review There is new code that needs to be reviewed. label Apr 14, 2016
@KiChjang KiChjang assigned KiChjang and unassigned Manishearth Apr 14, 2016
@KiChjang
Copy link
Contributor

-S-awaiting-review +S-needs-code-changes


Reviewed 5 of 5 files at r1.
Review status: all files reviewed at latest revision, 5 unresolved discussions, some commit checks failed.


components/script/dom/document.rs, line 1823 [r1] (raw file):
Since this method already returns a Fallible, we don't need a match statement and panic in the Err arm. Instead, an if let would do the job just fine.


components/script/dom/xmlhttprequest.rs, line 533 [r1] (raw file):
Were you not able to use DocumentOrBodyInit?


components/script/dom/xmlhttprequest.rs, line 1442 [r1] (raw file):
Use the mime! macro to generate Content-Type headers.


components/script/dom/webidls/XMLHttpRequest.webidl, line 58 [r1] (raw file):
This is a pretty bad sign if we have to deviate from the WebIDL spec def in order to support XHR.send.


tests/wpt/web-platform-tests/XMLHttpRequest/send-entity-body-document.htm, line 21 [r1] (raw file):
Does the test still pass without your changes here?


Comments from Reviewable

@KiChjang KiChjang added S-needs-code-changes Changes have not yet been made that were requested by a reviewer. and removed S-awaiting-review There is new code that needs to be reviewed. labels Apr 14, 2016
@jaysonsantos
Copy link
Author

Review status: all files reviewed at latest revision, 5 unresolved discussions, some commit checks failed.


components/script/dom/xmlhttprequest.rs, line 533 [r1] (raw file):
When I generate the data it will always generate DocumentOrBlobOrStringOrURLSearchParams even doing (Document or BodyInit) and it will not generate the BodyInit on the beginning of the file.


components/script/dom/webidls/XMLHttpRequest.webidl, line 58 [r1] (raw file):
As I said before if I try to use (Document or BodyInit)? it will generate a DocumentOrBlobOrStringOUrlSearchParam and will not generate the BodyInit, I can paste the error here if you want.


tests/wpt/web-platform-tests/XMLHttpRequest/send-entity-body-document.htm, line 21 [r1] (raw file):
No, the serializer always put a \n after <!DOCTYPE html>


Comments from Reviewable

@KiChjang
Copy link
Contributor

Review status: all files reviewed at latest revision, 5 unresolved discussions, some commit checks failed.


components/script/dom/webidls/XMLHttpRequest.webidl, line 58 [r1] (raw file):
This is a bug. Filed #10605. For now, we should revert these (i.e. use Document or BodyInit) and uncomment the typedef up in this file.


Comments from Reviewable

@jaysonsantos
Copy link
Author

Review status: all files reviewed at latest revision, 5 unresolved discussions, some commit checks failed.


components/script/dom/document.rs, line 1823 [r1] (raw file):
Do you mean something like this?

if let Ok(()) = serialize(&mut writer, &self.upcast::<Node>(),
    SerializeOpts {traversal_scope: ChildrenOnly,..Default::default()}) {
    Ok(DOMString::from(String::from_utf8(writer).unwrap()))
} else {
    Err(Error::InvalidState)
}

components/script/dom/xmlhttprequest.rs, line 1442 [r1] (raw file):
To use that macro I will have to add mime on lib.rs and cargo.toml, is that ok?


components/script/dom/webidls/XMLHttpRequest.webidl, line 58 [r1] (raw file):
So, this PR wait it then?


Comments from Reviewable

@KiChjang
Copy link
Contributor

Review status: all files reviewed at latest revision, 4 unresolved discussions, some commit checks failed.


components/script/dom/document.rs, line 1823 [r1] (raw file):
Yes.


components/script/dom/xmlhttprequest.rs, line 1442 [r1] (raw file):
Yes.


components/script/dom/webidls/XMLHttpRequest.webidl, line 58 [r1] (raw file):
Not necessarily. We can still use BodyInit, and in the Rust code where BodyInit is referenced, use BlobOrDOMStringOrURLSearchParams instead.


tests/wpt/web-platform-tests/XMLHttpRequest/send-entity-body-document.htm, line 21 [r1] (raw file):
That also looks undesirable. FWIW there's a discussion about this in a WPT PR here. If possible, I'd like to make changes to the serializer instead (it is located at servo/html5ever).


Comments from Reviewable

@highfive highfive added S-awaiting-review There is new code that needs to be reviewed. and removed S-needs-code-changes Changes have not yet been made that were requested by a reviewer. labels Apr 14, 2016
@jaysonsantos
Copy link
Author

Review status: 3 of 8 files reviewed at latest revision, 2 unresolved discussions.


components/script/dom/webidls/XMLHttpRequest.webidl, line 58 [r1] (raw file):
If I try to do it, this will happen:

   Compiling script v0.0.1 (file:///home/jayson/src/servo/components/script)
/home/jayson/src/servo/target/debug/build/script-2a4d8b76db936b97/out/Bindings/XMLHttpRequestBinding.rs:133:9: 133:86 error: unresolved import `dom::bindings::codegen::UnionTypes::BlobOrStringOrURLSearchParams`. There is no `BlobOrStringOrURLSearchParams` in `dom::bindings::codegen::UnionTypes`. Did you mean to use `USVStringOrURLSearchParams`? [E0432]
/home/jayson/src/servo/target/debug/build/script-2a4d8b76db936b97/out/Bindings/XMLHttpRequestBinding.rs:133 pub use dom::bindings::codegen::UnionTypes::BlobOrStringOrURLSearchParams as BodyInit;
                                                                                                                    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/jayson/src/servo/components/script/dom/bindings/mod.rs:160:9: 160:64 note: in this expansion of include!
/home/jayson/src/servo/target/debug/build/script-2a4d8b76db936b97/out/Bindings/XMLHttpRequestBinding.rs:133:9: 133:86 help: run `rustc --explain E0432` to see a detailed explanation
error: aborting due to previous error
error: Could not compile `script`.

To learn more, run the command again with --verbose.
Build completed in 90.63s

tests/wpt/web-platform-tests/XMLHttpRequest/send-entity-body-document.htm, line 21 [r1] (raw file):
Should I try to change it on this PR or open an issue?


Comments from Reviewable

@KiChjang
Copy link
Contributor

-S-awaiting-review +S-needs-code-changes +S-blocked-on-external


Reviewed 5 of 5 files at r2.
Review status: all files reviewed at latest revision, 3 unresolved discussions.


components/script/dom/xmlhttprequest.rs, line 1447 [r2] (raw file):
Does content_type.to_string() not work?


components/script/dom/webidls/XMLHttpRequest.webidl, line 58 [r1] (raw file):
Ugh, it looks like the BlobOrDOMStringOrURLSearchParams union type is completely forgotten and DocumentOrDOMStringOrURLSearchParams supersedes it. Looks like this PR will be blocked until this is fixed.


tests/wpt/web-platform-tests/XMLHttpRequest/send-entity-body-document.htm, line 21 [r1] (raw file):
You'll need to change it in servo/html5ever either way. This PR is not capable of making changes under that repo. For now, remove this extra \n and mark the test as a failure.


Comments from Reviewable

@KiChjang KiChjang added S-blocked-on-external Something, somewhere else, needs to happen before this PR can be merged. S-needs-code-changes Changes have not yet been made that were requested by a reviewer. and removed S-awaiting-review There is new code that needs to be reviewed. labels Apr 14, 2016
@jaysonsantos
Copy link
Author

Review status: all files reviewed at latest revision, 1 unresolved discussion.


components/script/dom/xmlhttprequest.rs, line 1447 [r2] (raw file):
I didn't find any to_string on https://doc.servo.org/mime/struct.Mime.html did I miss something?


Comments from Reviewable

@jaysonsantos
Copy link
Author

Review status: all files reviewed at latest revision, 1 unresolved discussion.


components/script/dom/xmlhttprequest.rs, line 1447 [r2] (raw file):
Ah sorry, I didn't pay attention it uses AsRef


Comments from Reviewable

@highfive highfive added S-awaiting-review There is new code that needs to be reviewed. and removed S-needs-code-changes Changes have not yet been made that were requested by a reviewer. labels Apr 14, 2016
@jaysonsantos
Copy link
Author

Review status: 5 of 7 files reviewed at latest revision, 1 unresolved discussion.


components/script/dom/xmlhttprequest.rs, line 1447 [r2] (raw file):
implements s/AsRef/Display/*


Comments from Reviewable

@KiChjang KiChjang removed the S-awaiting-review There is new code that needs to be reviewed. label Apr 14, 2016
@KiChjang
Copy link
Contributor

Reviewed 3 of 3 files at r3.
Review status: all files reviewed at latest revision, 1 unresolved discussion.


tests/wpt/web-platform-tests/XMLHttpRequest/send-entity-body-document.htm, line 21 [r3] (raw file):
I'm not very comfortable with changing the testfiles just to make our test cases pass; these tests are also used by other browsers, so if there's a problem with parsing in Rust, contact the mime crate author (@seanmonstar) instead.


Comments from Reviewable

@jaysonsantos
Copy link
Author

Review status: all files reviewed at latest revision, 1 unresolved discussion.


tests/wpt/web-platform-tests/XMLHttpRequest/send-entity-body-document.htm, line 21 [r3] (raw file):
Got it, I didn't know about this. I just opened this issue to talk about it hyperium/mime#41
I will revert it. If the pr get's accepted I will rebase to make the merge cleaner.


Comments from Reviewable

@highfive highfive added the S-awaiting-review There is new code that needs to be reviewed. label Apr 14, 2016
@jdm
Copy link
Member

jdm commented Sep 20, 2016

I'm not sure why it worked, but I'm glad it did :)

@highfive highfive added S-awaiting-review There is new code that needs to be reviewed. and removed S-needs-code-changes Changes have not yet been made that were requested by a reviewer. labels Sep 20, 2016
@jaysonsantos
Copy link
Author

Hey guys, I've pushed just the code moving because I want to check 2 tests with you. When the shift-js runs it returns some mojibake and fail. I did this, was it right?

fn serialize_document(document: &Document) -> Fallible<DOMString> {
let mut writer = vec![];
if let Ok(()) = serialize(&mut writer, &document.upcast::<Node>(),
SerializeOpts { traversal_scope: ChildrenOnly, ..Default::default() }) {
Copy link
Contributor

Choose a reason for hiding this comment

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

IIRC traversal_scope is ChildrenOnly by default, so the second argument can just be Default::default() instead.

// Serialize a Document struct
fn serialize_document(document: &Document) -> Fallible<DOMString> {
let mut writer = vec![];
if let Ok(()) = serialize(&mut writer, &document.upcast::<Node>(),
Copy link
Contributor

Choose a reason for hiding this comment

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

if serialize(&mut writer, &document.upcast::<Node>(), Default::default()).is_ok() {

let boundary = generate_boundary();
let bytes = encode_multipart_form_data(&mut formdata.datums(), boundary.clone(),
UTF_8 as EncodingRef);
(bytes, Some(DOMString::from(format!("multipart/form-data;boundary={}", boundary))))
},
DocumentOrBodyInit::Document(ref d) => {
Copy link
Contributor

@KiChjang KiChjang Sep 20, 2016

Choose a reason for hiding this comment

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

A document is not Extractable. This should be done in fn Send(), under step 4 (first half).

@KiChjang KiChjang added S-needs-code-changes Changes have not yet been made that were requested by a reviewer. and removed S-awaiting-review There is new code that needs to be reviewed. labels Sep 20, 2016
@bors-servo
Copy link
Contributor

☔ The latest upstream changes (presumably #13382) made this pull request unmergeable. Please resolve the merge conflicts.

@highfive highfive added the S-needs-rebase There are merge conflict errors. label Sep 28, 2016
@KiChjang
Copy link
Contributor

KiChjang commented Oct 1, 2016

@jaysonsantos Is this still being worked on?

@jaysonsantos
Copy link
Author

@KiChjang Hi, I did a small pause because my newborn is here, my local branch is already rebased but I didn't push to avoid you guys doing unnecessary code review. If you think this is critical you can assign to a new person, otherwise, I will get back to it probably in a few days.

@KiChjang
Copy link
Contributor

Let us know when you want to work on this again, in the meantime, I'm closing this.Thanks for your work so far!

@jaysonsantos
Copy link
Author

@KiChjang just a check even though this pr is not open anymore. I moved the serialize_document outside of the Extractable implementation here master...jaysonsantos:xhr-send and I don't know if this is related but it started to break with this error.

   Compiling script v0.0.1 (file:///D:/p/personal/servo/components/script)
error: no method named `extract` found for type `&dom::bindings::codegen::UnionTypes::BlobOrFormDataOrStringOrURLSearchParams` in the current scope
   --> D:\p\personal\servo\components\script\dom\request.rs:374:48
    |
374 |             let extracted_body_tmp = init_body.extract();
    |                                                ^^^^^^^
    |
    = help: items from traits can only be used if the trait is implemented and in scope; the following trait defines an item `extract`, perhaps you need to implement it:
    = help: candidate #1: `dom::xmlhttprequest::Extractable`

error: no method named `extract` found for type `&dom::bindings::codegen::UnionTypes::BlobOrFormDataOrStringOrURLSearchParams` in the current scope
   --> D:\p\personal\servo\components\script\dom\response.rs:115:55
    |
115 |             let (extracted_body, content_type) = body.extract();
    |                                                       ^^^^^^^
    |
    = help: items from traits can only be used if the trait is implemented and in scope; the following trait defines an item `extract`, perhaps you need to implement it:
    = help: candidate #1: `dom::xmlhttprequest::Extractable`

error: aborting due to 2 previous errors

error: Could not compile `script`.

To learn more, run the command again with --verbose.

Which shouldn't happen because on the webidl it has an alias typedef (Blob or /*BufferSource or */ FormData or DOMString or URLSearchParams) BodyInit;
Do you see why this would be happening?
Plus, can racer complete code that comes from the codegen?
Thank you very much!

@KiChjang
Copy link
Contributor

Extractable should only be implemented on BodyInit, not DocumentOrBodyInit.

@jaysonsantos
Copy link
Author

@KiChjang I think I got it. But if I implement Extractable for BodyInit and send gets Option<DocumentOrBodyInit> how would I cast it back to BodyInit in case it the value is not DocumentOrBodyInit::Document and I cannot match DocumentOrBodyInit::BodyInit because BodyInit expands to (Blob, FormData, String and something else)?

@KiChjang
Copy link
Contributor

KiChjang commented Oct 25, 2016

So, 4 possible solutions:

  1. Implement Extractable for DocumentOrBodyInit anyway, and serialize the document in the Document variant (what you have in this PR).
  2. Implement Extractable for the inner field types of the BodyInit struct variants, and instead of just calling data.extract() in step 4 of Send, have a match expression that handles the variants appropriately.
  3. Patch CodegenRust.py so that DocumentOrBodyInit would produce 2 variants: Document and BodyInit.
  4. Convert DocumentOrBodyInit variants (except for the Document variant) into BodyInit, and call .extract() on it.

4 sounds like the easiest, since you can just use a match expression and wrap BodyInit around it, e.g.

let body = match data {
    // ...
    DocumentOrBodyInit::String(s) => {
        let body_init = BodyInit::String(s);
        body_init.extract()
    }
    // ...
};

... though it may sound a bit silly, since you're creating another wrapper struct just to call a method on the wrapper. It may just be better to go with 2, since you'd be able to do the following:

let body = match data {
    // ...
    DocumentOrBodyInit::String(s) => s.extract(),
    // ...
};

1 is not acceptable, since it deviates from the spec, and aside from the difficulty of patching CodegenRust.py, I have reservations on 3 as to whether that's the best way to move forward.

@jaysonsantos
Copy link
Author

Hi @KiChjang I came up with this

diff --git a/components/script/dom/xmlhttprequest.rs b/components/script/dom/xmlhttprequest.rs
index 462ba85..3c215fd 100644
--- a/components/script/dom/xmlhttprequest.rs
+++ b/components/script/dom/xmlhttprequest.rs
@@ -8,7 +8,7 @@ use dom::bindings::codegen::Bindings::BlobBinding::BlobMethods;
 use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
 use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull;
 use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
-use dom::bindings::codegen::Bindings::XMLHttpRequestBinding;
+use dom::bindings::codegen::Bindings::XMLHttpRequestBinding::{BodyInit, self};
 use dom::bindings::codegen::Bindings::XMLHttpRequestBinding::XMLHttpRequestMethods;
 use dom::bindings::codegen::Bindings::XMLHttpRequestBinding::XMLHttpRequestResponseType;
 use dom::bindings::codegen::UnionTypes::DocumentOrBodyInit;
@@ -25,6 +25,7 @@ use dom::document::{Document, IsHTMLDocument};
 use dom::document::DocumentSource;
 use dom::event::{Event, EventBubbles, EventCancelable};
 use dom::eventtarget::EventTarget;
+use dom::formdata::FormData;
 use dom::globalscope::GlobalScope;
 use dom::headers::is_forbidden_header_name;
 use dom::htmlformelement::{encode_multipart_form_data, generate_boundary};
@@ -32,6 +33,7 @@ use dom::node::Node;
 use dom::progressevent::ProgressEvent;
 use dom::servoparser::html::{ParseContext, parse_html};
 use dom::servoparser::xml::{self, parse_xml};
+use dom::urlsearchparams::URLSearchParams;
 use dom::window::Window;
 use dom::workerglobalscope::WorkerGlobalScope;
 use dom::xmlhttprequesteventtarget::XMLHttpRequestEventTarget;
@@ -519,20 +521,45 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
             _ => data
         };
         // Step 4 (first half)
-        let extracted = data.as_ref().map(|d| d.extract());
+        let extracted = match data {
+            Some(DocumentOrBodyInit::Document(ref d)) => {
+                let data: Vec<u8> = serialize_document(d).unwrap().into();
+                let decoded_data: Vec<u8> = match &*d.CharacterSet() {
+                    "UTF-8" => {
+                        debug!("Document is already utf-8, skipping conversion {:?}", d.url());
+                        data
+                    },
+                    document_charset => {
+                        debug!("Document is {:?} we have to decode", document_charset);
+                        let charset = encoding_from_whatwg_label(&*d.CharacterSet()).unwrap_or(UTF_8);
+                        charset.decode(&*data, DecoderTrap::Replace).unwrap().into_bytes()
+                    }
+                };
+                let mut content_type = String::new();
+                if d.is_html_document() {
+                    content_type.push_str("text/html");
+                } else {
+                    content_type.push_str("application/xml");
+                };
+                content_type.push_str(";charset=UTF-8");
+                (decoded_data, Some(DOMString::from(content_type)))
+            },
+            Some(DocumentOrBodyInit::Blob(ref blob)) => blob.extract(),
+            Some(DocumentOrBodyInit::FormData(ref form_data)) => form_data.extract(),
+            Some(DocumentOrBodyInit::String(ref s)) => s.extract(),
+            Some(DocumentOrBodyInit::Blob(ref usp)) => usp.extract()
+        };

-        self.request_body_len.set(extracted.as_ref().map_or(0, |e| e.0.len()));
+        self.request_body_len.set(extracted.0.len());

         // todo preserved headers?

         // Step 6
         self.upload_complete.set(false);
         // Step 7
-        self.upload_complete.set(match extracted {
-            None => true,
-            Some (ref e) if e.0.is_empty() => true,
-            _ => false
-        });
+        if extracted.0.is_empty() {
+            self.upload_complete.set(true);
+        }
         // Step 8
         self.send_flag.set(true);

@@ -590,7 +617,7 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
             headers: (*self.request_headers.borrow()).clone(),
             unsafe_request: true,
             // XXXManishearth figure out how to avoid this clone
-            body: extracted.as_ref().map(|e| e.0.clone()),
+            body: Some(extracted.0.clone()),
             // XXXManishearth actually "subresource", but it doesn't exist
             // https://github.com/whatwg/xhr/issues/71
             destination: Destination::None,
@@ -612,7 +639,7 @@ impl XMLHttpRequestMethods for XMLHttpRequest {

         // step 4 (second half)
         match extracted {
-            Some((_, ref content_type)) => {
+            (_, ref content_type) => {
                 // this should handle Document bodies too, not just BodyInit
                 let encoding = if let Some(DocumentOrBodyInit::String(_)) = data {
                     // XHR spec differs from http, and says UTF-8 should be in capitals,
@@ -1370,61 +1397,47 @@ pub trait Extractable {
     fn extract(&self) -> (Vec<u8>, Option<DOMString>);
 }

-impl Extractable for DocumentOrBodyInit {
+impl Extractable for Root<Blob> {
     // https://fetch.spec.whatwg.org/#concept-bodyinit-extract
     fn extract(&self) -> (Vec<u8>, Option<DOMString>) {
-        match *self {
-            DocumentOrBodyInit::String(ref s) => {
-                let encoding = UTF_8 as EncodingRef;
-                (encoding.encode(s, EncoderTrap::Replace).unwrap(),
-                    Some(DOMString::from("text/plain;charset=UTF-8")))
-            }
-            DocumentOrBodyInit::URLSearchParams(ref usp) => {
-                // Default encoding is UTF-8.
-                (usp.serialize(None).into_bytes(),
-                    Some(DOMString::from("application/x-www-form-urlencoded;charset=UTF-8")))
-            }
-            DocumentOrBodyInit::Blob(ref b) => {
-                let content_type = if b.Type().as_ref().is_empty() {
-                    None
-                } else {
-                    Some(b.Type())
-                };
-                let bytes = b.get_bytes().unwrap_or(vec![]);
-                (bytes, content_type)
-            }
-            DocumentOrBodyInit::FormData(ref formdata) => {
-                let boundary = generate_boundary();
-                let bytes = encode_multipart_form_data(&mut formdata.datums(), boundary.clone(),
-                                                       UTF_8 as EncodingRef);
-                (bytes, Some(DOMString::from(format!("multipart/form-data;boundary={}", boundary))))
-            },
-            DocumentOrBodyInit::Document(ref d) => {
-                let data: Vec<u8> = serialize_document(d).unwrap().into();
-                let decoded_data: Vec<u8> = match &*d.CharacterSet() {
-                    "UTF-8" => {
-                        debug!("Document is already utf-8, skipping conversion {:?}", d.url());
-                        data
-                    },
-                    document_charset => {
-                        debug!("Document is {:?} we have to decode", document_charset);
-                        let charset = encoding_from_whatwg_label(&*d.CharacterSet()).unwrap_or(UTF_8);
-                        charset.decode(&*data, DecoderTrap::Replace).unwrap().into_bytes()
-                    }
-                };
-                let mut content_type = String::new();
-                if d.is_html_document() {
-                    content_type.push_str("text/html");
-                } else {
-                    content_type.push_str("application/xml");
-                };
-                content_type.push_str(";charset=UTF-8");
-                (decoded_data, Some(DOMString::from(content_type)))
-            }
-        }
+        let content_type = if self.Type().as_ref().is_empty() {
+            None
+        } else {
+            Some(self.Type())
+        };
+        let bytes = self.get_bytes().unwrap_or(vec![]);
+        (bytes, content_type)
     }
 }

+impl Extractable for Root<FormData> {
+    // https://fetch.spec.whatwg.org/#concept-bodyinit-extract
+    fn extract(&self) -> (Vec<u8>, Option<DOMString>) {
+        let boundary = generate_boundary();
+        let bytes = encode_multipart_form_data(&mut self.datums(), boundary.clone(),
+                                                UTF_8 as EncodingRef);
+        (bytes, Some(DOMString::from(format!("multipart/form-data;boundary={}", boundary))))
+    }
+}
+
+impl Extractable for DOMString {
+    // https://fetch.spec.whatwg.org/#concept-bodyinit-extract
+    fn extract(&self) -> (Vec<u8>, Option<DOMString>) {
+        let encoding = UTF_8 as EncodingRef;
+        (encoding.encode(self, EncoderTrap::Replace).unwrap(),
+            Some(DOMString::from("text/plain;charset=UTF-8")))
+    }
+}
+
+impl Extractable for Root<URLSearchParams> {
+    // https://fetch.spec.whatwg.org/#concept-bodyinit-extract
+    fn extract(&self) -> (Vec<u8>, Option<DOMString>) {
+        // Default encoding is UTF-8.
+        (self.serialize(None).into_bytes(),
+            Some(DOMString::from("application/x-www-form-urlencoded;charset=UTF-8")))
+    }
+}   
+
 /// Returns whether `bs` is a `field-value`, as defined by
 /// [RFC 2616](http://tools.ietf.org/html/rfc2616#page-32).
 pub fn is_field_value(slice: &[u8]) -> bool {
@@ -1492,8 +1505,8 @@ pub fn is_field_value(slice: &[u8]) -> bool {
 // Serialize a Document struct
 fn serialize_document(document: &Document) -> Fallible<DOMString> {
     let mut writer = vec![];
-    if let Ok(()) = serialize(&mut writer, &document.upcast::<Node>(),
-        SerializeOpts { traversal_scope: ChildrenOnly, ..Default::default() }) {
+    if serialize(&mut writer, &document.upcast::<Node>(),
+        SerializeOpts { traversal_scope: ChildrenOnly, ..Default::default() }).is_ok() {
         Ok(DOMString::from(String::from_utf8(writer).unwrap()))
     } else {
         Err(Error::InvalidState)

but without implementing it on Extractable I will repeat the code a lot because other places use it like here:

error: no method named `extract` found for type `&dom::bindings::codegen::UnionTypes::BlobOrFormDataOrStringOrURLSearchParams` in the current scope
   --> D:\p\personal\servo\components\script\dom\request.rs:374:48
    |
374 |             let extracted_body_tmp = init_body.extract();
    |                                                ^^^^^^^
    |
    = help: items from traits can only be used if the trait is implemented and in scope; the following trait defines an item `extract`, perhaps you need to implement it:
    = help: candidate #1: `dom::xmlhttprequest::Extractable`

error: no method named `extract` found for type `&dom::bindings::codegen::UnionTypes::BlobOrFormDataOrStringOrURLSearchParams` in the current scope
   --> D:\p\personal\servo\components\script\dom\response.rs:115:55
    |
115 |             let (extracted_body, content_type) = body.extract();
    |                                                       ^^^^^^^
    |
    = help: items from traits can only be used if the trait is implemented and in scope; the following trait defines an item `extract`, perhaps you need to implement it:
    = help: candidate #1: `dom::xmlhttprequest::Extractable`

error: aborting due to 2 previous errors

error: Could not compile `script`.

To learn more, run the command again with --verbose.
Build FAILED in 0:01:38
PS D:\p\personal\servo> .\mach build --dev
   Compiling script v0.0.1 (file:///D:/p/personal/servo/components/script)
error: no method named `extract` found for type `&dom::bindings::codegen::UnionTypes::BlobOrFormDataOrStringOrURLSearchParams` in the current scope
   --> D:\p\personal\servo\components\script\dom\request.rs:374:48
    |
374 |             let extracted_body_tmp = init_body.extract();
    |                                                ^^^^^^^
    |
    = help: items from traits can only be used if the trait is implemented and in scope; the following trait defines an item `extract`, perhaps you need to implement it:
    = help: candidate #1: `dom::xmlhttprequest::Extractable`

error: no method named `extract` found for type `&dom::bindings::codegen::UnionTypes::BlobOrFormDataOrStringOrURLSearchParams` in the current scope
   --> D:\p\personal\servo\components\script\dom\response.rs:115:55
    |
115 |             let (extracted_body, content_type) = body.extract();
    |                                                       ^^^^^^^
    |
    = help: items from traits can only be used if the trait is implemented and in scope; the following trait defines an item `extract`, perhaps you need to implement it:
    = help: candidate #1: `dom::xmlhttprequest::Extractable`

error: aborting due to 2 previous errors

error: Could not compile `script`.

any suggestions? i thought about creating a function that matches with values to return the tuple from extract but i am afraid of breaking the "code rule"

@KiChjang
Copy link
Contributor

The easiest way here is to simply implement Extractable on BodyInit, and have it call .extract() on its enum variants.

@KiChjang
Copy link
Contributor

Also, instead of implementing traits on Root<T>, you should implement traits on T instead.

@jaysonsantos
Copy link
Author

The easiest way here is to simply implement Extractable on BodyInit, and have it call .extract() on its enum variants.

To do it I would have to make a match on DocumentOrBodyInit and create a BodyInit, right? That let extracted_body_tmp = init_body.extract(); would only use BodyInit or would it still run with DocumentOrBodyInit?

@jaysonsantos
Copy link
Author

Hey @KiChjang I was trying to implement what you said and I got a really dumb error with the ownership but all the ways I tried to look real ugly and I thought that it might be because the way I tried to do the last changes. When I match data to extract the values, it's ownership is being transferred and it is being used down in the same function, I thought of using match as reference but when I started putting & to all returns of it started to feel strange.
this is the compared version master...jaysonsantos:xhr-send
this was the error:

   Compiling script v0.0.1 (file:///D:/p/personal/servo/components/script)
error[E0382]: use of partially moved value: `data`
   --> D:\p\personal\servo\components\script\dom\xmlhttprequest.rs:658:77
    |
546 |             Some(DocumentOrBodyInit::Blob(blob)) => {
    |                                           ---- value moved here
...
658 |                 let encoding = if let Some(DocumentOrBodyInit::String(_)) = data {
    |                                                                             ^^^^ value used here after move
    |
    = note: move occurs because `((data:core::prelude::v1::Some).0:dom::bindings::codegen::UnionTypes::DocumentOrBodyInit::Blob).0` has type `dom::bindings::js::Root<dom::blob::Blob>`, which does not implement t
he `Copy` trait

error: aborting due to previous error

error: Could not compile `script`.

To learn more, run the command again with --verbose.

@jdm
Copy link
Member

jdm commented Nov 7, 2016

Yeah, that's a tricky one. You probably need to do something like this:

           Some(DocumentOrBodyInit::Blob(ref blob)) => {
                let blob = BodyInit::Blob(Root::from_ref(&**blob));

@jaysonsantos
Copy link
Author

jaysonsantos commented Nov 8, 2016

is it too ugly / is it possible to do something like this?

let (extracted, data) = match data {
    Some(...) => (Some(extracted_data), data),
    _ => (None, data)
}

@jdm
Copy link
Member

jdm commented Nov 8, 2016

Seems reasonable.

@jaysonsantos
Copy link
Author

I tried both ways and in the end this happened:

PS D:\p\personal\servo> .\mach build -d
   Compiling script v0.0.1 (file:///D:/p/personal/servo/components/script)
error[E0507]: cannot move out of borrowed content
   --> D:\p\personal\servo\components\script\dom\xmlhttprequest.rs:555:47
    |
555 |                 let string = BodyInit::String(*string);
    |                                               ^^^^^^^ cannot move out of borrowed content

error[E0505]: cannot move out of `data` because it is borrowed
   --> D:\p\personal\servo\components\script\dom\xmlhttprequest.rs:544:77
    |
524 |             Some(DocumentOrBodyInit::Document(ref d)) => {
    |                                               ----- borrow of `data.0.0` occurs here
...
544 |                 (Some((decoded_data, Some(DOMString::from(content_type)))), data)
    |                                                                             ^^^^ move out of `data` occurs here

error[E0505]: cannot move out of `data` because it is borrowed
   --> D:\p\personal\servo\components\script\dom\xmlhttprequest.rs:548:40
    |
546 |             Some(DocumentOrBodyInit::Blob(ref blob)) => {
    |                                           -------- borrow of `data.0.0` occurs here
547 |                 let blob = BodyInit::Blob(Root::from_ref(blob));
548 |                 (Some(blob.extract()), data)
    |                                        ^^^^ move out of `data` occurs here

error[E0505]: cannot move out of `data` because it is borrowed
   --> D:\p\personal\servo\components\script\dom\xmlhttprequest.rs:552:45
    |
550 |             Some(DocumentOrBodyInit::FormData(ref form_data)) => {
    |                                               ------------- borrow of `data.0.0` occurs here
551 |                 let form_data = BodyInit::FormData(Root::from_ref(form_data));
552 |                 (Some(form_data.extract()), data)
    |                                             ^^^^ move out of `data` occurs here

error[E0505]: cannot move out of `data` because it is borrowed
   --> D:\p\personal\servo\components\script\dom\xmlhttprequest.rs:556:42
    |
554 |             Some(DocumentOrBodyInit::String(ref string)) => {
    |                                             ---------- borrow of `data.0.0` occurs here
555 |                 let string = BodyInit::String(*string);
556 |                 (Some(string.extract()), data)
    |                                          ^^^^ move out of `data` occurs here

error[E0505]: cannot move out of `data` because it is borrowed
   --> D:\p\personal\servo\components\script\dom\xmlhttprequest.rs:560:39
    |
558 |             Some(DocumentOrBodyInit::URLSearchParams(ref usp)) => {
    |                                                      ------- borrow of `data.0.0` occurs here
559 |                 let usp = BodyInit::URLSearchParams(Root::from_ref(usp));
560 |                 (Some(usp.extract()), data)
    |                                       ^^^^ move out of `data` occurs here

error: aborting due to 6 previous errors

error: Could not compile `script`.

When using Root:from_ref i tried both with two derefs and without and the result looks like the same.
I was wondering if it is not better to create a match on the top just to check if it is a string type to avoid the down match breaking.

@jdm
Copy link
Member

jdm commented Nov 9, 2016

Two things - when moving data back out, you want to get rid of the ref from the match arms. This also means that Root::from_ref will need to be Root::from_ref(&*usp) (for example).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-needs-code-changes Changes have not yet been made that were requested by a reviewer. S-needs-rebase There are merge conflict errors.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

8 participants