mirror of
https://github.com/servo/servo.git
synced 2025-08-06 06:00:15 +01:00
Merge branch 'master' of https://github.com/servo/servo
This commit is contained in:
commit
084a6e24d5
20 changed files with 1114 additions and 47 deletions
|
@ -57,9 +57,10 @@ If you're running servo on a guest machine, make sure 3D Acceleration is switche
|
|||
|
||||
## The Rust compiler
|
||||
|
||||
Servo uses a snapshot Rust compiler to build itself. This is normally a
|
||||
specific revision of Rust upstream, but sometimes has a backported patch or
|
||||
two. If you'd like to know the snapshot revision of Rust which we use, see
|
||||
Servo's build system automatically downloads a snapshot Rust compiler to build itself.
|
||||
This is normally a specific revision of Rust upstream, but sometimes has a
|
||||
backported patch or two.
|
||||
If you'd like to know the snapshot revision of Rust which we use, see
|
||||
`./rust-snapshot-hash`.
|
||||
|
||||
## Building
|
||||
|
|
|
@ -771,8 +771,9 @@ impl BlockFlow {
|
|||
}
|
||||
|
||||
let mut margin_collapse_info = MarginCollapseInfo::new();
|
||||
let writing_mode = self.base.floats.writing_mode;
|
||||
self.base.floats.translate(LogicalSize::new(
|
||||
self.fragment.style.writing_mode, -self.fragment.inline_start_offset(), Au(0)));
|
||||
writing_mode, -self.fragment.inline_start_offset(), Au(0)));
|
||||
|
||||
// The sum of our block-start border and block-start padding.
|
||||
let block_start_offset = self.fragment.border_padding.block_start;
|
||||
|
@ -1029,7 +1030,8 @@ impl BlockFlow {
|
|||
size: LogicalSize::new(
|
||||
self.fragment.style.writing_mode,
|
||||
self.base.position.size.inline,
|
||||
block_size + self.fragment.margin.block_start_end()),
|
||||
block_size + self.fragment.margin.block_start_end())
|
||||
.convert(self.fragment.style.writing_mode, self.base.floats.writing_mode),
|
||||
ceiling: clearance + float_info.float_ceiling,
|
||||
max_inline_size: float_info.containing_inline_size,
|
||||
kind: float_info.float_kind,
|
||||
|
@ -1039,11 +1041,17 @@ impl BlockFlow {
|
|||
// After, grab the position and use that to set our position.
|
||||
self.base.floats.add_float(&info);
|
||||
|
||||
// FIXME (mbrubeck) Get the correct container size for self.base.floats;
|
||||
let container_size = Size2D(self.base.block_container_inline_size, Au(0));
|
||||
|
||||
// Move in from the margin edge, as per CSS 2.1 § 9.5, floats may not overlap anything on
|
||||
// their margin edges.
|
||||
let float_offset = self.base.floats.last_float_pos().unwrap();
|
||||
let writing_mode = self.base.floats.writing_mode;
|
||||
let margin_offset = LogicalPoint::new(writing_mode,
|
||||
let float_offset = self.base.floats.last_float_pos().unwrap()
|
||||
.convert(self.base.floats.writing_mode,
|
||||
self.base.writing_mode,
|
||||
container_size)
|
||||
.start;
|
||||
let margin_offset = LogicalPoint::new(self.base.writing_mode,
|
||||
Au(0),
|
||||
self.fragment.margin.block_start);
|
||||
|
||||
|
@ -1379,7 +1387,8 @@ impl BlockFlow {
|
|||
}
|
||||
|
||||
let info = PlacementInfo {
|
||||
size: self.fragment.border_box.size,
|
||||
size: self.fragment.border_box.size.convert(self.fragment.style.writing_mode,
|
||||
self.base.floats.writing_mode),
|
||||
ceiling: self.base.position.start.b,
|
||||
max_inline_size: MAX_AU,
|
||||
kind: FloatKind::Left,
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
use util::geometry::Au;
|
||||
use util::logical_geometry::WritingMode;
|
||||
use util::logical_geometry::{LogicalPoint, LogicalRect, LogicalSize};
|
||||
use util::logical_geometry::{LogicalRect, LogicalSize};
|
||||
use util::persistent_list::PersistentList;
|
||||
use std::cmp::{max, min};
|
||||
use std::i32;
|
||||
|
@ -146,10 +146,10 @@ impl Floats {
|
|||
}
|
||||
|
||||
/// Returns the position of the last float in flow coordinates.
|
||||
pub fn last_float_pos(&self) -> Option<LogicalPoint<Au>> {
|
||||
pub fn last_float_pos(&self) -> Option<LogicalRect<Au>> {
|
||||
match self.list.floats.front() {
|
||||
None => None,
|
||||
Some(float) => Some(float.bounds.start + self.offset),
|
||||
Some(float) => Some(float.bounds.translate_by_size(self.offset)),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -959,14 +959,13 @@ impl InlineFlow {
|
|||
let mut expansion_opportunities = 0i32;
|
||||
for fragment_index in line.range.each_index() {
|
||||
let fragment = fragments.get(fragment_index.to_usize());
|
||||
let scanned_text_fragment_info =
|
||||
if let SpecificFragmentInfo::ScannedText(ref info) = fragment.specific {
|
||||
info
|
||||
} else {
|
||||
continue
|
||||
};
|
||||
for slice in scanned_text_fragment_info.run.character_slices_in_range(
|
||||
&scanned_text_fragment_info.range) {
|
||||
let scanned_text_fragment_info = match fragment.specific {
|
||||
SpecificFragmentInfo::ScannedText(ref info) if !info.range.is_empty() => info,
|
||||
_ => continue
|
||||
};
|
||||
let fragment_range = scanned_text_fragment_info.range;
|
||||
|
||||
for slice in scanned_text_fragment_info.run.character_slices_in_range(&fragment_range) {
|
||||
expansion_opportunities += slice.glyphs.space_count_in_range(&slice.range) as i32
|
||||
}
|
||||
}
|
||||
|
@ -976,12 +975,10 @@ impl InlineFlow {
|
|||
(expansion_opportunities as f64);
|
||||
for fragment_index in line.range.each_index() {
|
||||
let fragment = fragments.get_mut(fragment_index.to_usize());
|
||||
let mut scanned_text_fragment_info =
|
||||
if let SpecificFragmentInfo::ScannedText(ref mut info) = fragment.specific {
|
||||
info
|
||||
} else {
|
||||
continue
|
||||
};
|
||||
let mut scanned_text_fragment_info = match fragment.specific {
|
||||
SpecificFragmentInfo::ScannedText(ref mut info) if !info.range.is_empty() => info,
|
||||
_ => continue
|
||||
};
|
||||
let fragment_range = scanned_text_fragment_info.range;
|
||||
|
||||
// FIXME(pcwalton): This is an awful lot of uniqueness making. I don't see any easy way
|
||||
|
@ -1458,7 +1455,8 @@ impl Flow for InlineFlow {
|
|||
};
|
||||
|
||||
self.base.floats = scanner.floats.clone();
|
||||
self.base.floats.translate(LogicalSize::new(self.base.writing_mode,
|
||||
let writing_mode = self.base.floats.writing_mode;
|
||||
self.base.floats.translate(LogicalSize::new(writing_mode,
|
||||
Au(0),
|
||||
-self.base.position.size.block));
|
||||
|
||||
|
|
|
@ -114,7 +114,7 @@ pub struct Request {
|
|||
}
|
||||
|
||||
impl Request {
|
||||
pub fn new(url: Url, context: Context, isServiceWorkerGlobalScope: bool) -> Request {
|
||||
pub fn new(url: Url, context: Context, is_service_worker_global_scope: bool) -> Request {
|
||||
Request {
|
||||
method: Method::Get,
|
||||
url: url,
|
||||
|
@ -122,7 +122,7 @@ impl Request {
|
|||
unsafe_request: false,
|
||||
body: None,
|
||||
preserve_content_codings: false,
|
||||
is_service_worker_global_scope: isServiceWorkerGlobalScope,
|
||||
is_service_worker_global_scope: is_service_worker_global_scope,
|
||||
skip_service_worker: false,
|
||||
context: context,
|
||||
context_frame_type: ContextFrameType::ContextNone,
|
||||
|
@ -263,14 +263,14 @@ impl Request {
|
|||
if !response.headers.has::<Location>() {
|
||||
return response;
|
||||
}
|
||||
let location = response.headers.get::<Location>();
|
||||
if location.is_none() {
|
||||
return Response::network_error();
|
||||
}
|
||||
let location = match response.headers.get::<Location>() {
|
||||
None => return Response::network_error(),
|
||||
Some(location) => location,
|
||||
};
|
||||
// Step 5
|
||||
let locationUrl = Url::parse(location.unwrap());
|
||||
let location_url = Url::parse(location);
|
||||
// Step 6
|
||||
let locationUrl = match locationUrl {
|
||||
let location_url = match location_url {
|
||||
Ok(url) => url,
|
||||
Err(_) => return Response::network_error()
|
||||
};
|
||||
|
@ -286,10 +286,10 @@ impl Request {
|
|||
if self.redirect_mode == RedirectMode::Follow {
|
||||
// FIXME: Origin method of the Url crate hasn't been implemented (https://github.com/servo/rust-url/issues/54)
|
||||
// Substep 1
|
||||
// if cors_flag && locationUrl.origin() != self.url.origin() { self.origin = None; }
|
||||
// if cors_flag && location_url.origin() != self.url.origin() { self.origin = None; }
|
||||
// Substep 2
|
||||
if cors_flag && (!locationUrl.username().unwrap_or("").is_empty() ||
|
||||
locationUrl.password().is_some()) {
|
||||
if cors_flag && (!location_url.username().unwrap_or("").is_empty() ||
|
||||
location_url.password().is_some()) {
|
||||
return Response::network_error();
|
||||
}
|
||||
// Substep 3
|
||||
|
@ -299,7 +299,7 @@ impl Request {
|
|||
self.method = Method::Get;
|
||||
}
|
||||
// Substep 4
|
||||
self.url = locationUrl;
|
||||
self.url = location_url;
|
||||
// Substep 5
|
||||
return self.fetch(cors_flag);
|
||||
}
|
||||
|
|
|
@ -919,6 +919,13 @@ impl<T: Copy + Add<T, Output=T> + Sub<T, Output=T>> LogicalRect<T> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn translate_by_size(&self, offset: LogicalSize<T>) -> LogicalRect<T> {
|
||||
LogicalRect {
|
||||
start: self.start + offset,
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn translate(&self, offset: &LogicalPoint<T>) -> LogicalRect<T> {
|
||||
LogicalRect {
|
||||
start: self.start + LogicalSize {
|
||||
|
|
|
@ -264,6 +264,7 @@ experimental != overconstrained_block.html overconstrained_block_ref.html
|
|||
== root_margin_collapse_a.html root_margin_collapse_b.html
|
||||
== root_pseudo_a.html root_pseudo_b.html
|
||||
experimental == rtl_body.html rtl_body_ref.html
|
||||
experimental == rtl_float_a.html rtl_float_ref.html
|
||||
experimental == rtl_margin_a.html rtl_margin_ref.html
|
||||
experimental == rtl_simple.html rtl_simple_ref.html
|
||||
experimental == rtl_table_a.html rtl_table_ref.html
|
||||
|
|
20
tests/ref/rtl_float_a.html
Normal file
20
tests/ref/rtl_float_a.html
Normal file
|
@ -0,0 +1,20 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>RTL float test</title>
|
||||
<style>
|
||||
div {
|
||||
direction: rtl;
|
||||
float: left;
|
||||
background: green;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="a"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
19
tests/ref/rtl_float_ref.html
Normal file
19
tests/ref/rtl_float_ref.html
Normal file
|
@ -0,0 +1,19 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>RTL float reference</title>
|
||||
<style>
|
||||
div {
|
||||
float: left;
|
||||
background: green;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="a"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
[products]
|
||||
servo =
|
||||
servodriver =
|
||||
|
||||
[web-platform-tests]
|
||||
remote_url = https://github.com/w3c/web-platform-tests.git
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
[products]
|
||||
servo =
|
||||
servodriver =
|
||||
|
||||
[web-platform-tests]
|
||||
name = CSS tests
|
||||
|
|
|
@ -29,4 +29,5 @@ module global scope.
|
|||
product_list = ["b2g",
|
||||
"chrome",
|
||||
"firefox",
|
||||
"servo"]
|
||||
"servo",
|
||||
"servodriver"]
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import mozinfo
|
||||
from mozprocess import ProcessHandler
|
||||
|
@ -70,7 +71,6 @@ class FirefoxBrowser(Browser):
|
|||
self.binary = binary
|
||||
self.prefs_root = prefs_root
|
||||
self.marionette_port = None
|
||||
self.used_ports.add(self.marionette_port)
|
||||
self.runner = None
|
||||
self.debug_info = debug_info
|
||||
self.profile = None
|
||||
|
@ -81,6 +81,7 @@ class FirefoxBrowser(Browser):
|
|||
|
||||
def start(self):
|
||||
self.marionette_port = get_free_port(2828, exclude=self.used_ports)
|
||||
self.used_ports.add(self.marionette_port)
|
||||
|
||||
env = os.environ.copy()
|
||||
env["MOZ_DISABLE_NONLOCAL_CONNECTIONS"] = "1"
|
||||
|
@ -191,7 +192,8 @@ class FirefoxBrowser(Browser):
|
|||
|
||||
|
||||
env[env_var] = (os.path.pathsep.join([certutil_dir, env[env_var]])
|
||||
if env_var in env else certutil_dir)
|
||||
if env_var in env else certutil_dir).encode(
|
||||
sys.getfilesystemencoding() or 'utf-8', 'replace')
|
||||
|
||||
def certutil(*args):
|
||||
cmd = [self.certutil_binary] + list(args)
|
||||
|
|
141
tests/wpt/harness/wptrunner/browsers/servodriver.py
Normal file
141
tests/wpt/harness/wptrunner/browsers/servodriver.py
Normal file
|
@ -0,0 +1,141 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
from mozprocess import ProcessHandler
|
||||
|
||||
from .base import Browser, require_arg, get_free_port, browser_command, ExecutorBrowser
|
||||
from ..executors import executor_kwargs as base_executor_kwargs
|
||||
from ..executors.executorservodriver import (ServoWebDriverTestharnessExecutor,
|
||||
ServoWebDriverRefTestExecutor)
|
||||
|
||||
here = os.path.join(os.path.split(__file__)[0])
|
||||
|
||||
__wptrunner__ = {"product": "servodriver",
|
||||
"check_args": "check_args",
|
||||
"browser": "ServoWebDriverBrowser",
|
||||
"executor": {"testharness": "ServoWebDriverTestharnessExecutor",
|
||||
"reftest": "ServoWebDriverRefTestExecutor"},
|
||||
"browser_kwargs": "browser_kwargs",
|
||||
"executor_kwargs": "executor_kwargs",
|
||||
"env_options": "env_options"}
|
||||
|
||||
hosts_text = """127.0.0.1 web-platform.test
|
||||
127.0.0.1 www.web-platform.test
|
||||
127.0.0.1 www1.web-platform.test
|
||||
127.0.0.1 www2.web-platform.test
|
||||
127.0.0.1 xn--n8j6ds53lwwkrqhv28a.web-platform.test
|
||||
127.0.0.1 xn--lve-6lad.web-platform.test
|
||||
"""
|
||||
|
||||
|
||||
def check_args(**kwargs):
|
||||
require_arg(kwargs, "binary")
|
||||
|
||||
|
||||
def browser_kwargs(**kwargs):
|
||||
return {"binary": kwargs["binary"],
|
||||
"debug_info": kwargs["debug_info"]}
|
||||
|
||||
|
||||
def executor_kwargs(test_type, server_config, cache_manager, **kwargs):
|
||||
rv = base_executor_kwargs(test_type, server_config,
|
||||
cache_manager, **kwargs)
|
||||
return rv
|
||||
|
||||
|
||||
def env_options():
|
||||
return {"host": "web-platform.test",
|
||||
"bind_hostname": "true",
|
||||
"testharnessreport": "testharnessreport-servodriver.js",
|
||||
"supports_debugger": True}
|
||||
|
||||
|
||||
def make_hosts_file():
|
||||
hosts_fd, hosts_path = tempfile.mkstemp()
|
||||
with os.fdopen(hosts_fd, "w") as f:
|
||||
f.write(hosts_text)
|
||||
return hosts_path
|
||||
|
||||
|
||||
class ServoWebDriverBrowser(Browser):
|
||||
used_ports = set()
|
||||
|
||||
def __init__(self, logger, binary, debug_info=None, webdriver_host="127.0.0.1"):
|
||||
Browser.__init__(self, logger)
|
||||
self.binary = binary
|
||||
self.webdriver_host = webdriver_host
|
||||
self.webdriver_port = None
|
||||
self.proc = None
|
||||
self.debug_info = debug_info
|
||||
self.hosts_path = make_hosts_file()
|
||||
self.command = None
|
||||
|
||||
def start(self):
|
||||
self.webdriver_port = get_free_port(4444, exclude=self.used_ports)
|
||||
self.used_ports.add(self.webdriver_port)
|
||||
|
||||
env = os.environ.copy()
|
||||
env["HOST_FILE"] = self.hosts_path
|
||||
|
||||
debug_args, command = browser_command(self.binary,
|
||||
["--cpu", "--hard-fail",
|
||||
"--webdriver", str(self.webdriver_port),
|
||||
"about:blank"],
|
||||
self.debug_info)
|
||||
|
||||
self.command = command
|
||||
|
||||
self.command = debug_args + self.command
|
||||
|
||||
if not self.debug_info or not self.debug_info.interactive:
|
||||
self.proc = ProcessHandler(self.command,
|
||||
processOutputLine=[self.on_output],
|
||||
env=env,
|
||||
storeOutput=False)
|
||||
self.proc.run()
|
||||
else:
|
||||
self.proc = subprocess.Popen(self.command, env=env)
|
||||
|
||||
self.logger.debug("Servo Started")
|
||||
|
||||
def stop(self):
|
||||
self.logger.debug("Stopping browser")
|
||||
if self.proc is not None:
|
||||
try:
|
||||
self.proc.kill()
|
||||
except OSError:
|
||||
# This can happen on Windows if the process is already dead
|
||||
pass
|
||||
|
||||
def pid(self):
|
||||
if self.proc is None:
|
||||
return None
|
||||
|
||||
try:
|
||||
return self.proc.pid
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
def on_output(self, line):
|
||||
"""Write a line of output from the process to the log"""
|
||||
self.logger.process_output(self.pid(),
|
||||
line.decode("utf8", "replace"),
|
||||
command=" ".join(self.command))
|
||||
|
||||
def is_alive(self):
|
||||
if self.runner:
|
||||
return self.runner.is_running()
|
||||
return False
|
||||
|
||||
def cleanup(self):
|
||||
self.stop()
|
||||
|
||||
def executor_browser(self):
|
||||
assert self.webdriver_port is not None
|
||||
return ExecutorBrowser, {"webdriver_host": self.webdriver_host,
|
||||
"webdriver_port": self.webdriver_port}
|
|
@ -157,20 +157,19 @@ class MarionetteProtocol(Protocol):
|
|||
let prefInterface = Components.classes["@mozilla.org/preferences-service;1"]
|
||||
.getService(Components.interfaces.nsIPrefBranch);
|
||||
let pref = '%s';
|
||||
let value = '%s';
|
||||
let type = prefInterface.getPrefType(pref);
|
||||
switch(type) {
|
||||
case prefInterface.PREF_STRING:
|
||||
prefInterface.setCharPref(pref, value);
|
||||
prefInterface.setCharPref(pref, '%s');
|
||||
break;
|
||||
case prefInterface.PREF_BOOL:
|
||||
prefInterface.setBoolPref(pref, value);
|
||||
prefInterface.setBoolPref(pref, %s);
|
||||
break;
|
||||
case prefInterface.PREF_INT:
|
||||
prefInterface.setIntPref(pref, value);
|
||||
prefInterface.setIntPref(pref, %s);
|
||||
break;
|
||||
}
|
||||
""" % (name, value)
|
||||
""" % (name, value, value, value)
|
||||
self.marionette.execute_script(script)
|
||||
self.marionette.set_context(self.marionette.CONTEXT_CONTENT)
|
||||
|
||||
|
|
229
tests/wpt/harness/wptrunner/executors/executorservodriver.py
Normal file
229
tests/wpt/harness/wptrunner/executors/executorservodriver.py
Normal file
|
@ -0,0 +1,229 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import json
|
||||
import os
|
||||
import socket
|
||||
import threading
|
||||
import time
|
||||
import traceback
|
||||
|
||||
from .base import (Protocol,
|
||||
RefTestExecutor,
|
||||
RefTestImplementation,
|
||||
TestharnessExecutor,
|
||||
strip_server)
|
||||
import webdriver
|
||||
from ..testrunner import Stop
|
||||
|
||||
here = os.path.join(os.path.split(__file__)[0])
|
||||
|
||||
extra_timeout = 5
|
||||
|
||||
|
||||
class ServoWebDriverProtocol(Protocol):
|
||||
def __init__(self, executor, browser, capabilities, **kwargs):
|
||||
Protocol.__init__(self, executor, browser)
|
||||
self.capabilities = capabilities
|
||||
self.host = browser.webdriver_host
|
||||
self.port = browser.webdriver_port
|
||||
self.session = None
|
||||
|
||||
def setup(self, runner):
|
||||
"""Connect to browser via WebDriver."""
|
||||
self.runner = runner
|
||||
|
||||
session_started = False
|
||||
try:
|
||||
self.session = webdriver.Session(self.host, self.port)
|
||||
self.session.start()
|
||||
except:
|
||||
self.logger.warning(
|
||||
"Connecting with WebDriver failed:\n%s" % traceback.format_exc())
|
||||
else:
|
||||
self.logger.debug("session started")
|
||||
session_started = True
|
||||
|
||||
if not session_started:
|
||||
self.logger.warning("Failed to connect via WebDriver")
|
||||
self.executor.runner.send_message("init_failed")
|
||||
else:
|
||||
self.executor.runner.send_message("init_succeeded")
|
||||
|
||||
def teardown(self):
|
||||
self.logger.debug("Hanging up on WebDriver session")
|
||||
try:
|
||||
self.session.end()
|
||||
except:
|
||||
pass
|
||||
|
||||
def is_alive(self):
|
||||
try:
|
||||
# Get a simple property over the connection
|
||||
self.session.handle
|
||||
# TODO what exception?
|
||||
except Exception:
|
||||
return False
|
||||
return True
|
||||
|
||||
def after_connect(self):
|
||||
pass
|
||||
|
||||
def wait(self):
|
||||
while True:
|
||||
try:
|
||||
self.session.execute_async_script("")
|
||||
except webdriver.TimeoutException:
|
||||
pass
|
||||
except (socket.timeout, IOError):
|
||||
break
|
||||
except Exception as e:
|
||||
self.logger.error(traceback.format_exc(e))
|
||||
break
|
||||
|
||||
|
||||
class ServoWebDriverRun(object):
|
||||
def __init__(self, func, session, url, timeout):
|
||||
self.func = func
|
||||
self.result = None
|
||||
self.session = session
|
||||
self.url = url
|
||||
self.timeout = timeout
|
||||
self.result_flag = threading.Event()
|
||||
|
||||
def run(self):
|
||||
timeout = self.timeout
|
||||
|
||||
try:
|
||||
self.session.timeouts.script = timeout + extra_timeout
|
||||
except IOError:
|
||||
self.logger.error("Lost webdriver connection")
|
||||
return Stop
|
||||
|
||||
executor = threading.Thread(target=self._run)
|
||||
executor.start()
|
||||
|
||||
flag = self.result_flag.wait(timeout + 2 * extra_timeout)
|
||||
if self.result is None:
|
||||
assert not flag
|
||||
self.result = False, ("EXTERNAL-TIMEOUT", None)
|
||||
|
||||
return self.result
|
||||
|
||||
def _run(self):
|
||||
try:
|
||||
self.result = True, self.func(self.session, self.url, self.timeout)
|
||||
except webdriver.TimeoutException:
|
||||
self.result = False, ("EXTERNAL-TIMEOUT", None)
|
||||
except (socket.timeout, IOError):
|
||||
self.result = False, ("CRASH", None)
|
||||
except Exception as e:
|
||||
message = getattr(e, "message", "")
|
||||
if message:
|
||||
message += "\n"
|
||||
message += traceback.format_exc(e)
|
||||
self.result = False, ("ERROR", e)
|
||||
finally:
|
||||
self.result_flag.set()
|
||||
|
||||
|
||||
def timeout_func(timeout):
|
||||
if timeout:
|
||||
t0 = time.time()
|
||||
return lambda: time.time() - t0 > timeout + extra_timeout
|
||||
else:
|
||||
return lambda: False
|
||||
|
||||
|
||||
class ServoWebDriverTestharnessExecutor(TestharnessExecutor):
|
||||
def __init__(self, browser, server_config, timeout_multiplier=1,
|
||||
close_after_done=True, capabilities=None, debug_info=None):
|
||||
TestharnessExecutor.__init__(self, browser, server_config, timeout_multiplier=1,
|
||||
debug_info=None)
|
||||
self.protocol = ServoWebDriverProtocol(self, browser, capabilities=capabilities)
|
||||
with open(os.path.join(here, "testharness_servodriver.js")) as f:
|
||||
self.script = f.read()
|
||||
|
||||
def on_protocol_change(self, new_protocol):
|
||||
pass
|
||||
|
||||
def is_alive(self):
|
||||
return self.protocol.is_alive()
|
||||
|
||||
def do_test(self, test):
|
||||
url = self.test_url(test)
|
||||
|
||||
success, data = ServoWebDriverRun(self.do_testharness,
|
||||
self.protocol.session,
|
||||
url,
|
||||
test.timeout * self.timeout_multiplier).run()
|
||||
|
||||
if success:
|
||||
return self.convert_result(test, data)
|
||||
|
||||
return (test.result_cls(*data), [])
|
||||
|
||||
def do_testharness(self, session, url, timeout):
|
||||
session.url = url
|
||||
result = json.loads(
|
||||
session.execute_async_script(
|
||||
self.script % {"abs_url": url,
|
||||
"url": strip_server(url),
|
||||
"timeout_multiplier": self.timeout_multiplier,
|
||||
"timeout": timeout * 1000}))
|
||||
if "test" not in result:
|
||||
result["test"] = strip_server(url)
|
||||
return result
|
||||
|
||||
|
||||
class TimeoutError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ServoWebDriverRefTestExecutor(RefTestExecutor):
|
||||
def __init__(self, browser, server_config, timeout_multiplier=1,
|
||||
screenshot_cache=None, capabilities=None, debug_info=None):
|
||||
"""Selenium WebDriver-based executor for reftests"""
|
||||
RefTestExecutor.__init__(self,
|
||||
browser,
|
||||
server_config,
|
||||
screenshot_cache=screenshot_cache,
|
||||
timeout_multiplier=timeout_multiplier,
|
||||
debug_info=debug_info)
|
||||
self.protocol = ServoWebDriverProtocol(self, browser,
|
||||
capabilities=capabilities)
|
||||
self.implementation = RefTestImplementation(self)
|
||||
|
||||
with open(os.path.join(here, "reftest-wait_servodriver.js")) as f:
|
||||
self.wait_script = f.read()
|
||||
|
||||
def is_alive(self):
|
||||
return self.protocol.is_alive()
|
||||
|
||||
def do_test(self, test):
|
||||
try:
|
||||
result = self.implementation.run_test(test)
|
||||
return self.convert_result(test, result)
|
||||
except IOError:
|
||||
return test.result_cls("CRASH", None), []
|
||||
except TimeoutError:
|
||||
return test.result_cls("TIMEOUT", None), []
|
||||
except Exception as e:
|
||||
message = getattr(e, "message", "")
|
||||
if message:
|
||||
message += "\n"
|
||||
message += traceback.format_exc(e)
|
||||
return test.result_cls("ERROR", message), []
|
||||
|
||||
def screenshot(self, test):
|
||||
timeout = test.timeout * self.timeout_multiplier if self.debug_info is None else None
|
||||
return ServoWebDriverRun(self._screenshot,
|
||||
self.protocol.session,
|
||||
self.test_url(test),
|
||||
timeout).run()
|
||||
|
||||
def _screenshot(self, session, url, timeout):
|
||||
session.url = url
|
||||
session.execute_async_script(self.wait_script)
|
||||
return session.screenshot()
|
|
@ -0,0 +1,20 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
callback = arguments[arguments.length - 1];
|
||||
|
||||
function check_done() {
|
||||
if (!document.body.classList.contains('reftest-wait')) {
|
||||
callback();
|
||||
} else {
|
||||
setTimeout(check_done, 50);
|
||||
}
|
||||
}
|
||||
|
||||
if (document.readyState === 'complete') {
|
||||
check_done();
|
||||
} else {
|
||||
addEventListener("load", check_done);
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
window.__wd_results_callback__ = arguments[arguments.length - 1];
|
||||
window.__wd_results_timer__ = setTimeout(timeout, %(timeout)s);
|
587
tests/wpt/harness/wptrunner/executors/webdriver.py
Normal file
587
tests/wpt/harness/wptrunner/executors/webdriver.py
Normal file
|
@ -0,0 +1,587 @@
|
|||
import errno
|
||||
import httplib
|
||||
import json
|
||||
import socket
|
||||
import time
|
||||
import urlparse
|
||||
from collections import defaultdict
|
||||
|
||||
element_key = "element-6066-11e4-a52e-4f735466cecf"
|
||||
|
||||
|
||||
class WebDriverException(Exception):
|
||||
http_status = None
|
||||
status_code = None
|
||||
|
||||
def __init__(self, message):
|
||||
self.message = message
|
||||
|
||||
|
||||
class ElementNotSelectableException(WebDriverException):
|
||||
http_status = 400
|
||||
status_code = "element not selectable"
|
||||
|
||||
|
||||
class ElementNotVisibleException(WebDriverException):
|
||||
http_status = 400
|
||||
status_code = "element not visible"
|
||||
|
||||
|
||||
class InvalidArgumentException(WebDriverException):
|
||||
http_status = 400
|
||||
status_code = "invalid argument"
|
||||
|
||||
|
||||
class InvalidCookieDomainException(WebDriverException):
|
||||
http_status = 400
|
||||
status_code = "invalid cookie domain"
|
||||
|
||||
|
||||
class InvalidElementCoordinatesException(WebDriverException):
|
||||
http_status = 400
|
||||
status_code = "invalid element coordinates"
|
||||
|
||||
|
||||
class InvalidElementStateException(WebDriverException):
|
||||
http_status = 400
|
||||
status_code = "invalid cookie domain"
|
||||
|
||||
|
||||
class InvalidSelectorException(WebDriverException):
|
||||
http_status = 400
|
||||
status_code = "invalid selector"
|
||||
|
||||
|
||||
class InvalidSessionIdException(WebDriverException):
|
||||
http_status = 404
|
||||
status_code = "invalid session id"
|
||||
|
||||
|
||||
class JavascriptErrorException(WebDriverException):
|
||||
http_status = 500
|
||||
status_code = "javascript error"
|
||||
|
||||
|
||||
class MoveTargetOutOfBoundsException(WebDriverException):
|
||||
http_status = 500
|
||||
status_code = "move target out of bounds"
|
||||
|
||||
|
||||
class NoSuchAlertException(WebDriverException):
|
||||
http_status = 400
|
||||
status_code = "no such alert"
|
||||
|
||||
|
||||
class NoSuchElementException(WebDriverException):
|
||||
http_status = 404
|
||||
status_code = "no such element"
|
||||
|
||||
|
||||
class NoSuchFrameException(WebDriverException):
|
||||
http_status = 400
|
||||
status_code = "no such frame"
|
||||
|
||||
|
||||
class NoSuchWindowException(WebDriverException):
|
||||
http_status = 400
|
||||
status_code = "no such window"
|
||||
|
||||
|
||||
class ScriptTimeoutException(WebDriverException):
|
||||
http_status = 408
|
||||
status_code = "script timeout"
|
||||
|
||||
|
||||
class SessionNotCreatedException(WebDriverException):
|
||||
http_status = 500
|
||||
status_code = "session not created"
|
||||
|
||||
|
||||
class StaleElementReferenceException(WebDriverException):
|
||||
http_status = 400
|
||||
status_code = "stale element reference"
|
||||
|
||||
|
||||
class TimeoutException(WebDriverException):
|
||||
http_status = 408
|
||||
status_code = "timeout"
|
||||
|
||||
|
||||
class UnableToSetCookieException(WebDriverException):
|
||||
http_status = 500
|
||||
status_code = "unable to set cookie"
|
||||
|
||||
|
||||
class UnexpectedAlertOpenException(WebDriverException):
|
||||
http_status = 500
|
||||
status_code = "unexpected alert open"
|
||||
|
||||
|
||||
class UnknownErrorException(WebDriverException):
|
||||
http_status = 500
|
||||
status_code = "unknown error"
|
||||
|
||||
|
||||
class UnknownCommandException(WebDriverException):
|
||||
http_status = (404, 405)
|
||||
status_code = "unknown command"
|
||||
|
||||
|
||||
class UnsupportedOperationException(WebDriverException):
|
||||
http_status = 500
|
||||
status_code = "unsupported operation"
|
||||
|
||||
|
||||
def group_exceptions():
|
||||
exceptions = defaultdict(dict)
|
||||
for item in _objs:
|
||||
if type(item) == type and issubclass(item, WebDriverException):
|
||||
if not isinstance(item.http_status, tuple):
|
||||
statuses = (item.http_status,)
|
||||
else:
|
||||
statuses = item.http_status
|
||||
|
||||
for status in statuses:
|
||||
exceptions[status][item.status_code] = item
|
||||
return exceptions
|
||||
|
||||
|
||||
_objs = locals().values()
|
||||
_exceptions = group_exceptions()
|
||||
del _objs
|
||||
del group_exceptions
|
||||
|
||||
|
||||
def wait_for_port(host, port, timeout=60):
|
||||
""" Wait for the specified Marionette host/port to be available."""
|
||||
starttime = time.time()
|
||||
poll_interval = 0.1
|
||||
while time.time() - starttime < timeout:
|
||||
sock = None
|
||||
try:
|
||||
sock = socket.socket()
|
||||
sock.connect((host, port))
|
||||
return True
|
||||
except socket.error as e:
|
||||
if e[0] != errno.ECONNREFUSED:
|
||||
raise
|
||||
finally:
|
||||
if sock:
|
||||
sock.close()
|
||||
time.sleep(poll_interval)
|
||||
return False
|
||||
|
||||
|
||||
class Transport(object):
|
||||
def __init__(self, host, port, url_prefix="", port_timeout=60):
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.port_timeout = port_timeout
|
||||
if url_prefix == "":
|
||||
self.path_prefix = "/"
|
||||
else:
|
||||
self.path_prefix = "/%s/" % url_prefix.strip("/")
|
||||
self._connection = None
|
||||
|
||||
def connect(self):
|
||||
wait_for_port(self.host, self.port, self.port_timeout)
|
||||
self._connection = httplib.HTTPConnection(self.host, self.port)
|
||||
|
||||
def close_connection(self):
|
||||
if self._connection:
|
||||
self._connection.close()
|
||||
self._connection = None
|
||||
|
||||
def url(self, suffix):
|
||||
return urlparse.urljoin(self.url_prefix, suffix)
|
||||
|
||||
def send(self, method, url, body=None, headers=None, key=None):
|
||||
if not self._connection:
|
||||
self.connect()
|
||||
|
||||
if body is None and method == "POST":
|
||||
body = {}
|
||||
|
||||
if isinstance(body, dict):
|
||||
body = json.dumps(body)
|
||||
|
||||
if isinstance(body, unicode):
|
||||
body = body.encode("utf-8")
|
||||
|
||||
if headers is None:
|
||||
headers = {}
|
||||
|
||||
url = self.path_prefix + url
|
||||
|
||||
self._connection.request(method, url, body, headers)
|
||||
|
||||
try:
|
||||
resp = self._connection.getresponse()
|
||||
except Exception:
|
||||
# This should probably be more specific
|
||||
raise IOError
|
||||
body = resp.read()
|
||||
|
||||
try:
|
||||
data = json.loads(body)
|
||||
except:
|
||||
raise
|
||||
raise WebDriverException("Could not parse response body as JSON: %s" % body)
|
||||
|
||||
if resp.status != 200:
|
||||
cls = _exceptions.get(resp.status, {}).get(data.get("status", None), WebDriverException)
|
||||
raise cls(data.get("message", ""))
|
||||
|
||||
if key is not None:
|
||||
data = data[key]
|
||||
|
||||
if not data:
|
||||
data = None
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def command(func):
|
||||
def inner(self, *args, **kwargs):
|
||||
if hasattr(self, "session"):
|
||||
session_id = self.session.session_id
|
||||
else:
|
||||
session_id = self.session_id
|
||||
|
||||
if session_id is None:
|
||||
raise SessionNotCreatedException("Session not created")
|
||||
return func(self, *args, **kwargs)
|
||||
|
||||
inner.__name__ = func.__name__
|
||||
inner.__doc__ = func.__doc__
|
||||
|
||||
return inner
|
||||
|
||||
|
||||
class Timeouts(object):
|
||||
def __init__(self, session):
|
||||
self.session = session
|
||||
self._script = 30
|
||||
self._load = 0
|
||||
self._implicit_wait = 0
|
||||
|
||||
def _set_timeouts(self, name, value):
|
||||
body = {"type": name,
|
||||
"ms": value * 1000}
|
||||
return self.session.send_command("POST", "timeouts", body)
|
||||
|
||||
@property
|
||||
def script(self):
|
||||
return self._script
|
||||
|
||||
@script.setter
|
||||
def script(self, value):
|
||||
self._set_timeouts("script", value)
|
||||
self._script = value
|
||||
|
||||
@property
|
||||
def load(self):
|
||||
return self._load
|
||||
|
||||
@load.setter
|
||||
def set_load(self, value):
|
||||
self._set_timeouts("page load", value)
|
||||
self._script = value
|
||||
|
||||
@property
|
||||
def implicit_wait(self):
|
||||
return self._implicit_wait
|
||||
|
||||
@implicit_wait.setter
|
||||
def implicit_wait(self, value):
|
||||
self._set_timeouts("implicit wait", value)
|
||||
self._implicit_wait = value
|
||||
|
||||
|
||||
class Window(object):
|
||||
def __init__(self, session):
|
||||
self.session = session
|
||||
|
||||
@property
|
||||
@command
|
||||
def size(self):
|
||||
return self.session.send_command("GET", "window/size")
|
||||
|
||||
@size.setter
|
||||
@command
|
||||
def size(self, (height, width)):
|
||||
body = {"width": width,
|
||||
"height": height}
|
||||
|
||||
return self.session.send_command("POST", "window/size", body)
|
||||
|
||||
@property
|
||||
@command
|
||||
def maximize(self):
|
||||
return self.session.send_command("POST", "window/maximize")
|
||||
|
||||
|
||||
class Find(object):
|
||||
def __init__(self, session):
|
||||
self.session = session
|
||||
|
||||
@command
|
||||
def css(self, selector, all=True):
|
||||
return self._find_element("css selector", selector, all)
|
||||
|
||||
def _find_element(self, strategy, selector, all):
|
||||
route = "elements" if all else "element"
|
||||
|
||||
body = {"using": strategy,
|
||||
"value": selector}
|
||||
|
||||
data = self.session.send_command("POST", route, body, key="value")
|
||||
|
||||
if all:
|
||||
rv = [self.session._element(item) for item in data]
|
||||
else:
|
||||
rv = self.session._element(data)
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
class Session(object):
|
||||
def __init__(self, host, port, url_prefix="", desired_capabilities=None, port_timeout=60):
|
||||
self.transport = Transport(host, port, url_prefix, port_timeout)
|
||||
self.desired_capabilities = desired_capabilities
|
||||
self.session_id = None
|
||||
self.timeouts = None
|
||||
self.window = None
|
||||
self.find = None
|
||||
self._element_cache = {}
|
||||
|
||||
def start(self):
|
||||
desired_capabilities = self.desired_capabilities if self.desired_capabilities else {}
|
||||
body = {"capabilities": {"desiredCapabilites": desired_capabilities}}
|
||||
|
||||
rv = self.transport.send("POST", "session", body=body)
|
||||
self.session_id = rv["sessionId"]
|
||||
|
||||
self.timeouts = Timeouts(self)
|
||||
self.window = Window(self)
|
||||
self.find = Find(self)
|
||||
|
||||
return rv["value"]
|
||||
|
||||
@command
|
||||
def end(self):
|
||||
url = "session/%s" % self.session_id
|
||||
self.transport.send("DELETE", url)
|
||||
self.session_id = None
|
||||
self.timeouts = None
|
||||
self.window = None
|
||||
self.find = None
|
||||
self.transport.close_connection()
|
||||
|
||||
def __enter__(self):
|
||||
resp = self.start()
|
||||
if resp.error:
|
||||
raise Exception(resp)
|
||||
return self
|
||||
|
||||
def __exit__(self, *args, **kwargs):
|
||||
resp = self.end()
|
||||
if resp.error:
|
||||
raise Exception(resp)
|
||||
|
||||
def send_command(self, method, url, body=None, key=None):
|
||||
url = urlparse.urljoin("session/%s/" % self.session_id, url)
|
||||
return self.transport.send(method, url, body, key=key)
|
||||
|
||||
@property
|
||||
@command
|
||||
def url(self):
|
||||
return self.send_command("GET", "url", key="value")
|
||||
|
||||
@url.setter
|
||||
@command
|
||||
def url(self, url):
|
||||
if urlparse.urlsplit(url).netloc is None:
|
||||
return self.url(url)
|
||||
body = {"url": url}
|
||||
return self.send_command("POST", "url", body)
|
||||
|
||||
@command
|
||||
def back(self):
|
||||
return self.send_command("POST", "back")
|
||||
|
||||
@command
|
||||
def forward(self):
|
||||
return self.send_command("POST", "forward")
|
||||
|
||||
@command
|
||||
def refresh(self):
|
||||
return self.send_command("POST", "refresh")
|
||||
|
||||
@property
|
||||
@command
|
||||
def title(self):
|
||||
return self.send_command("GET", "title", key="value")
|
||||
|
||||
@property
|
||||
@command
|
||||
def handle(self):
|
||||
return self.send_command("GET", "window_handle", key="value")
|
||||
|
||||
@handle.setter
|
||||
@command
|
||||
def handle(self, handle):
|
||||
body = {"handle": handle}
|
||||
return self.send_command("POST", "window", body=body)
|
||||
|
||||
def switch_frame(self, frame):
|
||||
if frame == "parent":
|
||||
url = "frame/parent"
|
||||
body = None
|
||||
else:
|
||||
url = "frame"
|
||||
if isinstance(frame, Element):
|
||||
body = {"id": frame.json()}
|
||||
else:
|
||||
body = {"id": frame}
|
||||
print body
|
||||
return self.send_command("POST", url, body)
|
||||
|
||||
@command
|
||||
def close(self):
|
||||
return self.send_command("DELETE", "window_handle")
|
||||
|
||||
@property
|
||||
@command
|
||||
def handles(self):
|
||||
return self.send_command("GET", "window_handles", key="value")
|
||||
|
||||
@property
|
||||
@command
|
||||
def active_element(self):
|
||||
data = self.send_command("GET", "element/active", key="value")
|
||||
if data is not None:
|
||||
return self._element(data)
|
||||
|
||||
def _element(self, data):
|
||||
elem_id = data[element_key]
|
||||
assert elem_id
|
||||
if elem_id in self._element_cache:
|
||||
return self._element_cache[elem_id]
|
||||
return Element(self, elem_id)
|
||||
|
||||
@command
|
||||
def cookies(self, name=None):
|
||||
if name is None:
|
||||
url = "cookie"
|
||||
else:
|
||||
url = "cookie/%s" % name
|
||||
return self.send_command("GET", url, {}, key="value")
|
||||
|
||||
@command
|
||||
def set_cookie(self, name, value, path=None, domain=None, secure=None, expiry=None):
|
||||
body = {"name": name,
|
||||
"value": value}
|
||||
if path is not None:
|
||||
body["path"] = path
|
||||
if domain is not None:
|
||||
body["domain"] = domain
|
||||
if secure is not None:
|
||||
body["secure"] = secure
|
||||
if expiry is not None:
|
||||
body["expiry"] = expiry
|
||||
self.send_command("POST", "cookie", body)
|
||||
|
||||
def delete_cookie(self, name=None):
|
||||
if name is None:
|
||||
url = "cookie"
|
||||
else:
|
||||
url = "cookie/%s" % name
|
||||
self.send_command("DELETE", url, {}, key="value")
|
||||
|
||||
#[...]
|
||||
|
||||
@command
|
||||
def execute_script(self, script, args=None):
|
||||
if args is None:
|
||||
args = []
|
||||
|
||||
body = {
|
||||
"script": script,
|
||||
"args": args
|
||||
}
|
||||
return self.send_command("POST", "execute", body, key="value")
|
||||
|
||||
@command
|
||||
def execute_async_script(self, script, args=None):
|
||||
if args is None:
|
||||
args = []
|
||||
|
||||
body = {
|
||||
"script": script,
|
||||
"args": args
|
||||
}
|
||||
return self.send_command("POST", "execute_async", body, key="value")
|
||||
|
||||
#[...]
|
||||
|
||||
@command
|
||||
def screenshot(self):
|
||||
return self.send_command("GET", "screenshot", key="value")
|
||||
|
||||
|
||||
class Element(object):
|
||||
def __init__(self, session, id):
|
||||
self.session = session
|
||||
self.id = id
|
||||
assert id not in self.session._element_cache
|
||||
self.session._element_cache[self.id] = self
|
||||
|
||||
def json(self):
|
||||
return {element_key: self.id}
|
||||
|
||||
@property
|
||||
def session_id(self):
|
||||
return self.session.session_id
|
||||
|
||||
def url(self, suffix):
|
||||
return "element/%s/%s" % (self.id, suffix)
|
||||
|
||||
@command
|
||||
def find_element(self, strategy, selector):
|
||||
body = {"using": strategy,
|
||||
"value": selector}
|
||||
|
||||
elem = self.session.send_command("POST", self.url("element"), body, key="value")
|
||||
return self.session.element(elem)
|
||||
|
||||
@command
|
||||
def click(self):
|
||||
self.session.send_command("POST", self.url("click"), {})
|
||||
|
||||
@command
|
||||
def tap(self):
|
||||
self.session.send_command("POST", self.url("tap"), {})
|
||||
|
||||
@command
|
||||
def clear(self):
|
||||
self.session.send_command("POST", self.url("clear"), {})
|
||||
|
||||
@command
|
||||
def send_keys(self, keys):
|
||||
if isinstance(keys, (str, unicode)):
|
||||
keys = [char for char in keys]
|
||||
|
||||
body = {"value": keys}
|
||||
|
||||
return self.session.send_command("POST", self.url("value"), body)
|
||||
|
||||
@property
|
||||
@command
|
||||
def text(self):
|
||||
return self.session.send_command("GET", self.url("text"), key="value")
|
||||
|
||||
@property
|
||||
@command
|
||||
def name(self):
|
||||
return self.session.send_command("GET", self.url("name"), key="value")
|
25
tests/wpt/harness/wptrunner/testharnessreport-servodriver.js
Normal file
25
tests/wpt/harness/wptrunner/testharnessreport-servodriver.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
setup({output:%(output)d});
|
||||
|
||||
add_completion_callback(function() {
|
||||
add_completion_callback(function (tests, status) {
|
||||
var test_results = tests.map(function(x) {
|
||||
return {name:x.name, status:x.status, message:x.message, stack:x.stack}
|
||||
});
|
||||
var results = JSON.stringify({tests:test_results,
|
||||
status: status.status,
|
||||
message: status.message,
|
||||
stack: status.stack});
|
||||
(function done() {
|
||||
if (window.__wd_results_callback__) {
|
||||
clearTimeout(__wd_results_timer__);
|
||||
__wd_results_callback__(results)
|
||||
} else {
|
||||
setTimeout(done, 20);
|
||||
}
|
||||
})()
|
||||
})
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue