Auto merge of #23011 - dboyan:open-feature-tokenize, r=gterzian,cybai

Add tokenizer for features in window.open

<!-- Please describe your changes on the following line: -->
This is a prototype implementation of feature tokenizer ~~and "noreferrer" feature~~ for window.open. I'm not very sure where the tokenizer code should be placed. Building now generates the following warning

```
warning: unused attribute
  --> components/script/dom/bindings/conversions.rs:74:5
   |
74 |     rustc_on_unimplemented(message = "The IDL interface `{Self}` is not derived from `{T}`.")
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: #[warn(unused_attributes)] on by default
```

---
<!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `___` with appropriate data: -->
- [X] `./mach build -d` does not report any errors
- [X] `./mach test-tidy` does not report any errors
- [X] These changes _partially_ fix #22869 (GitHub issue number if applicable)

<!-- Either: -->
- [X] There are tests for these changes OR
- [ ] These changes do not require tests because ___

<!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.-->

<!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. -->

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/23011)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2019-03-24 05:31:24 -04:00 committed by GitHub
commit 5f4030d028
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 106 additions and 48 deletions

View file

@ -20,6 +20,7 @@ use crate::dom::window::Window;
use crate::script_thread::ScriptThread;
use dom_struct::dom_struct;
use embedder_traits::EmbedderMsg;
use indexmap::map::IndexMap;
use ipc_channel::ipc;
use js::glue::{CreateWrapperProxyHandler, ProxyTraps};
use js::glue::{GetProxyPrivate, GetProxyReservedSlot, SetProxyReservedSlot};
@ -48,6 +49,7 @@ use script_traits::{AuxiliaryBrowsingContextLoadInfo, LoadData, NewLayoutInfo, S
use servo_url::ServoUrl;
use std::cell::Cell;
use std::ptr;
use style::attr::parse_integer;
#[dom_struct]
// NOTE: the browsing context for a window is managed in two places:
@ -388,26 +390,32 @@ impl WindowProxy {
target: DOMString,
features: DOMString,
) -> Option<DomRoot<WindowProxy>> {
// Step 3.
// Step 4.
let non_empty_target = match target.as_ref() {
"" => DOMString::from("_blank"),
_ => target,
};
// TODO Step 4, properly tokenize features.
// Step 5
let noopener = features.contains("noopener");
// Step 6, 7
let tokenized_features = tokenize_open_features(features);
// Step 7-9
let noreferrer = parse_open_feature_boolean(&tokenized_features, "noreferrer");
let noopener = if noreferrer {
true
} else {
parse_open_feature_boolean(&tokenized_features, "noopener")
};
// Step 10, 11
let (chosen, new) = match self.choose_browsing_context(non_empty_target, noopener) {
(Some(chosen), new) => (chosen, new),
(None, _) => return None,
};
// TODO Step 8, set up browsing context features.
// TODO Step 12, set up browsing context features.
let target_document = match chosen.document() {
Some(target_document) => target_document,
None => return None,
};
let target_window = target_document.window();
// Step 9, and 10.2, will have happened elsewhere,
// Step 13, and 14.4, will have happened elsewhere,
// since we've created a new browsing context and loaded it with about:blank.
if !url.is_empty() {
let existing_document = self
@ -415,19 +423,20 @@ impl WindowProxy {
.get()
.and_then(|id| ScriptThread::find_document(id))
.unwrap();
// Step 10.1
// Step 14.1
let url = match existing_document.url().join(&url) {
Ok(url) => url,
Err(_) => return None, // TODO: throw a "SyntaxError" DOMException.
};
// Step 10.3
// TODO Step 14.3, handle noreferrer flag
// Step 14.5
target_window.load_url(url, new, false, target_document.get_referrer_policy());
}
if noopener {
// Step 11 (Dis-owning has been done in create_auxiliary_browsing_context).
// Step 15 (Dis-owning has been done in create_auxiliary_browsing_context).
return None;
}
// Step 12.
// Step 17.
return target_document.browsing_context();
}
@ -590,6 +599,93 @@ impl WindowProxy {
}
}
// https://html.spec.whatwg.org/multipage/#concept-window-open-features-tokenize
fn tokenize_open_features(features: DOMString) -> IndexMap<String, String> {
let is_feature_sep = |c: char| c.is_ascii_whitespace() || ['=', ','].contains(&c);
// Step 1
let mut tokenized_features = IndexMap::new();
// Step 2
let mut iter = features.chars();
let mut cur = iter.next();
// Step 3
while cur != None {
// Step 3.1 & 3.2
let mut name = String::new();
let mut value = String::new();
// Step 3.3
while let Some(cur_char) = cur {
if !is_feature_sep(cur_char) {
break;
}
cur = iter.next();
}
// Step 3.4
while let Some(cur_char) = cur {
if is_feature_sep(cur_char) {
break;
}
name.push(cur_char.to_ascii_lowercase());
cur = iter.next();
}
// Step 3.5
let normalized_name = String::from(match name.as_ref() {
"screenx" => "left",
"screeny" => "top",
"innerwidth" => "width",
"innerheight" => "height",
_ => name.as_ref(),
});
// Step 3.6
while let Some(cur_char) = cur {
if cur_char == '=' || cur_char == ',' || !is_feature_sep(cur_char) {
break;
}
cur = iter.next();
}
// Step 3.7
if cur.is_some() && is_feature_sep(cur.unwrap()) {
// Step 3.7.1
while let Some(cur_char) = cur {
if !is_feature_sep(cur_char) || cur_char == ',' {
break;
}
cur = iter.next();
}
// Step 3.7.2
while let Some(cur_char) = cur {
if is_feature_sep(cur_char) {
break;
}
value.push(cur_char.to_ascii_lowercase());
cur = iter.next();
}
}
// Step 3.8
if !name.is_empty() {
tokenized_features.insert(normalized_name, value);
}
}
// Step 4
tokenized_features
}
// https://html.spec.whatwg.org/multipage/#concept-window-open-features-parse-boolean
fn parse_open_feature_boolean(tokenized_features: &IndexMap<String, String>, name: &str) -> bool {
if let Some(value) = tokenized_features.get(name) {
// Step 1 & 2
if value == "" || value == "yes" {
return true;
}
// Step 3 & 4
if let Ok(int) = parse_integer(value.chars()) {
return int != 0;
}
}
// Step 5
return false;
}
// This is only called from extern functions,
// there's no use using the lifetimed handles here.
// https://html.spec.whatwg.org/multipage/#accessing-other-browsing-contexts

View file

@ -1,19 +0,0 @@
[open-features-tokenization-noopener.html]
[feature `name` should be converted to ASCII lowercase]
expected: FAIL
[invalid feature names should not tokenize as "noopener"]
expected: FAIL
[Integer value of 0 should not activate the feature]
expected: FAIL
[Invalid feature names should not tokenize as "noopener"]
expected: FAIL
[Integer value of 0 should not activate "noopener"]
expected: FAIL
[Feature "noopener" should be converted to ASCII lowercase]
expected: FAIL

View file

@ -1,19 +0,0 @@
[open-features-tokenization-noreferrer.html]
[Tokenizing "noreferrer" should ignore window feature separators except "," after initial "=" and before value]
expected: FAIL
[Tokenizing "noreferrer" should read characters until first window feature separator as `value`]
expected: FAIL
[After "noreferrer", tokenization should skip window features separators that are not "=" or ","]
expected: FAIL
[Integer values other than 0 should activate the feature]
expected: FAIL
[Tokenization of "noreferrer" should skip window features separators before feature]
expected: FAIL
[Feature "noreferrer" should be converted to ASCII lowercase]
expected: FAIL