Refactoring and window.document updates (for bug 680597)
This commit is contained in:
Родитель
15b110b16b
Коммит
b355e6a96d
26
jsmain.py
26
jsmain.py
|
@ -7,6 +7,7 @@ from validator.errorbundler import ErrorBundle
|
|||
from validator.outputhandlers.shellcolors import OutputHandler
|
||||
import validator.testcases.scripting as scripting
|
||||
import validator.testcases.javascript.traverser
|
||||
from validator.testcases.javascript.predefinedentities import GLOBAL_ENTITIES
|
||||
import validator.testcases.javascript.spidermonkey as spidermonkey
|
||||
validator.testcases.javascript.traverser.DEBUG = True
|
||||
|
||||
|
@ -23,6 +24,31 @@ if __name__ == '__main__':
|
|||
else:
|
||||
trav = validator.testcases.javascript.traverser.Traverser(err, "stdin")
|
||||
trav._push_context()
|
||||
|
||||
def do_inspect(wrapper, arguments, traverser):
|
||||
print "~" * 50
|
||||
for arg in arguments:
|
||||
if arg["type"] == "Identifier":
|
||||
print 'Identifier: "%s"' % arg["name"]
|
||||
else:
|
||||
print arg["type"]
|
||||
|
||||
a = traverser._traverse_node(arg)
|
||||
print a.output()
|
||||
|
||||
if a.is_global:
|
||||
print a.value
|
||||
print "Context: %s" % a.context
|
||||
print "<"
|
||||
print "~" * 50
|
||||
|
||||
def do_exit(wrapper, arguments, traverser):
|
||||
print "Goodbye!"
|
||||
sys.exit()
|
||||
|
||||
GLOBAL_ENTITIES[u"inspect"] = {"return": do_inspect}
|
||||
GLOBAL_ENTITIES[u"exit"] = {"return": do_exit}
|
||||
|
||||
while True:
|
||||
line = sys.stdin.readline()
|
||||
|
||||
|
|
|
@ -28,11 +28,21 @@ def _expand_globals(traverser, node):
|
|||
|
||||
result = node.value["value"](t=traverser)
|
||||
if isinstance(result, dict):
|
||||
return traverser._build_global("--", result)
|
||||
output = traverser._build_global("--", result)
|
||||
elif isinstance(result, JSWrapper):
|
||||
return result
|
||||
output = result
|
||||
else:
|
||||
return JSWrapper(result, traverser)
|
||||
output = JSWrapper(result, traverser)
|
||||
|
||||
# Set the node context.
|
||||
if "context" in node.value:
|
||||
traverser._debug("CONTEXT>>%s" % node.value["context"])
|
||||
output.context = node.value["context"]
|
||||
else:
|
||||
traverser._debug("CONTEXT>>INHERITED")
|
||||
output.context = node.context
|
||||
|
||||
return output
|
||||
|
||||
return node
|
||||
|
||||
|
@ -47,20 +57,25 @@ def trace_member(traverser, node, instantiate=False):
|
|||
base = trace_member(traverser, node["object"], instantiate)
|
||||
base = _expand_globals(traverser, base)
|
||||
|
||||
# If we've got an XPCOM wildcard, just return the base, minus the WC
|
||||
if base.is_global and "xpcom_wildcard" in base.value:
|
||||
traverser._debug("MEMBER_EXP>>XPCOM_WILDCARD")
|
||||
base.value = base.value.copy()
|
||||
del base.value["xpcom_wildcard"]
|
||||
return base
|
||||
# Handle the various global entity properties.
|
||||
if base.is_global:
|
||||
# If we've got an XPCOM wildcard, return a copy of the entity.
|
||||
if "xpcom_wildcard" in base.value:
|
||||
traverser._debug("MEMBER_EXP>>XPCOM_WILDCARD")
|
||||
base.value = base.value.copy()
|
||||
del base.value["xpcom_wildcard"]
|
||||
return base
|
||||
|
||||
identifier = _get_member_exp_property(traverser, node)
|
||||
test_identifier(traverser, identifier)
|
||||
|
||||
traverser._debug("MEMBER_EXP>>PROPERTY: %s" % identifier)
|
||||
return base.get(traverser=traverser,
|
||||
instantiate=instantiate,
|
||||
name=identifier)
|
||||
output = base.get(traverser=traverser,
|
||||
instantiate=instantiate,
|
||||
name=identifier)
|
||||
output.context = base.context
|
||||
return output
|
||||
|
||||
elif node["type"] == "Identifier":
|
||||
traverser._debug("MEMBER_EXP>>ROOT:IDENTIFIER")
|
||||
test_identifier(traverser, node["name"])
|
||||
|
@ -318,9 +333,7 @@ def _define_literal(traverser, node):
|
|||
|
||||
def _call_expression(traverser, node):
|
||||
args = node["arguments"]
|
||||
|
||||
for arg in args:
|
||||
traverser._traverse_node(arg)
|
||||
map(traverser._traverse_node, args)
|
||||
|
||||
member = traverser._traverse_node(node["callee"])
|
||||
if (member.is_global and
|
||||
|
@ -357,15 +370,12 @@ def _call_expression(traverser, node):
|
|||
identifier_name = node["callee"]["property"]["name"]
|
||||
if identifier_name in instanceactions.INSTANCE_DEFINITIONS:
|
||||
result = instanceactions.INSTANCE_DEFINITIONS[identifier_name](
|
||||
args, traverser, node)
|
||||
args, traverser, node, wrapper=member)
|
||||
return result
|
||||
|
||||
|
||||
if member.is_global and "return" in member.value:
|
||||
return member.value["return"](wrapper=member,
|
||||
arguments=args,
|
||||
return member.value["return"](wrapper=member, arguments=args,
|
||||
traverser=traverser)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
|
@ -411,10 +421,9 @@ def _expression(traverser, node):
|
|||
|
||||
def _get_this(traverser, node):
|
||||
"Returns the `this` object"
|
||||
|
||||
if not traverser.this_stack:
|
||||
return JSWrapper(traverser=traverser)
|
||||
|
||||
from predefinedentities import GLOBAL_ENTITIES
|
||||
return traverser._build_global(GLOBAL_ENTITIES[u"window"])
|
||||
return traverser.this_stack[-1]
|
||||
|
||||
|
||||
|
|
|
@ -9,13 +9,14 @@ traverser
|
|||
node
|
||||
the current node being evaluated
|
||||
"""
|
||||
|
||||
import types
|
||||
|
||||
import actions
|
||||
from jstypes import *
|
||||
|
||||
|
||||
def createElement(args, traverser, node):
|
||||
def createElement(args, traverser, node, wrapper):
|
||||
"""Handles createElement calls"""
|
||||
|
||||
if not args:
|
||||
|
@ -30,7 +31,7 @@ def createElement(args, traverser, node):
|
|||
_create_variable_element(traverser)
|
||||
|
||||
|
||||
def createElementNS(args, traverser, node):
|
||||
def createElementNS(args, traverser, node, wrapper):
|
||||
"""Handles createElementNS calls"""
|
||||
|
||||
if not args or len(args) < 2:
|
||||
|
@ -45,7 +46,7 @@ def createElementNS(args, traverser, node):
|
|||
_create_variable_element(traverser)
|
||||
|
||||
|
||||
def QueryInterface(args, traverser, node):
|
||||
def QueryInterface(args, traverser, node, wrapper):
|
||||
"""Handles QueryInterface calls"""
|
||||
|
||||
if not args:
|
||||
|
@ -57,7 +58,7 @@ def QueryInterface(args, traverser, node):
|
|||
arguments=args,
|
||||
traverser=traverser)
|
||||
|
||||
def getInterface(args, traverser, node):
|
||||
def getInterface(args, traverser, node, wrapper):
|
||||
"""Handles getInterface calls"""
|
||||
|
||||
# This really only needs to be handled for nsIInterfaceRequestor
|
||||
|
@ -109,7 +110,7 @@ def _create_variable_element(traverser):
|
|||
context=traverser.context)
|
||||
|
||||
|
||||
def setAttribute(args, traverser, node):
|
||||
def setAttribute(args, traverser, node, wrapper):
|
||||
"""This ensures that setAttribute calls don't set on* attributes"""
|
||||
|
||||
if not args:
|
||||
|
@ -132,7 +133,7 @@ def setAttribute(args, traverser, node):
|
|||
context=traverser.context)
|
||||
|
||||
|
||||
def nsIDOMFile_deprec(args, traverser, node):
|
||||
def nsIDOMFile_deprec(args, traverser, node, wrapper):
|
||||
"""A wrapper for call_definitions.nsIDOMFile_deprec."""
|
||||
from call_definitions import nsIDOMFile_deprec as cd_nsIDOMFile_deprec
|
||||
cd_nsIDOMFile_deprec(None, [], traverser)
|
||||
|
|
|
@ -82,7 +82,7 @@ class JSWrapper(object):
|
|||
|
||||
def __init__(self, value=None, const=False, dirty=False, lazy=False,
|
||||
is_global=False, traverser=None, callable=False,
|
||||
setter=None):
|
||||
setter=None, context="chrome"):
|
||||
|
||||
if is_global:
|
||||
assert not value
|
||||
|
@ -99,6 +99,7 @@ class JSWrapper(object):
|
|||
self.value = None # Instantiate the placeholder value
|
||||
self.is_global = False # Not yet......
|
||||
self.dirty = False # Also not yet...
|
||||
self.context = context
|
||||
|
||||
# Used for predetermining set operations
|
||||
self.setter = setter
|
||||
|
@ -145,12 +146,10 @@ class JSWrapper(object):
|
|||
|
||||
# We want to obey the permissions of global objects
|
||||
if (self.is_global and
|
||||
(not traverser or
|
||||
not traverser.is_jsm) and
|
||||
(not traverser or not traverser.is_jsm) and
|
||||
(isinstance(self.value, dict) and
|
||||
("overwritable" not in self.value or
|
||||
self.value["overwritable"] == False))):
|
||||
# TODO : Write in support for "readonly":False
|
||||
traverser.err.warning(("testcases_javascript_jstypes",
|
||||
"JSWrapper_set_value",
|
||||
"global_overwrite"),
|
||||
|
@ -171,6 +170,7 @@ class JSWrapper(object):
|
|||
self.lazy = value.lazy
|
||||
self.dirty = value.dirty
|
||||
self.is_global = value.is_global
|
||||
self.context = value.context
|
||||
# const does not carry over on reassignment
|
||||
return self
|
||||
elif isinstance(value, types.LambdaType):
|
||||
|
@ -178,45 +178,36 @@ class JSWrapper(object):
|
|||
|
||||
if not isinstance(value, dict):
|
||||
self.is_global = False
|
||||
elif "context" in value:
|
||||
self.context = value["context"]
|
||||
|
||||
self.value = value
|
||||
return self
|
||||
|
||||
def set_value_from_expression(self, traverser, node):
|
||||
"""Sets the value of the variable from a node object"""
|
||||
|
||||
self.set_value(traverser._traverse_node(node),
|
||||
traverser=traverser)
|
||||
|
||||
def has_property(self, property):
|
||||
"""Returns a boolean value representing the presence of a property"""
|
||||
|
||||
if self.value is None:
|
||||
return False
|
||||
|
||||
if isinstance(self.value, JSLiteral):
|
||||
return False
|
||||
elif isinstance(self.value, (JSObject, JSPrototype)):
|
||||
# JSPrototype and JSObject always have a value
|
||||
return True
|
||||
return isinstance(self.value, JSObject)
|
||||
|
||||
def get(self, traverser, name, instantiate=False):
|
||||
"""Retrieves a property from the variable"""
|
||||
|
||||
value = self.value
|
||||
dirty = value is None
|
||||
context = self.context
|
||||
if self.is_global:
|
||||
if "value" not in value:
|
||||
output = JSWrapper(traverser=traverser)
|
||||
output = JSWrapper(JSObject(), traverser=traverser)
|
||||
output.value = {}
|
||||
|
||||
def apply_value(name):
|
||||
if name in self.value:
|
||||
output.value[name] = self.value[name]
|
||||
|
||||
apply_value("dangerous")
|
||||
apply_value("readonly")
|
||||
map(apply_value, ("dangerous", "readonly", "context"))
|
||||
output.is_global = True
|
||||
output.context = self.context
|
||||
return output
|
||||
|
||||
def _evaluate_lambdas(node):
|
||||
|
@ -231,8 +222,11 @@ class JSWrapper(object):
|
|||
if isinstance(value_val, dict):
|
||||
if name in value_val:
|
||||
value_val = _evaluate_lambdas(value_val[name])
|
||||
return traverser._build_global(name=name,
|
||||
entity=value_val)
|
||||
output = traverser._build_global(name=name,
|
||||
entity=value_val)
|
||||
if "context" not in value_val:
|
||||
output.context = self.context
|
||||
return output
|
||||
else:
|
||||
value = value_val
|
||||
|
||||
|
@ -252,6 +246,8 @@ class JSWrapper(object):
|
|||
traverser=traverser,
|
||||
dirty=output is None or dirty)
|
||||
|
||||
output.context = context
|
||||
|
||||
# If we can predetermine the setter for the wrapper, we can save a ton
|
||||
# of lookbehinds in the future. This greatly simplifies the
|
||||
# MemberExpression support.
|
||||
|
|
|
@ -16,15 +16,10 @@ BANNED_IDENTIFIERS = {
|
|||
"instead wherever possible",
|
||||
}
|
||||
|
||||
# For "dangerous" elements, specifying True will throw an error on all
|
||||
# detected instances of the particular object. Specifying a lambda function
|
||||
# will allow the object to be referenced. If the object is called via a
|
||||
# CallExpression, "a" will contain the raw arguments values and "t" will
|
||||
# contain a reference to traverser._traverse_node(). "t" will always return a
|
||||
# JSWrapper object. The optional third argument "e" will be an ErrorBundle
|
||||
# object. The return value of the lambda function will be used as the value for
|
||||
# the "dangerous" property. Lastly, specifying a string functions identically to
|
||||
# "True", except the string will be outputted when the error is thrown.
|
||||
# See https://github.com/mattbasta/amo-validator/wiki/JS-Predefined-Entities
|
||||
# for details on entity properties.
|
||||
|
||||
CONTENT_DOCUMENT = None
|
||||
|
||||
INTERFACES = {
|
||||
u"nsICategoryManager":
|
||||
|
@ -179,31 +174,32 @@ GLOBAL_ENTITIES = {
|
|||
t)}}},
|
||||
|
||||
u"document":
|
||||
{"value": {u"title":
|
||||
{"overwritable": True,
|
||||
"readonly": False},
|
||||
u"createElement":
|
||||
{"dangerous":
|
||||
lambda a, t, e:
|
||||
not a or
|
||||
_get_as_str(t(a[0]).get_literal_value())
|
||||
.lower() == "script"},
|
||||
u"createElementNS":
|
||||
{"dangerous":
|
||||
lambda a, t, e:
|
||||
not a or
|
||||
_get_as_str(t(a[0]).get_literal_value())
|
||||
.lower() == "script"},
|
||||
u"getSelection":
|
||||
{"return": call_definitions.document_getSelection},
|
||||
u"loadOverlay":
|
||||
{"dangerous":
|
||||
lambda a, t, e:
|
||||
not a or
|
||||
not _get_as_str(t(a[0]).get_literal_value())
|
||||
.lower()
|
||||
.startswith(("chrome:",
|
||||
"resource:"))}}},
|
||||
{"value":
|
||||
{u"title":
|
||||
{"overwriteable": True,
|
||||
"readonly": False},
|
||||
u"defaultView":
|
||||
{"value": lambda t: {"value": GLOBAL_ENTITIES}},
|
||||
u"createElement":
|
||||
{"dangerous":
|
||||
lambda a, t, e:
|
||||
not a or
|
||||
unicode(t(a[0]).get_literal_value()).lower() ==
|
||||
"script"},
|
||||
u"createElementNS":
|
||||
{"dangerous":
|
||||
lambda a, t, e:
|
||||
not a or
|
||||
unicode(t(a[0]).get_literal_value()).lower() ==
|
||||
"script"},
|
||||
u"getSelection":
|
||||
{"return": call_definitions.document_getSelection},
|
||||
u"loadOverlay":
|
||||
{"dangerous":
|
||||
lambda a, t, e:
|
||||
not a or
|
||||
not unicode(t(a[0]).get_literal_value()).lower()
|
||||
.startswith(("chrome:", "resource:"))}}},
|
||||
|
||||
# The nefariuos timeout brothers!
|
||||
u"setTimeout": {"dangerous": actions._call_settimeout},
|
||||
|
@ -414,16 +410,16 @@ GLOBAL_ENTITIES = {
|
|||
|
||||
u"XMLHttpRequest":
|
||||
{"value":
|
||||
{u"open": {"dangerous":
|
||||
# Ban syncrhonous XHR by making sure the third arg
|
||||
# is not absent and falsey.
|
||||
lambda a, t, e:
|
||||
a and len(a) >= 3 and
|
||||
not t(a[2]).get_literal_value() and
|
||||
"Synchronous HTTP requests can cause "
|
||||
"serious UI performance problems, "
|
||||
"especially to users with slow network "
|
||||
"connections."}}},
|
||||
{u"open":
|
||||
{"dangerous":
|
||||
# Ban syncrhonous XHR by making sure the third arg
|
||||
# is absent and false.
|
||||
lambda a, t, e:
|
||||
a and len(a) >= 3 and
|
||||
not t(a[2]).get_literal_value() and
|
||||
"Synchronous HTTP requests can cause serious UI "
|
||||
"performance problems, especially to users with "
|
||||
"slow network connections."}}},
|
||||
|
||||
# Global properties are inherently read-only, though this formalizes it.
|
||||
u"Infinity":
|
||||
|
@ -439,5 +435,29 @@ GLOBAL_ENTITIES = {
|
|||
u"width": {"readonly": False},
|
||||
u"height": {"readonly": False},
|
||||
u"top": {"readonly": actions._readonly_top},
|
||||
|
||||
u"content":
|
||||
{"context": "content",
|
||||
"value":
|
||||
{u"document":
|
||||
{"value": lambda t: GLOBAL_ENTITIES[u"document"]}}},
|
||||
u"contentWindow":
|
||||
{"context": "content",
|
||||
"value":
|
||||
lambda t: {"value": GLOBAL_ENTITIES}},
|
||||
u"_content": {"value": lambda t: GLOBAL_ENTITIES[u"content"]},
|
||||
u"gBrowser":
|
||||
{"value":
|
||||
{u"contentDocument":
|
||||
{"context": "content",
|
||||
"value": lambda t: CONTENT_DOCUMENT},
|
||||
u"contentWindow":
|
||||
{"value":
|
||||
lambda t: {"value": GLOBAL_ENTITIES}}}},
|
||||
u"opener":
|
||||
{"value":
|
||||
lambda t: {"value": GLOBAL_ENTITIES}}
|
||||
}
|
||||
|
||||
CONTENT_DOCUMENT = GLOBAL_ENTITIES[u"content"]["value"][u"document"]
|
||||
|
||||
|
|
|
@ -285,28 +285,19 @@ class Traverser:
|
|||
if depth == -1:
|
||||
return JSWrapper(traverser=self)
|
||||
|
||||
def _is_global(self, name, globs=None):
|
||||
def _is_global(self, name):
|
||||
"Returns whether a name is a global entity"
|
||||
return name in GLOBAL_ENTITIES
|
||||
|
||||
if globs is None:
|
||||
globs = GLOBAL_ENTITIES
|
||||
|
||||
return name in globs
|
||||
|
||||
def _get_global(self, name, globs=None):
|
||||
def _get_global(self, name):
|
||||
"Gets a variable from the predefined variable context."
|
||||
|
||||
# Allow overriding of the global entities
|
||||
if globs is None:
|
||||
globs = GLOBAL_ENTITIES
|
||||
|
||||
self._debug("SEEK_GLOBAL>>%s" % name)
|
||||
if not self._is_global(name, globs):
|
||||
if not self._is_global(name):
|
||||
self._debug("SEEK_GLOBAL>>FAILED")
|
||||
return JSWrapper(traverser=self)
|
||||
|
||||
self._debug("SEEK_GLOBAL>>FOUND>>%s" % name)
|
||||
return self._build_global(name, globs[name])
|
||||
return self._build_global(name, GLOBAL_ENTITIES[name])
|
||||
|
||||
def _build_global(self, name, entity):
|
||||
"Builds an object based on an entity from the predefined entity list"
|
||||
|
@ -334,6 +325,9 @@ class Traverser:
|
|||
result.value = entity
|
||||
result = actions._expand_globals(self, result)
|
||||
|
||||
if "context" in entity:
|
||||
result.context = entity["context"]
|
||||
|
||||
self._debug("BUILT_GLOBAL")
|
||||
|
||||
return result
|
||||
|
|
Загрузка…
Ссылка в новой задаче