diff options
Diffstat (limited to 'components/script')
58 files changed, 1613 insertions, 984 deletions
diff --git a/components/script/Cargo.toml b/components/script/Cargo.toml index 9e7db847a86..63eaea80142 100644 --- a/components/script/Cargo.toml +++ b/components/script/Cargo.toml @@ -57,6 +57,9 @@ git = "https://github.com/servo/html5ever" [dependencies.js] git = "https://github.com/servo/rust-mozjs" +[dependencies.png] +git = "https://github.com/servo/rust-png" + [dependencies.uuid] git = "https://github.com/rust-lang/uuid" diff --git a/components/script/devtools.rs b/components/script/devtools.rs index 56588941667..271cc39ab35 100644 --- a/components/script/devtools.rs +++ b/components/script/devtools.rs @@ -2,7 +2,7 @@ * 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/. */ -use devtools_traits::{EvaluateJSReply, NodeInfo, Modification}; +use devtools_traits::{EvaluateJSReply, NodeInfo, Modification, TimelineMarker, TimelineMarkerType}; use dom::bindings::conversions::FromJSValConvertible; use dom::bindings::conversions::StringificationBehavior; use dom::bindings::js::{JSRef, Temporary, OptionalRootable}; @@ -16,7 +16,7 @@ use dom::element::Element; use dom::document::DocumentHelpers; use page::Page; use msg::constellation_msg::PipelineId; -use script_task::get_page; +use script_task::{get_page, ScriptTask}; use std::sync::mpsc::Sender; use std::rc::Rc; @@ -69,8 +69,8 @@ fn find_node_by_unique_id(page: &Rc<Page>, pipeline: PipelineId, node_id: String let node: JSRef<Node> = NodeCast::from_ref(document.r()); for candidate in node.traverse_preorder() { - if candidate.get_unique_id() == node_id { - return Temporary::from_rooted(candidate); + if candidate.root().r().get_unique_id() == node_id { + return candidate; } } @@ -114,3 +114,36 @@ pub fn handle_wants_live_notifications(page: &Rc<Page>, pipeline_id: PipelineId, let window = page.window().root(); window.r().set_devtools_wants_updates(send_notifications); } + +pub fn handle_set_timeline_markers(page: &Rc<Page>, + script_task: &ScriptTask, + marker_types: Vec<TimelineMarkerType>, + reply: Sender<TimelineMarker>) { + for marker_type in &marker_types { + match *marker_type { + TimelineMarkerType::Reflow => { + let window = page.window().root(); + window.r().set_devtools_timeline_marker(TimelineMarkerType::Reflow, reply.clone()); + } + TimelineMarkerType::DOMEvent => { + script_task.set_devtools_timeline_marker(TimelineMarkerType::DOMEvent, reply.clone()); + } + } + } +} + +pub fn handle_drop_timeline_markers(page: &Rc<Page>, + script_task: &ScriptTask, + marker_types: Vec<TimelineMarkerType>) { + let window = page.window().root(); + for marker_type in &marker_types { + match *marker_type { + TimelineMarkerType::Reflow => { + window.r().drop_devtools_timeline_markers(); + } + TimelineMarkerType::DOMEvent => { + script_task.drop_devtools_timeline_markers(); + } + } + } +} diff --git a/components/script/dom/bindings/codegen/CodegenRust.py b/components/script/dom/bindings/codegen/CodegenRust.py index b828ccb74bd..960a84b1717 100644 --- a/components/script/dom/bindings/codegen/CodegenRust.py +++ b/components/script/dom/bindings/codegen/CodegenRust.py @@ -582,7 +582,7 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, # A helper function for wrapping up the template body for # possibly-nullable objecty stuff - def wrapObjectTemplate(templateBody, isDefinitelyObject, type, + def wrapObjectTemplate(templateBody, nullValue, isDefinitelyObject, type, failureCode=None): if not isDefinitelyObject: # Handle the non-object cases by wrapping up the whole @@ -593,7 +593,7 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, if type.nullable(): templateBody += ( "} else if (${val}).is_null_or_undefined() {\n" - " None\n") + " %s\n") % nullValue templateBody += ( "} else {\n" + CGIndenter(onFailureNotAnObject(failureCode)).define() + @@ -632,8 +632,9 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, template = "%s::new((${val}).to_object())" % name if type.nullable(): declType = CGWrapper(declType, pre="Option<", post=">") - template = wrapObjectTemplate("Some(%s)" % template, isDefinitelyObject, type, - failureCode) + template = wrapObjectTemplate("Some(%s)" % template, "None", + isDefinitelyObject, type, + failureCode) return handleOptional(template, declType, handleDefaultNull("None")) @@ -675,8 +676,8 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, if isMember: templateBody += ".root()" - templateBody = wrapObjectTemplate(templateBody, isDefinitelyObject, - type, failureCode) + templateBody = wrapObjectTemplate(templateBody, "None", + isDefinitelyObject, type, failureCode) return handleOptional(templateBody, declType, handleDefaultNull("None")) @@ -821,6 +822,7 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, onFailureNotCallable(failureCode)).define() template = wrapObjectTemplate( template, + "None", isDefinitelyObject, type, failureCode) @@ -853,7 +855,15 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, return handleOptional("${val}", declType, default) if type.isObject(): - raise TypeError("Can't handle object arguments yet") + assert not isEnforceRange and not isClamp + + declType = CGGeneric("*mut JSObject") + templateBody = wrapObjectTemplate("${val}.to_object()", + "ptr::null_mut()", + isDefinitelyObject, type, failureCode) + + return handleOptional(templateBody, declType, + handleDefaultNull("ptr::null_mut()")) if type.isDictionary(): if failureCode is not None: @@ -887,23 +897,12 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, if type.nullable(): declType = CGWrapper(declType, pre="Option<", post=">") - template = "" - if type.isFloat() and not type.isUnrestricted(): - template = ( - "match FromJSValConvertible::from_jsval(cx, ${val}, ()) {\n" - " Ok(v) => v,\n" - " Err(_) => {\n" - " throw_type_error(cx, \"this argument is not a finite floating-point value\");\n" - " %s\n" - " }\n" - "}" % exceptionCode) - else: - #XXXjdm support conversionBehavior here - template = ( - "match FromJSValConvertible::from_jsval(cx, ${val}, ()) {\n" - " Ok(v) => v,\n" - " Err(_) => { %s }\n" - "}" % exceptionCode) + #XXXjdm support conversionBehavior here + template = ( + "match FromJSValConvertible::from_jsval(cx, ${val}, ()) {\n" + " Ok(v) => v,\n" + " Err(_) => { %s }\n" + "}" % exceptionCode) if defaultValue is not None: if isinstance(defaultValue, IDLNullValue): @@ -1241,9 +1240,14 @@ class MethodDefiner(PropertyDefiner): # FIXME https://bugzilla.mozilla.org/show_bug.cgi?id=772822 # We should be able to check for special operations without an # identifier. For now we check if the name starts with __ - methods = [m for m in descriptor.interface.members if - m.isMethod() and m.isStatic() == static and - not m.isIdentifierLess()] + + # Ignore non-static methods for callback interfaces + if not descriptor.interface.isCallback() or static: + methods = [m for m in descriptor.interface.members if + m.isMethod() and m.isStatic() == static and + not m.isIdentifierLess()] + else: + methods = [] self.regular = [{"name": m.identifier.name, "methodInfo": not m.isStatic(), "length": methodLength(m), @@ -1441,7 +1445,7 @@ class CGImports(CGWrapper): """ Generates the appropriate import/use statements. """ - def __init__(self, child, descriptors, imports): + def __init__(self, child, descriptors, callbacks, imports): """ Adds a set of imports. """ @@ -1461,8 +1465,55 @@ class CGImports(CGWrapper): 'dead_code', ] + def componentTypes(type): + if type.nullable(): + type = type.unroll() + if type.isUnion(): + return type.flatMemberTypes + return [type] + + def isImportable(type): + if not type.isType(): + assert type.isInterface() + return not type.isCallback() + return type.isNonCallbackInterface() and not type.builtin + + def relatedTypesForSignatures(method): + types = [] + for (returnType, arguments) in method.signatures(): + types += componentTypes(returnType) + for arg in arguments: + types += componentTypes(arg.type) + return types + + def getIdentifier(t): + if t.isType(): + return t.inner.identifier + assert t.isInterface() + return t.identifier + + types = [] + for d in descriptors: + types += [d.interface] + + members = d.interface.members + d.interface.namedConstructors + constructor = d.interface.ctor() + if constructor: + members += [constructor] + + for m in members: + if m.isMethod(): + types += relatedTypesForSignatures(m) + elif m.isAttr(): + types += componentTypes(m.type) + + for c in callbacks: + types += relatedTypesForSignatures(c) + + imports += ['dom::types::%s' % getIdentifier(t).name for t in types if isImportable(t)] + statements = ['#![allow(%s)]' % ','.join(ignored_warnings)] - statements.extend('use %s;' % i for i in sorted(imports)) + statements.extend('use %s;' % i for i in sorted(set(imports))) CGWrapper.__init__(self, child, pre='\n'.join(statements) + '\n\n') @@ -1784,7 +1835,7 @@ def UnionTypes(descriptors, dictionaries, callbacks, config): CGUnionConversionStruct(t, provider) ]) - return CGImports(CGList(SortedDictValues(unionStructs), "\n\n"), [], imports) + return CGImports(CGList(SortedDictValues(unionStructs), "\n\n"), [], [], imports) class Argument(): @@ -2049,7 +2100,6 @@ class CGCreateInterfaceObjectsMethod(CGAbstractMethod): properties should be a PropertyArrays instance. """ def __init__(self, descriptor, properties): - assert not descriptor.interface.isCallback() args = [Argument('*mut JSContext', 'cx'), Argument('*mut JSObject', 'global'), Argument('*mut JSObject', 'receiver')] CGAbstractMethod.__init__(self, descriptor, 'CreateInterfaceObjects', '*mut JSObject', args) @@ -2066,6 +2116,11 @@ class CGCreateInterfaceObjectsMethod(CGAbstractMethod): getParentProto = ("let parent_proto: *mut JSObject = %s;\n" "assert!(!parent_proto.is_null());\n") % getParentProto + if self.descriptor.interface.isCallback(): + protoClass = "None" + else: + protoClass = "Some(&PrototypeClass)" + if self.descriptor.concrete: if self.descriptor.proxy: domClass = "&Class" @@ -2090,9 +2145,9 @@ class CGCreateInterfaceObjectsMethod(CGAbstractMethod): call = """\ return do_create_interface_objects(cx, global, receiver, parent_proto, - &PrototypeClass, %s, + %s, %s, %s, - &sNativeProperties);""" % (constructor, domClass) + &sNativeProperties);""" % (protoClass, constructor, domClass) return CGList([ CGGeneric(getParentProto), @@ -2240,9 +2295,11 @@ class CGDefineDOMInterfaceMethod(CGAbstractMethod): return CGAbstractMethod.define(self) def definition_body(self): - return CGGeneric("""\ -assert!(!global.is_null()); -assert!(!GetProtoObject(cx, global, global).is_null());""") + if self.descriptor.interface.isCallback(): + code = "CreateInterfaceObjects(cx, global, global);" + else: + code = "assert!(!GetProtoObject(cx, global, global).is_null());" + return CGGeneric("assert!(!global.is_null());\n" + code) def needCx(returnType, arguments, considerTypes): return (considerTypes and @@ -4222,10 +4279,11 @@ class CGDescriptor(CGThing): def __init__(self, descriptor): CGThing.__init__(self) - assert not descriptor.interface.isCallback() + assert not descriptor.concrete or not descriptor.interface.isCallback() cgThings = [] - cgThings.append(CGGetProtoObjectMethod(descriptor)) + if not descriptor.interface.isCallback(): + cgThings.append(CGGetProtoObjectMethod(descriptor)) if descriptor.interface.hasInterfaceObject(): # https://github.com/mozilla/servo/issues/2665 # cgThings.append(CGGetConstructorObjectMethod(descriptor)) @@ -4239,7 +4297,7 @@ class CGDescriptor(CGThing): if m.isStatic(): assert descriptor.interface.hasInterfaceObject() cgThings.append(CGStaticMethod(descriptor, m)) - else: + elif not descriptor.interface.isCallback(): cgThings.append(CGSpecializedMethod(descriptor, m)) cgThings.append(CGMemberJITInfo(descriptor, m)) hasMethod = True @@ -4252,7 +4310,7 @@ class CGDescriptor(CGThing): if m.isStatic(): assert descriptor.interface.hasInterfaceObject() cgThings.append(CGStaticGetter(descriptor, m)) - else: + elif not descriptor.interface.isCallback(): cgThings.append(CGSpecializedGetter(descriptor, m)) if m.hasLenientThis(): hasLenientGetter = True @@ -4263,14 +4321,15 @@ class CGDescriptor(CGThing): if m.isStatic(): assert descriptor.interface.hasInterfaceObject() cgThings.append(CGStaticSetter(descriptor, m)) - else: + elif not descriptor.interface.isCallback(): cgThings.append(CGSpecializedSetter(descriptor, m)) if m.hasLenientThis(): hasLenientSetter = True else: hasSetter = True - if not m.isStatic(): + if (not m.isStatic() and + not descriptor.interface.isCallback()): cgThings.append(CGMemberJITInfo(descriptor, m)) if hasMethod: cgThings.append(CGGenericMethod(descriptor)) @@ -4291,7 +4350,8 @@ class CGDescriptor(CGThing): cgThings.append(CGClassConstructHook(descriptor)) cgThings.append(CGInterfaceObjectJSClass(descriptor)) - cgThings.append(CGPrototypeJSClass(descriptor)) + if not descriptor.interface.isCallback(): + cgThings.append(CGPrototypeJSClass(descriptor)) properties = PropertyArrays(descriptor) cgThings.append(CGGeneric(str(properties))) @@ -4337,8 +4397,9 @@ class CGDescriptor(CGThing): cgThings.append(CGWrapMethod(descriptor)) - cgThings.append(CGIDLInterface(descriptor)) - cgThings.append(CGInterfaceTrait(descriptor)) + if not descriptor.interface.isCallback(): + cgThings.append(CGIDLInterface(descriptor)) + cgThings.append(CGInterfaceTrait(descriptor)) cgThings = CGList(cgThings, "\n") #self.cgRoot = CGWrapper(CGNamespace(toBindingNamespace(descriptor.name), @@ -4592,7 +4653,14 @@ class CGBindingRoot(CGThing): """ def __init__(self, config, prefix, webIDLFile): descriptors = config.getDescriptors(webIDLFile=webIDLFile, - isCallback=False) + hasInterfaceObject=True) + # We also want descriptors that have an interface prototype object + # (isCallback=False), but we don't want to include a second copy + # of descriptors that we also matched in the previous line + # (hence hasInterfaceObject=False). + descriptors.extend(config.getDescriptors(webIDLFile=webIDLFile, + hasInterfaceObject=False, + isCallback=False)) dictionaries = config.getDictionaries(webIDLFile=webIDLFile) cgthings = [] @@ -4628,9 +4696,7 @@ class CGBindingRoot(CGThing): # CGWrapper(curr, pre="\n")) # Add imports - #XXXjdm This should only import the namespace for the current binding, - # not every binding ever. - curr = CGImports(curr, descriptors, [ + curr = CGImports(curr, descriptors + callbackDescriptors, mainCallbacks, [ 'js', 'js::{JS_ARGV, JS_CALLEE, JS_THIS_OBJECT}', 'js::{JSCLASS_GLOBAL_SLOT_COUNT, JSCLASS_IS_DOMJSCLASS}', @@ -4658,7 +4724,6 @@ class CGBindingRoot(CGThing): 'js::glue::{RUST_FUNCTION_VALUE_TO_JITINFO}', 'js::glue::{RUST_JS_NumberValue, RUST_JSID_IS_STRING}', 'js::rust::with_compartment', - 'dom::types::*', 'dom::bindings', 'dom::bindings::global::GlobalRef', 'dom::bindings::global::global_object_for_js_object', @@ -5317,7 +5382,7 @@ class GlobalGenRoots(): CGRegisterProxyHandlers(config), ], "\n") - return CGImports(code, [], [ + return CGImports(code, [], [], [ 'dom::bindings::codegen', 'dom::bindings::codegen::PrototypeList::Proxies', 'js::jsapi::JSContext', @@ -5409,6 +5474,16 @@ impl ${name}Cast { } #[inline(always)] + pub fn to_temporary<T: ${toBound}+Reflectable>(base: Temporary<T>) -> Option<Temporary<${name}>> { + let base = base.root(); + let base = base.r(); + match base.${checkFn}() { + true => Some(Temporary::from_rooted(unsafe { base.transmute() })), + false => None + } + } + + #[inline(always)] pub fn from_ref<'a, T: ${fromBound}+Reflectable>(derived: JSRef<'a, T>) -> JSRef<'a, ${name}> { unsafe { derived.transmute() } } @@ -5419,6 +5494,12 @@ impl ${name}Cast { } #[inline(always)] + #[allow(unrooted_must_root)] + pub fn from_layout_js<T: ${fromBound}+Reflectable>(derived: &LayoutJS<T>) -> LayoutJS<${name}> { + unsafe { derived.transmute_copy() } + } + + #[inline(always)] pub fn from_temporary<T: ${fromBound}+Reflectable>(derived: Temporary<T>) -> Temporary<${name}> { unsafe { derived.transmute() } } diff --git a/components/script/dom/bindings/codegen/Configuration.py b/components/script/dom/bindings/codegen/Configuration.py index e6712357d35..5ea521e15b8 100644 --- a/components/script/dom/bindings/codegen/Configuration.py +++ b/components/script/dom/bindings/codegen/Configuration.py @@ -162,10 +162,12 @@ class Descriptor(DescriptorProvider): self.concreteType = ifaceName self.register = desc.get('register', True) self.outerObjectHook = desc.get('outerObjectHook', 'None') + self.proxy = False # If we're concrete, we need to crawl our ancestor interfaces and mark # them as having a concrete descendant. - self.concrete = desc.get('concrete', True) + self.concrete = (not self.interface.isCallback() and + desc.get('concrete', True)) self.operations = { 'IndexedGetter': None, @@ -190,7 +192,6 @@ class Descriptor(DescriptorProvider): addOperation('Stringifier', m) if self.concrete: - self.proxy = False iface = self.interface while iface: for m in iface.members: diff --git a/components/script/dom/bindings/conversions.rs b/components/script/dom/bindings/conversions.rs index 1a96dc7c948..eb6d7d60680 100644 --- a/components/script/dom/bindings/conversions.rs +++ b/components/script/dom/bindings/conversions.rs @@ -33,6 +33,7 @@ //! | union types | `T` | use dom::bindings::codegen::PrototypeList; +use dom::bindings::error::throw_type_error; use dom::bindings::js::{JSRef, Root, Unrooted}; use dom::bindings::num::Finite; use dom::bindings::str::{ByteString, USVString}; @@ -58,6 +59,7 @@ use libc; use std::borrow::ToOwned; use std::default; use std::marker::MarkerTrait; +use std::num::Float; use std::slice; /// A trait to retrieve the constants necessary to check if a `JSObject` @@ -257,24 +259,6 @@ impl FromJSValConvertible for f32 { } } -impl ToJSValConvertible for Finite<f32> { - fn to_jsval(&self, cx: *mut JSContext) -> JSVal { - let value = **self; - value.to_jsval(cx) - } -} - -impl FromJSValConvertible for Finite<f32> { - type Config = (); - fn from_jsval(cx: *mut JSContext, val: JSVal, option: ()) -> Result<Finite<f32>, ()> { - let result = FromJSValConvertible::from_jsval(cx, val, option); - let result = result.and_then(|v| { - Finite::<f32>::new(v).ok_or(()) - }); - result - } -} - impl ToJSValConvertible for f64 { fn to_jsval(&self, _cx: *mut JSContext) -> JSVal { unsafe { @@ -290,7 +274,7 @@ impl FromJSValConvertible for f64 { } } -impl ToJSValConvertible for Finite<f64> { +impl<T: Float + ToJSValConvertible> ToJSValConvertible for Finite<T> { #[inline] fn to_jsval(&self, cx: *mut JSContext) -> JSVal { let value = **self; @@ -298,14 +282,18 @@ impl ToJSValConvertible for Finite<f64> { } } -impl FromJSValConvertible for Finite<f64> { +impl<T: Float + FromJSValConvertible<Config=()>> FromJSValConvertible for Finite<T> { type Config = (); - fn from_jsval(cx: *mut JSContext, val: JSVal, option: ()) -> Result<Finite<f64>, ()> { - let result = FromJSValConvertible::from_jsval(cx, val, option); - let result = result.and_then(|v| { - Finite::<f64>::new(v).ok_or(()) - }); - result + + fn from_jsval(cx: *mut JSContext, value: JSVal, option: ()) -> Result<Finite<T>, ()> { + let result = try!(FromJSValConvertible::from_jsval(cx, value, option)); + match Finite::new(result) { + Some(v) => Ok(v), + None => { + throw_type_error(cx, "this argument is not a finite floating-point value"); + Err(()) + }, + } } } @@ -569,17 +557,6 @@ pub fn native_from_reflector_jsmanaged<T>(mut obj: *mut JSObject) -> Result<Unro } } -impl<T: Reflectable+IDLInterface> FromJSValConvertible for Unrooted<T> { - type Config = (); - fn from_jsval(_cx: *mut JSContext, value: JSVal, _option: ()) - -> Result<Unrooted<T>, ()> { - if !value.is_object() { - return Err(()); - } - native_from_reflector_jsmanaged(value.to_object()) - } -} - impl<T: Reflectable> ToJSValConvertible for Root<T> { fn to_jsval(&self, cx: *mut JSContext) -> JSVal { self.r().reflector().to_jsval(cx) diff --git a/components/script/dom/bindings/error.rs b/components/script/dom/bindings/error.rs index 4908fa3b03d..426bff64d95 100644 --- a/components/script/dom/bindings/error.rs +++ b/components/script/dom/bindings/error.rs @@ -34,6 +34,8 @@ pub enum Error { InvalidCharacter, /// NotSupportedError DOMException NotSupported, + /// InUseAttributeError DOMException + InUseAttribute, /// InvalidStateError DOMException InvalidState, /// SyntaxError DOMException @@ -69,16 +71,16 @@ pub type Fallible<T> = Result<T, Error>; /// return `()`. pub type ErrorResult = Fallible<()>; -/// Set a pending DOM exception for the given `result` on `cx`. +/// Set a pending exception for the given `result` on `cx`. pub fn throw_dom_exception(cx: *mut JSContext, global: GlobalRef, result: Error) { - assert!(unsafe { JS_IsExceptionPending(cx) } == 0); let code = match result { Error::IndexSize => DOMErrorName::IndexSizeError, Error::NotFound => DOMErrorName::NotFoundError, Error::HierarchyRequest => DOMErrorName::HierarchyRequestError, Error::InvalidCharacter => DOMErrorName::InvalidCharacterError, Error::NotSupported => DOMErrorName::NotSupportedError, + Error::InUseAttribute => DOMErrorName::InUseAttributeError, Error::InvalidState => DOMErrorName::InvalidStateError, Error::Syntax => DOMErrorName::SyntaxError, Error::Namespace => DOMErrorName::NamespaceError, @@ -90,11 +92,17 @@ pub fn throw_dom_exception(cx: *mut JSContext, global: GlobalRef, Error::DataClone => DOMErrorName::DataCloneError, Error::NoModificationAllowed => DOMErrorName::NoModificationAllowedError, Error::Type(message) => { + assert!(unsafe { JS_IsExceptionPending(cx) } == 0); throw_type_error(cx, &message); return; } - Error::JSFailed => panic!(), + Error::JSFailed => { + assert!(unsafe { JS_IsExceptionPending(cx) } == 1); + return; + } }; + + assert!(unsafe { JS_IsExceptionPending(cx) } == 0); let exception = DOMException::new(global, code).root(); let thrown = exception.to_jsval(cx); unsafe { diff --git a/components/script/dom/bindings/global.rs b/components/script/dom/bindings/global.rs index 316c51a67e3..233143851e6 100644 --- a/components/script/dom/bindings/global.rs +++ b/components/script/dom/bindings/global.rs @@ -7,7 +7,7 @@ //! This module contains smart pointers to global scopes, to simplify writing //! code that works in workers as well as window scopes. -use dom::bindings::conversions::FromJSValConvertible; +use dom::bindings::conversions::native_from_reflector_jsmanaged; use dom::bindings::js::{JS, JSRef, Root, Unrooted}; use dom::bindings::utils::{Reflectable, Reflector}; use dom::workerglobalscope::{WorkerGlobalScope, WorkerGlobalScopeHelpers}; @@ -22,11 +22,8 @@ use js::{JSCLASS_IS_GLOBAL, JSCLASS_IS_DOMJSCLASS}; use js::glue::{GetGlobalForObjectCrossCompartment}; use js::jsapi::{JSContext, JSObject}; use js::jsapi::{JS_GetClass}; -use js::jsval::ObjectOrNullValue; use url::Url; -use std::ptr; - /// A freely-copyable reference to a rooted global object. #[derive(Copy)] pub enum GlobalRef<'a> { @@ -189,12 +186,12 @@ pub fn global_object_for_js_object(obj: *mut JSObject) -> GlobalUnrooted { let global = GetGlobalForObjectCrossCompartment(obj); let clasp = JS_GetClass(global); assert!(((*clasp).flags & (JSCLASS_IS_DOMJSCLASS | JSCLASS_IS_GLOBAL)) != 0); - match FromJSValConvertible::from_jsval(ptr::null_mut(), ObjectOrNullValue(global), ()) { + match native_from_reflector_jsmanaged(global) { Ok(window) => return GlobalUnrooted::Window(window), Err(_) => (), } - match FromJSValConvertible::from_jsval(ptr::null_mut(), ObjectOrNullValue(global), ()) { + match native_from_reflector_jsmanaged(global) { Ok(worker) => return GlobalUnrooted::Worker(worker), Err(_) => (), } diff --git a/components/script/dom/bindings/js.rs b/components/script/dom/bindings/js.rs index 357a3282f9d..6b3ce4a555c 100644 --- a/components/script/dom/bindings/js.rs +++ b/components/script/dom/bindings/js.rs @@ -50,6 +50,7 @@ //! `Option<Root<T>>` easy use dom::bindings::trace::JSTraceable; +use dom::bindings::trace::RootedVec; use dom::bindings::utils::{Reflector, Reflectable}; use dom::node::Node; use js::jsapi::JSObject; @@ -57,11 +58,11 @@ use js::jsval::JSVal; use layout_interface::TrustedNodeAddress; use script_task::STACK_ROOTS; -use util::smallvec::{SmallVec, SmallVec32}; - use core::nonzero::NonZero; +use libc; use std::cell::{Cell, UnsafeCell}; use std::default::Default; +use std::intrinsics::return_address; use std::marker::PhantomData; use std::mem; use std::ops::Deref; @@ -173,7 +174,7 @@ impl<T: Reflectable> Temporary<T> { } /// Create a stack-bounded root for this value. - pub fn root(self) -> Root<T> { + pub fn root(&self) -> Root<T> { STACK_ROOTS.with(|ref collection| { let RootCollectionPtr(collection) = collection.get().unwrap(); unsafe { @@ -610,7 +611,7 @@ impl<T: Assignable<U>, U: Reflectable> TemporaryPushable<T> for Vec<JS<U>> { /// See also [*Exact Stack Rooting - Storing a GCPointer on the CStack*] /// (https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/Internals/GC/Exact_Stack_Rooting). pub struct RootCollection { - roots: UnsafeCell<SmallVec32<*mut JSObject>>, + roots: UnsafeCell<RootedVec<*mut JSObject>>, } /// A pointer to a RootCollection, for use in global variables. @@ -621,8 +622,12 @@ impl Copy for RootCollectionPtr {} impl RootCollection { /// Create an empty collection of roots pub fn new() -> RootCollection { + let addr = unsafe { + return_address() as *const libc::c_void + }; + RootCollection { - roots: UnsafeCell::new(SmallVec32::new()), + roots: UnsafeCell::new(RootedVec::new_with_destination_address(addr)), } } @@ -632,7 +637,6 @@ impl RootCollection { let roots = self.roots.get(); (*roots).push(untracked_js_ptr); debug!(" rooting {:?}", untracked_js_ptr); - assert!(!(*roots).spilled()); } } diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs index bfdb1e83a6c..9d4e548e161 100644 --- a/components/script/dom/bindings/trace.rs +++ b/components/script/dom/bindings/trace.rs @@ -241,6 +241,7 @@ no_jsmanaged_fields!(ImageCacheTask, ScriptControlChan); no_jsmanaged_fields!(Atom, Namespace, Timer); no_jsmanaged_fields!(Trusted<T>); no_jsmanaged_fields!(PropertyDeclarationBlock); +no_jsmanaged_fields!(HashSet<T>); // These three are interdependent, if you plan to put jsmanaged data // in one of these make sure it is propagated properly to containing structs no_jsmanaged_fields!(SubpageId, WindowSizeData, PipelineId); @@ -389,12 +390,21 @@ impl<T: VecRootableType> RootedVec<T> { /// Create a vector of items of type T that is rooted for /// the lifetime of this struct pub fn new() -> RootedVec<T> { + let addr = unsafe { + return_address() as *const libc::c_void + }; + + RootedVec::new_with_destination_address(addr) + } + + /// Create a vector of items of type T. This constructor is specific + /// for RootCollection. + pub fn new_with_destination_address(addr: *const libc::c_void) -> RootedVec<T> { unsafe { - RootedCollectionSet::add::<T>(&*(return_address() as *const _)); + RootedCollectionSet::add::<T>(&*(addr as *const _)); } RootedVec::<T> { v: vec!() } } - } #[unsafe_destructor] @@ -419,7 +429,7 @@ impl<T> DerefMut for RootedVec<T> { /// SM Callback that traces the rooted collections -pub unsafe extern fn trace_collections(tracer: *mut JSTracer, _data: *mut libc::c_void) { +pub unsafe fn trace_collections(tracer: *mut JSTracer) { ROOTED_COLLECTIONS.with(|ref collections| { let collections = collections.borrow(); collections.trace(tracer); diff --git a/components/script/dom/bindings/utils.rs b/components/script/dom/bindings/utils.rs index 7faca76b6c6..7d4bf9b88c7 100644 --- a/components/script/dom/bindings/utils.rs +++ b/components/script/dom/bindings/utils.rs @@ -7,14 +7,17 @@ use dom::bindings::codegen::PrototypeList; use dom::bindings::codegen::PrototypeList::MAX_PROTO_CHAIN_LENGTH; use dom::bindings::conversions::{native_from_reflector_jsmanaged, is_dom_class}; -use dom::bindings::error::throw_type_error; +use dom::bindings::error::{Error, ErrorResult, Fallible, throw_type_error}; use dom::bindings::global::GlobalRef; use dom::bindings::js::{Temporary, Root}; use dom::browsercontext; use dom::window; +use util::namespace; +use util::str::DOMString; use libc; use libc::c_uint; +use std::borrow::ToOwned; use std::boxed; use std::cell::Cell; use std::ffi::CString; @@ -43,6 +46,7 @@ use js::rust::with_compartment; use js::{JSPROP_ENUMERATE, JSPROP_READONLY, JSPROP_PERMANENT}; use js::JSFUN_CONSTRUCTOR; use js; +use string_cache::{Atom, Namespace}; /// Proxy handler for a WindowProxy. pub struct WindowProxyHandler(pub *const libc::c_void); @@ -176,36 +180,38 @@ unsafe impl Sync for NativeProperties {} pub type NonNullJSNative = unsafe extern "C" fn (arg1: *mut JSContext, arg2: c_uint, arg3: *mut JSVal) -> JSBool; -/// Creates the *interface prototype object* and the *interface object* (if -/// needed). +/// Creates the *interface prototype object* (if a `proto_class` is given) +/// and the *interface object* (if a `constructor` is given). /// Fails on JSAPI failure. pub fn do_create_interface_objects(cx: *mut JSContext, global: *mut JSObject, receiver: *mut JSObject, proto_proto: *mut JSObject, - proto_class: &'static JSClass, + proto_class: Option<&'static JSClass>, constructor: Option<(NonNullJSNative, &'static str, u32)>, dom_class: *const DOMClass, members: &'static NativeProperties) -> *mut JSObject { - let proto = create_interface_prototype_object(cx, global, proto_proto, - proto_class, members); - unsafe { - JS_SetReservedSlot(proto, DOM_PROTO_INSTANCE_CLASS_SLOT, - PrivateValue(dom_class as *const libc::c_void)); - } - - match constructor { - Some((native, name, nargs)) => { + let proto = match proto_class { + Some(proto_class) => { + let proto = create_interface_prototype_object(cx, global, proto_proto, + proto_class, members); + JS_SetReservedSlot(proto, DOM_PROTO_INSTANCE_CLASS_SLOT, + PrivateValue(dom_class as *const libc::c_void)); + proto + }, + None => ptr::null_mut() + }; + + if let Some((native, name, nargs)) = constructor { let s = CString::new(name).unwrap(); create_interface_object(cx, global, receiver, native, nargs, proto, members, s.as_ptr()) - }, - None => (), - } + } - proto + proto + } } /// Creates the *interface object*. @@ -604,6 +610,68 @@ pub unsafe fn delete_property_by_id(cx: *mut JSContext, object: *mut JSObject, return true; } +/// Validate a qualified name. See https://dom.spec.whatwg.org/#validate for details. +pub fn validate_qualified_name(qualified_name: &str) -> ErrorResult { + match xml_name_type(qualified_name) { + XMLName::InvalidXMLName => { + // Step 1. + return Err(Error::InvalidCharacter); + }, + XMLName::Name => { + // Step 2. + return Err(Error::Namespace); + }, + XMLName::QName => Ok(()) + } +} + +/// Validate a namespace and qualified name and extract their parts. +/// See https://dom.spec.whatwg.org/#validate-and-extract for details. +pub fn validate_and_extract(namespace: Option<DOMString>, qualified_name: &str) + -> Fallible<(Namespace, Option<DOMString>, Atom)> { + // Step 1. + let namespace = namespace::from_domstring(namespace); + + // Step 2. + try!(validate_qualified_name(qualified_name)); + + let (prefix, local_name) = if qualified_name.contains(":") { + // Step 5. + let mut parts = qualified_name.splitn(1, ':'); + let prefix = parts.next().unwrap(); + debug_assert!(!prefix.is_empty()); + let local_name = parts.next().unwrap(); + debug_assert!(!local_name.contains(":")); + (Some(prefix), local_name) + } else { + (None, qualified_name) + }; + + match (namespace, prefix) { + (ns!(""), Some(_)) => { + // Step 6. + Err(Error::Namespace) + }, + (ref ns, Some("xml")) if ns != &ns!(XML) => { + // Step 7. + Err(Error::Namespace) + }, + (ref ns, p) if ns != &ns!(XMLNS) && + (qualified_name == "xmlns" || p == Some("xmlns")) => { + // Step 8. + Err(Error::Namespace) + }, + (ns!(XMLNS), p) if qualified_name != "xmlns" && p != Some("xmlns") => { + // Step 9. + Err(Error::Namespace) + }, + (ns, p) => { + // Step 10. + Ok((ns, p.map(|s| s.to_owned()), Atom::from_slice(local_name))) + } + } +} + /// Results of `xml_name_type`. #[derive(PartialEq)] #[allow(missing_docs)] diff --git a/components/script/dom/canvasrenderingcontext2d.rs b/components/script/dom/canvasrenderingcontext2d.rs index fc8573923d0..17dda025def 100644 --- a/components/script/dom/canvasrenderingcontext2d.rs +++ b/components/script/dom/canvasrenderingcontext2d.rs @@ -6,9 +6,9 @@ use dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding; use dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasRenderingContext2DMethods; use dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasWindingRule; use dom::bindings::codegen::Bindings::ImageDataBinding::ImageDataMethods; -use dom::bindings::codegen::UnionTypes::HTMLCanvasElementOrCanvasRenderingContext2D; +use dom::bindings::codegen::UnionTypes::HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D; use dom::bindings::codegen::UnionTypes::StringOrCanvasGradientOrCanvasPattern; -use dom::bindings::error::Error::{IndexSize, NotSupported, Type}; +use dom::bindings::error::Error::{IndexSize, NotSupported, Type, InvalidState}; use dom::bindings::error::Fallible; use dom::bindings::global::{GlobalRef, GlobalField}; use dom::bindings::js::{JS, JSRef, LayoutJS, Temporary}; @@ -16,7 +16,9 @@ use dom::bindings::num::Finite; use dom::bindings::utils::{Reflector, reflect_dom_object}; use dom::canvasgradient::{CanvasGradient, CanvasGradientStyle, ToFillOrStrokeStyle}; use dom::htmlcanvaselement::{HTMLCanvasElement, HTMLCanvasElementHelpers}; +use dom::htmlimageelement::{HTMLImageElement, HTMLImageElementHelpers}; use dom::imagedata::{ImageData, ImageDataHelpers}; +use dom::node::{window_from_node}; use cssparser::Color as CSSColor; use cssparser::{Parser, RGBA, ToCss}; @@ -28,19 +30,30 @@ use geom::size::Size2D; use canvas::canvas_paint_task::{CanvasMsg, CanvasPaintTask, FillOrStrokeStyle}; use canvas::canvas_paint_task::{LinearGradientStyle, RadialGradientStyle}; +use net_traits::image::base::Image; +use net_traits::image_cache_task::{ImageResponseMsg, Msg}; +use png::PixelsByColorType; + use std::borrow::ToOwned; use std::cell::Cell; use std::num::{Float, ToPrimitive}; +use std::sync::{Arc}; use std::sync::mpsc::{channel, Sender}; +use url::Url; +use util::vec::byte_swap; + #[dom_struct] pub struct CanvasRenderingContext2D { reflector_: Reflector, global: GlobalField, renderer: Sender<CanvasMsg>, canvas: JS<HTMLCanvasElement>, + global_alpha: Cell<f64>, image_smoothing_enabled: Cell<bool>, stroke_color: Cell<RGBA>, + line_width: Cell<f64>, + miter_limit: Cell<f64>, fill_color: Cell<RGBA>, transform: Cell<Matrix2D<f32>>, } @@ -59,8 +72,11 @@ impl CanvasRenderingContext2D { global: GlobalField::from_rooted(&global), renderer: CanvasPaintTask::start(size), canvas: JS::from_rooted(canvas), + global_alpha: Cell::new(1.0), image_smoothing_enabled: Cell::new(true), stroke_color: Cell::new(black), + line_width: Cell::new(1.0), + miter_limit: Cell::new(10.0), fill_color: Cell::new(black), transform: Cell::new(Matrix2D::identity()), } @@ -85,10 +101,9 @@ impl CanvasRenderingContext2D { // source rectangle = area of the original image to be copied // destination rectangle = area of the destination canvas where the source image is going to be drawn fn adjust_source_dest_rects(&self, - canvas: JSRef<HTMLCanvasElement>, + image_size: Size2D<f64>, sx: f64, sy: f64, sw: f64, sh: f64, - dx: f64, dy: f64, dw: f64, dh: f64) -> (Rect<i32>, Rect<i32>) { - let image_size = canvas.get_size(); + dx: f64, dy: f64, dw: f64, dh: f64) -> (Rect<f64>, Rect<f64>) { let image_rect = Rect(Point2D(0f64, 0f64), Size2D(image_size.width as f64, image_size.height as f64)); @@ -112,15 +127,13 @@ impl CanvasRenderingContext2D { // The destination rectangle is the rectangle whose corners are the four points (dx, dy), // (dx+dw, dy), (dx+dw, dy+dh), (dx, dy+dh). - let dest_rect = Rect(Point2D(dx.to_i32().unwrap(), - dy.to_i32().unwrap()), - Size2D(dest_rect_width_scaled.to_i32().unwrap(), - dest_rect_height_scaled.to_i32().unwrap())); + let dest_rect = Rect(Point2D(dx, dy), + Size2D(dest_rect_width_scaled, dest_rect_height_scaled)); - let source_rect = Rect(Point2D(source_rect_clipped.origin.x.to_i32().unwrap(), - source_rect_clipped.origin.y.to_i32().unwrap()), - Size2D(source_rect_clipped.size.width.to_i32().unwrap(), - source_rect_clipped.size.height.to_i32().unwrap())); + let source_rect = Rect(Point2D(source_rect_clipped.origin.x, + source_rect_clipped.origin.y), + Size2D(source_rect_clipped.size.width, + source_rect_clipped.size.height)); return (source_rect, dest_rect) } @@ -150,39 +163,115 @@ impl CanvasRenderingContext2D { canvas: JSRef<HTMLCanvasElement>, sx: f64, sy: f64, sw: f64, sh: f64, dx: f64, dy: f64, dw: f64, dh: f64) -> Fallible<()> { - // 1. Check the usability of the image argument if !canvas.is_valid() { - return Ok(()) + return Err(InvalidState) } + let canvas_size = canvas.get_size(); + let image_size = Size2D(canvas_size.width as f64, canvas_size.height as f64); // 2. Establish the source and destination rectangles - let (source_rect, dest_rect) = self.adjust_source_dest_rects(canvas, sx, sy, sw, sh, dx, dy, dw, dh); + let (source_rect, dest_rect) = self.adjust_source_dest_rects(image_size, sx, sy, sw, sh, dx, dy, dw, dh); if !is_rect_valid(source_rect) || !is_rect_valid(dest_rect) { return Err(IndexSize) } let smoothing_enabled = self.image_smoothing_enabled.get(); - let canvas_size = canvas.get_size(); // If the source and target canvas are the same let msg = if self.canvas.root().r() == canvas { - CanvasMsg::DrawImageSelf(canvas_size, dest_rect, source_rect, smoothing_enabled) + CanvasMsg::DrawImageSelf(image_size, dest_rect, source_rect, smoothing_enabled) } else { // Source and target canvases are different let context = canvas.get_2d_context().root(); let renderer = context.r().get_renderer(); let (sender, receiver) = channel::<Vec<u8>>(); // Reads pixels from source image - renderer.send(CanvasMsg::GetImageData(source_rect, canvas_size, sender)).unwrap(); + renderer.send(CanvasMsg::GetImageData(source_rect, image_size, sender)).unwrap(); let imagedata = receiver.recv().unwrap(); // Writes pixels to destination canvas - CanvasMsg::DrawImage(imagedata, dest_rect, source_rect, smoothing_enabled) + CanvasMsg::DrawImage(imagedata, source_rect.size, dest_rect, source_rect, smoothing_enabled) }; self.renderer.send(msg).unwrap(); Ok(()) } + + fn draw_image_data(&self, + image_data: Vec<u8>, + image_size: Size2D<f64>, + sx: f64, sy: f64, sw: f64, sh: f64, + dx: f64, dy: f64, dw: f64, dh: f64) -> Fallible<()> { + // Establish the source and destination rectangles + let (source_rect, dest_rect) = self.adjust_source_dest_rects(image_size, sx, sy, sw, sh, dx, dy, dw, dh); + + if !is_rect_valid(source_rect) || !is_rect_valid(dest_rect) { + return Err(IndexSize) + } + + let smoothing_enabled = self.image_smoothing_enabled.get(); + self.renderer.send(CanvasMsg::DrawImage( + image_data, image_size, dest_rect, + source_rect, smoothing_enabled)).unwrap(); + Ok(()) + } + + fn fetch_image_data(&self, + image_element: &JSRef<HTMLImageElement>) + -> Option<(Vec<u8>, Size2D<f64>)> { + let url = match image_element.get_url() { + Some(url) => url, + None => return None, + }; + + let img = match self.request_image_from_cache(url) { + Some(img) => img, + None => return None, + }; + + let image_size = Size2D(img.width as f64, img.height as f64); + let mut image_data = match img.pixels { + PixelsByColorType::RGBA8(ref pixels) => pixels.to_vec(), + PixelsByColorType::K8(_) => panic!("K8 color type not supported"), + PixelsByColorType::RGB8(_) => panic!("RGB8 color type not supported"), + PixelsByColorType::KA8(_) => panic!("KA8 color type not supported"), + }; + // Pixels come from cache in BGRA order and drawImage expects RGBA so we + // have to swap the color values + { + let mut pixel_colors = image_data.as_mut_slice(); + byte_swap(pixel_colors); + } + return Some((image_data, image_size)); + } + + fn request_image_from_cache(&self, url: Url) -> Option<Arc<Box<Image>>> { + let canvas = self.canvas.root(); + let window = window_from_node(canvas.r()).root(); + let window = window.r(); + let image_cache_task = window.image_cache_task().clone(); + image_cache_task.send(Msg::Prefetch(url.clone())); + image_cache_task.send(Msg::Decode(url.clone())); + let (response_chan, response_port) = channel(); + image_cache_task.send(Msg::WaitForImage(url, response_chan)); + match response_port.recv().unwrap() { + ImageResponseMsg::ImageReady(image) => Some(image), + ImageResponseMsg::ImageFailed => None, + _ => panic!("Image Cache: Unknown Result") + } + } + + fn create_drawable_rect(&self, x: f64, y: f64, w: f64, h: f64) -> Option<Rect<f32>> { + if !([x, y, w, h].iter().all(|val| val.is_finite())) { + return None; + } + + if w == 0.0 && h == 0.0 { + return None; + } + + Some(Rect(Point2D(x as f32, y as f32), Size2D(w as f32, h as f32))) + } } pub trait CanvasRenderingContext2DHelpers { @@ -269,34 +358,35 @@ impl<'a> CanvasRenderingContext2DMethods for JSRef<'a, CanvasRenderingContext2D> self.update_transform() } - fn FillRect(self, x: f64, y: f64, width: f64, height: f64) { - if !(x.is_finite() && y.is_finite() && - width.is_finite() && height.is_finite()) { + fn GlobalAlpha(self) -> f64 { + self.global_alpha.get() + } + + fn SetGlobalAlpha(self, alpha: f64) { + if !alpha.is_finite() || alpha > 1.0 || alpha < 0.0 { return; } - let rect = Rect(Point2D(x as f32, y as f32), Size2D(width as f32, height as f32)); - self.renderer.send(CanvasMsg::FillRect(rect)).unwrap(); + self.global_alpha.set(alpha); + self.renderer.send(CanvasMsg::SetGlobalAlpha(alpha as f32)).unwrap() } - fn ClearRect(self, x: f64, y: f64, width: f64, height: f64) { - if !(x.is_finite() && y.is_finite() && - width.is_finite() && height.is_finite()) { - return; + fn FillRect(self, x: f64, y: f64, width: f64, height: f64) { + if let Some(rect) = self.create_drawable_rect(x, y, width, height) { + self.renderer.send(CanvasMsg::FillRect(rect)).unwrap(); } + } - let rect = Rect(Point2D(x as f32, y as f32), Size2D(width as f32, height as f32)); - self.renderer.send(CanvasMsg::ClearRect(rect)).unwrap(); + fn ClearRect(self, x: f64, y: f64, width: f64, height: f64) { + if let Some(rect) = self.create_drawable_rect(x, y, width, height) { + self.renderer.send(CanvasMsg::ClearRect(rect)).unwrap(); + } } fn StrokeRect(self, x: f64, y: f64, width: f64, height: f64) { - if !(x.is_finite() && y.is_finite() && - width.is_finite() && height.is_finite()) { - return; + if let Some(rect) = self.create_drawable_rect(x, y, width, height) { + self.renderer.send(CanvasMsg::StrokeRect(rect)).unwrap(); } - - let rect = Rect(Point2D(x as f32, y as f32), Size2D(width as f32, height as f32)); - self.renderer.send(CanvasMsg::StrokeRect(rect)).unwrap(); } fn BeginPath(self) { @@ -316,7 +406,7 @@ impl<'a> CanvasRenderingContext2DMethods for JSRef<'a, CanvasRenderingContext2D> } // https://html.spec.whatwg.org/multipage/scripting.html#dom-context-2d-drawimage - fn DrawImage(self, image: HTMLCanvasElementOrCanvasRenderingContext2D, + fn DrawImage(self, image: HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D, dx: f64, dy: f64) -> Fallible<()> { if !(dx.is_finite() && dy.is_finite()) { return Ok(()); @@ -330,7 +420,7 @@ impl<'a> CanvasRenderingContext2DMethods for JSRef<'a, CanvasRenderingContext2D> let sy: f64 = 0f64; match image { - HTMLCanvasElementOrCanvasRenderingContext2D::eHTMLCanvasElement(image) => { + HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::eHTMLCanvasElement(image) => { let canvas = image.root(); let canvas_size = canvas.r().get_size(); let dw: f64 = canvas_size.width as f64; @@ -341,7 +431,7 @@ impl<'a> CanvasRenderingContext2DMethods for JSRef<'a, CanvasRenderingContext2D> sx, sy, sw, sh, dx, dy, dw, dh) } - HTMLCanvasElementOrCanvasRenderingContext2D::eCanvasRenderingContext2D(image) => { + HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::eCanvasRenderingContext2D(image) => { let image = image.root(); let context = image.r(); let canvas = context.Canvas().root(); @@ -354,11 +444,31 @@ impl<'a> CanvasRenderingContext2DMethods for JSRef<'a, CanvasRenderingContext2D> sx, sy, sw, sh, dx, dy, dw, dh) } + HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::eHTMLImageElement(image) => { + let image = image.root(); + let image_element = image.r(); + // https://html.spec.whatwg.org/multipage/embedded-content.html#img-error + // If the image argument is an HTMLImageElement object that is in the broken state, + // then throw an InvalidStateError exception + let (image_data, image_size) = match self.fetch_image_data(&image_element) { + Some((data, size)) => (data, size), + None => return Err(InvalidState), + }; + let dw: f64 = image_size.width as f64; + let dh: f64 = image_size.height as f64; + let sw: f64 = dw; + let sh: f64 = dh; + return self.draw_image_data(image_data, + image_size, + sx, sy, sw, sh, + dx, dy, dw, dh) + } + } } // https://html.spec.whatwg.org/multipage/scripting.html#dom-context-2d-drawimage - fn DrawImage_(self, image: HTMLCanvasElementOrCanvasRenderingContext2D, + fn DrawImage_(self, image: HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D, dx: f64, dy: f64, dw: f64, dh: f64) -> Fallible<()> { if !(dx.is_finite() && dy.is_finite() && dw.is_finite() && dh.is_finite()) { @@ -373,7 +483,7 @@ impl<'a> CanvasRenderingContext2DMethods for JSRef<'a, CanvasRenderingContext2D> let sy: f64 = 0f64; match image { - HTMLCanvasElementOrCanvasRenderingContext2D::eHTMLCanvasElement(image) => { + HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::eHTMLCanvasElement(image) => { let canvas = image.root(); let canvas_size = canvas.r().get_size(); let sw: f64 = canvas_size.width as f64; @@ -382,7 +492,7 @@ impl<'a> CanvasRenderingContext2DMethods for JSRef<'a, CanvasRenderingContext2D> sx, sy, sw, sh, dx, dy, dw, dh) } - HTMLCanvasElementOrCanvasRenderingContext2D::eCanvasRenderingContext2D(image) => { + HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::eCanvasRenderingContext2D(image) => { let image = image.root(); let context = image.r(); let canvas = context.Canvas().root(); @@ -393,11 +503,28 @@ impl<'a> CanvasRenderingContext2DMethods for JSRef<'a, CanvasRenderingContext2D> sx, sy, sw, sh, dx, dy, dw, dh) } + HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::eHTMLImageElement(image) => { + let image = image.root(); + let image_element = image.r(); + // https://html.spec.whatwg.org/multipage/embedded-content.html#img-error + // If the image argument is an HTMLImageElement object that is in the broken state, + // then throw an InvalidStateError exception + let (image_data, image_size) = match self.fetch_image_data(&image_element) { + Some((data, size)) => (data, size), + None => return Err(InvalidState), + }; + let sw: f64 = image_size.width as f64; + let sh: f64 = image_size.height as f64; + return self.draw_image_data(image_data, + image_size, + sx, sy, sw, sh, + dx, dy, dw, dh) + } } } // https://html.spec.whatwg.org/multipage/scripting.html#dom-context-2d-drawimage - fn DrawImage__(self, image: HTMLCanvasElementOrCanvasRenderingContext2D, + fn DrawImage__(self, image: HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D, sx: f64, sy: f64, sw: f64, sh: f64, dx: f64, dy: f64, dw: f64, dh: f64) -> Fallible<()> { if !(sx.is_finite() && sy.is_finite() && sw.is_finite() && sh.is_finite() && @@ -406,13 +533,13 @@ impl<'a> CanvasRenderingContext2DMethods for JSRef<'a, CanvasRenderingContext2D> } match image { - HTMLCanvasElementOrCanvasRenderingContext2D::eHTMLCanvasElement(image) => { + HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::eHTMLCanvasElement(image) => { let canvas = image.root(); return self.draw_html_canvas_element(canvas.r(), sx, sy, sw, sh, dx, dy, dw, dh) } - HTMLCanvasElementOrCanvasRenderingContext2D::eCanvasRenderingContext2D(image) => { + HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::eCanvasRenderingContext2D(image) => { let image = image.root(); let context = image.r(); let canvas = context.Canvas().root(); @@ -420,6 +547,21 @@ impl<'a> CanvasRenderingContext2DMethods for JSRef<'a, CanvasRenderingContext2D> sx, sy, sw, sh, dx, dy, dw, dh) } + HTMLImageElementOrHTMLCanvasElementOrCanvasRenderingContext2D::eHTMLImageElement(image) => { + let image = image.root(); + let image_element = image.r(); + // https://html.spec.whatwg.org/multipage/embedded-content.html#img-error + // If the image argument is an HTMLImageElement object that is in the broken state, + // then throw an InvalidStateError exception + let (image_data, image_size) = match self.fetch_image_data(&image_element) { + Some((data, size)) => (data, size), + None => return Err(InvalidState), + }; + return self.draw_image_data(image_data, + image_size, + sx, sy, sw, sh, + dx, dy, dw, dh) + } } } @@ -574,6 +716,8 @@ impl<'a> CanvasRenderingContext2DMethods for JSRef<'a, CanvasRenderingContext2D> } fn GetImageData(self, sx: Finite<f64>, sy: Finite<f64>, sw: Finite<f64>, sh: Finite<f64>) -> Fallible<Temporary<ImageData>> { + let sx = *sx; + let sy = *sy; let sw = *sw; let sh = *sh; @@ -582,14 +726,18 @@ impl<'a> CanvasRenderingContext2DMethods for JSRef<'a, CanvasRenderingContext2D> } let (sender, receiver) = channel::<Vec<u8>>(); - let dest_rect = Rect(Point2D(sx.to_i32().unwrap(), sy.to_i32().unwrap()), Size2D(sw.to_i32().unwrap(), sh.to_i32().unwrap())); + let dest_rect = Rect(Point2D(sx as f64, sy as f64), Size2D(sw as f64, sh as f64)); let canvas_size = self.canvas.root().r().get_size(); + let canvas_size = Size2D(canvas_size.width as f64, canvas_size.height as f64); self.renderer.send(CanvasMsg::GetImageData(dest_rect, canvas_size, sender)).unwrap(); let data = receiver.recv().unwrap(); Ok(ImageData::new(self.global.root().r(), sw.abs().to_u32().unwrap(), sh.abs().to_u32().unwrap(), Some(data))) } fn PutImageData(self, imagedata: JSRef<ImageData>, dx: Finite<f64>, dy: Finite<f64>) { + let dx = *dx; + let dy = *dy; + // XXX: // By the spec: http://www.w3.org/html/wg/drafts/2dcontext/html5_canvas_CR/#dom-context-2d-putimagedata // "If any of the arguments to the method are infinite or NaN, the method must throw a NotSupportedError exception" @@ -597,13 +745,22 @@ impl<'a> CanvasRenderingContext2DMethods for JSRef<'a, CanvasRenderingContext2D> // they will be TypeError by WebIDL spec before call this methods. let data = imagedata.get_data_array(&self.global.root().r()); - let image_data_rect = Rect(Point2D(dx.to_i32().unwrap(), dy.to_i32().unwrap()), imagedata.get_size()); + let image_data_size = imagedata.get_size(); + let image_data_size = Size2D(image_data_size.width as f64, image_data_size.height as f64); + let image_data_rect = Rect(Point2D(dx, dy), image_data_size); let dirty_rect = None; self.renderer.send(CanvasMsg::PutImageData(data, image_data_rect, dirty_rect)).unwrap() } fn PutImageData_(self, imagedata: JSRef<ImageData>, dx: Finite<f64>, dy: Finite<f64>, dirtyX: Finite<f64>, dirtyY: Finite<f64>, dirtyWidth: Finite<f64>, dirtyHeight: Finite<f64>) { + let dx = *dx; + let dy = *dy; + let dirtyX = *dirtyX; + let dirtyY = *dirtyY; + let dirtyWidth = *dirtyWidth; + let dirtyHeight = *dirtyHeight; + // XXX: // By the spec: http://www.w3.org/html/wg/drafts/2dcontext/html5_canvas_CR/#dom-context-2d-putimagedata // "If any of the arguments to the method are infinite or NaN, the method must throw a NotSupportedError exception" @@ -611,12 +768,11 @@ impl<'a> CanvasRenderingContext2DMethods for JSRef<'a, CanvasRenderingContext2D> // they will be TypeError by WebIDL spec before call this methods. let data = imagedata.get_data_array(&self.global.root().r()); - let image_data_rect = Rect(Point2D(dx.to_i32().unwrap(), dy.to_i32().unwrap()), - Size2D(imagedata.Width().to_i32().unwrap(), - imagedata.Height().to_i32().unwrap())); - let dirty_rect = Some(Rect(Point2D(dirtyX.to_i32().unwrap(), dirtyY.to_i32().unwrap()), - Size2D(dirtyWidth.to_i32().unwrap(), - dirtyHeight.to_i32().unwrap()))); + let image_data_rect = Rect(Point2D(dx, dy), + Size2D(imagedata.Width() as f64, + imagedata.Height() as f64)); + let dirty_rect = Some(Rect(Point2D(dirtyX, dirtyY), + Size2D(dirtyWidth, dirtyHeight))); self.renderer.send(CanvasMsg::PutImageData(data, image_data_rect, dirty_rect)).unwrap() } @@ -649,6 +805,32 @@ impl<'a> CanvasRenderingContext2DMethods for JSRef<'a, CanvasRenderingContext2D> Ok(CanvasGradient::new(self.global.root().r(), CanvasGradientStyle::Radial(RadialGradientStyle::new(x0, y0, r0, x1, y1, r1, Vec::new())))) } + + fn LineWidth(self) -> f64 { + self.line_width.get() + } + + fn SetLineWidth(self, width: f64) { + if !width.is_finite() || width <= 0.0 { + return; + } + + self.line_width.set(width); + self.renderer.send(CanvasMsg::SetLineWidth(width as f32)).unwrap() + } + + fn MiterLimit(self) -> f64 { + self.miter_limit.get() + } + + fn SetMiterLimit(self, limit: f64) { + if !limit.is_finite() || limit <= 0.0 { + return; + } + + self.miter_limit.set(limit); + self.renderer.send(CanvasMsg::SetMiterLimit(limit as f32)).unwrap() + } } #[unsafe_destructor] @@ -667,6 +849,6 @@ pub fn parse_color(string: &str) -> Result<RGBA,()> { // Used by drawImage to determine if a source or destination rectangle is valid // Origin coordinates and size cannot be negative. Size has to be greater than zero -fn is_rect_valid(rect: Rect<i32>) -> bool { - rect.origin.x >= 0 && rect.origin.y >= 0 && rect.size.width > 0 && rect.size.height > 0 +fn is_rect_valid(rect: Rect<f64>) -> bool { + rect.size.width > 0.0 && rect.size.height > 0.0 } diff --git a/components/script/dom/characterdata.rs b/components/script/dom/characterdata.rs index 1f2b18f1b99..92f3283c759 100644 --- a/components/script/dom/characterdata.rs +++ b/components/script/dom/characterdata.rs @@ -6,11 +6,13 @@ use dom::bindings::cell::DOMRefCell; use dom::bindings::codegen::Bindings::CharacterDataBinding::CharacterDataMethods; -use dom::bindings::codegen::InheritTypes::{CharacterDataDerived, NodeCast}; +use dom::bindings::codegen::InheritTypes::{CharacterDataDerived, ElementCast}; +use dom::bindings::codegen::InheritTypes::NodeCast; use dom::bindings::error::{Fallible, ErrorResult}; use dom::bindings::error::Error::IndexSize; -use dom::bindings::js::JSRef; +use dom::bindings::js::{JSRef, LayoutJS, Temporary}; use dom::document::Document; +use dom::element::Element; use dom::eventtarget::{EventTarget, EventTargetTypeId}; use dom::node::{Node, NodeHelpers, NodeTypeId}; @@ -18,6 +20,7 @@ use util::str::DOMString; use std::borrow::ToOwned; use std::cell::Ref; +use std::cmp; #[dom_struct] pub struct CharacterData { @@ -43,67 +46,59 @@ impl CharacterData { data: DOMRefCell::new(data), } } - - #[inline] - pub fn node<'a>(&'a self) -> &'a Node { - &self.node - } - - #[inline] - pub fn data(&self) -> Ref<DOMString> { - self.data.borrow() - } - - #[inline] - pub fn set_data(&self, data: DOMString) { - *self.data.borrow_mut() = data; - } - - #[inline] - #[allow(unsafe_code)] - pub unsafe fn data_for_layout<'a>(&'a self) -> &'a str { - self.data.borrow_for_layout().as_slice() - } - } impl<'a> CharacterDataMethods for JSRef<'a, CharacterData> { + // https://dom.spec.whatwg.org/#dom-characterdata-data fn Data(self) -> DOMString { // FIXME(https://github.com/rust-lang/rust/issues/23338) let data = self.data.borrow(); data.clone() } - fn SetData(self, arg: DOMString) -> ErrorResult { - *self.data.borrow_mut() = arg; - Ok(()) + // https://dom.spec.whatwg.org/#dom-characterdata-data + fn SetData(self, data: DOMString) { + *self.data.borrow_mut() = data; } + // https://dom.spec.whatwg.org/#dom-characterdata-length fn Length(self) -> u32 { // FIXME(https://github.com/rust-lang/rust/issues/23338) let data = self.data.borrow(); data.chars().count() as u32 } + // https://dom.spec.whatwg.org/#dom-characterdata-substringdata fn SubstringData(self, offset: u32, count: u32) -> Fallible<DOMString> { - // FIXME(https://github.com/rust-lang/rust/issues/23338) let data = self.data.borrow(); - Ok(data.slice_chars(offset as usize, (offset + count) as usize).to_owned()) + // Step 1. + let len = data.chars().count(); + if offset as usize > len { + // Step 2. + return Err(IndexSize); + } + // Step 3. + let end = cmp::min((offset + count) as usize, len); + // Step 4. + Ok(data.slice_chars(offset as usize, end).to_owned()) } - fn AppendData(self, arg: DOMString) -> ErrorResult { - self.data.borrow_mut().push_str(arg.as_slice()); - Ok(()) + // https://dom.spec.whatwg.org/#dom-characterdata-appenddata + fn AppendData(self, data: DOMString) { + self.data.borrow_mut().push_str(&data); } + // https://dom.spec.whatwg.org/#dom-characterdata-insertdata fn InsertData(self, offset: u32, arg: DOMString) -> ErrorResult { self.ReplaceData(offset, 0, arg) } + // https://dom.spec.whatwg.org/#dom-characterdata-deletedata fn DeleteData(self, offset: u32, count: u32) -> ErrorResult { self.ReplaceData(offset, count, "".to_owned()) } + // https://dom.spec.whatwg.org/#dom-characterdata-replacedata fn ReplaceData(self, offset: u32, count: u32, arg: DOMString) -> ErrorResult { let length = self.data.borrow().chars().count() as u32; if offset > length { @@ -127,5 +122,40 @@ impl<'a> CharacterDataMethods for JSRef<'a, CharacterData> { let node: JSRef<Node> = NodeCast::from_ref(self); node.remove_self(); } + + // https://dom.spec.whatwg.org/#dom-nondocumenttypechildnode-previouselementsibling + fn GetPreviousElementSibling(self) -> Option<Temporary<Element>> { + NodeCast::from_ref(self).preceding_siblings() + .filter_map(ElementCast::to_temporary).next() + } + + // https://dom.spec.whatwg.org/#dom-nondocumenttypechildnode-nextelementsibling + fn GetNextElementSibling(self) -> Option<Temporary<Element>> { + NodeCast::from_ref(self).following_siblings() + .filter_map(ElementCast::to_temporary).next() + } +} + +pub trait CharacterDataHelpers<'a> { + fn data(self) -> Ref<'a, DOMString>; } +impl<'a> CharacterDataHelpers<'a> for JSRef<'a, CharacterData> { + #[inline] + fn data(self) -> Ref<'a, DOMString> { + self.extended_deref().data.borrow() + } +} + +#[allow(unsafe_code)] +pub trait LayoutCharacterDataHelpers { + unsafe fn data_for_layout<'a>(&'a self) -> &'a str; +} + +#[allow(unsafe_code)] +impl LayoutCharacterDataHelpers for LayoutJS<CharacterData> { + #[inline] + unsafe fn data_for_layout<'a>(&'a self) -> &'a str { + &(*self.unsafe_get()).data.borrow_for_layout() + } +} diff --git a/components/script/dom/comment.rs b/components/script/dom/comment.rs index 85cca18b2b2..743e935db01 100644 --- a/components/script/dom/comment.rs +++ b/components/script/dom/comment.rs @@ -42,10 +42,5 @@ impl Comment { let document = global.as_window().Document().root(); Ok(Comment::new(data, document.r())) } - - #[inline] - pub fn characterdata<'a>(&'a self) -> &'a CharacterData { - &self.characterdata - } } diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index 7e8a570960d..f001f0268f3 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -18,24 +18,26 @@ use dom::bindings::codegen::InheritTypes::{EventTargetCast, HTMLAnchorElementCas use dom::bindings::codegen::InheritTypes::{HTMLAnchorElementDerived, HTMLAppletElementDerived}; use dom::bindings::codegen::InheritTypes::{HTMLAreaElementDerived, HTMLEmbedElementDerived}; use dom::bindings::codegen::InheritTypes::{HTMLFormElementDerived, HTMLImageElementDerived}; -use dom::bindings::codegen::InheritTypes::{HTMLScriptElementDerived}; +use dom::bindings::codegen::InheritTypes::{HTMLScriptElementDerived, CharacterDataCast}; use dom::bindings::error::{ErrorResult, Fallible}; use dom::bindings::error::Error::{NotSupported, InvalidCharacter, Security}; -use dom::bindings::error::Error::{HierarchyRequest, Namespace}; +use dom::bindings::error::Error::HierarchyRequest; use dom::bindings::global::GlobalRef; use dom::bindings::js::{MutNullableJS, JS, JSRef, LayoutJS, Temporary, TemporaryPushable}; use dom::bindings::js::{OptionalRootable, RootedReference}; use dom::bindings::refcounted::Trusted; +use dom::bindings::trace::RootedVec; use dom::bindings::utils::reflect_dom_object; -use dom::bindings::utils::xml_name_type; -use dom::bindings::utils::XMLName::{QName, Name, InvalidXMLName}; +use dom::bindings::utils::{xml_name_type, validate_and_extract}; +use dom::bindings::utils::XMLName::InvalidXMLName; +use dom::characterdata::CharacterDataHelpers; use dom::comment::Comment; use dom::customevent::CustomEvent; use dom::documentfragment::DocumentFragment; use dom::documenttype::DocumentType; use dom::domimplementation::DOMImplementation; -use dom::element::{Element, ElementCreator, AttributeHandlers, get_attribute_parts}; -use dom::element::{ElementTypeId, ActivationElementHelpers}; +use dom::element::{Element, ElementCreator, AttributeHandlers}; +use dom::element::{ElementTypeId, ActivationElementHelpers, FocusElementHelpers}; use dom::event::{Event, EventBubbles, EventCancelable, EventHelpers}; use dom::eventtarget::{EventTarget, EventTargetTypeId, EventTargetHelpers}; use dom::htmlanchorelement::HTMLAnchorElement; @@ -67,7 +69,7 @@ use net_traits::CookieSource::NonHTTP; use net_traits::ControlMsg::{SetCookiesForUrl, GetCookiesForUrl}; use script_task::Runnable; use script_traits::{MouseButton, UntrustedNodeAddress}; -use util::{opts, namespace}; +use util::opts; use util::str::{DOMString, split_html_space_chars}; use layout_interface::{ReflowGoal, ReflowQueryType}; @@ -342,12 +344,12 @@ impl<'a> DocumentHelpers<'a> for JSRef<'a, Document> { let mut head: usize = 0; let root: JSRef<Node> = NodeCast::from_ref(root.r()); for node in root.traverse_preorder() { - let elem: Option<JSRef<Element>> = ElementCast::to_ref(node); - if let Some(elem) = elem { + let node = node.root(); + if let Some(elem) = ElementCast::to_ref(node.r()) { if (*elements)[head].root().r() == elem { head += 1; } - if new_node == node || head == elements.len() { + if new_node == node.r() || head == elements.len() { break; } } @@ -378,9 +380,9 @@ impl<'a> DocumentHelpers<'a> for JSRef<'a, Document> { }; let doc_node: JSRef<Node> = NodeCast::from_ref(self); doc_node.traverse_preorder() - .filter_map(HTMLAnchorElementCast::to_ref) - .find(check_anchor) - .map(|node| Temporary::from_rooted(ElementCast::from_ref(node))) + .filter_map(HTMLAnchorElementCast::to_temporary) + .find(|node| check_anchor(&node.root().r())) + .map(ElementCast::from_temporary) }) } @@ -447,7 +449,9 @@ impl<'a> DocumentHelpers<'a> for JSRef<'a, Document> { /// Request that the given element receive focus once the current transaction is complete. fn request_focus(self, elem: JSRef<Element>) { - self.possibly_focused.assign(Some(elem)) + if elem.is_focusable_area() { + self.possibly_focused.assign(Some(elem)) + } } /// Reassign the focus context to the element that last requested focus during this @@ -490,7 +494,7 @@ impl<'a> DocumentHelpers<'a> for JSRef<'a, Document> { fn dirty_all_nodes(self) { let root: JSRef<Node> = NodeCast::from_ref(self); for node in root.traverse_preorder() { - node.dirty(NodeDamage::OtherNodeDamage) + node.root().r().dirty(NodeDamage::OtherNodeDamage) } } @@ -506,20 +510,17 @@ impl<'a> DocumentHelpers<'a> for JSRef<'a, Document> { }.root(); let el = match ElementCast::to_ref(node.r()) { - Some(el) => el, + Some(el) => Temporary::from_rooted(el), None => { - let ancestor = node.r() - .ancestors() - .filter_map(ElementCast::to_ref) - .next(); - match ancestor { - Some(ancestor) => ancestor, + let parent = node.r().parent_node(); + match parent.and_then(ElementCast::to_temporary) { + Some(parent) => parent, None => return, } }, - }; + }.root(); - let node: JSRef<Node> = NodeCast::from_ref(el); + let node: JSRef<Node> = NodeCast::from_ref(el.r()); debug!("clicked on {:?}", node.debug_str()); // Prevent click event if form control element is disabled. if node.click_event_filter_by_disabled_state() { @@ -548,7 +549,7 @@ impl<'a> DocumentHelpers<'a> for JSRef<'a, Document> { // https://dvcs.w3.org/hg/dom3events/raw-file/tip/html/DOM3-Events.html#trusted-events event.set_trusted(true); // https://html.spec.whatwg.org/multipage/interaction.html#run-authentic-click-activation-steps - el.authentic_click_activation(event); + el.r().authentic_click_activation(event); self.commit_focus_transaction(); window.r().reflow(ReflowGoal::ForDisplay, ReflowQueryType::NoQuery, ReflowReason::MouseEvent); @@ -563,7 +564,10 @@ impl<'a> DocumentHelpers<'a> for JSRef<'a, Document> { let mouse_over_targets: Vec<JS<Node>> = mouse_over_addresses.iter() .filter_map(|node_address| { let node = node::from_untrusted_node_address(js_runtime, *node_address); - node.root().r().inclusive_ancestors().find(|node| node.is_element()).map(JS::from_rooted) + node.root().r().inclusive_ancestors() + .map(|node| node.root()) + .find(|node| node.r().is_element()) + .map(|node| JS::from_rooted(node.r())) }).collect(); // Remove hover from any elements in the previous list that are no longer @@ -814,22 +818,24 @@ impl Document { } trait PrivateDocumentHelpers { - fn createNodeList<F: Fn(JSRef<Node>) -> bool>(self, callback: F) -> Temporary<NodeList>; + fn create_node_list<F: Fn(JSRef<Node>) -> bool>(self, callback: F) -> Temporary<NodeList>; fn get_html_element(self) -> Option<Temporary<HTMLHtmlElement>>; } impl<'a> PrivateDocumentHelpers for JSRef<'a, Document> { - fn createNodeList<F: Fn(JSRef<Node>) -> bool>(self, callback: F) -> Temporary<NodeList> { + fn create_node_list<F: Fn(JSRef<Node>) -> bool>(self, callback: F) -> Temporary<NodeList> { let window = self.window.root(); let document_element = self.GetDocumentElement().root(); - let nodes = match document_element { - None => vec!(), - Some(ref root) => { - let root: JSRef<Node> = NodeCast::from_ref(root.r()); - root.traverse_preorder().filter(|&node| callback(node)).collect() + let mut nodes = RootedVec::new(); + if let Some(ref root) = document_element { + for node in NodeCast::from_ref(root.r()).traverse_preorder() { + let node = node.root(); + if callback(node.r()) { + nodes.push(node.r().unrooted()); + } } }; - NodeList::new_simple_list(window.r(), nodes) + NodeList::new_simple_list(window.r(), &nodes) } fn get_html_element(self) -> Option<Temporary<HTMLHtmlElement>> { @@ -927,7 +933,7 @@ impl<'a> DocumentMethods for JSRef<'a, Document> { // http://dom.spec.whatwg.org/#dom-document-documentelement fn GetDocumentElement(self) -> Option<Temporary<Element>> { let node: JSRef<Node> = NodeCast::from_ref(self); - node.child_elements().next().map(Temporary::from_rooted) + node.child_elements().next() } // http://dom.spec.whatwg.org/#dom-document-getelementsbytagname @@ -975,45 +981,10 @@ impl<'a> DocumentMethods for JSRef<'a, Document> { fn CreateElementNS(self, namespace: Option<DOMString>, qualified_name: DOMString) -> Fallible<Temporary<Element>> { - let ns = namespace::from_domstring(namespace); - match xml_name_type(&qualified_name) { - InvalidXMLName => { - debug!("Not a valid element name"); - return Err(InvalidCharacter); - }, - Name => { - debug!("Not a valid qualified element name"); - return Err(Namespace); - }, - QName => {} - } - - let (prefix_from_qname, local_name_from_qname) = get_attribute_parts(&qualified_name); - match (&ns, prefix_from_qname, local_name_from_qname) { - // throw if prefix is not null and namespace is null - (&ns!(""), Some(_), _) => { - debug!("Namespace can't be null with a non-null prefix"); - return Err(Namespace); - }, - // throw if prefix is "xml" and namespace is not the XML namespace - (_, Some(ref prefix), _) if "xml" == *prefix && ns != ns!(XML) => { - debug!("Namespace must be the xml namespace if the prefix is 'xml'"); - return Err(Namespace); - }, - // throw if namespace is the XMLNS namespace and neither qualifiedName nor prefix is - // "xmlns" - (&ns!(XMLNS), Some(ref prefix), _) if "xmlns" == *prefix => {}, - (&ns!(XMLNS), _, "xmlns") => {}, - (&ns!(XMLNS), _, _) => { - debug!("The prefix or the qualified name must be 'xmlns' if namespace is the XMLNS namespace "); - return Err(Namespace); - }, - _ => {} - } - - let name = QualName::new(ns, Atom::from_slice(local_name_from_qname)); - Ok(Element::create(name, prefix_from_qname.map(|s| s.to_owned()), self, - ElementCreator::ScriptCreated)) + let (namespace, prefix, local_name) = + try!(validate_and_extract(namespace, &qualified_name)); + let name = QualName::new(namespace, local_name); + Ok(Element::create(name, prefix, self, ElementCreator::ScriptCreated)) } // http://dom.spec.whatwg.org/#dom-document-createattribute @@ -1032,6 +1003,18 @@ impl<'a> DocumentMethods for JSRef<'a, Document> { Ok(Attr::new(window.r(), name, value, l_name, ns!(""), None, None)) } + // http://dom.spec.whatwg.org/#dom-document-createattributens + fn CreateAttributeNS(self, namespace: Option<DOMString>, qualified_name: DOMString) + -> Fallible<Temporary<Attr>> { + let (namespace, prefix, local_name) = + try!(validate_and_extract(namespace, &qualified_name)); + let window = self.window.root(); + let value = AttrValue::String("".to_owned()); + let qualified_name = Atom::from_slice(&qualified_name); + Ok(Attr::new(window.r(), local_name, value, qualified_name, + namespace, prefix, None)) + } + // http://dom.spec.whatwg.org/#dom-document-createdocumentfragment fn CreateDocumentFragment(self) -> Temporary<DocumentFragment> { DocumentFragment::new(self) @@ -1138,17 +1121,17 @@ impl<'a> DocumentMethods for JSRef<'a, Document> { // http://www.whatwg.org/specs/web-apps/current-work/#document.title fn Title(self) -> DOMString { let title_element = self.GetDocumentElement().root().and_then(|root| { - NodeCast::from_ref(root.get_unsound_ref_forever()) - .traverse_preorder() - .find(|node| node.type_id() == NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTitleElement))) - }); + NodeCast::from_ref(root.r()).traverse_preorder().find(|node| { + node.root().r().type_id() == NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTitleElement)) + }) + }).root(); let mut title = String::new(); if let Some(title_element) = title_element { - for child in title_element.children() { + for child in title_element.r().children() { let child = child.root(); if let Some(text) = TextCast::to_ref(child.r()) { - title.push_str(&text.characterdata().data()); + title.push_str(&CharacterDataCast::from_ref(text).data()); } } } @@ -1162,9 +1145,9 @@ impl<'a> DocumentMethods for JSRef<'a, Document> { self.GetDocumentElement().root().map(|root| { let root: JSRef<Node> = NodeCast::from_ref(root.r()); let head_node = root.traverse_preorder().find(|child| { - child.type_id() == NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLHeadElement)) - }); - head_node.map(|head| { + child.root().r().type_id() == NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLHeadElement)) + }).root(); + head_node.r().map(|head| { let title_node = head.children().map(|c| c.root()).find(|child| { child.r().type_id() == NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTitleElement)) }); @@ -1275,7 +1258,7 @@ impl<'a> DocumentMethods for JSRef<'a, Document> { // http://www.whatwg.org/specs/web-apps/current-work/#dom-document-getelementsbyname fn GetElementsByName(self, name: DOMString) -> Temporary<NodeList> { - self.createNodeList(|node| { + self.create_node_list(|node| { let element: JSRef<Element> = match ElementCast::to_ref(node) { Some(element) => element, None => return false, @@ -1378,6 +1361,21 @@ impl<'a> DocumentMethods for JSRef<'a, Document> { HTMLCollection::children(window.r(), NodeCast::from_ref(self)) } + // https://dom.spec.whatwg.org/#dom-parentnode-firstelementchild + fn GetFirstElementChild(self) -> Option<Temporary<Element>> { + NodeCast::from_ref(self).child_elements().next() + } + + // https://dom.spec.whatwg.org/#dom-parentnode-lastelementchild + fn GetLastElementChild(self) -> Option<Temporary<Element>> { + NodeCast::from_ref(self).rev_children().filter_map(ElementCast::to_temporary).next() + } + + // https://dom.spec.whatwg.org/#dom-parentnode-childelementcount + fn ChildElementCount(self) -> u32 { + NodeCast::from_ref(self).child_elements().count() as u32 + } + // http://dom.spec.whatwg.org/#dom-parentnode-queryselector fn QuerySelector(self, selectors: DOMString) -> Fallible<Option<Temporary<Element>>> { let root: JSRef<Node> = NodeCast::from_ref(self); diff --git a/components/script/dom/documentfragment.rs b/components/script/dom/documentfragment.rs index f8e76103d7d..2a61aa98460 100644 --- a/components/script/dom/documentfragment.rs +++ b/components/script/dom/documentfragment.rs @@ -5,7 +5,8 @@ use dom::bindings::codegen::Bindings::DocumentFragmentBinding; use dom::bindings::codegen::Bindings::DocumentFragmentBinding::DocumentFragmentMethods; use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; -use dom::bindings::codegen::InheritTypes::{DocumentFragmentDerived, NodeCast}; +use dom::bindings::codegen::InheritTypes::DocumentFragmentDerived; +use dom::bindings::codegen::InheritTypes::{ElementCast, NodeCast}; use dom::bindings::js::{JSRef, Temporary}; use dom::bindings::error::Fallible; use dom::bindings::global::GlobalRef; @@ -56,6 +57,21 @@ impl<'a> DocumentFragmentMethods for JSRef<'a, DocumentFragment> { HTMLCollection::children(window.r(), NodeCast::from_ref(self)) } + // https://dom.spec.whatwg.org/#dom-parentnode-firstelementchild + fn GetFirstElementChild(self) -> Option<Temporary<Element>> { + NodeCast::from_ref(self).child_elements().next() + } + + // https://dom.spec.whatwg.org/#dom-parentnode-lastelementchild + fn GetLastElementChild(self) -> Option<Temporary<Element>> { + NodeCast::from_ref(self).rev_children().filter_map(ElementCast::to_temporary).next() + } + + // https://dom.spec.whatwg.org/#dom-parentnode-childelementcount + fn ChildElementCount(self) -> u32 { + NodeCast::from_ref(self).child_elements().count() as u32 + } + // http://dom.spec.whatwg.org/#dom-parentnode-queryselector fn QuerySelector(self, selectors: DOMString) -> Fallible<Option<Temporary<Element>>> { let root: JSRef<Node> = NodeCast::from_ref(self); diff --git a/components/script/dom/domexception.rs b/components/script/dom/domexception.rs index 59f8c0a0743..d066021e304 100644 --- a/components/script/dom/domexception.rs +++ b/components/script/dom/domexception.rs @@ -23,6 +23,7 @@ pub enum DOMErrorName { NoModificationAllowedError = DOMExceptionConstants::NO_MODIFICATION_ALLOWED_ERR, NotFoundError = DOMExceptionConstants::NOT_FOUND_ERR, NotSupportedError = DOMExceptionConstants::NOT_SUPPORTED_ERR, + InUseAttributeError = DOMExceptionConstants::INUSE_ATTRIBUTE_ERR, InvalidStateError = DOMExceptionConstants::INVALID_STATE_ERR, SyntaxError = DOMExceptionConstants::SYNTAX_ERR, InvalidModificationError = DOMExceptionConstants::INVALID_MODIFICATION_ERR, @@ -83,6 +84,7 @@ impl<'a> DOMExceptionMethods for JSRef<'a, DOMException> { DOMErrorName::NoModificationAllowedError => "The object can not be modified.", DOMErrorName::NotFoundError => "The object can not be found here.", DOMErrorName::NotSupportedError => "The operation is not supported.", + DOMErrorName::InUseAttributeError => "The attribute already in use.", DOMErrorName::InvalidStateError => "The object is in an invalid state.", DOMErrorName::SyntaxError => "The string did not match the expected pattern.", DOMErrorName::InvalidModificationError => "The object can not be modified in this way.", diff --git a/components/script/dom/domimplementation.rs b/components/script/dom/domimplementation.rs index 7e2a3dba03a..e5cac62f4e7 100644 --- a/components/script/dom/domimplementation.rs +++ b/components/script/dom/domimplementation.rs @@ -8,12 +8,10 @@ use dom::bindings::codegen::Bindings::DOMImplementationBinding::DOMImplementatio use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; use dom::bindings::codegen::InheritTypes::NodeCast; use dom::bindings::error::Fallible; -use dom::bindings::error::Error::{InvalidCharacter, Namespace}; use dom::bindings::global::GlobalRef; use dom::bindings::js::{JS, JSRef, Root, Temporary, OptionalRootable}; use dom::bindings::utils::{Reflector, reflect_dom_object}; -use dom::bindings::utils::xml_name_type; -use dom::bindings::utils::XMLName::{QName, Name, InvalidXMLName}; +use dom::bindings::utils::validate_qualified_name; use dom::document::{Document, DocumentHelpers, IsHTMLDocument}; use dom::document::DocumentSource; use dom::documenttype::DocumentType; @@ -52,18 +50,11 @@ impl DOMImplementation { // http://dom.spec.whatwg.org/#domimplementation impl<'a> DOMImplementationMethods for JSRef<'a, DOMImplementation> { // http://dom.spec.whatwg.org/#dom-domimplementation-createdocumenttype - fn CreateDocumentType(self, qname: DOMString, pubid: DOMString, sysid: DOMString) -> Fallible<Temporary<DocumentType>> { - match xml_name_type(&qname) { - // Step 1. - InvalidXMLName => Err(InvalidCharacter), - // Step 2. - Name => Err(Namespace), - // Step 3. - QName => { - let document = self.document.root(); - Ok(DocumentType::new(qname, Some(pubid), Some(sysid), document.r())) - } - } + fn CreateDocumentType(self, qualified_name: DOMString, pubid: DOMString, sysid: DOMString) + -> Fallible<Temporary<DocumentType>> { + try!(validate_qualified_name(&qualified_name)); + let document = self.document.root(); + Ok(DocumentType::new(qualified_name, Some(pubid), Some(sysid), document.r())) } // http://dom.spec.whatwg.org/#dom-domimplementation-createdocument diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index d8d5a9ecf93..d7c7e1908cb 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -24,14 +24,13 @@ use dom::bindings::codegen::InheritTypes::{HTMLTableRowElementDerived, HTMLTextA use dom::bindings::codegen::InheritTypes::{HTMLTableSectionElementDerived, NodeCast}; use dom::bindings::codegen::InheritTypes::HTMLAnchorElementCast; use dom::bindings::error::{ErrorResult, Fallible}; -use dom::bindings::error::Error; use dom::bindings::error::Error::{InvalidCharacter, Syntax}; use dom::bindings::error::Error::NoModificationAllowed; use dom::bindings::js::{MutNullableJS, JS, JSRef, LayoutJS, Temporary, TemporaryPushable}; -use dom::bindings::js::OptionalRootable; +use dom::bindings::js::{OptionalRootable, RootedReference}; use dom::bindings::trace::RootedVec; -use dom::bindings::utils::xml_name_type; -use dom::bindings::utils::XMLName::{QName, Name, InvalidXMLName}; +use dom::bindings::utils::{xml_name_type, validate_and_extract}; +use dom::bindings::utils::XMLName::InvalidXMLName; use dom::create::create_element; use dom::domrect::DOMRect; use dom::domrectlist::DOMRectList; @@ -508,7 +507,7 @@ impl<'a> ElementHelpers<'a> for JSRef<'a, Element> { fn remove_inline_style_property(self, property: DOMString) { let mut inline_declarations = self.style_attribute.borrow_mut(); - inline_declarations.as_mut().map(|declarations| { + if let &mut Some(ref mut declarations) = &mut *inline_declarations { let index = declarations.normal .iter() .position(|decl| decl.name() == property); @@ -524,7 +523,7 @@ impl<'a> ElementHelpers<'a> for JSRef<'a, Element> { declarations.important.make_unique().remove(index); return; } - }); + } } fn update_inline_style(self, property_decl: PropertyDeclaration, style_priority: StylePriority) { @@ -595,13 +594,60 @@ impl<'a> ElementHelpers<'a> for JSRef<'a, Element> { // https://html.spec.whatwg.org/multipage/infrastructure.html#root-element fn get_root_element(self) -> Option<Temporary<Element>> { let node: JSRef<Node> = NodeCast::from_ref(self); - match node.ancestors().last().map(ElementCast::to_ref) { - Some(n) => n.map(Temporary::from_rooted), + match node.ancestors().last().map(ElementCast::to_temporary) { + Some(n) => n, None => Some(self).map(Temporary::from_rooted), } } } +pub trait FocusElementHelpers { + /// https://html.spec.whatwg.org/multipage/interaction.html#focusable-area + fn is_focusable_area(self) -> bool; + + /// https://html.spec.whatwg.org/multipage/scripting.html#concept-element-disabled + fn is_actually_disabled(self) -> bool; +} + +impl<'a> FocusElementHelpers for JSRef<'a, Element> { + fn is_focusable_area(self) -> bool { + if self.is_actually_disabled() { + return false; + } + // TODO: Check whether the element is being rendered (i.e. not hidden). + // TODO: Check the tabindex focus flag. + // https://html.spec.whatwg.org/multipage/interaction.html#specially-focusable + let node: JSRef<Node> = NodeCast::from_ref(self); + match node.type_id() { + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLAnchorElement)) | + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLInputElement)) | + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLSelectElement)) | + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTextAreaElement)) => { + true + } + _ => false + } + } + + fn is_actually_disabled(self) -> bool { + let node: JSRef<Node> = NodeCast::from_ref(self); + match node.type_id() { + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLButtonElement)) | + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLInputElement)) | + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLSelectElement)) | + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTextAreaElement)) | + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLOptionElement)) => { + node.get_disabled_state() + } + // TODO: + // an optgroup element that has a disabled attribute + // a menuitem element that has a disabled attribute + // a fieldset element that is a disabled fieldset + _ => false + } + } +} + pub trait AttributeHandlers { /// Returns the attribute with given namespace and case-sensitive local /// name, if any. @@ -627,11 +673,13 @@ pub trait AttributeHandlers { /// Removes the first attribute with any given namespace and case-sensitive local /// name, if any. - fn remove_attribute(self, namespace: &Namespace, local_name: &Atom); + fn remove_attribute(self, namespace: &Namespace, local_name: &Atom) + -> Option<Temporary<Attr>>; /// Removes the first attribute with any namespace and given case-sensitive name. - fn remove_attribute_by_name(self, name: &Atom); + fn remove_attribute_by_name(self, name: &Atom) -> Option<Temporary<Attr>>; /// Removes the first attribute that satisfies `find`. - fn do_remove_attribute<F>(self, find: F) where F: Fn(JSRef<Attr>) -> bool; + fn do_remove_attribute<F>(self, find: F) -> Option<Temporary<Attr>> + where F: Fn(JSRef<Attr>) -> bool; fn has_class(self, name: &Atom) -> bool; @@ -764,22 +812,25 @@ impl<'a> AttributeHandlers for JSRef<'a, Element> { } } - fn remove_attribute(self, namespace: &Namespace, local_name: &Atom) { + fn remove_attribute(self, namespace: &Namespace, local_name: &Atom) + -> Option<Temporary<Attr>> { self.do_remove_attribute(|attr| { attr.namespace() == namespace && attr.local_name() == local_name - }); + }) } - fn remove_attribute_by_name(self, name: &Atom) { - self.do_remove_attribute(|attr| attr.name() == name); + fn remove_attribute_by_name(self, name: &Atom) -> Option<Temporary<Attr>> { + self.do_remove_attribute(|attr| attr.name() == name) } - fn do_remove_attribute<F>(self, find: F) where F: Fn(JSRef<Attr>) -> bool { + fn do_remove_attribute<F>(self, find: F) -> Option<Temporary<Attr>> + where F: Fn(JSRef<Attr>) -> bool + { let idx = self.attrs.borrow().iter() .map(|attr| attr.root()) .position(|attr| find(attr.r())); - if let Some(idx) = idx { + idx.map(|idx| { let attr = (*self.attrs.borrow())[idx].root(); if attr.r().namespace() == &ns!("") { vtable_for(&NodeCast::from_ref(self)).before_remove_attr(attr.r()); @@ -798,7 +849,8 @@ impl<'a> AttributeHandlers for JSRef<'a, Element> { }; document.r().content_changed(node, damage); } - }; + Temporary::from_rooted(attr.r()) + }) } fn has_class(self, name: &Atom) -> bool { @@ -929,6 +981,7 @@ impl<'a> ElementMethods for JSRef<'a, Element> { } } + // https://dom.spec.whatwg.org/#dom-element-localname fn LocalName(self) -> DOMString { self.local_name.as_slice().to_owned() } @@ -1031,58 +1084,14 @@ impl<'a> ElementMethods for JSRef<'a, Element> { // http://dom.spec.whatwg.org/#dom-element-setattributens fn SetAttributeNS(self, - namespace_url: Option<DOMString>, - name: DOMString, + namespace: Option<DOMString>, + qualified_name: DOMString, value: DOMString) -> ErrorResult { - // Step 1. - let namespace = namespace::from_domstring(namespace_url); - - let name_type = xml_name_type(&name); - match name_type { - // Step 2. - InvalidXMLName => return Err(InvalidCharacter), - // Step 3. - Name => return Err(Error::Namespace), - QName => {} - } - - // Step 4. - let (prefix, local_name) = get_attribute_parts(&name); - - if let Some(ref prefix_str) = prefix { - // Step 5. - if namespace == ns!("") { - return Err(Error::Namespace); - } - - // Step 6. - if "xml" == *prefix_str && namespace != ns!(XML) { - return Err(Error::Namespace); - } - - // Step 7b. - if "xmlns" == *prefix_str && namespace != ns!(XMLNS) { - return Err(Error::Namespace); - } - } - - let name = Atom::from_slice(&name); - let local_name = Atom::from_slice(local_name); - let xmlns = atom!("xmlns"); - - // Step 7a. - if xmlns == name && namespace != ns!(XMLNS) { - return Err(Error::Namespace); - } - - // Step 8. - if namespace == ns!(XMLNS) && xmlns != name && Some("xmlns") != prefix { - return Err(Error::Namespace); - } - - // Step 9. + let (namespace, prefix, local_name) = + try!(validate_and_extract(namespace, &qualified_name)); + let qualified_name = Atom::from_slice(&qualified_name); let value = self.parse_attribute(&namespace, &local_name, value); - self.do_set_attribute(local_name.clone(), value, name, + self.do_set_attribute(local_name.clone(), value, qualified_name, namespace.clone(), prefix.map(|s| s.to_owned()), |attr| { *attr.local_name() == local_name && @@ -1118,17 +1127,20 @@ impl<'a> ElementMethods for JSRef<'a, Element> { self.GetAttributeNS(namespace, local_name).is_some() } + // https://dom.spec.whatwg.org/#dom-element-getelementsbytagname fn GetElementsByTagName(self, localname: DOMString) -> Temporary<HTMLCollection> { let window = window_from_node(self).root(); HTMLCollection::by_tag_name(window.r(), NodeCast::from_ref(self), localname) } + // https://dom.spec.whatwg.org/#dom-element-getelementsbytagnamens fn GetElementsByTagNameNS(self, maybe_ns: Option<DOMString>, localname: DOMString) -> Temporary<HTMLCollection> { let window = window_from_node(self).root(); HTMLCollection::by_tag_name_ns(window.r(), NodeCast::from_ref(self), localname, maybe_ns) } + // https://dom.spec.whatwg.org/#dom-element-getelementsbyclassname fn GetElementsByClassName(self, classes: DOMString) -> Temporary<HTMLCollection> { let window = window_from_node(self).root(); HTMLCollection::by_class_name(window.r(), NodeCast::from_ref(self), classes) @@ -1165,12 +1177,13 @@ impl<'a> ElementMethods for JSRef<'a, Element> { rect.origin.x + rect.size.width) } - // https://dvcs.w3.org/hg/innerhtml/raw-file/tip/index.html#extensions-to-the-element-interface + // https://dvcs.w3.org/hg/innerhtml/raw-file/tip/index.html#widl-Element-innerHTML fn GetInnerHTML(self) -> Fallible<DOMString> { //XXX TODO: XML case self.serialize(ChildrenOnly) } + // https://dvcs.w3.org/hg/innerhtml/raw-file/tip/index.html#widl-Element-innerHTML fn SetInnerHTML(self, value: DOMString) -> Fallible<()> { let context_node: JSRef<Node> = NodeCast::from_ref(self); // Step 1. @@ -1180,10 +1193,12 @@ impl<'a> ElementMethods for JSRef<'a, Element> { Ok(()) } + // https://dvcs.w3.org/hg/innerhtml/raw-file/tip/index.html#widl-Element-outerHTML fn GetOuterHTML(self) -> Fallible<DOMString> { self.serialize(IncludeNode) } + // https://dvcs.w3.org/hg/innerhtml/raw-file/tip/index.html#widl-Element-outerHTML fn SetOuterHTML(self, value: DOMString) -> Fallible<()> { let context_document = document_from_node(self).root(); let context_node: JSRef<Node> = NodeCast::from_ref(self); @@ -1217,12 +1232,39 @@ impl<'a> ElementMethods for JSRef<'a, Element> { Ok(()) } + // https://dom.spec.whatwg.org/#dom-nondocumenttypechildnode-previouselementsibling + fn GetPreviousElementSibling(self) -> Option<Temporary<Element>> { + NodeCast::from_ref(self).preceding_siblings() + .filter_map(ElementCast::to_temporary).next() + } + + // https://dom.spec.whatwg.org/#dom-nondocumenttypechildnode-nextelementsibling + fn GetNextElementSibling(self) -> Option<Temporary<Element>> { + NodeCast::from_ref(self).following_siblings() + .filter_map(ElementCast::to_temporary).next() + } + // http://dom.spec.whatwg.org/#dom-parentnode-children fn Children(self) -> Temporary<HTMLCollection> { let window = window_from_node(self).root(); HTMLCollection::children(window.r(), NodeCast::from_ref(self)) } + // https://dom.spec.whatwg.org/#dom-parentnode-firstelementchild + fn GetFirstElementChild(self) -> Option<Temporary<Element>> { + NodeCast::from_ref(self).child_elements().next() + } + + // https://dom.spec.whatwg.org/#dom-parentnode-lastelementchild + fn GetLastElementChild(self) -> Option<Temporary<Element>> { + NodeCast::from_ref(self).rev_children().filter_map(ElementCast::to_temporary).next() + } + + // https://dom.spec.whatwg.org/#dom-parentnode-childelementcount + fn ChildElementCount(self) -> u32 { + NodeCast::from_ref(self).child_elements().count() as u32 + } + // http://dom.spec.whatwg.org/#dom-parentnode-queryselector fn QuerySelector(self, selectors: DOMString) -> Fallible<Option<Temporary<Element>>> { let root: JSRef<Node> = NodeCast::from_ref(self); @@ -1258,26 +1300,20 @@ impl<'a> ElementMethods for JSRef<'a, Element> { Err(()) => Err(Syntax), Ok(ref selectors) => { let root: JSRef<Node> = NodeCast::from_ref(self); - Ok(root.inclusive_ancestors() - .filter_map(ElementCast::to_ref) - .find(|element| matches(selectors, &NodeCast::from_ref(*element), &mut None)) - .map(Temporary::from_rooted)) + for element in root.inclusive_ancestors() { + let element = element.root(); + if let Some(element) = ElementCast::to_ref(element.r()) { + if matches(selectors, &NodeCast::from_ref(element), &mut None) { + return Ok(Some(Temporary::from_rooted(element))); + } + } + } + Ok(None) } } } } -pub fn get_attribute_parts<'a>(name: &'a str) -> (Option<&'a str>, &'a str) { - //FIXME: Throw for XML-invalid names - //FIXME: Throw for XMLNS-invalid names - if name.contains(":") { - let mut parts = name.splitn(1, ':'); - (Some(parts.next().unwrap()), parts.next().unwrap()) - } else { - (None, name) - } -} - impl<'a> VirtualMethods for JSRef<'a, Element> { fn super_type<'b>(&'b self) -> Option<&'b VirtualMethods> { let node: &JSRef<Node> = NodeCast::from_borrowed_ref(self); @@ -1599,13 +1635,15 @@ impl<'a> ActivationElementHelpers<'a> for JSRef<'a, Element> { Some(el) => Some(Temporary::from_rooted(el.as_element().root().r())), None => { let node: JSRef<Node> = NodeCast::from_ref(self); - node.ancestors() - .filter_map(|node| { - let e: Option<JSRef<Element>> = ElementCast::to_ref(node); - e - }) - .filter(|e| e.as_maybe_activatable().is_some()).next() - .map(|r| Temporary::from_rooted(r)) + for node in node.ancestors() { + let node = node.root(); + if let Some(node) = ElementCast::to_ref(node.r()) { + if node.as_maybe_activatable().is_some() { + return Some(Temporary::from_rooted(node)) + } + } + } + None } } } diff --git a/components/script/dom/event.rs b/components/script/dom/event.rs index aa588d09984..6c2e6fa860f 100644 --- a/components/script/dom/event.rs +++ b/components/script/dom/event.rs @@ -174,55 +174,67 @@ impl Event { } impl<'a> EventMethods for JSRef<'a, Event> { + // https://dom.spec.whatwg.org/#dom-event-eventphase fn EventPhase(self) -> u16 { self.phase.get() as u16 } + // https://dom.spec.whatwg.org/#dom-event-type fn Type(self) -> DOMString { // FIXME(https://github.com/rust-lang/rust/issues/23338) let type_ = self.type_.borrow(); type_.clone() } + // https://dom.spec.whatwg.org/#dom-event-target fn GetTarget(self) -> Option<Temporary<EventTarget>> { self.target.get() } + // https://dom.spec.whatwg.org/#dom-event-currenttarget fn GetCurrentTarget(self) -> Option<Temporary<EventTarget>> { self.current_target.get() } + // https://dom.spec.whatwg.org/#dom-event-defaultprevented fn DefaultPrevented(self) -> bool { self.canceled.get() } + // https://dom.spec.whatwg.org/#dom-event-preventdefault fn PreventDefault(self) { if self.cancelable.get() { self.canceled.set(true) } } + // https://dom.spec.whatwg.org/#dom-event-stoppropagation fn StopPropagation(self) { self.stop_propagation.set(true); } + // https://dom.spec.whatwg.org/#dom-event-stopimmediatepropagation fn StopImmediatePropagation(self) { self.stop_immediate.set(true); self.stop_propagation.set(true); } + // https://dom.spec.whatwg.org/#dom-event-bubbles fn Bubbles(self) -> bool { self.bubbles.get() } + // https://dom.spec.whatwg.org/#dom-event-cancelable fn Cancelable(self) -> bool { self.cancelable.get() } + // https://dom.spec.whatwg.org/#dom-event-timestamp fn TimeStamp(self) -> u64 { self.timestamp } + // https://dom.spec.whatwg.org/#dom-event-initevent fn InitEvent(self, type_: DOMString, bubbles: bool, @@ -242,6 +254,7 @@ impl<'a> EventMethods for JSRef<'a, Event> { self.cancelable.set(cancelable); } + // https://dom.spec.whatwg.org/#dom-event-istrusted fn IsTrusted(self) -> bool { self.trusted.get() } diff --git a/components/script/dom/eventdispatcher.rs b/components/script/dom/eventdispatcher.rs index 029691f702c..6882c3f4599 100644 --- a/components/script/dom/eventdispatcher.rs +++ b/components/script/dom/eventdispatcher.rs @@ -31,7 +31,8 @@ pub fn dispatch_event<'a, 'b>(target: JSRef<'a, EventTarget>, let mut chain: RootedVec<JS<EventTarget>> = RootedVec::new(); if let Some(target_node) = NodeCast::to_ref(target) { for ancestor in target_node.ancestors() { - let ancestor_target: JSRef<EventTarget> = EventTargetCast::from_ref(ancestor); + let ancestor = ancestor.root(); + let ancestor_target = EventTargetCast::from_ref(ancestor.r()); chain.push(JS::from_rooted(ancestor_target)) } } diff --git a/components/script/dom/htmlbuttonelement.rs b/components/script/dom/htmlbuttonelement.rs index 7355b1448b1..b79c966d993 100644 --- a/components/script/dom/htmlbuttonelement.rs +++ b/components/script/dom/htmlbuttonelement.rs @@ -162,7 +162,7 @@ impl<'a> VirtualMethods for JSRef<'a, HTMLButtonElement> { } let node: JSRef<Node> = NodeCast::from_ref(*self); - if node.ancestors().any(|ancestor| ancestor.is_htmlfieldsetelement()) { + if node.ancestors().any(|ancestor| ancestor.root().r().is_htmlfieldsetelement()) { node.check_ancestors_disabled_state_for_form_control(); } else { node.check_disabled_attribute(); @@ -225,12 +225,10 @@ impl<'a> Activatable for JSRef<'a, HTMLButtonElement> { // and only then performing actions which may modify the DOM tree unsafe { node.query_selector_iter("button[type=submit]".to_owned()).unwrap() - .filter_map(|t| { - let h: Option<JSRef<HTMLButtonElement>> = HTMLButtonElementCast::to_ref(t); - h - }) - .find(|r| r.form_owner() == owner) - .map(|s| s.synthetic_click_activation(ctrlKey, shiftKey, altKey, metaKey)); + .filter_map(HTMLButtonElementCast::to_temporary) + .map(|t| t.root()) + .find(|r| r.r().form_owner() == owner) + .map(|s| s.r().synthetic_click_activation(ctrlKey, shiftKey, altKey, metaKey)); } } } diff --git a/components/script/dom/htmlcollection.rs b/components/script/dom/htmlcollection.rs index a6e216cffdb..a2cf605bc7e 100644 --- a/components/script/dom/htmlcollection.rs +++ b/components/script/dom/htmlcollection.rs @@ -164,12 +164,13 @@ impl HTMLCollection { HTMLCollection::create(window, root, box ElementChildFilter) } - fn traverse<'a>(root: JSRef<'a, Node>) - -> FilterMap<Skip<TreeIterator<'a>>, - fn(JSRef<Node>) -> Option<JSRef<Element>>> { + fn traverse(root: JSRef<Node>) + -> FilterMap<Skip<TreeIterator>, + fn(Temporary<Node>) -> Option<Temporary<Element>>> { root.traverse_preorder() .skip(1) - .filter_map(ElementCast::to_ref as fn(JSRef<Node>) -> Option<JSRef<Element>>) + .filter_map(ElementCast::to_temporary as + fn(Temporary<Node>) -> Option<Temporary<Element>>) } } @@ -181,7 +182,7 @@ impl<'a> HTMLCollectionMethods for JSRef<'a, HTMLCollection> { CollectionTypeId::Live(ref root, ref filter) => { let root = root.root(); HTMLCollection::traverse(root.r()) - .filter(|element| filter.filter(*element, root.r())) + .filter(|element| filter.filter(element.root().r(), root.r())) .count() as u32 } } @@ -198,10 +199,8 @@ impl<'a> HTMLCollectionMethods for JSRef<'a, HTMLCollection> { CollectionTypeId::Live(ref root, ref filter) => { let root = root.root(); HTMLCollection::traverse(root.r()) - .filter(|element| filter.filter(*element, root.r())) + .filter(|element| filter.filter(element.root().r(), root.r())) .nth(index) - .clone() - .map(Temporary::from_rooted) } } } @@ -224,11 +223,12 @@ impl<'a> HTMLCollectionMethods for JSRef<'a, HTMLCollection> { CollectionTypeId::Live(ref root, ref filter) => { let root = root.root(); HTMLCollection::traverse(root.r()) - .filter(|element| filter.filter(*element, root.r())) + .map(|element| element.root()) + .filter(|element| filter.filter(element.r(), root.r())) .find(|elem| { - elem.get_string_attribute(&atom!("name")) == key || - elem.get_string_attribute(&atom!("id")) == key }) - .map(Temporary::from_rooted) + elem.r().get_string_attribute(&atom!("name")) == key || + elem.r().get_string_attribute(&atom!("id")) == key }) + .map(|elem| Temporary::from_rooted(elem.r())) } } } diff --git a/components/script/dom/htmlfieldsetelement.rs b/components/script/dom/htmlfieldsetelement.rs index 39088810eab..e3b8397e4bc 100644 --- a/components/script/dom/htmlfieldsetelement.rs +++ b/components/script/dom/htmlfieldsetelement.rs @@ -104,13 +104,14 @@ impl<'a> VirtualMethods for JSRef<'a, HTMLFieldSetElement> { } for descendant in child.r().traverse_preorder() { - match descendant.type_id() { + let descendant = descendant.root(); + match descendant.r().type_id() { NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLButtonElement)) | NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLInputElement)) | NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLSelectElement)) | NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTextAreaElement)) => { - descendant.set_disabled_state(true); - descendant.set_enabled_state(false); + descendant.r().set_disabled_state(true); + descendant.r().set_enabled_state(false); }, _ => () } @@ -142,13 +143,14 @@ impl<'a> VirtualMethods for JSRef<'a, HTMLFieldSetElement> { } for descendant in child.r().traverse_preorder() { - match descendant.type_id() { + let descendant = descendant.root(); + match descendant.r().type_id() { NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLButtonElement)) | NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLInputElement)) | NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLSelectElement)) | NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTextAreaElement)) => { - descendant.check_disabled_attribute(); - descendant.check_ancestors_disabled_state_for_form_control(); + descendant.r().check_disabled_attribute(); + descendant.r().check_ancestors_disabled_state_for_form_control(); }, _ => () } diff --git a/components/script/dom/htmlformelement.rs b/components/script/dom/htmlformelement.rs index ebe033cea37..7bd9925ffd2 100644 --- a/components/script/dom/htmlformelement.rs +++ b/components/script/dom/htmlformelement.rs @@ -20,7 +20,7 @@ use dom::element::ElementTypeId; use dom::htmlelement::{HTMLElement, HTMLElementTypeId}; use dom::htmlinputelement::{HTMLInputElement, HTMLInputElementHelpers}; use dom::htmlbuttonelement::{HTMLButtonElement}; -use dom::htmltextareaelement::{HTMLTextAreaElement, HTMLTextAreaElementHelpers}; +use dom::htmltextareaelement::HTMLTextAreaElementHelpers; use dom::node::{Node, NodeHelpers, NodeTypeId, document_from_node, window_from_node}; use hyper::method::Method; use hyper::header::ContentType; @@ -255,16 +255,18 @@ impl<'a> HTMLFormElementHelpers for JSRef<'a, HTMLFormElement> { // TODO: This is an incorrect way of getting controls owned // by the form, but good enough until html5ever lands let data_set = node.traverse_preorder().filter_map(|child| { - if child.get_disabled_state() { + let child = child.root(); + if child.r().get_disabled_state() { return None; } - if child.ancestors().any(|a| a.type_id() == NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLDataListElement))) { + if child.r().ancestors() + .any(|a| a.root().r().type_id() == NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLDataListElement))) { return None; } // XXXManishearth don't include it if it is a button but not the submitter - match child.type_id() { + match child.r().type_id() { NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLInputElement)) => { - let input: JSRef<HTMLInputElement> = HTMLInputElementCast::to_ref(child).unwrap(); + let input = HTMLInputElementCast::to_ref(child.r()).unwrap(); let ty = input.Type(); let name = input.Name(); match ty.as_slice() { @@ -368,10 +370,10 @@ impl<'a> HTMLFormElementHelpers for JSRef<'a, HTMLFormElement> { // TODO: This is an incorrect way of getting controls owned // by the form, but good enough until html5ever lands for child in node.traverse_preorder() { - match child.type_id() { + let child = child.root(); + match child.r().type_id() { NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLInputElement)) => { - let input: JSRef<HTMLInputElement> = HTMLInputElementCast::to_ref(child) - .unwrap(); + let input = HTMLInputElementCast::to_ref(child.r()).unwrap(); input.reset() } // TODO HTMLKeygenElement unimplemented @@ -384,8 +386,7 @@ impl<'a> HTMLFormElementHelpers for JSRef<'a, HTMLFormElement> { {} } NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTextAreaElement)) => { - let textarea: JSRef<HTMLTextAreaElement> = HTMLTextAreaElementCast::to_ref(child) - .unwrap(); + let textarea = HTMLTextAreaElementCast::to_ref(child.r()).unwrap(); textarea.reset() } NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLOutputElement)) => { @@ -531,8 +532,13 @@ pub trait FormControl<'a> : Copy + Sized { } } let node: JSRef<Node> = NodeCast::from_ref(elem); - node.ancestors().filter_map(|a| HTMLFormElementCast::to_ref(a)).next() - .map(Temporary::from_rooted) + for ancestor in node.ancestors() { + let ancestor = ancestor.root(); + if let Some(ancestor) = HTMLFormElementCast::to_ref(ancestor.r()) { + return Some(Temporary::from_rooted(ancestor)) + } + } + None } fn get_form_attribute<InputFn, OwnerFn>(self, diff --git a/components/script/dom/htmlimageelement.rs b/components/script/dom/htmlimageelement.rs index dadcd776238..f93d525f1fc 100644 --- a/components/script/dom/htmlimageelement.rs +++ b/components/script/dom/htmlimageelement.rs @@ -39,6 +39,16 @@ impl HTMLImageElementDerived for EventTarget { } } +pub trait HTMLImageElementHelpers { + fn get_url(&self) -> Option<Url>; +} + +impl<'a> HTMLImageElementHelpers for JSRef<'a, HTMLImageElement> { + fn get_url(&self) -> Option<Url>{ + self.image.borrow().clone() + } +} + trait PrivateHTMLImageElementHelpers { fn update_image(self, value: Option<(DOMString, &Url)>); } diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs index 3748ce9bac5..5bd170ebea7 100644 --- a/components/script/dom/htmlinputelement.rs +++ b/components/script/dom/htmlinputelement.rs @@ -346,12 +346,13 @@ fn broadcast_radio_checked(broadcaster: JSRef<HTMLInputElement>, group: Option<& // There is no DOM tree manipulation here, so this is safe let iter = unsafe { doc_node.query_selector_iter("input[type=radio]".to_owned()).unwrap() - .filter_map(|t| HTMLInputElementCast::to_ref(t)) - .filter(|&r| in_same_group(r, owner, group) && broadcaster != r) + .filter_map(HTMLInputElementCast::to_temporary) + .map(|t| t.root()) + .filter(|r| in_same_group(r.r(), owner, group) && broadcaster != r.r()) }; for r in iter { - if r.Checked() { - r.SetChecked(false); + if r.r().Checked() { + r.r().SetChecked(false); } } } @@ -583,7 +584,7 @@ impl<'a> VirtualMethods for JSRef<'a, HTMLInputElement> { } let node: JSRef<Node> = NodeCast::from_ref(*self); - if node.ancestors().any(|ancestor| ancestor.is_htmlfieldsetelement()) { + if node.ancestors().any(|ancestor| ancestor.root().r().is_htmlfieldsetelement()) { node.check_ancestors_disabled_state_for_form_control(); } else { node.check_disabled_attribute(); @@ -684,12 +685,14 @@ impl<'a> Activatable for JSRef<'a, HTMLInputElement> { // Safe since we only manipulate the DOM tree after finding an element let checked_member = unsafe { doc_node.query_selector_iter("input[type=radio]".to_owned()).unwrap() - .filter_map(|t| HTMLInputElementCast::to_ref(t)) - .filter(|&r| in_same_group(r, owner.r(), - group.as_ref().map(|gr| gr.as_slice()))) - .find(|r| r.Checked()) + .filter_map(HTMLInputElementCast::to_temporary) + .map(|t| t.root()) + .find(|r| { + in_same_group(r.r(), owner.r(), group.as_ref().map(|gr| gr.as_slice())) && + r.r().Checked() + }) }; - cache.checked_radio.assign(checked_member); + cache.checked_radio.assign(checked_member.r()); cache.checked_changed = self.checked_changed.get(); self.SetChecked(true); } @@ -815,12 +818,10 @@ impl<'a> Activatable for JSRef<'a, HTMLInputElement> { // and only then performing actions which may modify the DOM tree unsafe { node.query_selector_iter("input[type=submit]".to_owned()).unwrap() - .filter_map(|t| { - let h: Option<JSRef<HTMLInputElement>> = HTMLInputElementCast::to_ref(t); - h - }) - .find(|r| r.form_owner() == owner) - .map(|s| s.synthetic_click_activation(ctrlKey, shiftKey, altKey, metaKey)); + .filter_map(HTMLInputElementCast::to_temporary) + .map(|t| t.root()) + .find(|r| r.r().form_owner() == owner) + .map(|s| s.r().synthetic_click_activation(ctrlKey, shiftKey, altKey, metaKey)); } } } diff --git a/components/script/dom/htmlscriptelement.rs b/components/script/dom/htmlscriptelement.rs index 7b4f7a8aea0..49cf5f257ed 100644 --- a/components/script/dom/htmlscriptelement.rs +++ b/components/script/dom/htmlscriptelement.rs @@ -524,8 +524,7 @@ impl<'a> HTMLScriptElementMethods for JSRef<'a, HTMLScriptElement> { // http://www.whatwg.org/html/#dom-script-text fn Text(self) -> DOMString { - let node: JSRef<Node> = NodeCast::from_ref(self); - Node::collect_text_contents(node.children().map(|c| c.root()).map(|c| c.get_unsound_ref_forever())) + Node::collect_text_contents(NodeCast::from_ref(self).children()) } // http://www.whatwg.org/html/#dom-script-text diff --git a/components/script/dom/htmlselectelement.rs b/components/script/dom/htmlselectelement.rs index 14459ca841b..12f88281eba 100644 --- a/components/script/dom/htmlselectelement.rs +++ b/components/script/dom/htmlselectelement.rs @@ -129,7 +129,7 @@ impl<'a> VirtualMethods for JSRef<'a, HTMLSelectElement> { } let node: JSRef<Node> = NodeCast::from_ref(*self); - if node.ancestors().any(|ancestor| ancestor.is_htmlfieldsetelement()) { + if node.ancestors().any(|ancestor| ancestor.root().r().is_htmlfieldsetelement()) { node.check_ancestors_disabled_state_for_form_control(); } else { node.check_disabled_attribute(); diff --git a/components/script/dom/htmltextareaelement.rs b/components/script/dom/htmltextareaelement.rs index 2b53e09a1a2..f45bedd791d 100644 --- a/components/script/dom/htmltextareaelement.rs +++ b/components/script/dom/htmltextareaelement.rs @@ -319,7 +319,7 @@ impl<'a> VirtualMethods for JSRef<'a, HTMLTextAreaElement> { } let node: JSRef<Node> = NodeCast::from_ref(*self); - if node.ancestors().any(|ancestor| ancestor.is_htmlfieldsetelement()) { + if node.ancestors().any(|ancestor| ancestor.root().r().is_htmlfieldsetelement()) { node.check_ancestors_disabled_state_for_form_control(); } else { node.check_disabled_attribute(); diff --git a/components/script/dom/htmltitleelement.rs b/components/script/dom/htmltitleelement.rs index df2592acef9..5fb9cd3b25f 100644 --- a/components/script/dom/htmltitleelement.rs +++ b/components/script/dom/htmltitleelement.rs @@ -6,8 +6,9 @@ use dom::bindings::codegen::Bindings::HTMLTitleElementBinding; use dom::bindings::codegen::Bindings::HTMLTitleElementBinding::HTMLTitleElementMethods; use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; use dom::bindings::codegen::InheritTypes::{HTMLElementCast, HTMLTitleElementDerived, NodeCast}; -use dom::bindings::codegen::InheritTypes::{TextCast}; +use dom::bindings::codegen::InheritTypes::{CharacterDataCast, TextCast}; use dom::bindings::js::{JSRef, Temporary}; +use dom::characterdata::CharacterDataHelpers; use dom::document::{Document, DocumentHelpers}; use dom::eventtarget::{EventTarget, EventTargetTypeId}; use dom::element::ElementTypeId; @@ -51,7 +52,7 @@ impl<'a> HTMLTitleElementMethods for JSRef<'a, HTMLTitleElement> { let child = child.root(); let text: Option<JSRef<Text>> = TextCast::to_ref(child.r()); match text { - Some(text) => content.push_str(text.characterdata().data().as_slice()), + Some(text) => content.push_str(&CharacterDataCast::from_ref(text).data()), None => (), } } diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs index d1dd72a30b6..6c7204f7765 100644 --- a/components/script/dom/mod.rs +++ b/components/script/dom/mod.rs @@ -315,6 +315,7 @@ pub mod servohtmlparser; pub mod storage; pub mod storageevent; pub mod text; +pub mod textdecoder; pub mod textencoder; pub mod treewalker; pub mod uievent; diff --git a/components/script/dom/namednodemap.rs b/components/script/dom/namednodemap.rs index bfa8f1652f1..50b24aafcc9 100644 --- a/components/script/dom/namednodemap.rs +++ b/components/script/dom/namednodemap.rs @@ -5,11 +5,16 @@ use dom::attr::Attr; use dom::bindings::codegen::Bindings::NamedNodeMapBinding; use dom::bindings::codegen::Bindings::NamedNodeMapBinding::NamedNodeMapMethods; +use dom::bindings::error::{Error, Fallible}; use dom::bindings::global::GlobalRef; use dom::bindings::js::{JS, JSRef, Temporary}; use dom::bindings::utils::{Reflector, reflect_dom_object}; -use dom::element::{Element, ElementHelpers}; +use dom::element::{AttributeHandlers, Element, ElementHelpers}; use dom::window::Window; +use util::namespace; +use util::str::DOMString; + +use string_cache::Atom; #[dom_struct] pub struct NamedNodeMap { @@ -32,6 +37,7 @@ impl NamedNodeMap { } impl<'a> NamedNodeMapMethods for JSRef<'a, NamedNodeMap> { + // https://dom.spec.whatwg.org/#dom-namednodemap-length fn Length(self) -> u32 { let owner = self.owner.root(); // FIXME(https://github.com/rust-lang/rust/issues/23338) @@ -40,6 +46,7 @@ impl<'a> NamedNodeMapMethods for JSRef<'a, NamedNodeMap> { attrs.len() as u32 } + // https://dom.spec.whatwg.org/#dom-namednodemap-item fn Item(self, index: u32) -> Option<Temporary<Attr>> { let owner = self.owner.root(); // FIXME(https://github.com/rust-lang/rust/issues/23338) @@ -48,10 +55,54 @@ impl<'a> NamedNodeMapMethods for JSRef<'a, NamedNodeMap> { attrs.as_slice().get(index as usize).map(|x| Temporary::new(x.clone())) } + // https://dom.spec.whatwg.org/#dom-namednodemap-getnameditem + fn GetNamedItem(self, name: DOMString) -> Option<Temporary<Attr>> { + let owner = self.owner.root(); + // FIXME(https://github.com/rust-lang/rust/issues/23338) + let owner = owner.r(); + let name = owner.parsed_name(name); + owner.get_attribute_by_name(&Atom::from_slice(&name)) + } + + // https://dom.spec.whatwg.org/#dom-namednodemap-getnameditemns + fn GetNamedItemNS(self, namespace: Option<DOMString>, local_name: DOMString) + -> Option<Temporary<Attr>> { + let owner = self.owner.root(); + // FIXME(https://github.com/rust-lang/rust/issues/23338) + let owner = owner.r(); + let ns = namespace::from_domstring(namespace); + owner.get_attribute(&ns, &Atom::from_slice(&local_name)) + } + + // https://dom.spec.whatwg.org/#dom-namednodemap-removenameditem + fn RemoveNamedItem(self, name: DOMString) -> Fallible<Temporary<Attr>> { + let owner = self.owner.root(); + // FIXME(https://github.com/rust-lang/rust/issues/23338) + let owner = owner.r(); + let name = owner.parsed_name(name); + owner.remove_attribute_by_name(&Atom::from_slice(&name)).ok_or(Error::NotFound) + } + + // https://dom.spec.whatwg.org/#dom-namednodemap-removenameditemns + fn RemoveNamedItemNS(self, namespace: Option<DOMString>, local_name: DOMString) + -> Fallible<Temporary<Attr>> { + let owner = self.owner.root(); + // FIXME(https://github.com/rust-lang/rust/issues/23338) + let owner = owner.r(); + let ns = namespace::from_domstring(namespace); + owner.remove_attribute(&ns, &Atom::from_slice(&local_name)).ok_or(Error::NotFound) + } + fn IndexedGetter(self, index: u32, found: &mut bool) -> Option<Temporary<Attr>> { let item = self.Item(index); *found = item.is_some(); item } + + fn NamedGetter(self, name: DOMString, found: &mut bool) -> Option<Temporary<Attr>> { + let item = self.GetNamedItem(name); + *found = item.is_some(); + item + } } diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs index 7cd01875b15..6e96e560101 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -13,12 +13,11 @@ use dom::bindings::codegen::Bindings::NamedNodeMapBinding::NamedNodeMapMethods; use dom::bindings::codegen::Bindings::NodeBinding::{NodeConstants, NodeMethods}; use dom::bindings::codegen::Bindings::NodeListBinding::NodeListMethods; use dom::bindings::codegen::Bindings::ProcessingInstructionBinding::ProcessingInstructionMethods; -use dom::bindings::codegen::InheritTypes::{CommentCast, DocumentCast, DocumentTypeCast}; -use dom::bindings::codegen::InheritTypes::{ElementCast, TextCast, NodeCast, ElementDerived}; -use dom::bindings::codegen::InheritTypes::{CharacterDataCast, NodeBase, NodeDerived}; -use dom::bindings::codegen::InheritTypes::{ProcessingInstructionCast, EventTargetCast}; +use dom::bindings::codegen::InheritTypes::{CharacterDataCast, DocumentCast, DocumentTypeCast}; +use dom::bindings::codegen::InheritTypes::{ElementCast, NodeCast, ElementDerived, EventTargetCast}; use dom::bindings::codegen::InheritTypes::{HTMLLegendElementDerived, HTMLFieldSetElementDerived}; -use dom::bindings::codegen::InheritTypes::HTMLOptGroupElementDerived; +use dom::bindings::codegen::InheritTypes::{HTMLOptGroupElementDerived, NodeBase, NodeDerived}; +use dom::bindings::codegen::InheritTypes::{ProcessingInstructionCast, TextCast}; use dom::bindings::conversions; use dom::bindings::error::Fallible; use dom::bindings::error::Error::{NotFound, HierarchyRequest, Syntax}; @@ -29,7 +28,7 @@ use dom::bindings::js::{ResultRootable, OptionalRootable, MutNullableJS}; use dom::bindings::trace::JSTraceable; use dom::bindings::trace::RootedVec; use dom::bindings::utils::{Reflectable, reflect_dom_object}; -use dom::characterdata::CharacterData; +use dom::characterdata::{CharacterData, CharacterDataHelpers}; use dom::comment::Comment; use dom::document::{Document, DocumentHelpers, IsHTMLDocument, DocumentSource}; use dom::documentfragment::DocumentFragment; @@ -39,7 +38,7 @@ use dom::element::ElementHelpers; use dom::eventtarget::{EventTarget, EventTargetTypeId}; use dom::htmlelement::HTMLElementTypeId; use dom::nodelist::NodeList; -use dom::processinginstruction::ProcessingInstruction; +use dom::processinginstruction::{ProcessingInstruction, ProcessingInstructionHelpers}; use dom::text::Text; use dom::virtualmethods::{VirtualMethods, vtable_for}; use dom::window::{Window, WindowHelpers}; @@ -291,7 +290,8 @@ impl<'a> PrivateNodeHelpers for JSRef<'a, Node> { let is_in_doc = self.is_in_doc(); for node in self.traverse_preorder() { - vtable_for(&node).bind_to_tree(is_in_doc); + let node = node.root(); + vtable_for(&node.r()).bind_to_tree(is_in_doc); } let parent = self.parent_node().root(); @@ -303,7 +303,8 @@ impl<'a> PrivateNodeHelpers for JSRef<'a, Node> { fn node_removed(self, parent_in_doc: bool) { assert!(self.parent_node().is_none()); for node in self.traverse_preorder() { - vtable_for(&node).unbind_from_tree(parent_in_doc); + let node = node.root(); + vtable_for(&node.r()).unbind_from_tree(parent_in_doc); } self.layout_data.dispose(); } @@ -382,14 +383,15 @@ impl<'a> PrivateNodeHelpers for JSRef<'a, Node> { } } -pub struct QuerySelectorIterator<'a> { +pub struct QuerySelectorIterator { selectors: Vec<Selector>, - iterator: TreeIterator<'a>, + iterator: TreeIterator, } -impl<'a> QuerySelectorIterator<'a> { +impl<'a> QuerySelectorIterator { #[allow(unsafe_code)] - unsafe fn new(iter: TreeIterator<'a>, selectors: Vec<Selector>) -> QuerySelectorIterator<'a> { + unsafe fn new(iter: TreeIterator, selectors: Vec<Selector>) + -> QuerySelectorIterator { QuerySelectorIterator { selectors: selectors, iterator: iter, @@ -397,24 +399,28 @@ impl<'a> QuerySelectorIterator<'a> { } } -impl<'a> Iterator for QuerySelectorIterator<'a> { - type Item = JSRef<'a, Node>; +impl<'a> Iterator for QuerySelectorIterator { + type Item = Temporary<Node>; - fn next(&mut self) -> Option<JSRef<'a, Node>> { + fn next(&mut self) -> Option<Temporary<Node>> { let selectors = &self.selectors; // TODO(cgaebel): Is it worth it to build a bloom filter here // (instead of passing `None`)? Probably. - self.iterator.find(|node| node.is_element() && matches(selectors, node, &mut None)) + self.iterator.find(|node| { + let node = node.root(); + node.r().is_element() && matches(selectors, &node.r(), &mut None) + }) } } -pub trait NodeHelpers<'a> { - fn ancestors(self) -> AncestorIterator<'a>; - fn inclusive_ancestors(self) -> AncestorIterator<'a>; - fn children(self) -> NodeChildrenIterator; - fn rev_children(self) -> ReverseChildrenIterator; - fn child_elements(self) -> ChildElementIterator<'a>; - fn following_siblings(self) -> NodeChildrenIterator; +pub trait NodeHelpers { + fn ancestors(self) -> AncestorIterator; + fn inclusive_ancestors(self) -> AncestorIterator; + fn children(self) -> NodeSiblingIterator; + fn rev_children(self) -> ReverseSiblingIterator; + fn child_elements(self) -> ChildElementIterator; + fn following_siblings(self) -> NodeSiblingIterator; + fn preceding_siblings(self) -> ReverseSiblingIterator; fn is_in_doc(self) -> bool; fn is_inclusive_ancestor_of(self, parent: JSRef<Node>) -> bool; fn is_parent_of(self, child: JSRef<Node>) -> bool; @@ -484,8 +490,9 @@ pub trait NodeHelpers<'a> { fn dump_indent(self, indent: u32); fn debug_str(self) -> String; - fn traverse_preorder(self) -> TreeIterator<'a>; - fn inclusively_following_siblings(self) -> NodeChildrenIterator; + fn traverse_preorder(self) -> TreeIterator; + fn inclusively_following_siblings(self) -> NodeSiblingIterator; + fn inclusively_preceding_siblings(self) -> ReverseSiblingIterator; fn to_trusted_node_address(self) -> TrustedNodeAddress; @@ -494,7 +501,7 @@ pub trait NodeHelpers<'a> { fn query_selector(self, selectors: DOMString) -> Fallible<Option<Temporary<Element>>>; #[allow(unsafe_code)] - unsafe fn query_selector_iter(self, selectors: DOMString) -> Fallible<QuerySelectorIterator<'a>>; + unsafe fn query_selector_iter(self, selectors: DOMString) -> Fallible<QuerySelectorIterator>; fn query_selector_all(self, selectors: DOMString) -> Fallible<Temporary<NodeList>>; fn remove_self(self); @@ -507,7 +514,7 @@ pub trait NodeHelpers<'a> { fn parse_fragment(self, markup: DOMString) -> Fallible<Temporary<DocumentFragment>>; } -impl<'a> NodeHelpers<'a> for JSRef<'a, Node> { +impl<'a> NodeHelpers for JSRef<'a, Node> { fn teardown(self) { self.layout_data.dispose(); for kid in self.children() { @@ -623,7 +630,8 @@ impl<'a> NodeHelpers<'a> for JSRef<'a, Node> { } fn set_hover_state(self, state: bool) { - self.set_flag(IN_HOVER_STATE, state) + self.set_flag(IN_HOVER_STATE, state); + self.dirty(NodeDamage::OtherNodeDamage); } fn get_focus_state(self) -> bool { @@ -631,7 +639,8 @@ impl<'a> NodeHelpers<'a> for JSRef<'a, Node> { } fn set_focus_state(self, state: bool) { - self.set_flag(IN_FOCUS_STATE, state) + self.set_flag(IN_FOCUS_STATE, state); + self.dirty(NodeDamage::OtherNodeDamage); } fn get_disabled_state(self) -> bool { @@ -735,32 +744,45 @@ impl<'a> NodeHelpers<'a> for JSRef<'a, Node> { // 4. Dirty ancestors. for ancestor in self.ancestors() { - if !force_ancestors && ancestor.get_has_dirty_descendants() { break } - ancestor.set_has_dirty_descendants(true); + let ancestor = ancestor.root(); + if !force_ancestors && ancestor.r().get_has_dirty_descendants() { break } + ancestor.r().set_has_dirty_descendants(true); } } /// Iterates over this node and all its descendants, in preorder. - fn traverse_preorder(self) -> TreeIterator<'a> { + fn traverse_preorder(self) -> TreeIterator { TreeIterator::new(self) } - fn inclusively_following_siblings(self) -> NodeChildrenIterator { - NodeChildrenIterator { + fn inclusively_following_siblings(self) -> NodeSiblingIterator { + NodeSiblingIterator { + current: Some(Temporary::from_rooted(self)), + } + } + + fn inclusively_preceding_siblings(self) -> ReverseSiblingIterator { + ReverseSiblingIterator { current: Some(Temporary::from_rooted(self)), } } fn is_inclusive_ancestor_of(self, parent: JSRef<Node>) -> bool { - self == parent || parent.ancestors().any(|ancestor| ancestor == self) + self == parent || parent.ancestors().any(|ancestor| ancestor.root().r() == self) } - fn following_siblings(self) -> NodeChildrenIterator { - NodeChildrenIterator { + fn following_siblings(self) -> NodeSiblingIterator { + NodeSiblingIterator { current: self.next_sibling(), } } + fn preceding_siblings(self) -> ReverseSiblingIterator { + ReverseSiblingIterator { + current: self.prev_sibling(), + } + } + fn is_parent_of(self, child: JSRef<Node>) -> bool { match child.parent_node() { Some(ref parent) if parent == &Temporary::from_rooted(self) => true, @@ -788,11 +810,11 @@ impl<'a> NodeHelpers<'a> for JSRef<'a, Node> { Err(()) => return Err(Syntax), // Step 3. Ok(ref selectors) => { - let root = self.ancestors().last().unwrap_or(self.clone()); - Ok(root.traverse_preorder() - .filter_map(ElementCast::to_ref) - .find(|element| matches(selectors, &NodeCast::from_ref(*element), &mut None)) - .map(Temporary::from_rooted)) + let root = self.ancestors().last().root(); + let root = root.r().unwrap_or(self.clone()); + Ok(root.traverse_preorder().filter_map(ElementCast::to_temporary).find(|element| { + matches(selectors, &NodeCast::from_ref(element.root().r()), &mut None) + })) } } } @@ -802,10 +824,12 @@ impl<'a> NodeHelpers<'a> for JSRef<'a, Node> { /// the iterator may be invalidated #[allow(unsafe_code)] unsafe fn query_selector_iter(self, selectors: DOMString) - -> Fallible<QuerySelectorIterator<'a>> { + -> Fallible<QuerySelectorIterator> { // Step 1. let nodes; - let root = self.ancestors().last().unwrap_or(self.clone()); + let root = self.ancestors().last().root() + .map(|node| node.get_unsound_ref_forever()) + .unwrap_or(self.clone()); match parse_author_origin_selector_list_from_str(selectors.as_slice()) { // Step 2. Err(()) => return Err(Syntax), @@ -820,25 +844,24 @@ impl<'a> NodeHelpers<'a> for JSRef<'a, Node> { // http://dom.spec.whatwg.org/#dom-parentnode-queryselectorall #[allow(unsafe_code)] fn query_selector_all(self, selectors: DOMString) -> Fallible<Temporary<NodeList>> { - // Step 1. - unsafe { - self.query_selector_iter(selectors).map(|iter| { - let window = window_from_node(self).root(); - NodeList::new_simple_list(window.r(), iter.collect()) - }) + let mut nodes = RootedVec::new(); + for node in try!(unsafe { self.query_selector_iter(selectors) }) { + nodes.push(JS::from_rooted(node)); } + let window = window_from_node(self).root(); + Ok(NodeList::new_simple_list(window.r(), &nodes)) } - fn ancestors(self) -> AncestorIterator<'a> { + fn ancestors(self) -> AncestorIterator { AncestorIterator { - current: self.parent_node.get().map(|node| node.root().get_unsound_ref_forever()), + current: self.parent_node() } } - fn inclusive_ancestors(self) -> AncestorIterator<'a> { + fn inclusive_ancestors(self) -> AncestorIterator { AncestorIterator { - current: Some(self.clone()) + current: Some(Temporary::from_rooted(self)) } } @@ -854,26 +877,21 @@ impl<'a> NodeHelpers<'a> for JSRef<'a, Node> { self.owner_doc().root().r().is_html_document() } - fn children(self) -> NodeChildrenIterator { - NodeChildrenIterator { + fn children(self) -> NodeSiblingIterator { + NodeSiblingIterator { current: self.first_child.get(), } } - fn rev_children(self) -> ReverseChildrenIterator { - ReverseChildrenIterator { - current: self.last_child.get().root(), + fn rev_children(self) -> ReverseSiblingIterator { + ReverseSiblingIterator { + current: self.last_child(), } } - fn child_elements(self) -> ChildElementIterator<'a> { - fn cast<'a>(n: Temporary<Node>) -> Option<JSRef<'a, Element>> { - let n = n.root(); - ElementCast::to_ref(n.get_unsound_ref_forever()) - } - + fn child_elements(self) -> ChildElementIterator { self.children() - .filter_map(cast as fn(_) -> _) + .filter_map(ElementCast::to_temporary as fn(_) -> _) .peekable() } @@ -902,7 +920,7 @@ impl<'a> NodeHelpers<'a> for JSRef<'a, Node> { uniqueId: unique_id.clone(), baseURI: self.GetBaseURI().unwrap_or("".to_owned()), parent: self.GetParentNode().root().map(|node| node.r().get_unique_id()).unwrap_or("".to_owned()), - nodeType: self.NodeType() as usize, + nodeType: self.NodeType(), namespaceURI: "".to_owned(), //FIXME nodeName: self.NodeName(), numChildren: self.ChildNodes().root().r().Length() as usize, @@ -1110,15 +1128,15 @@ impl RawLayoutNodeHelpers for Node { // Iteration and traversal // -pub type ChildElementIterator<'a> = - Peekable<FilterMap<NodeChildrenIterator, - fn(Temporary<Node>) -> Option<JSRef<'a, Element>>>>; +pub type ChildElementIterator = + Peekable<FilterMap<NodeSiblingIterator, + fn(Temporary<Node>) -> Option<Temporary<Element>>>>; -pub struct NodeChildrenIterator { +pub struct NodeSiblingIterator { current: Option<Temporary<Node>>, } -impl Iterator for NodeChildrenIterator { +impl Iterator for NodeSiblingIterator { type Item = Temporary<Node>; fn next(&mut self) -> Option<Temporary<Node>> { @@ -1131,58 +1149,82 @@ impl Iterator for NodeChildrenIterator { } } -pub struct ReverseChildrenIterator { - current: Option<Root<Node>>, +pub struct ReverseSiblingIterator { + current: Option<Temporary<Node>>, } -impl Iterator for ReverseChildrenIterator { +impl Iterator for ReverseSiblingIterator { type Item = Temporary<Node>; fn next(&mut self) -> Option<Temporary<Node>> { - let node = self.current.r().map(Temporary::from_rooted); - self.current = self.current.take().and_then(|node| node.r().prev_sibling()).root(); - node + let current = match self.current.take() { + None => return None, + Some(current) => current, + }.root(); + self.current = current.r().prev_sibling(); + Some(Temporary::from_rooted(current.r())) } } -pub struct AncestorIterator<'a> { - current: Option<JSRef<'a, Node>>, +pub struct AncestorIterator { + current: Option<Temporary<Node>>, } -impl<'a> Iterator for AncestorIterator<'a> { - type Item = JSRef<'a, Node>; +impl Iterator for AncestorIterator { + type Item = Temporary<Node>; - fn next(&mut self) -> Option<JSRef<'a, Node>> { - let node = self.current; - self.current = node.and_then(|node| node.parent_node().map(|node| node.root().get_unsound_ref_forever())); - node + fn next(&mut self) -> Option<Temporary<Node>> { + let current = match self.current.take() { + None => return None, + Some(current) => current, + }.root(); + self.current = current.r().parent_node(); + Some(Temporary::from_rooted(current.r())) } } -pub struct TreeIterator<'a> { - stack: Vec<JSRef<'a, Node>>, +pub struct TreeIterator { + current: Option<Temporary<Node>>, + depth: usize, } -impl<'a> TreeIterator<'a> { - fn new(root: JSRef<'a, Node>) -> TreeIterator<'a> { - let mut stack = vec!(); - stack.push(root); - +impl<'a> TreeIterator { + fn new(root: JSRef<'a, Node>) -> TreeIterator { TreeIterator { - stack: stack, + current: Some(Temporary::from_rooted(root)), + depth: 0, } } } -impl<'a> Iterator for TreeIterator<'a> { - type Item = JSRef<'a, Node>; +impl Iterator for TreeIterator { + type Item = Temporary<Node>; - fn next(&mut self) -> Option<JSRef<'a, Node>> { - let ret = self.stack.pop(); - ret.map(|node| { - self.stack.extend(node.rev_children().map(|c| c.root().get_unsound_ref_forever())) - }); - ret + // https://dom.spec.whatwg.org/#concept-tree-order + fn next(&mut self) -> Option<Temporary<Node>> { + let current = match self.current.take() { + None => return None, + Some(current) => current, + }; + let node = current.root(); + if let Some(first_child) = node.r().first_child() { + self.current = Some(first_child); + self.depth += 1; + return Some(current); + }; + for ancestor in node.r().inclusive_ancestors() { + if self.depth == 0 { + break; + } + if let Some(next_sibling) = ancestor.root().r().next_sibling() { + self.current = Some(next_sibling); + return Some(current); + } + self.depth -= 1; + } + debug_assert!(self.depth == 0); + self.current = None; + Some(current) } } @@ -1264,7 +1306,7 @@ impl Node { let node_doc = document_from_node(node).root(); if node_doc.r() != document { for descendant in node.traverse_preorder() { - descendant.set_owner_doc(document); + descendant.root().r().set_owner_doc(document); } } @@ -1419,13 +1461,14 @@ impl Node { parent.add_child(node, child); let is_in_doc = parent.is_in_doc(); for kid in node.traverse_preorder() { - let mut flags = kid.flags.get(); + let kid = kid.root(); + let mut flags = kid.r().flags.get(); if is_in_doc { flags.insert(IS_IN_DOC); } else { flags.remove(IS_IN_DOC); } - kid.flags.set(flags); + kid.r().flags.set(flags); } } @@ -1587,8 +1630,8 @@ impl Node { NodeCast::from_temporary(doc_fragment) }, NodeTypeId::Comment => { - let comment: JSRef<Comment> = CommentCast::to_ref(node).unwrap(); - let comment = Comment::new(comment.characterdata().data().clone(), document.r()); + let cdata = CharacterDataCast::to_ref(node).unwrap(); + let comment = Comment::new(cdata.Data(), document.r()); NodeCast::from_temporary(comment) }, NodeTypeId::Document => { @@ -1615,14 +1658,14 @@ impl Node { NodeCast::from_temporary(element) }, NodeTypeId::Text => { - let text: JSRef<Text> = TextCast::to_ref(node).unwrap(); - let text = Text::new(text.characterdata().data().clone(), document.r()); + let cdata = CharacterDataCast::to_ref(node).unwrap(); + let text = Text::new(cdata.Data(), document.r()); NodeCast::from_temporary(text) }, NodeTypeId::ProcessingInstruction => { let pi: JSRef<ProcessingInstruction> = ProcessingInstructionCast::to_ref(node).unwrap(); - let pi = ProcessingInstruction::new(pi.target().clone(), - pi.characterdata().data().clone(), document.r()); + let pi = ProcessingInstruction::new(pi.Target(), + CharacterDataCast::from_ref(pi).Data(), document.r()); NodeCast::from_temporary(pi) }, }.root(); @@ -1676,12 +1719,13 @@ impl Node { Temporary::from_rooted(copy.r()) } - pub fn collect_text_contents<'a, T: Iterator<Item=JSRef<'a, Node>>>(iterator: T) -> String { + pub fn collect_text_contents<T: Iterator<Item=Temporary<Node>>>(iterator: T) -> String { let mut content = String::new(); for node in iterator { - let text: Option<JSRef<Text>> = TextCast::to_ref(node); + let node = node.root(); + let text = TextCast::to_ref(node.r()); match text { - Some(text) => content.push_str(text.characterdata().data().as_slice()), + Some(text) => content.push_str(&CharacterDataCast::from_ref(text).Data()), None => (), } } @@ -1864,7 +1908,7 @@ impl<'a> NodeMethods for JSRef<'a, Node> { NodeTypeId::Text | NodeTypeId::ProcessingInstruction => { let characterdata: JSRef<CharacterData> = CharacterDataCast::to_ref(self).unwrap(); - characterdata.set_data(value); + characterdata.SetData(value); // Notify the document that the content of this node is different let document = self.owner_doc().root(); @@ -1936,7 +1980,9 @@ impl<'a> NodeMethods for JSRef<'a, Node> { 0 => (), // Step 6.1.2 1 => { - if self.child_elements().any(|c| NodeCast::from_ref(c) != child) { + if self.child_elements() + .map(|c| c.root()) + .any(|c| NodeCast::from_ref(c.r()) != child) { return Err(HierarchyRequest); } if child.following_siblings() @@ -1952,8 +1998,8 @@ impl<'a> NodeMethods for JSRef<'a, Node> { // Step 6.2 NodeTypeId::Element(..) => { if self.child_elements() - .any(|c| NodeCast::from_ref(c) != child) - { + .map(|c| c.root()) + .any(|c| NodeCast::from_ref(c.r()) != child) { return Err(HierarchyRequest); } if child.following_siblings() @@ -2105,7 +2151,7 @@ impl<'a> NodeMethods for JSRef<'a, Node> { let pi: JSRef<ProcessingInstruction> = ProcessingInstructionCast::to_ref(node).unwrap(); let other_pi: JSRef<ProcessingInstruction> = ProcessingInstructionCast::to_ref(other).unwrap(); (*pi.target() == *other_pi.target()) && - (*pi.characterdata().data() == *other_pi.characterdata().data()) + (*CharacterDataCast::from_ref(pi).data() == *CharacterDataCast::from_ref(other_pi).data()) } fn is_equal_characterdata(node: JSRef<Node>, other: JSRef<Node>) -> bool { let characterdata: JSRef<CharacterData> = CharacterDataCast::to_ref(node).unwrap(); @@ -2171,23 +2217,25 @@ impl<'a> NodeMethods for JSRef<'a, Node> { // step 2. 0 } else { - let mut lastself = self.clone(); - let mut lastother = other.clone(); + let mut lastself = Temporary::from_rooted(self.clone()); + let mut lastother = Temporary::from_rooted(other.clone()); for ancestor in self.ancestors() { - if ancestor == other { + let ancestor = ancestor.root(); + if ancestor.r() == other { // step 4. return NodeConstants::DOCUMENT_POSITION_CONTAINS + NodeConstants::DOCUMENT_POSITION_PRECEDING; } - lastself = ancestor.clone(); + lastself = Temporary::from_rooted(ancestor.r()); } for ancestor in other.ancestors() { - if ancestor == self { + let ancestor = ancestor.root(); + if ancestor.r() == self { // step 5. return NodeConstants::DOCUMENT_POSITION_CONTAINED_BY + NodeConstants::DOCUMENT_POSITION_FOLLOWING; } - lastother = ancestor.clone(); + lastother = Temporary::from_rooted(ancestor.r()); } if lastself != lastother { @@ -2205,12 +2253,14 @@ impl<'a> NodeMethods for JSRef<'a, Node> { NodeConstants::DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC; } - for child in lastself.traverse_preorder() { - if child == other { + let lastself = lastself.root(); + for child in lastself.r().traverse_preorder() { + let child = child.root(); + if child.r() == other { // step 6. return NodeConstants::DOCUMENT_POSITION_PRECEDING; } - if child == self { + if child.r() == self { // step 7. return NodeConstants::DOCUMENT_POSITION_FOLLOWING; } @@ -2281,7 +2331,7 @@ impl<'a> style::node::TNode<'a> for JSRef<'a, Node> { fn parent_node(self) -> Option<JSRef<'a, Node>> { // FIXME(zwarich): Remove this when UFCS lands and there is a better way // of disambiguating methods. - fn parent_node<'a, T: NodeHelpers<'a>>(this: T) -> Option<Temporary<Node>> { + fn parent_node<'a, T: NodeHelpers>(this: T) -> Option<Temporary<Node>> { this.parent_node() } @@ -2291,7 +2341,7 @@ impl<'a> style::node::TNode<'a> for JSRef<'a, Node> { fn first_child(self) -> Option<JSRef<'a, Node>> { // FIXME(zwarich): Remove this when UFCS lands and there is a better way // of disambiguating methods. - fn first_child<'a, T: NodeHelpers<'a>>(this: T) -> Option<Temporary<Node>> { + fn first_child<'a, T: NodeHelpers>(this: T) -> Option<Temporary<Node>> { this.first_child() } @@ -2301,7 +2351,7 @@ impl<'a> style::node::TNode<'a> for JSRef<'a, Node> { fn last_child(self) -> Option<JSRef<'a, Node>> { // FIXME(zwarich): Remove this when UFCS lands and there is a better way // of disambiguating methods. - fn last_child<'a, T: NodeHelpers<'a>>(this: T) -> Option<Temporary<Node>> { + fn last_child<'a, T: NodeHelpers>(this: T) -> Option<Temporary<Node>> { this.last_child() } @@ -2311,7 +2361,7 @@ impl<'a> style::node::TNode<'a> for JSRef<'a, Node> { fn prev_sibling(self) -> Option<JSRef<'a, Node>> { // FIXME(zwarich): Remove this when UFCS lands and there is a better way // of disambiguating methods. - fn prev_sibling<'a, T: NodeHelpers<'a>>(this: T) -> Option<Temporary<Node>> { + fn prev_sibling<'a, T: NodeHelpers>(this: T) -> Option<Temporary<Node>> { this.prev_sibling() } @@ -2321,7 +2371,7 @@ impl<'a> style::node::TNode<'a> for JSRef<'a, Node> { fn next_sibling(self) -> Option<JSRef<'a, Node>> { // FIXME(zwarich): Remove this when UFCS lands and there is a better way // of disambiguating methods. - fn next_sibling<'a, T: NodeHelpers<'a>>(this: T) -> Option<Temporary<Node>> { + fn next_sibling<'a, T: NodeHelpers>(this: T) -> Option<Temporary<Node>> { this.next_sibling() } @@ -2331,7 +2381,7 @@ impl<'a> style::node::TNode<'a> for JSRef<'a, Node> { fn is_document(self) -> bool { // FIXME(zwarich): Remove this when UFCS lands and there is a better way // of disambiguating methods. - fn is_document<'a, T: NodeHelpers<'a>>(this: T) -> bool { + fn is_document<'a, T: NodeHelpers>(this: T) -> bool { this.is_document() } @@ -2341,7 +2391,7 @@ impl<'a> style::node::TNode<'a> for JSRef<'a, Node> { fn is_element(self) -> bool { // FIXME(zwarich): Remove this when UFCS lands and there is a better way // of disambiguating methods. - fn is_element<'a, T: NodeHelpers<'a>>(this: T) -> bool { + fn is_element<'a, T: NodeHelpers>(this: T) -> bool { this.is_element() } @@ -2415,7 +2465,10 @@ pub trait DisabledStateHelpers { impl<'a> DisabledStateHelpers for JSRef<'a, Node> { fn check_ancestors_disabled_state_for_form_control(self) { if self.get_disabled_state() { return; } - for ancestor in self.ancestors().filter(|ancestor| ancestor.is_htmlfieldsetelement()) { + for ancestor in self.ancestors() { + let ancestor = ancestor.root(); + let ancestor = ancestor.r(); + if !ancestor.is_htmlfieldsetelement() { continue; } if !ancestor.get_disabled_state() { continue; } if ancestor.is_parent_of(self) { self.set_disabled_state(true); @@ -2428,7 +2481,7 @@ impl<'a> DisabledStateHelpers for JSRef<'a, Node> { { Some(legend) => { // XXXabinader: should we save previous ancestor to avoid this iteration? - if self.ancestors().any(|ancestor| ancestor == legend.r()) { continue; } + if self.ancestors().any(|ancestor| ancestor.root().r() == legend.r()) { continue; } }, None => () } diff --git a/components/script/dom/nodelist.rs b/components/script/dom/nodelist.rs index 84aa5d6e2ea..8826733cfa6 100644 --- a/components/script/dom/nodelist.rs +++ b/components/script/dom/nodelist.rs @@ -6,6 +6,7 @@ use dom::bindings::codegen::Bindings::NodeListBinding; use dom::bindings::codegen::Bindings::NodeListBinding::NodeListMethods; use dom::bindings::global::GlobalRef; use dom::bindings::js::{JS, JSRef, Temporary}; +use dom::bindings::trace::RootedVec; use dom::bindings::utils::{Reflector, reflect_dom_object}; use dom::node::{Node, NodeHelpers}; use dom::window::Window; @@ -38,8 +39,8 @@ impl NodeList { GlobalRef::Window(window), NodeListBinding::Wrap) } - pub fn new_simple_list(window: JSRef<Window>, elements: Vec<JSRef<Node>>) -> Temporary<NodeList> { - NodeList::new(window, NodeListType::Simple(elements.iter().map(|element| JS::from_rooted(*element)).collect())) + pub fn new_simple_list(window: JSRef<Window>, elements: &RootedVec<JS<Node>>) -> Temporary<NodeList> { + NodeList::new(window, NodeListType::Simple((**elements).clone())) } pub fn new_child_list(window: JSRef<Window>, node: JSRef<Node>) -> Temporary<NodeList> { diff --git a/components/script/dom/processinginstruction.rs b/components/script/dom/processinginstruction.rs index 528c4cdd64a..7eeb1a7e460 100644 --- a/components/script/dom/processinginstruction.rs +++ b/components/script/dom/processinginstruction.rs @@ -37,17 +37,20 @@ impl ProcessingInstruction { Node::reflect_node(box ProcessingInstruction::new_inherited(target, data, document), document, ProcessingInstructionBinding::Wrap) } +} - pub fn characterdata<'a>(&'a self) -> &'a CharacterData { - &self.characterdata - } +pub trait ProcessingInstructionHelpers<'a> { + fn target(self) -> &'a DOMString; +} - pub fn target<'a>(&'a self) -> &'a DOMString { - &self.target +impl<'a> ProcessingInstructionHelpers<'a> for JSRef<'a, ProcessingInstruction> { + fn target(self) -> &'a DOMString { + &self.extended_deref().target } } impl<'a> ProcessingInstructionMethods for JSRef<'a, ProcessingInstruction> { + // https://dom.spec.whatwg.org/#dom-processinginstruction-target fn Target(self) -> DOMString { self.target.clone() } diff --git a/components/script/dom/storage.rs b/components/script/dom/storage.rs index 7868e53f21c..9ec49555c9c 100644 --- a/components/script/dom/storage.rs +++ b/components/script/dom/storage.rs @@ -63,7 +63,7 @@ impl<'a> StorageMethods for JSRef<'a, Storage> { let (sender, receiver) = channel(); self.get_storage_task().send(StorageTaskMsg::Length(sender, self.get_url(), self.storage_type)).unwrap(); - receiver.recv().unwrap() + receiver.recv().unwrap() as u32 } fn Key(self, index: u32) -> Option<DOMString> { diff --git a/components/script/dom/testbinding.rs b/components/script/dom/testbinding.rs index 2b42f26f294..a4bda4eb817 100644 --- a/components/script/dom/testbinding.rs +++ b/components/script/dom/testbinding.rs @@ -24,6 +24,7 @@ use js::jsapi::{JSContext, JSObject}; use js::jsval::{JSVal, NullValue}; use std::borrow::ToOwned; +use std::ptr; #[dom_struct] pub struct TestBinding { @@ -78,6 +79,8 @@ impl<'a> TestBindingMethods for JSRef<'a, TestBinding> { fn ArrayAttribute(self, _: *mut JSContext) -> *mut JSObject { NullValue().to_object_or_null() } fn AnyAttribute(self, _: *mut JSContext) -> JSVal { NullValue() } fn SetAnyAttribute(self, _: *mut JSContext, _: JSVal) {} + fn ObjectAttribute(self, _: *mut JSContext) -> *mut JSObject { panic!() } + fn SetObjectAttribute(self, _: *mut JSContext, _: *mut JSObject) {} fn GetBooleanAttributeNullable(self) -> Option<bool> { Some(false) } fn SetBooleanAttributeNullable(self, _: Option<bool>) {} @@ -119,6 +122,8 @@ impl<'a> TestBindingMethods for JSRef<'a, TestBinding> { Some(Blob::new(global.r(), None, "")) } fn SetInterfaceAttributeNullable(self, _: Option<JSRef<Blob>>) {} + fn GetObjectAttributeNullable(self, _: *mut JSContext) -> *mut JSObject { ptr::null_mut() } + fn SetObjectAttributeNullable(self, _: *mut JSContext, _: *mut JSObject) {} fn GetUnionAttributeNullable(self) -> Option<HTMLElementOrLong> { Some(eLong(0)) } fn SetUnionAttributeNullable(self, _: Option<HTMLElementOrLong>) {} fn GetUnion2AttributeNullable(self) -> Option<EventOrString> { Some(eString("".to_owned())) } @@ -147,6 +152,7 @@ impl<'a> TestBindingMethods for JSRef<'a, TestBinding> { Blob::new(global.r(), None, "") } fn ReceiveAny(self, _: *mut JSContext) -> JSVal { NullValue() } + fn ReceiveObject(self, _: *mut JSContext) -> *mut JSObject { panic!() } fn ReceiveUnion(self) -> HTMLElementOrLong { eLong(0) } fn ReceiveUnion2(self) -> EventOrString { eString("".to_owned()) } @@ -171,6 +177,7 @@ impl<'a> TestBindingMethods for JSRef<'a, TestBinding> { let global = self.global.root(); Some(Blob::new(global.r(), None, "")) } + fn ReceiveNullableObject(self, _: *mut JSContext) -> *mut JSObject { ptr::null_mut() } fn ReceiveNullableUnion(self) -> Option<HTMLElementOrLong> { Some(eLong(0)) } fn ReceiveNullableUnion2(self) -> Option<EventOrString> { Some(eString("".to_owned())) } @@ -196,6 +203,7 @@ impl<'a> TestBindingMethods for JSRef<'a, TestBinding> { fn PassUnion2(self, _: EventOrString) {} fn PassUnion3(self, _: BlobOrString) {} fn PassAny(self, _: *mut JSContext, _: JSVal) {} + fn PassObject(self, _: *mut JSContext, _: *mut JSObject) {} fn PassCallbackFunction(self, _: Function) {} fn PassCallbackInterface(self, _: EventListener) {} @@ -217,6 +225,7 @@ impl<'a> TestBindingMethods for JSRef<'a, TestBinding> { fn PassNullableByteString(self, _: Option<ByteString>) {} // fn PassNullableEnum(self, _: Option<TestEnum>) {} fn PassNullableInterface(self, _: Option<JSRef<Blob>>) {} + fn PassNullableObject(self, _: *mut JSContext, _: *mut JSObject) {} fn PassNullableUnion(self, _: Option<HTMLElementOrLong>) {} fn PassNullableUnion2(self, _: Option<EventOrString>) {} fn PassNullableCallbackFunction(self, _: Option<Function>) {} @@ -243,6 +252,7 @@ impl<'a> TestBindingMethods for JSRef<'a, TestBinding> { fn PassOptionalUnion(self, _: Option<HTMLElementOrLong>) {} fn PassOptionalUnion2(self, _: Option<EventOrString>) {} fn PassOptionalAny(self, _: *mut JSContext, _: JSVal) {} + fn PassOptionalObject(self, _: *mut JSContext, _: Option<*mut JSObject>) {} fn PassOptionalCallbackFunction(self, _: Option<Function>) {} fn PassOptionalCallbackInterface(self, _: Option<EventListener>) {} @@ -264,6 +274,7 @@ impl<'a> TestBindingMethods for JSRef<'a, TestBinding> { fn PassOptionalNullableByteString(self, _: Option<Option<ByteString>>) {} // fn PassOptionalNullableEnum(self, _: Option<Option<TestEnum>>) {} fn PassOptionalNullableInterface(self, _: Option<Option<JSRef<Blob>>>) {} + fn PassOptionalNullableObject(self, _: *mut JSContext, _: Option<*mut JSObject>) {} fn PassOptionalNullableUnion(self, _: Option<Option<HTMLElementOrLong>>) {} fn PassOptionalNullableUnion2(self, _: Option<Option<EventOrString>>) {} fn PassOptionalNullableCallbackFunction(self, _: Option<Option<Function>>) {} @@ -300,6 +311,7 @@ impl<'a> TestBindingMethods for JSRef<'a, TestBinding> { fn PassOptionalNullableByteStringWithDefault(self, _: Option<ByteString>) {} // fn PassOptionalNullableEnumWithDefault(self, _: Option<TestEnum>) {} fn PassOptionalNullableInterfaceWithDefault(self, _: Option<JSRef<Blob>>) {} + fn PassOptionalNullableObjectWithDefault(self, _: *mut JSContext, _: *mut JSObject) {} fn PassOptionalNullableUnionWithDefault(self, _: Option<HTMLElementOrLong>) {} fn PassOptionalNullableUnion2WithDefault(self, _: Option<EventOrString>) {} // fn PassOptionalNullableCallbackFunctionWithDefault(self, _: Option<Function>) {} @@ -345,6 +357,7 @@ impl<'a> TestBindingMethods for JSRef<'a, TestBinding> { fn PassVariadicUnion2(self, _: Vec<EventOrString>) {} fn PassVariadicUnion3(self, _: Vec<BlobOrString>) {} fn PassVariadicAny(self, _: *mut JSContext, _: Vec<JSVal>) {} + fn PassVariadicObject(self, _: *mut JSContext, _: Vec<*mut JSObject>) {} } impl TestBinding { diff --git a/components/script/dom/text.rs b/components/script/dom/text.rs index ea6c2791e96..5df59274e9e 100644 --- a/components/script/dom/text.rs +++ b/components/script/dom/text.rs @@ -2,16 +2,17 @@ * 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/. */ -use dom::bindings::codegen::Bindings::TextBinding; +use dom::bindings::codegen::Bindings::TextBinding::{self, TextMethods}; use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; -use dom::bindings::codegen::InheritTypes::TextDerived; +use dom::bindings::codegen::InheritTypes::{CharacterDataCast, TextDerived}; +use dom::bindings::codegen::InheritTypes::NodeCast; use dom::bindings::error::Fallible; use dom::bindings::global::GlobalRef; use dom::bindings::js::{JSRef, Temporary}; -use dom::characterdata::CharacterData; +use dom::characterdata::{CharacterData, CharacterDataHelpers}; use dom::document::Document; use dom::eventtarget::{EventTarget, EventTargetTypeId}; -use dom::node::{Node, NodeTypeId}; +use dom::node::{Node, NodeHelpers, NodeTypeId}; use util::str::DOMString; /// An HTML text node. @@ -42,10 +43,23 @@ impl Text { let document = global.as_window().Document().root(); Ok(Text::new(text, document.r())) } +} - #[inline] - pub fn characterdata<'a>(&'a self) -> &'a CharacterData { - &self.characterdata +impl<'a> TextMethods for JSRef<'a, Text> { + // https://dom.spec.whatwg.org/#dom-text-wholetext + fn WholeText(self) -> DOMString { + let first = NodeCast::from_ref(self).inclusively_preceding_siblings() + .map(|node| node.root()) + .take_while(|node| node.r().is_text()) + .last().unwrap(); + let nodes = first.r().inclusively_following_siblings().map(|node| node.root()) + .take_while(|node| node.r().is_text()); + let mut text = DOMString::new(); + for node in nodes { + let cdata = CharacterDataCast::to_ref(node.r()).unwrap(); + text.push_str(&cdata.data()); + } + text } } diff --git a/components/script/dom/textdecoder.rs b/components/script/dom/textdecoder.rs new file mode 100644 index 00000000000..714200f9a8f --- /dev/null +++ b/components/script/dom/textdecoder.rs @@ -0,0 +1,99 @@ +/* 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/. */ + +use dom::bindings::codegen::Bindings::TextDecoderBinding; +use dom::bindings::codegen::Bindings::TextDecoderBinding::TextDecoderMethods; +use dom::bindings::error::{Error, Fallible}; +use dom::bindings::global::GlobalRef; +use dom::bindings::js::{JSRef, Temporary}; +use dom::bindings::str::USVString; +use dom::bindings::trace::JSTraceable; +use dom::bindings::utils::{Reflector, reflect_dom_object}; + +use util::str::DOMString; + +use encoding::Encoding; +use encoding::types::{EncodingRef, DecoderTrap}; +use encoding::label::encoding_from_whatwg_label; +use js::jsapi::{JSContext, JSObject}; +use js::jsfriendapi::bindgen::JS_GetObjectAsArrayBufferView; + +use std::borrow::ToOwned; +use std::ptr; +use std::slice; + +#[dom_struct] +pub struct TextDecoder { + reflector_: Reflector, + encoding: EncodingRef, + fatal: bool, +} + +impl TextDecoder { + fn new_inherited(encoding: EncodingRef, fatal: bool) -> TextDecoder { + TextDecoder { + reflector_: Reflector::new(), + encoding: encoding, + fatal: fatal, + } + } + + pub fn new(global: GlobalRef, encoding: EncodingRef, fatal: bool) -> Temporary<TextDecoder> { + reflect_dom_object(box TextDecoder::new_inherited(encoding, fatal), + global, + TextDecoderBinding::Wrap) + } + + /// https://encoding.spec.whatwg.org/#dom-textdecoder + pub fn Constructor(global: GlobalRef, + label: DOMString, + options: &TextDecoderBinding::TextDecoderOptions) + -> Fallible<Temporary<TextDecoder>> { + let encoding = match encoding_from_whatwg_label(&label) { + Some(enc) => enc, + // FIXME: Should throw a RangeError as per spec + None => return Err(Error::Syntax), + }; + Ok(TextDecoder::new(global, encoding, options.fatal)) + } +} + +impl<'a> TextDecoderMethods for JSRef<'a, TextDecoder> { + fn Encoding(self) -> DOMString { + self.encoding.whatwg_name().unwrap().to_owned() + } + + fn Fatal(self) -> bool { + self.fatal + } + + #[allow(unsafe_code)] + fn Decode(self, cx: *mut JSContext, input: Option<*mut JSObject>) + -> Fallible<USVString> { + let input = match input { + Some(input) => input, + None => return Ok(USVString("".to_owned())), + }; + + let mut length = 0; + let mut data = ptr::null_mut(); + if unsafe { JS_GetObjectAsArrayBufferView(cx, input, &mut length, &mut data).is_null() } { + return Err(Error::Type("Argument to TextDecoder.decode is not an ArrayBufferView".to_owned())); + } + + let buffer = unsafe { + slice::from_raw_parts(data as *const _, length as usize) + }; + let trap = if self.fatal { + DecoderTrap::Strict + } else { + DecoderTrap::Replace + }; + match self.encoding.decode(buffer, trap) { + Ok(s) => Ok(USVString(s)), + Err(_) => Err(Error::Type("Decoding failed".to_owned())), + } + } + +} diff --git a/components/script/dom/treewalker.rs b/components/script/dom/treewalker.rs index 1957999bcf4..d25dba20d1b 100644 --- a/components/script/dom/treewalker.rs +++ b/components/script/dom/treewalker.rs @@ -7,10 +7,7 @@ use dom::bindings::codegen::Bindings::TreeWalkerBinding; use dom::bindings::codegen::Bindings::TreeWalkerBinding::TreeWalkerMethods; use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; use dom::bindings::codegen::Bindings::NodeFilterBinding::NodeFilter; -// FIXME: Uncomment when codegen fix allows NodeFilterConstants -// to move to the NodeFilter binding file (#3149). -// For now, it is defined in this file. -// use dom::bindings::codegen::Bindings::NodeFilterBinding::NodeFilterConstants; +use dom::bindings::codegen::Bindings::NodeFilterBinding::NodeFilterConstants; use dom::bindings::error::Fallible; use dom::bindings::global::GlobalRef; use dom::bindings::js::{JS, JSRef, OptionalRootable, Temporary, MutHeap}; @@ -563,24 +560,3 @@ pub enum Filter { Native(fn (node: JSRef<Node>) -> u16), JS(NodeFilter) } - -// FIXME: NodeFilterConstants will be defined in NodeFilterBindings.rs -// when codegen supports a callback interface with constants (#3149). -pub mod NodeFilterConstants { - pub const FILTER_ACCEPT: u16 = 1; - pub const FILTER_REJECT: u16 = 2; - pub const FILTER_SKIP: u16 = 3; - pub const SHOW_ALL: u32 = 4294967295; - pub const SHOW_ELEMENT: u32 = 1; - pub const SHOW_ATTRIBUTE: u32 = 2; - pub const SHOW_TEXT: u32 = 4; - pub const SHOW_CDATA_SECTION: u32 = 8; - pub const SHOW_ENTITY_REFERENCE: u32 = 16; - pub const SHOW_ENTITY: u32 = 32; - pub const SHOW_PROCESSING_INSTRUCTION: u32 = 64; - pub const SHOW_COMMENT: u32 = 128; - pub const SHOW_DOCUMENT: u32 = 256; - pub const SHOW_DOCUMENT_TYPE: u32 = 512; - pub const SHOW_DOCUMENT_FRAGMENT: u32 = 1024; - pub const SHOW_NOTATION: u32 = 2048; -} // mod NodeFilterConstants diff --git a/components/script/dom/webidls/CanvasRenderingContext2D.webidl b/components/script/dom/webidls/CanvasRenderingContext2D.webidl index 511a26ae66b..4c4b94374f2 100644 --- a/components/script/dom/webidls/CanvasRenderingContext2D.webidl +++ b/components/script/dom/webidls/CanvasRenderingContext2D.webidl @@ -6,8 +6,8 @@ enum CanvasWindingRule { "nonzero", "evenodd" }; // http://www.whatwg.org/html/#2dcontext -typedef (/* HTMLImageElement or - HTMLVideoElement or */ +typedef (HTMLImageElement or + /* HTMLVideoElement or */ HTMLCanvasElement or CanvasRenderingContext2D /* or ImageBitmap */) CanvasImageSource; @@ -49,7 +49,7 @@ interface CanvasRenderingContext2D { //void resetTransform(); // compositing - // attribute unrestricted double globalAlpha; // (default 1.0) + attribute unrestricted double globalAlpha; // (default 1.0) // attribute DOMString globalCompositeOperation; // (default source-over) // image smoothing @@ -131,6 +131,26 @@ interface CanvasRenderingContext2D { }; [NoInterfaceObject] +interface CanvasDrawingStyles { + // line caps/joins + attribute unrestricted double lineWidth; // (default 1) + //attribute DOMString lineCap; // "butt", "round", "square" (default "butt") + //attribute DOMString lineJoin; // "round", "bevel", "miter" (default "miter") + attribute unrestricted double miterLimit; // (default 10) + + // dashed lines + //void setLineDash(sequence<unrestricted double> segments); // default empty + //sequence<unrestricted double> getLineDash(); + //attribute unrestricted double lineDashOffset; + + // text + //attribute DOMString font; // (default 10px sans-serif) + //attribute DOMString textAlign; // "start", "end", "left", "right", "center" (default: "start") + //attribute DOMString textBaseline; // "top", "hanging", "middle", "alphabetic", "ideographic", "bottom" (default: "alphabetic") + //attribute DOMString direction; // "ltr", "rtl", "inherit" (default: "inherit") +}; + +[NoInterfaceObject] interface CanvasPathMethods { // shared path API methods void closePath(); @@ -159,5 +179,6 @@ interface CanvasPathMethods { }; +CanvasRenderingContext2D implements CanvasDrawingStyles; CanvasRenderingContext2D implements CanvasPathMethods; diff --git a/components/script/dom/webidls/CharacterData.webidl b/components/script/dom/webidls/CharacterData.webidl index d1b222bc168..e758adb4d30 100644 --- a/components/script/dom/webidls/CharacterData.webidl +++ b/components/script/dom/webidls/CharacterData.webidl @@ -11,11 +11,10 @@ */ interface CharacterData : Node { - [TreatNullAs=EmptyString,SetterThrows] attribute DOMString data; + [TreatNullAs=EmptyString] attribute DOMString data; readonly attribute unsigned long length; [Throws] DOMString substringData(unsigned long offset, unsigned long count); - [Throws] void appendData(DOMString data); [Throws] void insertData(unsigned long offset, DOMString data); @@ -26,3 +25,4 @@ interface CharacterData : Node { }; CharacterData implements ChildNode; +CharacterData implements NonDocumentTypeChildNode; diff --git a/components/script/dom/webidls/ChildNode.webidl b/components/script/dom/webidls/ChildNode.webidl index 16562fbafbf..fbcf8ea1208 100644 --- a/components/script/dom/webidls/ChildNode.webidl +++ b/components/script/dom/webidls/ChildNode.webidl @@ -16,10 +16,10 @@ interface ChildNode { void remove(); }; -// [NoInterfaceObject] -// interface NonDocumentTypeChildNode { -// [Pure] -// readonly attribute Element? previousElementSibling; -// [Pure] -// readonly attribute Element? nextElementSibling; -// }; +[NoInterfaceObject] +interface NonDocumentTypeChildNode { + [Pure] + readonly attribute Element? previousElementSibling; + [Pure] + readonly attribute Element? nextElementSibling; +}; diff --git a/components/script/dom/webidls/Document.webidl b/components/script/dom/webidls/Document.webidl index 984f79eb528..c57b09fff1b 100644 --- a/components/script/dom/webidls/Document.webidl +++ b/components/script/dom/webidls/Document.webidl @@ -27,27 +27,33 @@ interface Document : Node { HTMLCollection getElementsByClassName(DOMString classNames); Element? getElementById(DOMString elementId); - [Throws] + [NewObject, Throws] Element createElement(DOMString localName); - [Throws] + [NewObject, Throws] Element createElementNS(DOMString? namespace, DOMString qualifiedName); + [NewObject] DocumentFragment createDocumentFragment(); + [NewObject] Text createTextNode(DOMString data); + [NewObject] Comment createComment(DOMString data); - [Throws] + [NewObject, Throws] ProcessingInstruction createProcessingInstruction(DOMString target, DOMString data); - [Throws] - Attr createAttribute(DOMString localName); - - [Throws] + [NewObject, Throws] Node importNode(Node node, optional boolean deep = false); [Throws] Node adoptNode(Node node); - [Throws] + [NewObject, Throws] + Attr createAttribute(DOMString localName); + [NewObject, Throws] + Attr createAttributeNS(DOMString? namespace, DOMString localName); + + [NewObject, Throws] Event createEvent(DOMString interface_); + [NewObject] Range createRange(); // NodeFilter.SHOW_ALL = 0xFFFFFFFF diff --git a/components/script/dom/webidls/Element.webidl b/components/script/dom/webidls/Element.webidl index 8d08717cd31..1090ad91588 100644 --- a/components/script/dom/webidls/Element.webidl +++ b/components/script/dom/webidls/Element.webidl @@ -70,4 +70,5 @@ partial interface Element { }; Element implements ChildNode; +Element implements NonDocumentTypeChildNode; Element implements ParentNode; diff --git a/components/script/dom/webidls/NamedNodeMap.webidl b/components/script/dom/webidls/NamedNodeMap.webidl index 636c4a2782f..5b85e755804 100644 --- a/components/script/dom/webidls/NamedNodeMap.webidl +++ b/components/script/dom/webidls/NamedNodeMap.webidl @@ -5,4 +5,14 @@ interface NamedNodeMap { readonly attribute unsigned long length; getter Attr? item(unsigned long index); + getter Attr? getNamedItem(DOMString name); + Attr? getNamedItemNS(DOMString? namespace, DOMString localName); + //[Throws] + //Attr? setNamedItem(Attr attr); + //[Throws] + //Attr? setNamedItemNS(Attr attr); + [Throws] + Attr removeNamedItem(DOMString name); + [Throws] + Attr removeNamedItemNS(DOMString? namespace, DOMString name); }; diff --git a/components/script/dom/webidls/NodeFilter.webidl b/components/script/dom/webidls/NodeFilter.webidl index b84b369829e..2b0b1763c89 100644 --- a/components/script/dom/webidls/NodeFilter.webidl +++ b/components/script/dom/webidls/NodeFilter.webidl @@ -6,28 +6,28 @@ * The origin of this IDL file is * http://dom.spec.whatwg.org/#interface-nodefilter */ -// Import form http://hg.mozilla.org/mozilla-central/file/a5a720259d79/dom/webidl/NodeFilter.webidl +// Import from http://hg.mozilla.org/mozilla-central/file/a5a720259d79/dom/webidl/NodeFilter.webidl callback interface NodeFilter { // Constants for acceptNode() - // const unsigned short FILTER_ACCEPT = 1; - // const unsigned short FILTER_REJECT = 2; - // const unsigned short FILTER_SKIP = 3; + const unsigned short FILTER_ACCEPT = 1; + const unsigned short FILTER_REJECT = 2; + const unsigned short FILTER_SKIP = 3; // Constants for whatToShow - // const unsigned long SHOW_ALL = 0xFFFFFFFF; - // const unsigned long SHOW_ELEMENT = 0x1; - // const unsigned long SHOW_ATTRIBUTE = 0x2; // historical - // const unsigned long SHOW_TEXT = 0x4; - // const unsigned long SHOW_CDATA_SECTION = 0x8; // historical - // const unsigned long SHOW_ENTITY_REFERENCE = 0x10; // historical - // const unsigned long SHOW_ENTITY = 0x20; // historical - // const unsigned long SHOW_PROCESSING_INSTRUCTION = 0x40; - // const unsigned long SHOW_COMMENT = 0x80; - // const unsigned long SHOW_DOCUMENT = 0x100; - // const unsigned long SHOW_DOCUMENT_TYPE = 0x200; - // const unsigned long SHOW_DOCUMENT_FRAGMENT = 0x400; - // const unsigned long SHOW_NOTATION = 0x800; // historical + const unsigned long SHOW_ALL = 0xFFFFFFFF; + const unsigned long SHOW_ELEMENT = 0x1; + const unsigned long SHOW_ATTRIBUTE = 0x2; // historical + const unsigned long SHOW_TEXT = 0x4; + const unsigned long SHOW_CDATA_SECTION = 0x8; // historical + const unsigned long SHOW_ENTITY_REFERENCE = 0x10; // historical + const unsigned long SHOW_ENTITY = 0x20; // historical + const unsigned long SHOW_PROCESSING_INSTRUCTION = 0x40; + const unsigned long SHOW_COMMENT = 0x80; + const unsigned long SHOW_DOCUMENT = 0x100; + const unsigned long SHOW_DOCUMENT_TYPE = 0x200; + const unsigned long SHOW_DOCUMENT_FRAGMENT = 0x400; + const unsigned long SHOW_NOTATION = 0x800; // historical unsigned short acceptNode(Node node); }; diff --git a/components/script/dom/webidls/ParentNode.webidl b/components/script/dom/webidls/ParentNode.webidl index daa4339611f..9df1143f625 100644 --- a/components/script/dom/webidls/ParentNode.webidl +++ b/components/script/dom/webidls/ParentNode.webidl @@ -11,14 +11,13 @@ interface ParentNode { [Constant] readonly attribute HTMLCollection children; - /* [Pure] readonly attribute Element? firstElementChild; [Pure] readonly attribute Element? lastElementChild; [Pure] readonly attribute unsigned long childElementCount; - */ + // Not implemented yet // void prepend((Node or DOMString)... nodes); // void append((Node or DOMString)... nodes); diff --git a/components/script/dom/webidls/TestBinding.webidl b/components/script/dom/webidls/TestBinding.webidl index e5c5aa1772c..9906de4e696 100644 --- a/components/script/dom/webidls/TestBinding.webidl +++ b/components/script/dom/webidls/TestBinding.webidl @@ -23,6 +23,7 @@ dictionary TestDictionary { TestEnum enumValue; Blob interfaceValue; any anyValue; + object objectValue; }; dictionary TestDictionaryDefaults { @@ -60,6 +61,7 @@ dictionary TestDictionaryDefaults { DOMString? nullableStringValue = "foo"; USVString? nullableUsvstringValue = "foo"; // TestEnum? nullableEnumValue = "bar"; + object? nullableObjectValue = null; }; interface TestBinding { @@ -85,6 +87,7 @@ interface TestBinding { attribute (Event or DOMString) union2Attribute; readonly attribute Uint8ClampedArray arrayAttribute; attribute any anyAttribute; + attribute object objectAttribute; attribute boolean? booleanAttributeNullable; attribute byte? byteAttributeNullable; @@ -104,6 +107,7 @@ interface TestBinding { attribute ByteString? byteStringAttributeNullable; readonly attribute TestEnum? enumAttributeNullable; attribute Blob? interfaceAttributeNullable; + attribute object? objectAttributeNullable; attribute (HTMLElement or long)? unionAttributeNullable; attribute (Event or DOMString)? union2AttributeNullable; [BinaryName="BinaryRenamedAttribute"] attribute DOMString attrToBinaryRename; @@ -129,6 +133,7 @@ interface TestBinding { TestEnum receiveEnum(); Blob receiveInterface(); any receiveAny(); + object receiveObject(); (HTMLElement or long) receiveUnion(); (Event or DOMString) receiveUnion2(); @@ -150,6 +155,7 @@ interface TestBinding { ByteString? receiveNullableByteString(); TestEnum? receiveNullableEnum(); Blob? receiveNullableInterface(); + object? receiveNullableObject(); (HTMLElement or long)? receiveNullableUnion(); (Event or DOMString)? receiveNullableUnion2(); @@ -175,6 +181,7 @@ interface TestBinding { void passUnion2((Event or DOMString) data); void passUnion3((Blob or DOMString) data); void passAny(any arg); + void passObject(object arg); void passCallbackFunction(Function fun); void passCallbackInterface(EventListener listener); @@ -196,6 +203,7 @@ interface TestBinding { void passNullableByteString(ByteString? arg); // void passNullableEnum(TestEnum? arg); void passNullableInterface(Blob? arg); + void passNullableObject(object? arg); void passNullableUnion((HTMLElement or long)? arg); void passNullableUnion2((Event or DOMString)? data); void passNullableCallbackFunction(Function? fun); @@ -222,6 +230,7 @@ interface TestBinding { void passOptionalUnion(optional (HTMLElement or long) arg); void passOptionalUnion2(optional (Event or DOMString) data); void passOptionalAny(optional any arg); + void passOptionalObject(optional object arg); void passOptionalCallbackFunction(optional Function fun); void passOptionalCallbackInterface(optional EventListener listener); @@ -243,6 +252,7 @@ interface TestBinding { void passOptionalNullableByteString(optional ByteString? arg); // void passOptionalNullableEnum(optional TestEnum? arg); void passOptionalNullableInterface(optional Blob? arg); + void passOptionalNullableObject(optional object? arg); void passOptionalNullableUnion(optional (HTMLElement or long)? arg); void passOptionalNullableUnion2(optional (Event or DOMString)? data); void passOptionalNullableCallbackFunction(optional Function? fun); @@ -277,6 +287,7 @@ interface TestBinding { void passOptionalNullableByteStringWithDefault(optional ByteString? arg = null); // void passOptionalNullableEnumWithDefault(optional TestEnum? arg = null); void passOptionalNullableInterfaceWithDefault(optional Blob? arg = null); + void passOptionalNullableObjectWithDefault(optional object? arg = null); void passOptionalNullableUnionWithDefault(optional (HTMLElement or long)? arg = null); void passOptionalNullableUnion2WithDefault(optional (Event or DOMString)? data = null); // void passOptionalNullableCallbackFunctionWithDefault(optional Function? fun = null); @@ -324,6 +335,7 @@ interface TestBinding { void passVariadicUnion2((Event or DOMString)... args); void passVariadicUnion3((Blob or DOMString)... args); void passVariadicAny(any... args); + void passVariadicObject(object... args); static attribute boolean booleanAttributeStatic; static void receiveVoidStatic(); diff --git a/components/script/dom/webidls/Text.webidl b/components/script/dom/webidls/Text.webidl index 972797c73c8..e46d4e24d9c 100644 --- a/components/script/dom/webidls/Text.webidl +++ b/components/script/dom/webidls/Text.webidl @@ -14,5 +14,5 @@ [Constructor(optional DOMString data = "")] interface Text : CharacterData { //[NewObject] Text splitText(unsigned long offset); - //readonly attribute DOMString wholeText; + readonly attribute DOMString wholeText; }; diff --git a/components/script/dom/webidls/TextDecoder.webidl b/components/script/dom/webidls/TextDecoder.webidl new file mode 100644 index 00000000000..4ec66f07d7a --- /dev/null +++ b/components/script/dom/webidls/TextDecoder.webidl @@ -0,0 +1,21 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +// https://encoding.spec.whatwg.org/#interface-textdecoder +dictionary TextDecoderOptions { + boolean fatal = false; + //boolean ignoreBOM = false; +}; + +[Constructor(optional DOMString label = "utf-8", optional TextDecoderOptions options)/*, + Exposed=Window,Worker*/] +interface TextDecoder { + readonly attribute DOMString encoding; + readonly attribute boolean fatal; + //readonly attribute boolean ignoreBOM; + //USVString decode(optional BufferSource input, optional TextDecodeOptions options); + [Throws] + USVString decode(optional object input); +}; diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index 9fe135eb53f..408f4da4ea7 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -34,7 +34,7 @@ use script_task::ScriptMsg; use script_traits::ScriptControlChan; use timers::{IsInterval, TimerId, TimerManager, TimerCallback}; -use devtools_traits::DevtoolsControlChan; +use devtools_traits::{DevtoolsControlChan, TimelineMarker, TimelineMarkerType, TracingMetadata}; use msg::compositor_msg::ScriptListener; use msg::constellation_msg::{LoadData, PipelineId, SubpageId, ConstellationChan, WindowSizeData, WorkerId}; use net_traits::ResourceTask; @@ -54,13 +54,15 @@ use url::{Url, UrlParser}; use libc; use rustc_serialize::base64::{FromBase64, ToBase64, STANDARD}; -use std::cell::{Cell, Ref, RefMut}; +use std::borrow::ToOwned; +use std::cell::{Cell, Ref, RefMut, RefCell}; +use std::collections::HashSet; use std::default::Default; use std::ffi::CString; use std::mem; use std::num::Float; use std::rc::Rc; -use std::sync::mpsc::{channel, Receiver}; +use std::sync::mpsc::{channel, Receiver, Sender}; use std::sync::mpsc::TryRecvError::{Empty, Disconnected}; use time; @@ -102,6 +104,10 @@ pub struct Window { /// For providing instructions to an optional devtools server. devtools_chan: Option<DevtoolsControlChan>, + /// For sending timeline markers. Will be ignored if + /// no devtools server + devtools_markers: RefCell<HashSet<TimelineMarkerType>>, + devtools_marker_sender: RefCell<Option<Sender<TimelineMarker>>>, /// A flag to indicate whether the developer tools have requested live updates of /// page changes. @@ -477,6 +483,10 @@ pub trait WindowHelpers { fn IndexedGetter(self, _index: u32, _found: &mut bool) -> Option<Temporary<Window>>; fn thaw(self); fn freeze(self); + fn need_emit_timeline_marker(self, timeline_type: TimelineMarkerType) -> bool; + fn emit_timeline_marker(self, marker: TimelineMarker); + fn set_devtools_timeline_marker(self, marker: TimelineMarkerType, reply: Sender<TimelineMarker>); + fn drop_devtools_timeline_markers(self); } pub trait ScriptHelpers { @@ -547,6 +557,11 @@ impl<'a> WindowHelpers for JSRef<'a, Window> { None => return, }; + if self.need_emit_timeline_marker(TimelineMarkerType::Reflow) { + let marker = TimelineMarker::new("Reflow".to_owned(), TracingMetadata::IntervalStart); + self.emit_timeline_marker(marker); + } + // Layout will let us know when it's done. let (join_chan, join_port) = channel(); @@ -583,6 +598,11 @@ impl<'a> WindowHelpers for JSRef<'a, Window> { debug!("script: layout forked"); self.join_layout(); + + if self.need_emit_timeline_marker(TimelineMarkerType::Reflow) { + let marker = TimelineMarker::new("Reflow".to_owned(), TracingMetadata::IntervalEnd); + self.emit_timeline_marker(marker); + } } // FIXME(cgaebel): join_layout is racey. What if the compositor triggers a @@ -776,6 +796,27 @@ impl<'a> WindowHelpers for JSRef<'a, Window> { fn freeze(self) { self.timers.suspend(); } + + fn need_emit_timeline_marker(self, timeline_type: TimelineMarkerType) -> bool { + let markers = self.devtools_markers.borrow(); + markers.contains(&timeline_type) + } + + fn emit_timeline_marker(self, marker: TimelineMarker) { + let sender = self.devtools_marker_sender.borrow(); + let sender = sender.as_ref().expect("There is no marker sender"); + sender.send(marker).unwrap(); + } + + fn set_devtools_timeline_marker(self, marker: TimelineMarkerType, reply: Sender<TimelineMarker>) { + *self.devtools_marker_sender.borrow_mut() = Some(reply); + self.devtools_markers.borrow_mut().insert(marker); + } + + fn drop_devtools_timeline_markers(self) { + self.devtools_markers.borrow_mut().clear(); + *self.devtools_marker_sender.borrow_mut() = None; + } } impl Window { @@ -836,6 +877,9 @@ impl Window { layout_rpc: layout_rpc, layout_join_port: DOMRefCell::new(None), window_size: Cell::new(window_size), + + devtools_marker_sender: RefCell::new(None), + devtools_markers: RefCell::new(HashSet::new()), devtools_wants_updates: Cell::new(false), }; diff --git a/components/script/lib.rs b/components/script/lib.rs index 75699c419cc..4a9fcdbd09c 100644 --- a/components/script/lib.rs +++ b/components/script/lib.rs @@ -39,6 +39,7 @@ extern crate js; extern crate libc; extern crate msg; extern crate net_traits; +extern crate png; extern crate "rustc-serialize" as rustc_serialize; extern crate time; extern crate canvas; @@ -65,6 +66,3 @@ pub mod script_task; mod timers; pub mod textinput; mod devtools; - -#[cfg(all(test, target_pointer_width = "64"))] -mod tests; diff --git a/components/script/page.rs b/components/script/page.rs index cea2ddefc27..0a226211016 100644 --- a/components/script/page.rs +++ b/components/script/page.rs @@ -76,7 +76,7 @@ impl Page { Temporary::new(self.frame.borrow().as_ref().unwrap().window.clone()) } - pub fn window_for_script_dealloation(&self) -> Unrooted<Window> { + pub fn window_for_script_deallocation(&self) -> Unrooted<Window> { Unrooted::from_js(self.frame.borrow().as_ref().unwrap().window) } diff --git a/components/script/parse/html.rs b/components/script/parse/html.rs index 2aecd9264e9..7509fd5d447 100644 --- a/components/script/parse/html.rs +++ b/components/script/parse/html.rs @@ -7,12 +7,14 @@ use dom::attr::AttrHelpers; use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods; use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; -use dom::bindings::codegen::InheritTypes::{NodeCast, ElementCast, HTMLScriptElementCast}; -use dom::bindings::codegen::InheritTypes::{DocumentTypeCast, TextCast, CommentCast}; +use dom::bindings::codegen::InheritTypes::{CharacterDataCast, DocumentTypeCast}; +use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLScriptElementCast}; +use dom::bindings::codegen::InheritTypes::{HTMLFormElementDerived, NodeCast}; use dom::bindings::codegen::InheritTypes::ProcessingInstructionCast; -use dom::bindings::codegen::InheritTypes::HTMLFormElementDerived; use dom::bindings::js::{JS, JSRef, Temporary, OptionalRootable, Root}; +use dom::bindings::js::RootedReference; use dom::bindings::trace::RootedVec; +use dom::characterdata::CharacterDataHelpers; use dom::comment::Comment; use dom::document::{Document, DocumentHelpers}; use dom::document::{DocumentSource, IsHTMLDocument}; @@ -23,6 +25,7 @@ use dom::htmlscriptelement::HTMLScriptElementHelpers; use dom::node::{Node, NodeHelpers, NodeTypeId}; use dom::node::{document_from_node, window_from_node}; use dom::processinginstruction::ProcessingInstruction; +use dom::processinginstruction::ProcessingInstructionHelpers; use dom::servohtmlparser; use dom::servohtmlparser::{ServoHTMLParser, FragmentContext}; use dom::text::Text; @@ -233,22 +236,19 @@ impl<'a> Serializable for JSRef<'a, Node> { }, (IncludeNode, NodeTypeId::Text) => { - let text: JSRef<Text> = TextCast::to_ref(node).unwrap(); - let data = text.characterdata().data(); - serializer.write_text(data.as_slice()) + let cdata = CharacterDataCast::to_ref(node).unwrap(); + serializer.write_text(&cdata.data()) }, (IncludeNode, NodeTypeId::Comment) => { - let comment: JSRef<Comment> = CommentCast::to_ref(node).unwrap(); - let data = comment.characterdata().data(); - serializer.write_comment(data.as_slice()) + let cdata = CharacterDataCast::to_ref(node).unwrap(); + serializer.write_comment(&cdata.data()) }, (IncludeNode, NodeTypeId::ProcessingInstruction) => { let pi: JSRef<ProcessingInstruction> = ProcessingInstructionCast::to_ref(node).unwrap(); - let data = pi.characterdata().data(); - serializer.write_processing_instruction(pi.target().as_slice(), - data.as_slice()) + let data = CharacterDataCast::from_ref(pi).data(); + serializer.write_processing_instruction(&pi.target(), &data) }, (IncludeNode, NodeTypeId::DocumentFragment) => Ok(()), @@ -350,10 +350,11 @@ pub fn parse_html_fragment(context_node: JSRef<Node>, // Step 11. let form = context_node.inclusive_ancestors() - .find(|element| element.is_htmlformelement()); + .map(|element| element.root()) + .find(|element| element.r().is_htmlformelement()); let fragment_context = FragmentContext { context_elem: context_node, - form_elem: form, + form_elem: form.r(), }; parse_html(document.r(), HTMLInput::InputString(input), &url, Some(fragment_context)); diff --git a/components/script/script_task.rs b/components/script/script_task.rs index dbb73117857..62f7381f77e 100644 --- a/components/script/script_task.rs +++ b/components/script/script_task.rs @@ -24,8 +24,8 @@ use dom::bindings::codegen::Bindings::DocumentBinding::{DocumentMethods, Documen use dom::bindings::codegen::InheritTypes::{ElementCast, EventTargetCast, HTMLIFrameElementCast, NodeCast, EventCast}; use dom::bindings::conversions::FromJSValConvertible; use dom::bindings::conversions::StringificationBehavior; -use dom::bindings::js::{JS, JSRef, Temporary, OptionalRootable, RootedReference}; -use dom::bindings::js::{RootCollection, RootCollectionPtr, Unrooted}; +use dom::bindings::js::{JS, JSRef, OptionalRootable, RootedReference}; +use dom::bindings::js::{RootCollection, RootCollectionPtr}; use dom::bindings::refcounted::{LiveDOMReferences, Trusted, TrustedReference}; use dom::bindings::structuredclone::StructuredCloneData; use dom::bindings::trace::{JSTraceable, trace_collections}; @@ -47,6 +47,7 @@ use devtools; use devtools_traits::{DevtoolsControlChan, DevtoolsControlPort, DevtoolsPageInfo}; use devtools_traits::{DevtoolsControlMsg, DevtoolScriptControlMsg}; +use devtools_traits::{TimelineMarker, TimelineMarkerType, TracingMetadata}; use script_traits::CompositorEvent; use script_traits::CompositorEvent::{ResizeEvent, ReflowEvent, ClickEvent}; use script_traits::CompositorEvent::{MouseDownEvent, MouseUpEvent}; @@ -74,11 +75,10 @@ use util::task_state; use geom::Rect; use geom::point::Point2D; use hyper::header::{LastModified, Headers}; -use js::jsapi::{JS_SetWrapObjectCallbacks, JS_SetGCZeal, JS_SetExtraGCRootsTracer, JS_DEFAULT_ZEAL_FREQ}; +use js::jsapi::{JS_SetWrapObjectCallbacks, JS_SetExtraGCRootsTracer}; use js::jsapi::{JSContext, JSRuntime, JSObject, JSTracer}; -use js::jsapi::{JS_SetGCParameter, JSGC_MAX_BYTES}; use js::jsapi::{JS_SetGCCallback, JSGCStatus, JSGC_BEGIN, JSGC_END}; -use js::rust::{Cx, RtUtils}; +use js::rust::{Runtime, Cx, RtUtils}; use js; use url::Url; @@ -87,24 +87,26 @@ use std::ascii::AsciiExt; use std::any::Any; use std::borrow::ToOwned; use std::cell::{Cell, RefCell}; +use std::collections::HashSet; use std::num::ToPrimitive; use std::option::Option; use std::ptr; use std::rc::Rc; use std::result::Result; use std::sync::mpsc::{channel, Sender, Receiver, Select}; -use std::u32; use time::Tm; thread_local!(pub static STACK_ROOTS: Cell<Option<RootCollectionPtr>> = Cell::new(None)); thread_local!(static SCRIPT_TASK_ROOT: RefCell<Option<*const ScriptTask>> = RefCell::new(None)); -unsafe extern fn trace_script_tasks(tr: *mut JSTracer, _data: *mut libc::c_void) { +unsafe extern fn trace_rust_roots(tr: *mut JSTracer, _data: *mut libc::c_void) { SCRIPT_TASK_ROOT.with(|root| { if let Some(script_task) = *root.borrow() { (*script_task).trace(tr); } }); + + trace_collections(tr); } /// A document load that is in the process of fetching the requested resource. Contains @@ -237,6 +239,7 @@ impl Drop for StackRootTLS { } } + /// Information for an entire page. Pages are top-level browsing contexts and can contain multiple /// frames. /// @@ -278,6 +281,10 @@ pub struct ScriptTask { /// no such server exists. devtools_port: DevtoolsControlPort, devtools_sender: Sender<DevtoolScriptControlMsg>, + /// For sending timeline markers. Will be ignored if + /// no devtools server + devtools_markers: RefCell<HashSet<TimelineMarkerType>>, + devtools_marker_sender: RefCell<Option<Sender<TimelineMarker>>>, /// The JavaScript runtime. js_runtime: js::rust::rt, @@ -316,7 +323,7 @@ impl<'a> Drop for ScriptMemoryFailsafe<'a> { unsafe { let page = owner.page.borrow_for_script_deallocation(); for page in page.iter() { - let window = Unrooted::from_temporary(page.window()); + let window = page.window_for_script_deallocation(); (*window.unsafe_get()).clear_js_context_for_script_deallocation(); } *owner.js_context.borrow_for_script_deallocation() = None; @@ -446,6 +453,8 @@ impl ScriptTask { devtools_chan: devtools_chan, devtools_port: devtools_receiver, devtools_sender: devtools_sender, + devtools_markers: RefCell::new(HashSet::new()), + devtools_marker_sender: RefCell::new(None), js_runtime: js_runtime, js_context: DOMRefCell::new(Some(js_context)), @@ -455,46 +464,21 @@ impl ScriptTask { pub fn new_rt_and_cx() -> (js::rust::rt, Rc<Cx>) { LiveDOMReferences::initialize(); - let js_runtime = js::rust::rt(); - assert!({ - let ptr: *mut JSRuntime = (*js_runtime).ptr; - !ptr.is_null() - }); - - - unsafe { - JS_SetExtraGCRootsTracer((*js_runtime).ptr, Some(trace_collections), ptr::null_mut()); - } - // Unconstrain the runtime's threshold on nominal heap size, to avoid - // triggering GC too often if operating continuously near an arbitrary - // finite threshold. This leaves the maximum-JS_malloc-bytes threshold - // still in effect to cause periodical, and we hope hygienic, - // last-ditch GCs from within the GC's allocator. - unsafe { - JS_SetGCParameter(js_runtime.ptr, JSGC_MAX_BYTES, u32::MAX); - } + let runtime = Runtime::new(); - let js_context = js_runtime.cx(); - assert!({ - let ptr: *mut JSContext = (*js_context).ptr; - !ptr.is_null() - }); - js_context.set_default_options_and_version(); - js_context.set_logging_error_reporter(); unsafe { - JS_SetGCZeal((*js_context).ptr, 0, JS_DEFAULT_ZEAL_FREQ); - JS_SetExtraGCRootsTracer((*js_runtime).ptr, Some(trace_script_tasks), ptr::null_mut()); + JS_SetExtraGCRootsTracer(runtime.rt(), Some(trace_rust_roots), ptr::null_mut()); } // Needed for debug assertions about whether GC is running. if !cfg!(ndebug) { unsafe { - JS_SetGCCallback(js_runtime.ptr, + JS_SetGCCallback(runtime.rt(), Some(debug_gc_callback as unsafe extern "C" fn(*mut JSRuntime, JSGCStatus))); } } - (js_runtime, js_context) + (runtime.rt, runtime.cx) } // Return the root page in the frame tree. Panics if it doesn't exist. @@ -724,6 +708,10 @@ impl ScriptTask { devtools::handle_modify_attribute(&page, id, node_id, modifications), DevtoolScriptControlMsg::WantsLiveNotifications(pipeline_id, to_send) => devtools::handle_wants_live_notifications(&page, pipeline_id, to_send), + DevtoolScriptControlMsg::SetTimelineMarkers(_pipeline_id, marker_types, reply) => + devtools::handle_set_timeline_markers(&page, self, marker_types, reply), + DevtoolScriptControlMsg::DropTimelineMarkers(_pipeline_id, marker_types) => + devtools::handle_drop_timeline_markers(&page, self, marker_types), } } @@ -835,9 +823,8 @@ impl ScriptTask { let doc: JSRef<Node> = NodeCast::from_ref(doc.r()); doc.traverse_preorder() - .filter_map(HTMLIFrameElementCast::to_ref) - .find(|node| node.subpage_id() == Some(subpage_id)) - .map(Temporary::from_rooted) + .filter_map(HTMLIFrameElementCast::to_temporary) + .find(|node| node.root().r().subpage_id() == Some(subpage_id)) }).root(); if let Some(frame_element) = frame_element { @@ -856,9 +843,8 @@ impl ScriptTask { let doc: JSRef<Node> = NodeCast::from_ref(doc.r()); doc.traverse_preorder() - .filter_map(HTMLIFrameElementCast::to_ref) - .find(|node| node.subpage_id() == Some(old_subpage_id)) - .map(Temporary::from_rooted) + .filter_map(HTMLIFrameElementCast::to_temporary) + .find(|node| node.root().r().subpage_id() == Some(old_subpage_id)) }).root(); frame_element.unwrap().r().update_subpage_id(new_subpage_id); @@ -976,10 +962,9 @@ impl ScriptTask { let doc: JSRef<Node> = NodeCast::from_ref(doc.r()); doc.traverse_preorder() - .filter_map(HTMLIFrameElementCast::to_ref) - .find(|node| node.subpage_id() == Some(subpage_id)) - .map(ElementCast::from_ref) - .map(Temporary::from_rooted) + .filter_map(HTMLIFrameElementCast::to_temporary) + .find(|node| node.root().r().subpage_id() == Some(subpage_id)) + .map(ElementCast::from_temporary) }) }) }).root(); @@ -1032,7 +1017,7 @@ impl ScriptTask { match self.page { PageToRemove::Root => *self.script_task.page.borrow_mut() = None, PageToRemove::Child(id) => { - let _ = self.script_task.root_page().remove(id); + self.script_task.root_page().remove(id).unwrap(); } } } @@ -1177,8 +1162,14 @@ impl ScriptTask { /// /// TODO: Actually perform DOM event dispatch. fn handle_event(&self, pipeline_id: PipelineId, event: CompositorEvent) { + match event { ResizeEvent(new_size) => { + let _marker; + if self.need_emit_timeline_marker(TimelineMarkerType::DOMEvent) { + _marker = AutoDOMEventMarker::new(self); + } + self.handle_resize_event(pipeline_id, new_size); } @@ -1206,6 +1197,10 @@ impl ScriptTask { } ClickEvent(button, point) => { + let _marker; + if self.need_emit_timeline_marker(TimelineMarkerType::DOMEvent) { + _marker = AutoDOMEventMarker::new(self); + } let page = get_page(&self.root_page(), pipeline_id); let document = page.document().root(); document.r().handle_click_event(self.js_runtime.ptr, button, point); @@ -1214,6 +1209,10 @@ impl ScriptTask { MouseDownEvent(..) => {} MouseUpEvent(..) => {} MouseMoveEvent(point) => { + let _marker; + if self.need_emit_timeline_marker(TimelineMarkerType::DOMEvent) { + _marker = AutoDOMEventMarker::new(self); + } let page = get_page(&self.root_page(), pipeline_id); let document = page.document().root(); let mouse_over_targets = &mut *self.mouse_over_targets.borrow_mut(); @@ -1222,6 +1221,10 @@ impl ScriptTask { } KeyEvent(key, state, modifiers) => { + let _marker; + if self.need_emit_timeline_marker(TimelineMarkerType::DOMEvent) { + _marker = AutoDOMEventMarker::new(self); + } let page = get_page(&self.root_page(), pipeline_id); let document = page.document().root(); document.r().dispatch_key_event( @@ -1242,9 +1245,8 @@ impl ScriptTask { let doc: JSRef<Node> = NodeCast::from_ref(doc.r()); doc.traverse_preorder() - .filter_map(HTMLIFrameElementCast::to_ref) - .find(|node| node.subpage_id() == Some(subpage_id)) - .map(Temporary::from_rooted) + .filter_map(HTMLIFrameElementCast::to_temporary) + .find(|node| node.root().r().subpage_id() == Some(subpage_id)) }).root(); if let Some(iframe) = iframe.r() { iframe.navigate_child_browsing_context(load_data.url); @@ -1339,6 +1341,26 @@ impl ScriptTask { self.incomplete_loads.borrow_mut().push(incomplete); } + + fn need_emit_timeline_marker(&self, timeline_type: TimelineMarkerType) -> bool { + self.devtools_markers.borrow().contains(&timeline_type) + } + + fn emit_timeline_marker(&self, marker: TimelineMarker) { + let sender = self.devtools_marker_sender.borrow(); + let sender = sender.as_ref().expect("There is no marker sender"); + sender.send(marker).unwrap(); + } + + pub fn set_devtools_timeline_marker(&self, marker: TimelineMarkerType, reply: Sender<TimelineMarker>) { + *self.devtools_marker_sender.borrow_mut() = Some(reply); + self.devtools_markers.borrow_mut().insert(marker); + } + + pub fn drop_devtools_timeline_markers(&self) { + self.devtools_markers.borrow_mut().clear(); + *self.devtools_marker_sender.borrow_mut() = None; + } } impl Drop for ScriptTask { @@ -1349,6 +1371,28 @@ impl Drop for ScriptTask { } } +struct AutoDOMEventMarker<'a> { + script_task: &'a ScriptTask +} + +impl<'a> AutoDOMEventMarker<'a> { + fn new(script_task: &'a ScriptTask) -> AutoDOMEventMarker<'a> { + let marker = TimelineMarker::new("DOMEvent".to_owned(), TracingMetadata::IntervalStart); + script_task.emit_timeline_marker(marker); + AutoDOMEventMarker { + script_task: script_task + } + } +} + +#[unsafe_destructor] +impl<'a> Drop for AutoDOMEventMarker<'a> { + fn drop(&mut self) { + let marker = TimelineMarker::new("DOMEvent".to_owned(), TracingMetadata::IntervalEnd); + self.script_task.emit_timeline_marker(marker); + } +} + /// Shuts down layout for the given page tree. fn shut_down_layout(page_tree: &Rc<Page>, exit_type: PipelineExitType) { let mut channels = vec!(); diff --git a/components/script/tests.rs b/components/script/tests.rs deleted file mode 100644 index a0f82628cd6..00000000000 --- a/components/script/tests.rs +++ /dev/null @@ -1,48 +0,0 @@ -/* 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/. */ - -use dom::characterdata::CharacterData; -use dom::element::Element; -use dom::eventtarget::EventTarget; -use dom::htmldivelement::HTMLDivElement; -use dom::htmlelement::HTMLElement; -use dom::htmlspanelement::HTMLSpanElement; -use dom::node::Node; -use dom::text::Text; - -use std::mem::size_of; - -// Macro so that we can stringify type names -// I'd really prefer the tests themselves to be run at plugin time, -// however rustc::middle doesn't have access to the full type data -macro_rules! sizeof_checker ( - ($testname: ident, $t:ty, $known_size:expr) => ( - #[test] - fn $testname() { - let new = size_of::<$t>(); - let old = $known_size; - if new < old { - panic!("Your changes have decreased the stack size of commonly used DOM struct {} from {} to {}. \ - Good work! Please update the size in script/tests.rs", - stringify!($t), old, new) - } else if new > old { - panic!("Your changes have increased the stack size of commonly used DOM struct {} from {} to {}. \ - These structs are present in large quantities in the DOM, and increasing the size \ - may dramatically affect our memory footprint. Please consider choosing a design which \ - avoids this increase. If you feel that the increase is necessary, \ - update to the new size in script/tests.rs.", - stringify!($t), old, new) - } - }); -); - -// Update the sizes here -sizeof_checker!(size_event_target, EventTarget, 48); -sizeof_checker!(size_node, Node, 216); -sizeof_checker!(size_element, Element, 328); -sizeof_checker!(size_htmlelement, HTMLElement, 344); -sizeof_checker!(size_div, HTMLDivElement, 344); -sizeof_checker!(size_span, HTMLSpanElement, 344); -sizeof_checker!(size_text, Text, 248); -sizeof_checker!(size_characterdata, CharacterData, 248); diff --git a/components/script/textinput.rs b/components/script/textinput.rs index 8239c08c58f..c26549bff73 100644 --- a/components/script/textinput.rs +++ b/components/script/textinput.rs @@ -15,18 +15,18 @@ use std::default::Default; use std::num::SignedInt; #[derive(Copy, PartialEq)] -enum Selection { +pub enum Selection { Selected, NotSelected } #[jstraceable] #[derive(Copy)] -struct TextPoint { +pub struct TextPoint { /// 0-based line number - line: usize, + pub line: usize, /// 0-based column number - index: usize, + pub index: usize, } /// Encapsulated state for handling keyboard input in a single or multiline text input control. @@ -35,7 +35,7 @@ pub struct TextInput { /// Current text input content, split across lines without trailing '\n' lines: Vec<DOMString>, /// Current cursor input point - edit_point: TextPoint, + pub edit_point: TextPoint, /// Beginning of selection range with edit_point as end that can span multiple lines. selection_begin: Option<TextPoint>, /// Is this a multiline input? @@ -67,7 +67,7 @@ pub enum Lines { /// The direction in which to delete a character. #[derive(PartialEq)] -enum DeleteDir { +pub enum DeleteDir { Forward, Backward } @@ -99,7 +99,7 @@ impl TextInput { } /// Remove a character at the current editing point - fn delete_char(&mut self, dir: DeleteDir) { + pub fn delete_char(&mut self, dir: DeleteDir) { if self.selection_begin.is_none() { self.adjust_horizontal(if dir == DeleteDir::Forward { 1 @@ -111,14 +111,14 @@ impl TextInput { } /// Insert a character at the current editing point - fn insert_char(&mut self, ch: char) { + pub fn insert_char(&mut self, ch: char) { if self.selection_begin.is_none() { self.selection_begin = Some(self.edit_point); } self.replace_selection(ch.to_string()); } - fn get_sorted_selection(&self) -> (TextPoint, TextPoint) { + pub fn get_sorted_selection(&self) -> (TextPoint, TextPoint) { let begin = self.selection_begin.unwrap(); let end = self.edit_point; @@ -129,7 +129,7 @@ impl TextInput { } } - fn replace_selection(&mut self, insert: String) { + pub fn replace_selection(&mut self, insert: String) { let (begin, end) = self.get_sorted_selection(); self.clear_selection(); @@ -166,13 +166,13 @@ impl TextInput { } /// Return the length of the current line under the editing point. - fn current_line_length(&self) -> usize { + pub fn current_line_length(&self) -> usize { self.lines[self.edit_point.line].chars().count() } /// Adjust the editing point position by a given of lines. The resulting column is /// as close to the original column position as possible. - fn adjust_vertical(&mut self, adjust: isize, select: Selection) { + pub fn adjust_vertical(&mut self, adjust: isize, select: Selection) { if !self.multiline { return; } @@ -206,7 +206,7 @@ impl TextInput { /// Adjust the editing point position by a given number of columns. If the adjustment /// requested is larger than is available in the current line, the editing point is /// adjusted vertically and the process repeats with the remaining adjustment requested. - fn adjust_horizontal(&mut self, adjust: isize, select: Selection) { + pub fn adjust_horizontal(&mut self, adjust: isize, select: Selection) { if select == Selection::Selected { if self.selection_begin.is_none() { self.selection_begin = Some(self.edit_point); @@ -244,7 +244,7 @@ impl TextInput { } /// Deal with a newline input. - fn handle_return(&mut self) -> KeyReaction { + pub fn handle_return(&mut self) -> KeyReaction { if !self.multiline { return KeyReaction::TriggerDefaultAction; } @@ -253,7 +253,7 @@ impl TextInput { } /// Select all text in the input control. - fn select_all(&mut self) { + pub fn select_all(&mut self) { self.selection_begin = Some(TextPoint { line: 0, index: 0, @@ -264,7 +264,7 @@ impl TextInput { } /// Remove the current selection. - fn clear_selection(&mut self) { + pub fn clear_selection(&mut self) { self.selection_begin = None; } @@ -361,159 +361,3 @@ impl TextInput { self.edit_point.index = min(self.edit_point.index, self.current_line_length()); } } - -#[test] -fn test_textinput_delete_char() { - let mut textinput = TextInput::new(Lines::Single, "abcdefg".to_owned()); - textinput.adjust_horizontal(2, Selection::NotSelected); - textinput.delete_char(DeleteDir::Backward); - assert_eq!(textinput.get_content(), "acdefg"); - - textinput.delete_char(DeleteDir::Forward); - assert_eq!(textinput.get_content(), "adefg"); - - textinput.adjust_horizontal(2, Selection::Selected); - textinput.delete_char(DeleteDir::Forward); - assert_eq!(textinput.get_content(), "afg"); -} - -#[test] -fn test_textinput_insert_char() { - let mut textinput = TextInput::new(Lines::Single, "abcdefg".to_owned()); - textinput.adjust_horizontal(2, Selection::NotSelected); - textinput.insert_char('a'); - assert_eq!(textinput.get_content(), "abacdefg"); - - textinput.adjust_horizontal(2, Selection::Selected); - textinput.insert_char('b'); - assert_eq!(textinput.get_content(), "ababefg"); -} - -#[test] -fn test_textinput_get_sorted_selection() { - let mut textinput = TextInput::new(Lines::Single, "abcdefg".to_owned()); - textinput.adjust_horizontal(2, Selection::NotSelected); - textinput.adjust_horizontal(2, Selection::Selected); - let (begin, end) = textinput.get_sorted_selection(); - assert_eq!(begin.index, 2); - assert_eq!(end.index, 4); - - textinput.clear_selection(); - - textinput.adjust_horizontal(-2, Selection::Selected); - let (begin, end) = textinput.get_sorted_selection(); - assert_eq!(begin.index, 2); - assert_eq!(end.index, 4); -} - -#[test] -fn test_textinput_replace_selection() { - let mut textinput = TextInput::new(Lines::Single, "abcdefg".to_owned()); - textinput.adjust_horizontal(2, Selection::NotSelected); - textinput.adjust_horizontal(2, Selection::Selected); - - textinput.replace_selection("xyz".to_owned()); - assert_eq!(textinput.get_content(), "abxyzefg"); -} - -#[test] -fn test_textinput_current_line_length() { - let mut textinput = TextInput::new(Lines::Multiple, "abc\nde\nf".to_owned()); - assert_eq!(textinput.current_line_length(), 3); - - textinput.adjust_vertical(1, Selection::NotSelected); - assert_eq!(textinput.current_line_length(), 2); - - textinput.adjust_vertical(1, Selection::NotSelected); - assert_eq!(textinput.current_line_length(), 1); -} - -#[test] -fn test_textinput_adjust_vertical() { - let mut textinput = TextInput::new(Lines::Multiple, "abc\nde\nf".to_owned()); - textinput.adjust_horizontal(3, Selection::NotSelected); - textinput.adjust_vertical(1, Selection::NotSelected); - assert_eq!(textinput.edit_point.line, 1); - assert_eq!(textinput.edit_point.index, 2); - - textinput.adjust_vertical(-1, Selection::NotSelected); - assert_eq!(textinput.edit_point.line, 0); - assert_eq!(textinput.edit_point.index, 2); - - textinput.adjust_vertical(2, Selection::NotSelected); - assert_eq!(textinput.edit_point.line, 2); - assert_eq!(textinput.edit_point.index, 1); -} - -#[test] -fn test_textinput_adjust_horizontal() { - let mut textinput = TextInput::new(Lines::Multiple, "abc\nde\nf".to_owned()); - textinput.adjust_horizontal(4, Selection::NotSelected); - assert_eq!(textinput.edit_point.line, 1); - assert_eq!(textinput.edit_point.index, 0); - - textinput.adjust_horizontal(1, Selection::NotSelected); - assert_eq!(textinput.edit_point.line, 1); - assert_eq!(textinput.edit_point.index, 1); - - textinput.adjust_horizontal(2, Selection::NotSelected); - assert_eq!(textinput.edit_point.line, 2); - assert_eq!(textinput.edit_point.index, 0); - - textinput.adjust_horizontal(-1, Selection::NotSelected); - assert_eq!(textinput.edit_point.line, 1); - assert_eq!(textinput.edit_point.index, 2); -} - -#[test] -fn test_textinput_handle_return() { - let mut single_line_textinput = TextInput::new(Lines::Single, "abcdef".to_owned()); - single_line_textinput.adjust_horizontal(3, Selection::NotSelected); - single_line_textinput.handle_return(); - assert_eq!(single_line_textinput.get_content(), "abcdef"); - - let mut multi_line_textinput = TextInput::new(Lines::Multiple, "abcdef".to_owned()); - multi_line_textinput.adjust_horizontal(3, Selection::NotSelected); - multi_line_textinput.handle_return(); - assert_eq!(multi_line_textinput.get_content(), "abc\ndef"); -} - -#[test] -fn test_textinput_select_all() { - let mut textinput = TextInput::new(Lines::Multiple, "abc\nde\nf".to_owned()); - assert_eq!(textinput.edit_point.line, 0); - assert_eq!(textinput.edit_point.index, 0); - - textinput.select_all(); - assert_eq!(textinput.edit_point.line, 2); - assert_eq!(textinput.edit_point.index, 1); -} - -#[test] -fn test_textinput_get_content() { - let single_line_textinput = TextInput::new(Lines::Single, "abcdefg".to_owned()); - assert_eq!(single_line_textinput.get_content(), "abcdefg"); - - let multi_line_textinput = TextInput::new(Lines::Multiple, "abc\nde\nf".to_owned()); - assert_eq!(multi_line_textinput.get_content(), "abc\nde\nf"); -} - -#[test] -fn test_textinput_set_content() { - let mut textinput = TextInput::new(Lines::Multiple, "abc\nde\nf".to_owned()); - assert_eq!(textinput.get_content(), "abc\nde\nf"); - - textinput.set_content("abc\nf".to_owned()); - assert_eq!(textinput.get_content(), "abc\nf"); - - assert_eq!(textinput.edit_point.line, 0); - assert_eq!(textinput.edit_point.index, 0); - textinput.adjust_horizontal(3, Selection::Selected); - assert_eq!(textinput.edit_point.line, 0); - assert_eq!(textinput.edit_point.index, 3); - textinput.set_content("de".to_owned()); - assert_eq!(textinput.get_content(), "de"); - assert_eq!(textinput.edit_point.line, 0); - assert_eq!(textinput.edit_point.index, 2); -} - |