aboutsummaryrefslogtreecommitdiffstats
path: root/libmproxy
diff options
context:
space:
mode:
authorJim Shaver <dcypherd@gmail.com>2015-05-31 01:21:44 -0400
committerJim Shaver <dcypherd@gmail.com>2015-05-31 01:21:44 -0400
commitb51363b3ca43f6572acb673186e6ae78a1f48434 (patch)
treea7488b32871c142141a813dc6ff2ede172672c31 /libmproxy
parent4fe2c069cca07aadf983f54e18dac4de492d5d69 (diff)
parent06fba18106a8f759ec6f08453e86772a170c653b (diff)
downloadmitmproxy-b51363b3ca43f6572acb673186e6ae78a1f48434.tar.gz
mitmproxy-b51363b3ca43f6572acb673186e6ae78a1f48434.tar.bz2
mitmproxy-b51363b3ca43f6572acb673186e6ae78a1f48434.zip
Merge remote-tracking branch 'upstream/master' into print-bracket-fix
Conflicts: examples/har_extractor.py examples/nonblocking.py examples/read_dumpfile libmproxy/web/app.py
Diffstat (limited to 'libmproxy')
-rw-r--r--libmproxy/cmdline.py34
-rw-r--r--libmproxy/console/__init__.py789
-rw-r--r--libmproxy/console/common.py172
-rw-r--r--libmproxy/console/contentview.py53
-rw-r--r--libmproxy/console/flowdetailview.py233
-rw-r--r--libmproxy/console/flowlist.py157
-rw-r--r--libmproxy/console/flowview.py983
-rw-r--r--libmproxy/console/grideditor.py354
-rw-r--r--libmproxy/console/help.py125
-rw-r--r--libmproxy/console/options.py269
-rw-r--r--libmproxy/console/palettepicker.py81
-rw-r--r--libmproxy/console/palettes.py115
-rw-r--r--libmproxy/console/pathedit.py69
-rw-r--r--libmproxy/console/searchable.py91
-rw-r--r--libmproxy/console/select.py115
-rw-r--r--libmproxy/console/signals.py32
-rw-r--r--libmproxy/console/statusbar.py254
-rw-r--r--libmproxy/console/tabs.py39
-rw-r--r--libmproxy/console/window.py72
-rw-r--r--libmproxy/controller.py11
-rw-r--r--libmproxy/dump.py29
-rw-r--r--libmproxy/encoding.py10
-rw-r--r--libmproxy/filt.py80
-rw-r--r--libmproxy/flow.py120
-rw-r--r--libmproxy/main.py16
-rw-r--r--libmproxy/onboarding/app.py13
-rw-r--r--libmproxy/platform/__init__.py4
-rw-r--r--libmproxy/platform/linux.py3
-rw-r--r--libmproxy/platform/osx.py5
-rw-r--r--libmproxy/platform/pf.py4
-rw-r--r--libmproxy/platform/windows.py76
-rw-r--r--libmproxy/protocol/__init__.py2
-rw-r--r--libmproxy/protocol/handle.py5
-rw-r--r--libmproxy/protocol/http.py396
-rw-r--r--libmproxy/protocol/primitives.py13
-rw-r--r--libmproxy/protocol/tcp.py3
-rw-r--r--libmproxy/proxy/config.py72
-rw-r--r--libmproxy/proxy/connection.py32
-rw-r--r--libmproxy/proxy/primitives.py15
-rw-r--r--libmproxy/proxy/server.py95
-rw-r--r--libmproxy/script.py35
-rw-r--r--libmproxy/tnetstring.py92
-rw-r--r--libmproxy/utils.py118
-rw-r--r--libmproxy/version.py2
-rw-r--r--libmproxy/web/__init__.py12
-rw-r--r--libmproxy/web/app.py67
-rw-r--r--libmproxy/web/static/app.css66
-rw-r--r--libmproxy/web/static/app.js2302
-rw-r--r--libmproxy/web/static/vendor.css46
-rw-r--r--libmproxy/web/static/vendor.js12959
50 files changed, 12547 insertions, 8193 deletions
diff --git a/libmproxy/cmdline.py b/libmproxy/cmdline.py
index ece476f2..eb24bed7 100644
--- a/libmproxy/cmdline.py
+++ b/libmproxy/cmdline.py
@@ -65,7 +65,7 @@ def parse_replace_hook(s):
patt, regex, replacement = _parse_hook(s)
try:
re.compile(regex)
- except re.error, e:
+ except re.error as e:
raise ParseException("Malformed replacement regex: %s" % str(e.message))
return patt, regex, replacement
@@ -127,7 +127,6 @@ def parse_server_spec_special(url):
return ret
-
def get_common_options(options):
stickycookie, stickyauth = None, None
if options.stickycookie_filt:
@@ -142,17 +141,17 @@ def get_common_options(options):
for i in options.replace:
try:
p = parse_replace_hook(i)
- except ParseException, e:
+ except ParseException as e:
raise configargparse.ArgumentTypeError(e.message)
reps.append(p)
for i in options.replace_file:
try:
patt, rex, path = parse_replace_hook(i)
- except ParseException, e:
+ except ParseException as e:
raise configargparse.ArgumentTypeError(e.message)
try:
v = open(path, "rb").read()
- except IOError, e:
+ except IOError as e:
raise configargparse.ArgumentTypeError(
"Could not read replace file: %s" % path
)
@@ -162,7 +161,7 @@ def get_common_options(options):
for i in options.setheader:
try:
p = parse_setheader(i)
- except ParseException, e:
+ except ParseException as e:
raise configargparse.ArgumentTypeError(e.message)
setheaders.append(p)
@@ -221,7 +220,7 @@ def common_options(parser):
parser.add_argument(
"--cadir",
action="store", type=str, dest="cadir", default=config.CA_DIR,
- help="Location of the default mitmproxy CA files. (%s)"%config.CA_DIR
+ help="Location of the default mitmproxy CA files. (%s)" % config.CA_DIR
)
parser.add_argument(
"--host",
@@ -466,7 +465,7 @@ def common_options(parser):
"--replay-ignore-payload-param",
action="append", dest="replay_ignore_payload_params", type=str,
help="""
- Request's payload parameters (application/x-www-form-urlencoded) to
+ Request's payload parameters (application/x-www-form-urlencoded or multipart/form-data) to
be ignored while searching for a saved flow to replay.
Can be passed multiple times.
"""
@@ -482,9 +481,10 @@ def common_options(parser):
)
group.add_argument(
"--replay-ignore-host",
- action="store_true", dest="replay_ignore_host", default=False,
- help="Ignore request's destination host while searching for a saved flow to replay"
- )
+ action="store_true",
+ dest="replay_ignore_host",
+ default=False,
+ help="Ignore request's destination host while searching for a saved flow to replay")
group = parser.add_argument_group(
"Replacements",
@@ -575,12 +575,17 @@ def mitmproxy():
)
common_options(parser)
parser.add_argument(
- "--palette", type=str, default="dark",
+ "--palette", type=str, default=palettes.DEFAULT,
action="store", dest="palette",
choices=sorted(palettes.palettes.keys()),
help="Select color palette: " + ", ".join(palettes.palettes.keys())
)
parser.add_argument(
+ "--palette-transparent",
+ action="store_true", dest="palette_transparent", default=False,
+ help="Set transparent background for palette."
+ )
+ parser.add_argument(
"-e", "--eventlog",
action="store_true", dest="eventlog",
help="Show event log."
@@ -594,6 +599,11 @@ def mitmproxy():
type=str, dest="intercept", default=None,
help="Intercept filter expression."
)
+ group.add_argument(
+ "-l", "--limit", action="store",
+ type=str, dest="limit", default=None,
+ help="Limit filter expression."
+ )
return parser
diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py
index 198b7bbe..8f39e283 100644
--- a/libmproxy/console/__init__.py
+++ b/libmproxy/console/__init__.py
@@ -1,281 +1,38 @@
from __future__ import absolute_import
-import glob
import mailcap
import mimetypes
import tempfile
import os
import os.path
import shlex
+import signal
import stat
import subprocess
import sys
-import time
import traceback
import urwid
import weakref
-from .. import controller, utils, flow, script
-from . import flowlist, flowview, help, common
-from . import grideditor, palettes, contentview, flowdetailview
+from .. import controller, flow, script
+from . import flowlist, flowview, help, window, signals, options
+from . import grideditor, palettes, contentview, statusbar, palettepicker
EVENTLOG_SIZE = 500
-class _PathCompleter:
- def __init__(self, _testing=False):
- """
- _testing: disables reloading of the lookup table to make testing possible.
- """
- self.lookup, self.offset = None, None
- self.final = None
- self._testing = _testing
-
- def reset(self):
- self.lookup = None
- self.offset = -1
-
- def complete(self, txt):
- """
- Returns the next completion for txt, or None if there is no completion.
- """
- path = os.path.expanduser(txt)
- if not self.lookup:
- if not self._testing:
- # Lookup is a set of (display value, actual value) tuples.
- self.lookup = []
- if os.path.isdir(path):
- files = glob.glob(os.path.join(path, "*"))
- prefix = txt
- else:
- files = glob.glob(path+"*")
- prefix = os.path.dirname(txt)
- prefix = prefix or "./"
- for f in files:
- display = os.path.join(prefix, os.path.basename(f))
- if os.path.isdir(f):
- display += "/"
- self.lookup.append((display, f))
- if not self.lookup:
- self.final = path
- return path
- self.lookup.sort()
- self.offset = -1
- self.lookup.append((txt, txt))
- self.offset += 1
- if self.offset >= len(self.lookup):
- self.offset = 0
- ret = self.lookup[self.offset]
- self.final = ret[1]
- return ret[0]
-
-
-class PathEdit(urwid.Edit, _PathCompleter):
- def __init__(self, *args, **kwargs):
- urwid.Edit.__init__(self, *args, **kwargs)
- _PathCompleter.__init__(self)
-
- def keypress(self, size, key):
- if key == "tab":
- comp = self.complete(self.get_edit_text())
- self.set_edit_text(comp)
- self.set_edit_pos(len(comp))
- else:
- self.reset()
- return urwid.Edit.keypress(self, size, key)
-
-
-class ActionBar(urwid.WidgetWrap):
- def __init__(self):
- self.message("")
-
- def selectable(self):
- return True
-
- def path_prompt(self, prompt, text):
- self.expire = None
- self._w = PathEdit(prompt, text)
-
- def prompt(self, prompt, text = ""):
- self.expire = None
- # A (partial) workaround for this Urwid issue:
- # https://github.com/Nic0/tyrs/issues/115
- # We can remove it once veryone is beyond 1.0.1
- if isinstance(prompt, basestring):
- prompt = unicode(prompt)
- self._w = urwid.Edit(prompt, text or "")
-
- def message(self, message, expire=None):
- self.expire = expire
- self._w = urwid.Text(message)
-
-
-class StatusBar(urwid.WidgetWrap):
- def __init__(self, master, helptext):
- self.master, self.helptext = master, helptext
- self.ab = ActionBar()
- self.ib = urwid.WidgetWrap(urwid.Text(""))
- self._w = urwid.Pile([self.ib, self.ab])
-
- def get_status(self):
- r = []
-
- if self.master.setheaders.count():
- r.append("[")
- r.append(("heading_key", "H"))
- r.append("eaders]")
- if self.master.replacehooks.count():
- r.append("[")
- r.append(("heading_key", "R"))
- r.append("eplacing]")
- if self.master.client_playback:
- r.append("[")
- r.append(("heading_key", "cplayback"))
- r.append(":%s to go]"%self.master.client_playback.count())
- if self.master.server_playback:
- r.append("[")
- r.append(("heading_key", "splayback"))
- if self.master.nopop:
- r.append(":%s in file]"%self.master.server_playback.count())
- else:
- r.append(":%s to go]"%self.master.server_playback.count())
- if self.master.get_ignore_filter():
- r.append("[")
- r.append(("heading_key", "I"))
- r.append("gnore:%d]" % len(self.master.get_ignore_filter()))
- if self.master.get_tcp_filter():
- r.append("[")
- r.append(("heading_key", "T"))
- r.append("CP:%d]" % len(self.master.get_tcp_filter()))
- if self.master.state.intercept_txt:
- r.append("[")
- r.append(("heading_key", "i"))
- r.append(":%s]"%self.master.state.intercept_txt)
- if self.master.state.limit_txt:
- r.append("[")
- r.append(("heading_key", "l"))
- r.append(":%s]"%self.master.state.limit_txt)
- if self.master.stickycookie_txt:
- r.append("[")
- r.append(("heading_key", "t"))
- r.append(":%s]"%self.master.stickycookie_txt)
- if self.master.stickyauth_txt:
- r.append("[")
- r.append(("heading_key", "u"))
- r.append(":%s]"%self.master.stickyauth_txt)
- if self.master.state.default_body_view.name != "Auto":
- r.append("[")
- r.append(("heading_key", "M"))
- r.append(":%s]"%self.master.state.default_body_view.name)
-
- opts = []
- if self.master.anticache:
- opts.append("anticache")
- if self.master.anticomp:
- opts.append("anticomp")
- if self.master.showhost:
- opts.append("showhost")
- if not self.master.refresh_server_playback:
- opts.append("norefresh")
- if self.master.killextra:
- opts.append("killextra")
- if self.master.server.config.no_upstream_cert:
- opts.append("no-upstream-cert")
- if self.master.state.follow_focus:
- opts.append("following")
- if self.master.stream_large_bodies:
- opts.append("stream:%s" % utils.pretty_size(self.master.stream_large_bodies.max_size))
-
- if opts:
- r.append("[%s]"%(":".join(opts)))
-
- if self.master.server.config.mode in ["reverse", "upstream"]:
- dst = self.master.server.config.mode.dst
- scheme = "https" if dst[0] else "http"
- if dst[1] != dst[0]:
- scheme += "2https" if dst[1] else "http"
- r.append("[dest:%s]"%utils.unparse_url(scheme, *dst[2:]))
- if self.master.scripts:
- r.append("[")
- r.append(("heading_key", "s"))
- r.append("cripts:%s]"%len(self.master.scripts))
- # r.append("[lt:%0.3f]"%self.master.looptime)
-
- if self.master.stream:
- r.append("[W:%s]"%self.master.stream_path)
-
- return r
-
- def redraw(self):
- if self.ab.expire and time.time() > self.ab.expire:
- self.message("")
-
- fc = self.master.state.flow_count()
- if self.master.state.focus is None:
- offset = 0
- else:
- offset = min(self.master.state.focus + 1, fc)
- t = [
- ('heading', ("[%s/%s]"%(offset, fc)).ljust(9))
- ]
-
- if self.master.server.bound:
- host = self.master.server.address.host
- if host == "0.0.0.0":
- host = "*"
- boundaddr = "[%s:%s]"%(host, self.master.server.address.port)
- else:
- boundaddr = ""
- t.extend(self.get_status())
- status = urwid.AttrWrap(urwid.Columns([
- urwid.Text(t),
- urwid.Text(
- [
- self.helptext,
- boundaddr
- ],
- align="right"
- ),
- ]), "heading")
- self.ib._w = status
-
- def update(self, text):
- self.helptext = text
- self.redraw()
- self.master.loop.draw_screen()
-
- def selectable(self):
- return True
-
- def get_edit_text(self):
- return self.ab._w.get_edit_text()
-
- def path_prompt(self, prompt, text):
- return self.ab.path_prompt(prompt, text)
-
- def prompt(self, prompt, text = ""):
- self.ab.prompt(prompt, text)
-
- def message(self, msg, expire=None):
- if expire:
- expire = time.time() + float(expire)/1000
- self.ab.message(msg, expire)
- self.master.loop.draw_screen()
-
-
class ConsoleState(flow.State):
def __init__(self):
flow.State.__init__(self)
self.focus = None
self.follow_focus = None
self.default_body_view = contentview.get("Auto")
-
- self.view_mode = common.VIEW_LIST
- self.view_flow_mode = common.VIEW_FLOW_REQUEST
-
- self.last_script = ""
- self.last_saveload = ""
self.flowsettings = weakref.WeakKeyDictionary()
+ self.last_search = None
+
+ def __setattr__(self, name, value):
+ self.__dict__[name] = value
+ signals.update_settings.send(self)
def add_flow_setting(self, flow, key, value):
d = self.flowsettings.setdefault(flow, {})
@@ -316,6 +73,8 @@ class ConsoleState(flow.State):
elif idx < 0:
idx = 0
self.focus = idx
+ else:
+ self.focus = None
def set_focus_flow(self, f):
self.set_focus(self.view.index(f))
@@ -326,10 +85,10 @@ class ConsoleState(flow.State):
return self.view[pos], pos
def get_next(self, pos):
- return self.get_from_pos(pos+1)
+ return self.get_from_pos(pos + 1)
def get_prev(self, pos):
- return self.get_from_pos(pos-1)
+ return self.get_from_pos(pos - 1)
def delete_flow(self, f):
if f in self.view and self.view.index(f) <= self.focus:
@@ -357,6 +116,7 @@ class Options(object):
"keepserving",
"kill",
"intercept",
+ "limit",
"no_server",
"refresh_server_playback",
"rfile",
@@ -373,6 +133,7 @@ class Options(object):
"wfile",
"nopop",
"palette",
+ "palette_transparent"
]
def __init__(self, **kwargs):
@@ -388,7 +149,6 @@ class ConsoleMaster(flow.FlowMaster):
def __init__(self, server, options):
flow.FlowMaster.__init__(self, server, ConsoleState())
- self.looptime = 0
self.stream_path = None
self.options = options
@@ -398,14 +158,14 @@ class ConsoleMaster(flow.FlowMaster):
for i in options.setheaders:
self.setheaders.add(*i)
- self.flow_list_walker = None
- self.set_palette(options.palette)
-
r = self.set_intercept(options.intercept)
if r:
print >> sys.stderr, "Intercept error:", r
sys.exit(1)
+ if options.limit:
+ self.set_limit(options.limit)
+
r = self.set_stickycookie(options.stickycookie)
if r:
print >> sys.stderr, "Sticky cookies error:", r
@@ -425,12 +185,12 @@ class ConsoleMaster(flow.FlowMaster):
self.rheaders = options.rheaders
self.nopop = options.nopop
self.showhost = options.showhost
+ self.palette = options.palette
+ self.palette_transparent = options.palette_transparent
self.eventlog = options.eventlog
self.eventlist = urwid.SimpleListWalker([])
- self.statusbar = None
-
if options.client_replay:
self.client_playback_path(options.client_replay)
@@ -453,15 +213,49 @@ class ConsoleMaster(flow.FlowMaster):
print >> sys.stderr, "Stream file error:", err
sys.exit(1)
+ self.view_stack = []
+
if options.app:
self.start_app(self.options.app_host, self.options.app_port)
+ signals.call_in.connect(self.sig_call_in)
+ signals.pop_view_state.connect(self.sig_pop_view_state)
+ signals.push_view_state.connect(self.sig_push_view_state)
+
+ def __setattr__(self, name, value):
+ self.__dict__[name] = value
+ signals.update_settings.send(self)
+
+ def sig_call_in(self, sender, seconds, callback, args=()):
+ def cb(*_):
+ return callback(*args)
+ self.loop.set_alarm_in(seconds, cb)
+
+ def sig_pop_view_state(self, sender):
+ if len(self.view_stack) > 1:
+ self.view_stack.pop()
+ self.loop.widget = self.view_stack[-1]
+ else:
+ signals.status_prompt_onekey.send(
+ self,
+ prompt = "Quit",
+ keys = (
+ ("yes", "y"),
+ ("no", "n"),
+ ),
+ callback = self.quit,
+ )
+
+ def sig_push_view_state(self, sender, window):
+ self.view_stack.append(window)
+ self.loop.widget = window
+ self.loop.draw_screen()
def start_stream_to_path(self, path, mode="wb"):
path = os.path.expanduser(path)
try:
f = file(path, mode)
self.start_stream(f, None)
- except IOError, v:
+ except IOError as v:
return str(v)
self.stream_path = path
@@ -469,20 +263,24 @@ class ConsoleMaster(flow.FlowMaster):
status, val = s.run(method, f)
if val:
if status:
- self.add_event("Method %s return: %s"%(method, val), "debug")
+ self.add_event("Method %s return: %s" % (method, val), "debug")
else:
- self.add_event("Method %s error: %s"%(method, val[1]), "error")
+ self.add_event(
+ "Method %s error: %s" %
+ (method, val[1]), "error")
def run_script_once(self, command, f):
if not command:
return
- self.add_event("Running script on flow: %s"%command, "debug")
+ self.add_event("Running script on flow: %s" % command, "debug")
try:
s = script.Script(command, self)
- except script.ScriptError, v:
- self.statusbar.message("Error loading script.")
- self.add_event("Error loading script:\n%s"%v.args[0], "error")
+ except script.ScriptError as v:
+ signals.status_message.send(
+ message = "Error loading script."
+ )
+ self.add_event("Error loading script:\n%s" % v.args[0], "error")
return
if f.request:
@@ -492,22 +290,21 @@ class ConsoleMaster(flow.FlowMaster):
if f.error:
self._run_script_method("error", s, f)
s.unload()
- self.refresh_flow(f)
- self.state.last_script = command
+ signals.flow_change.send(self, flow = f)
def set_script(self, command):
if not command:
return
ret = self.load_script(command)
if ret:
- self.statusbar.message(ret)
- self.state.last_script = command
+ signals.status_message.send(message=ret)
def toggle_eventlog(self):
self.eventlog = not self.eventlog
+ signals.pop_view_state.send(self)
self.view_flowlist()
- def _readflow(self, paths):
+ def _readflows(self, path):
"""
Utitility function that reads a list of flows
or prints an error to the UI if that fails.
@@ -516,22 +313,21 @@ class ConsoleMaster(flow.FlowMaster):
- a list of flows, otherwise.
"""
try:
- return flow.read_flows_from_paths(paths)
+ return flow.read_flows_from_paths(path)
except flow.FlowReadError as e:
- if not self.statusbar:
- print >> sys.stderr, e.strerror
- sys.exit(1)
- else:
- self.statusbar.message(e.strerror)
- return None
+ signals.status_message.send(message=e.strerror)
def client_playback_path(self, path):
- flows = self._readflow(path)
+ if not isinstance(path, list):
+ path = [path]
+ flows = self._readflows(path)
if flows:
self.start_client_playback(flows, False)
def server_playback_path(self, path):
- flows = self._readflow(path)
+ if not isinstance(path, list):
+ path = [path]
+ flows = self._readflows(path)
if flows:
self.start_server_playback(
flows,
@@ -557,7 +353,9 @@ class ConsoleMaster(flow.FlowMaster):
try:
subprocess.call(cmd)
except:
- self.statusbar.message("Can't start editor: %s" % " ".join(c))
+ signals.status_message.send(
+ message = "Can't start editor: %s" % " ".join(c)
+ )
else:
data = open(name, "rb").read()
self.ui.start()
@@ -596,191 +394,34 @@ class ConsoleMaster(flow.FlowMaster):
try:
subprocess.call(cmd, shell=shell)
except:
- self.statusbar.message(
- "Can't start external viewer: %s" % " ".join(c)
+ signals.status_message.send(
+ message="Can't start external viewer: %s" % " ".join(c)
)
self.ui.start()
os.unlink(name)
def set_palette(self, name):
- self.palette = palettes.palettes[name]
-
- def input_filter(self, keys, raw):
- for k in keys:
- if self.prompting:
- if k == "esc":
- self.prompt_cancel()
- elif self.onekey:
- if k == "enter":
- self.prompt_cancel()
- elif k in self.onekey:
- self.prompt_execute(k)
- elif k == "enter":
- self.prompt_execute()
- else:
- self.view.keypress(self.loop.screen_size, k)
- else:
- k = self.view.keypress(self.loop.screen_size, k)
- if k:
- self.statusbar.message("")
- if k == "?":
- self.view_help()
- elif k == "c":
- if not self.client_playback:
- self.path_prompt(
- "Client replay: ",
- self.state.last_saveload,
- self.client_playback_path
- )
- else:
- self.prompt_onekey(
- "Stop current client replay?",
- (
- ("yes", "y"),
- ("no", "n"),
- ),
- self.stop_client_playback_prompt,
- )
- elif k == "H":
- self.view_grideditor(
- grideditor.SetHeadersEditor(
- self,
- self.setheaders.get_specs(),
- self.setheaders.set
- )
- )
- elif k == "I":
- self.view_grideditor(
- grideditor.HostPatternEditor(
- self,
- [[x] for x in self.get_ignore_filter()],
- self.edit_ignore_filter
- )
- )
- elif k == "T":
- self.view_grideditor(
- grideditor.HostPatternEditor(
- self,
- [[x] for x in self.get_tcp_filter()],
- self.edit_tcp_filter
- )
- )
- elif k == "i":
- self.prompt(
- "Intercept filter: ",
- self.state.intercept_txt,
- self.set_intercept
- )
- elif k == "Q":
- raise urwid.ExitMainLoop
- elif k == "q":
- self.prompt_onekey(
- "Quit",
- (
- ("yes", "y"),
- ("no", "n"),
- ),
- self.quit,
- )
- elif k == "M":
- self.prompt_onekey(
- "Global default display mode",
- contentview.view_prompts,
- self.change_default_display_mode
- )
- elif k == "R":
- self.view_grideditor(
- grideditor.ReplaceEditor(
- self,
- self.replacehooks.get_specs(),
- self.replacehooks.set
- )
- )
- elif k == "s":
- self.view_grideditor(
- grideditor.ScriptEditor(
- self,
- [[i.command] for i in self.scripts],
- self.edit_scripts
- )
- )
- #if self.scripts:
- # self.load_script(None)
- #else:
- # self.path_prompt(
- # "Set script: ",
- # self.state.last_script,
- # self.set_script
- # )
- elif k == "S":
- if not self.server_playback:
- self.path_prompt(
- "Server replay path: ",
- self.state.last_saveload,
- self.server_playback_path
- )
- else:
- self.prompt_onekey(
- "Stop current server replay?",
- (
- ("yes", "y"),
- ("no", "n"),
- ),
- self.stop_server_playback_prompt,
- )
- elif k == "o":
- self.prompt_onekey(
- "Options",
- (
- ("anticache", "a"),
- ("anticomp", "c"),
- ("showhost", "h"),
- ("killextra", "k"),
- ("norefresh", "n"),
- ("no-upstream-certs", "u"),
- ),
- self._change_options
- )
- elif k == "t":
- self.prompt(
- "Sticky cookie filter: ",
- self.stickycookie_txt,
- self.set_stickycookie
- )
- elif k == "u":
- self.prompt(
- "Sticky auth filter: ",
- self.stickyauth_txt,
- self.set_stickyauth
- )
- self.statusbar.redraw()
+ self.palette = name
+ self.ui.register_palette(
+ palettes.palettes[name].palette(self.palette_transparent)
+ )
+ self.ui.clear()
def ticker(self, *userdata):
changed = self.tick(self.masterq, timeout=0)
if changed:
self.loop.draw_screen()
- self.statusbar.redraw()
+ signals.update_settings.send()
self.loop.set_alarm_in(0.01, self.ticker)
def run(self):
self.ui = urwid.raw_display.Screen()
self.ui.set_terminal_properties(256)
- self.ui.register_palette(self.palette.palette())
- self.flow_list_walker = flowlist.FlowListWalker(self, self.state)
- self.view = None
- self.statusbar = None
- self.header = None
- self.body = None
- self.help_context = None
- self.prompting = False
- self.onekey = False
+ self.set_palette(self.palette)
self.loop = urwid.MainLoop(
- self.view,
+ urwid.SolidFill("x"),
screen = self.ui,
- input_filter = self.input_filter
)
- self.view_flowlist()
- self.statusbar.redraw()
self.server.start_slave(
controller.Slave,
@@ -801,6 +442,19 @@ class ConsoleMaster(flow.FlowMaster):
sys.exit(1)
self.loop.set_alarm_in(0.01, self.ticker)
+
+ # It's not clear why we need to handle this explicitly - without this,
+ # mitmproxy hangs on keyboard interrupt. Remove if we ever figure it
+ # out.
+ def exit(s, f):
+ raise urwid.ExitMainLoop
+ signal.signal(signal.SIGINT, exit)
+
+ self.loop.set_alarm_in(
+ 0.0001,
+ lambda *args: self.view_flowlist()
+ )
+
try:
self.loop.run()
except Exception:
@@ -814,43 +468,56 @@ class ConsoleMaster(flow.FlowMaster):
sys.stderr.flush()
self.shutdown()
- def make_view(self):
- self.view = urwid.Frame(
- self.body,
- header = self.header,
- footer = self.statusbar
+ def view_help(self, helpctx):
+ signals.push_view_state.send(
+ self,
+ window = window.Window(
+ self,
+ help.HelpView(helpctx),
+ None,
+ statusbar.StatusBar(self, help.footer),
+ None
+ )
)
- self.view.set_focus("body")
- return self.view
- def view_help(self):
- h = help.HelpView(
+ def view_options(self):
+ for i in self.view_stack:
+ if isinstance(i["body"], options.Options):
+ return
+ signals.push_view_state.send(
self,
- self.help_context,
- (self.statusbar, self.body, self.header)
+ window = window.Window(
+ self,
+ options.Options(self),
+ None,
+ statusbar.StatusBar(self, options.footer),
+ options.help_context,
+ )
)
- self.statusbar = StatusBar(self, help.footer)
- self.body = h
- self.header = None
- self.loop.widget = self.make_view()
- def view_flowdetails(self, flow):
- h = flowdetailview.FlowDetailsView(
+ def view_palette_picker(self):
+ signals.push_view_state.send(
self,
- flow,
- (self.statusbar, self.body, self.header)
+ window = window.Window(
+ self,
+ palettepicker.PalettePicker(self),
+ None,
+ statusbar.StatusBar(self, palettepicker.footer),
+ palettepicker.help_context,
+ )
)
- self.statusbar = StatusBar(self, flowdetailview.footer)
- self.body = h
- self.header = None
- self.loop.widget = self.make_view()
def view_grideditor(self, ge):
- self.body = ge
- self.header = None
- self.help_context = ge.make_help()
- self.statusbar = StatusBar(self, grideditor.footer)
- self.loop.widget = self.make_view()
+ signals.push_view_state.send(
+ self,
+ window = window.Window(
+ self,
+ ge,
+ None,
+ statusbar.StatusBar(self, grideditor.FOOTER),
+ ge.make_help()
+ )
+ )
def view_flowlist(self):
if self.ui.started:
@@ -859,27 +526,35 @@ class ConsoleMaster(flow.FlowMaster):
self.state.set_focus(self.state.flow_count())
if self.eventlog:
- self.body = flowlist.BodyPile(self)
+ body = flowlist.BodyPile(self)
else:
- self.body = flowlist.FlowListBox(self)
- self.statusbar = StatusBar(self, flowlist.footer)
- self.header = None
- self.state.view_mode = common.VIEW_LIST
-
- self.loop.widget = self.make_view()
- self.help_context = flowlist.help_context
-
- def view_flow(self, flow):
- self.body = flowview.FlowView(self, self.state, flow)
- self.header = flowview.FlowViewHeader(self, flow)
- self.statusbar = StatusBar(self, flowview.footer)
+ body = flowlist.FlowListBox(self)
+
+ signals.push_view_state.send(
+ self,
+ window = window.Window(
+ self,
+ body,
+ None,
+ statusbar.StatusBar(self, flowlist.footer),
+ flowlist.help_context
+ )
+ )
+
+ def view_flow(self, flow, tab_offset=0):
self.state.set_focus_flow(flow)
- self.state.view_mode = common.VIEW_FLOW
- self.loop.widget = self.make_view()
- self.help_context = flowview.help_context
+ signals.push_view_state.send(
+ self,
+ window = window.Window(
+ self,
+ flowview.FlowView(self, self.state, flow, tab_offset),
+ flowview.FlowViewHeader(self, flow),
+ statusbar.StatusBar(self, flowview.footer),
+ flowview.help_context
+ )
+ )
def _write_flows(self, path, flows):
- self.state.last_saveload = path
if not path:
return
path = os.path.expanduser(path)
@@ -889,8 +564,8 @@ class ConsoleMaster(flow.FlowMaster):
for i in flows:
fw.add(i)
f.close()
- except IOError, v:
- self.statusbar.message(v.strerror)
+ except IOError as v:
+ signals.status_message.send(message=v.strerror)
def save_one_flow(self, path, flow):
return self._write_flows(path, [flow])
@@ -902,74 +577,23 @@ class ConsoleMaster(flow.FlowMaster):
if not path:
return
ret = self.load_flows_path(path)
- return ret or "Flows loaded from %s"%path
+ return ret or "Flows loaded from %s" % path
def load_flows_path(self, path):
- self.state.last_saveload = path
reterr = None
try:
flow.FlowMaster.load_flows_file(self, path)
- except flow.FlowReadError, v:
+ except flow.FlowReadError as v:
reterr = str(v)
- if self.flow_list_walker:
- self.sync_list_view()
+ signals.flowlist_change.send(self)
return reterr
- def path_prompt(self, prompt, text, callback, *args):
- self.statusbar.path_prompt(prompt, text)
- self.view.set_focus("footer")
- self.prompting = (callback, args)
-
- def prompt(self, prompt, text, callback, *args):
- self.statusbar.prompt(prompt, text)
- self.view.set_focus("footer")
- self.prompting = (callback, args)
-
- def prompt_edit(self, prompt, text, callback):
- self.statusbar.prompt(prompt + ": ", text)
- self.view.set_focus("footer")
- self.prompting = (callback, [])
-
- def prompt_onekey(self, prompt, keys, callback, *args):
- """
- Keys are a set of (word, key) tuples. The appropriate key in the
- word is highlighted.
- """
- prompt = [prompt, " ("]
- mkup = []
- for i, e in enumerate(keys):
- mkup.extend(common.highlight_key(e[0], e[1]))
- if i < len(keys)-1:
- mkup.append(",")
- prompt.extend(mkup)
- prompt.append(")? ")
- self.onekey = "".join(i[1] for i in keys)
- self.prompt(prompt, "", callback, *args)
-
- def prompt_done(self):
- self.prompting = False
- self.onekey = False
- self.view.set_focus("body")
- self.statusbar.message("")
-
- def prompt_execute(self, txt=None):
- if not txt:
- txt = self.statusbar.get_edit_text()
- p, args = self.prompting
- self.prompt_done()
- msg = p(txt, *args)
- if msg:
- self.statusbar.message(msg, 1000)
-
- def prompt_cancel(self):
- self.prompt_done()
-
def accept_all(self):
self.state.accept_all(self)
def set_limit(self, txt):
v = self.state.set_limit(txt)
- self.sync_list_view()
+ signals.flowlist_change.send(self)
return v
def set_intercept(self, txt):
@@ -980,12 +604,6 @@ class ConsoleMaster(flow.FlowMaster):
self.state.default_body_view = v
self.refresh_focus()
- def pop_view(self):
- if self.state.view_mode == common.VIEW_FLOW:
- self.view_flow(self.state.view[self.state.focus])
- else:
- self.view_flowlist()
-
def edit_scripts(self, scripts):
commands = [x[0] for x in scripts] # remove outer array
if commands == [s.command for s in self.scripts]:
@@ -994,14 +612,7 @@ class ConsoleMaster(flow.FlowMaster):
self.unload_scripts()
for command in commands:
self.load_script(command)
-
- def edit_ignore_filter(self, ignore):
- patterns = (x[0] for x in ignore)
- self.set_ignore_filter(patterns)
-
- def edit_tcp_filter(self, tcp):
- patterns = (x[0] for x in tcp)
- self.set_tcp_filter(patterns)
+ signals.update_settings.send(self)
def stop_client_playback_prompt(self, a):
if a != "n":
@@ -1015,33 +626,13 @@ class ConsoleMaster(flow.FlowMaster):
if a != "n":
raise urwid.ExitMainLoop
- def _change_options(self, a):
- if a == "a":
- self.anticache = not self.anticache
- if a == "c":
- self.anticomp = not self.anticomp
- if a == "h":
- self.showhost = not self.showhost
- self.sync_list_view()
- self.refresh_focus()
- elif a == "k":
- self.killextra = not self.killextra
- elif a == "n":
- self.refresh_server_playback = not self.refresh_server_playback
- elif a == "u":
- self.server.config.no_upstream_cert =\
- not self.server.config.no_upstream_cert
-
def shutdown(self):
self.state.killall(self)
flow.FlowMaster.shutdown(self)
- def sync_list_view(self):
- self.flow_list_walker._modified()
-
def clear_flows(self):
self.state.clear()
- self.sync_list_view()
+ signals.flowlist_change.send(self)
def toggle_follow_flows(self):
# toggle flow follow
@@ -1049,31 +640,27 @@ class ConsoleMaster(flow.FlowMaster):
# jump to most recent flow if follow is now on
if self.state.follow_focus:
self.state.set_focus(self.state.flow_count())
- self.sync_list_view()
+ signals.flowlist_change.send(self)
def delete_flow(self, f):
self.state.delete_flow(f)
- self.sync_list_view()
+ signals.flowlist_change.send(self)
def refresh_focus(self):
if self.state.view:
- self.refresh_flow(self.state.view[self.state.focus])
-
- def refresh_flow(self, c):
- if hasattr(self.header, "refresh_flow"):
- self.header.refresh_flow(c)
- if hasattr(self.body, "refresh_flow"):
- self.body.refresh_flow(c)
- if hasattr(self.statusbar, "refresh_flow"):
- self.statusbar.refresh_flow(c)
+ signals.flow_change.send(
+ self,
+ flow = self.state.view[self.state.focus]
+ )
def process_flow(self, f):
- if self.state.intercept and f.match(self.state.intercept) and not f.request.is_replay:
+ if self.state.intercept and f.match(
+ self.state.intercept) and not f.request.is_replay:
f.intercept(self)
else:
f.reply()
- self.sync_list_view()
- self.refresh_flow(f)
+ signals.flowlist_change.send(self)
+ signals.flow_change.send(self, flow = f)
def clear_events(self):
self.eventlist[:] = []
@@ -1090,7 +677,7 @@ class ConsoleMaster(flow.FlowMaster):
self.eventlist.append(e)
if len(self.eventlist) > EVENTLOG_SIZE:
self.eventlist.pop(0)
- self.eventlist.set_focus(len(self.eventlist)-1)
+ self.eventlist.set_focus(len(self.eventlist) - 1)
# Handlers
def handle_error(self, f):
diff --git a/libmproxy/console/common.py b/libmproxy/console/common.py
index 3a708c7c..3180170d 100644
--- a/libmproxy/console/common.py
+++ b/libmproxy/console/common.py
@@ -6,15 +6,14 @@ import os
from .. import utils
from ..protocol.http import CONTENT_MISSING, decoded
+from . import signals
+import netlib.utils
try:
import pyperclip
except:
pyperclip = False
-VIEW_LIST = 0
-VIEW_FLOW = 1
-
VIEW_FLOW_REQUEST = 0
VIEW_FLOW_RESPONSE = 1
@@ -31,14 +30,22 @@ METHOD_OPTIONS = [
]
-def highlight_key(s, k):
+def is_keypress(k):
+ """
+ Is this input event a keypress?
+ """
+ if isinstance(k, basestring):
+ return True
+
+
+def highlight_key(str, key, textattr="text", keyattr="key"):
l = []
- parts = s.split(k, 1)
+ parts = str.split(key, 1)
if parts[0]:
- l.append(("text", parts[0]))
- l.append(("key", k))
+ l.append((textattr, parts[0]))
+ l.append((keyattr, key))
if parts[1]:
- l.append(("text", parts[1]))
+ l.append((textattr, parts[1]))
return l
@@ -60,20 +67,26 @@ def format_keyvals(lst, key="key", val="text", indent=0):
if kv is None:
ret.append(urwid.Text(""))
else:
- cols = []
- # This cumbersome construction process is here for a reason:
- # Urwid < 1.0 barfs if given a fixed size column of size zero.
- if indent:
- cols.append(("fixed", indent, urwid.Text("")))
- cols.extend([
- (
- "fixed",
- maxk,
- urwid.Text([(key, kv[0] or "")])
- ),
- kv[1] if isinstance(kv[1], urwid.Widget) else urwid.Text([(val, kv[1])])
- ])
- ret.append(urwid.Columns(cols, dividechars = 2))
+ if isinstance(kv[1], urwid.Widget):
+ v = kv[1]
+ elif kv[1] is None:
+ v = urwid.Text("")
+ else:
+ v = urwid.Text([(val, kv[1])])
+ ret.append(
+ urwid.Columns(
+ [
+ ("fixed", indent, urwid.Text("")),
+ (
+ "fixed",
+ maxk,
+ urwid.Text([(key, kv[0] or "")])
+ ),
+ v
+ ],
+ dividechars = 2
+ )
+ )
return ret
@@ -151,7 +164,7 @@ def raw_format_flow(f, focus, extended, padding):
4: "code_400",
5: "code_500",
}
- ccol = codes.get(f["resp_code"]/100, "code_other")
+ ccol = codes.get(f["resp_code"] / 100, "code_other")
resp.append(fcol(SYMBOL_RETURN, ccol))
if f["resp_is_replay"]:
resp.append(fcol(SYMBOL_REPLAY, "replay"))
@@ -184,23 +197,39 @@ def raw_format_flow(f, focus, extended, padding):
def save_data(path, data, master, state):
if not path:
return
- state.last_saveload = path
- path = os.path.expanduser(path)
try:
with file(path, "wb") as f:
f.write(data)
- except IOError, v:
- master.statusbar.message(v.strerror)
+ except IOError as v:
+ signals.status_message.send(message=v.strerror)
+
+
+def ask_save_overwite(path, data, master, state):
+ if not path:
+ return
+ path = os.path.expanduser(path)
+ if os.path.exists(path):
+ def save_overwite(k):
+ if k == "y":
+ save_data(path, data, master, state)
+
+ signals.status_prompt_onekey.send(
+ prompt = "'" + path + "' already exists. Overwite?",
+ keys = (
+ ("yes", "y"),
+ ("no", "n"),
+ ),
+ callback = save_overwite
+ )
+ else:
+ save_data(path, data, master, state)
def ask_save_path(prompt, data, master, state):
- master.path_prompt(
- prompt,
- state.last_saveload,
- save_data,
- data,
- master,
- state
+ signals.status_prompt_path.send(
+ prompt = prompt,
+ callback = ask_save_overwite,
+ args = (data, master, state)
)
@@ -210,6 +239,8 @@ def copy_flow_format_data(part, scope, flow):
else:
data = ""
if scope in ("q", "a"):
+ if flow.request.content is None or flow.request.content == CONTENT_MISSING:
+ return None, "Request content is missing"
with decoded(flow.request):
if part == "h":
data += flow.request.assemble()
@@ -221,6 +252,8 @@ def copy_flow_format_data(part, scope, flow):
# Add padding between request and response
data += "\r\n" * 2
if scope in ("s", "a") and flow.response:
+ if flow.response.content is None or flow.response.content == CONTENT_MISSING:
+ return None, "Response content is missing"
with decoded(flow.response):
if part == "h":
data += flow.response.assemble()
@@ -228,40 +261,43 @@ def copy_flow_format_data(part, scope, flow):
data += flow.response.content
else:
raise ValueError("Unknown part: {}".format(part))
- return data
+ return data, False
def copy_flow(part, scope, flow, master, state):
"""
- part: _c_ontent, _a_ll, _u_rl
+ part: _c_ontent, _h_eaders+content, _u_rl
scope: _a_ll, re_q_uest, re_s_ponse
"""
- data = copy_flow_format_data(part, scope, flow)
+ data, err = copy_flow_format_data(part, scope, flow)
+
+ if err:
+ signals.status_message.send(message=err)
+ return
if not data:
if scope == "q":
- master.statusbar.message("No request content to copy.")
+ signals.status_message.send(message="No request content to copy.")
elif scope == "s":
- master.statusbar.message("No response content to copy.")
+ signals.status_message.send(message="No response content to copy.")
else:
- master.statusbar.message("No contents to copy.")
+ signals.status_message.send(message="No contents to copy.")
return
try:
master.add_event(str(len(data)))
pyperclip.copy(data)
- except RuntimeError:
+ except (RuntimeError, UnicodeDecodeError):
def save(k):
if k == "y":
- ask_save_path("Save data: ", data, master, state)
-
- master.prompt_onekey(
- "Cannot copy binary data to clipboard. Save as file?",
- (
+ ask_save_path("Save data", data, master, state)
+ signals.status_prompt_onekey.send(
+ prompt = "Cannot copy binary data to clipboard. Save as file?",
+ keys = (
("yes", "y"),
("no", "n"),
),
- save
+ callback = save
)
@@ -273,14 +309,11 @@ def ask_copy_part(scope, flow, master, state):
if scope != "s":
choices.append(("url", "u"))
- master.prompt_onekey(
- "Copy",
- choices,
- copy_flow,
- scope,
- flow,
- master,
- state
+ signals.status_prompt_onekey.send(
+ prompt = "Copy",
+ keys = choices,
+ callback = copy_flow,
+ args = (scope, flow, master, state)
)
@@ -297,16 +330,14 @@ def ask_save_body(part, master, state, flow):
# We first need to determine whether we want to save the request or the
# response content.
if request_has_content and response_has_content:
- master.prompt_onekey(
- "Save",
- (
+ signals.status_prompt_onekey.send(
+ prompt = "Save",
+ keys = (
("request", "q"),
("response", "s"),
),
- ask_save_body,
- master,
- state,
- flow
+ callback = ask_save_body,
+ args = (master, state, flow)
)
elif response_has_content:
ask_save_body("s", master, state, flow)
@@ -315,27 +346,23 @@ def ask_save_body(part, master, state, flow):
elif part == "q" and request_has_content:
ask_save_path(
- "Save request content: ",
+ "Save request content",
flow.request.get_decoded_content(),
master,
state
)
elif part == "s" and response_has_content:
ask_save_path(
- "Save response content: ",
+ "Save response content",
flow.response.get_decoded_content(),
master,
state
)
else:
- master.statusbar.message("No content to save.")
+ signals.status_message.send(message="No content to save.")
-class FlowCache:
- @utils.LRUCache(200)
- def format_flow(self, *args):
- return raw_format_flow(*args)
-flowcache = FlowCache()
+flowcache = utils.LRUCache(800)
def format_flow(f, focus, extended=False, hostheader=False, padding=2):
@@ -353,7 +380,7 @@ def format_flow(f, focus, extended=False, hostheader=False, padding=2):
)
if f.response:
if f.response.content:
- contentdesc = utils.pretty_size(len(f.response.content))
+ contentdesc = netlib.utils.pretty_size(len(f.response.content))
elif f.response.content == CONTENT_MISSING:
contentdesc = "[content missing]"
else:
@@ -374,6 +401,7 @@ def format_flow(f, focus, extended=False, hostheader=False, padding=2):
d["resp_ctype"] = t[0].split(";")[0]
else:
d["resp_ctype"] = ""
- return flowcache.format_flow(
+ return flowcache.get(
+ raw_format_flow,
tuple(sorted(d.items())), focus, extended, padding
)
diff --git a/libmproxy/console/contentview.py b/libmproxy/console/contentview.py
index 95d908a4..2b3c6def 100644
--- a/libmproxy/console/contentview.py
+++ b/libmproxy/console/contentview.py
@@ -6,27 +6,27 @@ import lxml.html
import lxml.etree
from PIL import Image
from PIL.ExifTags import TAGS
-import re
import subprocess
import traceback
import urwid
import netlib.utils
+from netlib import odict
from . import common
-from .. import utils, encoding, flow
+from .. import utils, encoding
from ..contrib import jsbeautifier, html2text
from ..contrib.wbxml.ASCommandResponse import ASCommandResponse
try:
import pyamf
from pyamf import remoting, flex
-except ImportError: # pragma nocover
+except ImportError: # pragma nocover
pyamf = None
try:
import cssutils
-except ImportError: # pragma nocover
+except ImportError: # pragma nocover
cssutils = None
else:
cssutils.log.setLevel(logging.CRITICAL)
@@ -36,7 +36,7 @@ else:
cssutils.ser.prefs.indentClosingBrace = False
cssutils.ser.prefs.validOnly = False
-VIEW_CUTOFF = 1024*50
+VIEW_CUTOFF = 1024 * 50
def _view_text(content, total, limit):
@@ -59,7 +59,7 @@ def trailer(clen, txt, limit):
txt.append(
urwid.Text(
[
- ("highlight", "... %s of data not shown. Press "%utils.pretty_size(rem)),
+ ("highlight", "... %s of data not shown. Press " % netlib.utils.pretty_size(rem)),
("key", "f"),
("highlight", " to load all data.")
]
@@ -76,7 +76,7 @@ class ViewAuto:
ctype = hdrs.get_first("content-type")
if ctype:
ct = utils.parse_content_type(ctype) if ctype else None
- ct = "%s/%s"%(ct[0], ct[1])
+ ct = "%s/%s" % (ct[0], ct[1])
if ct in content_types_map:
return content_types_map[ct][0](hdrs, content, limit)
elif utils.isXML(content):
@@ -227,7 +227,7 @@ class ViewURLEncoded:
lines = utils.urldecode(content)
if lines:
body = common.format_keyvals(
- [(k+":", v) for (k, v) in lines],
+ [(k + ":", v) for (k, v) in lines],
key = "header",
val = "text"
)
@@ -240,33 +240,13 @@ class ViewMultipart:
content_types = ["multipart/form-data"]
def __call__(self, hdrs, content, limit):
- v = hdrs.get_first("content-type")
+ v = utils.multipartdecode(hdrs, content)
if v:
- v = utils.parse_content_type(v)
- if not v:
- return
- boundary = v[2].get("boundary")
- if not boundary:
- return
-
- rx = re.compile(r'\bname="([^"]+)"')
- keys = []
- vals = []
-
- for i in content.split("--" + boundary):
- parts = i.splitlines()
- if len(parts) > 1 and parts[0][0:2] != "--":
- match = rx.search(parts[1])
- if match:
- keys.append(match.group(1) + ":")
- vals.append(netlib.utils.cleanBin(
- "\n".join(parts[3+parts[2:].index(""):])
- ))
r = [
urwid.Text(("highlight", "Form data:\n")),
]
r.extend(common.format_keyvals(
- zip(keys, vals),
+ v,
key = "header",
val = "text"
))
@@ -324,7 +304,6 @@ if pyamf:
if not envelope:
return None
-
txt = []
for target, message in iter(envelope):
if isinstance(message, pyamf.remoting.Request):
@@ -335,13 +314,13 @@ if pyamf:
else:
txt.append(urwid.Text([
("header", "Response: "),
- ("text", "%s, code %s"%(target, message.status)),
+ ("text", "%s, code %s" % (target, message.status)),
]))
s = json.dumps(self.unpack(message), indent=4)
txt.extend(_view_text(s[:limit], len(s), limit))
- return "AMF v%s"%envelope.amfVersion, txt
+ return "AMF v%s" % envelope.amfVersion, txt
class ViewJavaScript:
@@ -395,7 +374,7 @@ class ViewImage:
return None
parts = [
("Format", str(img.format_description)),
- ("Size", "%s x %s px"%img.size),
+ ("Size", "%s x %s px" % img.size),
("Mode", str(img.mode)),
]
for i in sorted(img.info.keys()):
@@ -421,7 +400,7 @@ class ViewImage:
key = "header",
val = "text"
)
- return "%s image"%img.format, fmt
+ return "%s image" % img.format, fmt
class ViewProtobuf:
@@ -539,14 +518,14 @@ def get_content_view(viewmode, hdrItems, content, limit, logfunc, is_request):
return "No content", ""
msg = []
- hdrs = flow.ODictCaseless([list(i) for i in hdrItems])
+ hdrs = odict.ODictCaseless([list(i) for i in hdrItems])
enc = hdrs.get_first("content-encoding")
if enc and enc != "identity":
decoded = encoding.decode(enc, content)
if decoded:
content = decoded
- msg.append("[decoded %s]"%enc)
+ msg.append("[decoded %s]" % enc)
try:
ret = viewmode(hdrs, content, limit)
# Third-party viewers can fail in unexpected ways...
diff --git a/libmproxy/console/flowdetailview.py b/libmproxy/console/flowdetailview.py
index f351bff1..40769c95 100644
--- a/libmproxy/console/flowdetailview.py
+++ b/libmproxy/console/flowdetailview.py
@@ -1,113 +1,154 @@
from __future__ import absolute_import
import urwid
-from . import common
+from . import common, searchable
from .. import utils
-footer = [
- ('heading_key', "q"), ":back ",
-]
-class FlowDetailsView(urwid.ListBox):
- def __init__(self, master, flow, state):
- self.master, self.flow, self.state = master, flow, state
- urwid.ListBox.__init__(
- self,
- self.flowtext()
- )
+def maybe_timestamp(base, attr):
+ if base and getattr(base, attr):
+ return utils.format_timestamp_with_milli(getattr(base, attr))
+ else:
+ return "active"
+ pass
- def keypress(self, size, key):
- key = common.shortcuts(key)
- if key == "q":
- self.master.statusbar = self.state[0]
- self.master.body = self.state[1]
- self.master.header = self.state[2]
- self.master.loop.widget = self.master.make_view()
- return None
- elif key == "?":
- key = None
- return urwid.ListBox.keypress(self, size, key)
-
- def flowtext(self):
- text = []
-
- title = urwid.Text("Flow details")
- title = urwid.Padding(title, align="left", width=("relative", 100))
- title = urwid.AttrWrap(title, "heading")
- text.append(title)
-
- cc = self.flow.client_conn
- sc = self.flow.server_conn
- req = self.flow.request
- resp = self.flow.response
-
- if sc:
- text.append(urwid.Text([("head", "Server Connection:")]))
- parts = [
- ["Address", "%s:%s" % sc.address()],
- ]
- text.extend(common.format_keyvals(parts, key="key", val="text", indent=4))
-
- c = sc.cert
- if c:
- text.append(urwid.Text([("head", "Server Certificate:")]))
- parts = [
- ["Type", "%s, %s bits"%c.keyinfo],
- ["SHA1 digest", c.digest("sha1")],
- ["Valid to", str(c.notafter)],
- ["Valid from", str(c.notbefore)],
- ["Serial", str(c.serial)],
- [
- "Subject",
- urwid.BoxAdapter(
- urwid.ListBox(common.format_keyvals(c.subject, key="highlight", val="text")),
- len(c.subject)
- )
- ],
- [
- "Issuer",
- urwid.BoxAdapter(
- urwid.ListBox(common.format_keyvals(c.issuer, key="highlight", val="text")),
- len(c.issuer)
- )
- ]
- ]
+def flowdetails(state, flow):
+ text = []
- if c.altnames:
- parts.append(
- [
- "Alt names",
- ", ".join(c.altnames)
- ]
- )
- text.extend(common.format_keyvals(parts, key="key", val="text", indent=4))
+ cc = flow.client_conn
+ sc = flow.server_conn
+ req = flow.request
+ resp = flow.response
- if cc:
- text.append(urwid.Text([("head", "Client Connection:")]))
+ if sc:
+ text.append(urwid.Text([("head", "Server Connection:")]))
+ parts = [
+ ["Address", "%s:%s" % sc.address()],
+ ]
+
+ text.extend(
+ common.format_keyvals(parts, key="key", val="text", indent=4)
+ )
+ c = sc.cert
+ if c:
+ text.append(urwid.Text([("head", "Server Certificate:")]))
parts = [
- ["Address", "%s:%s" % cc.address()],
- # ["Requests", "%s"%cc.requestcount],
+ ["Type", "%s, %s bits" % c.keyinfo],
+ ["SHA1 digest", c.digest("sha1")],
+ ["Valid to", str(c.notafter)],
+ ["Valid from", str(c.notbefore)],
+ ["Serial", str(c.serial)],
+ [
+ "Subject",
+ urwid.BoxAdapter(
+ urwid.ListBox(
+ common.format_keyvals(
+ c.subject,
+ key="highlight",
+ val="text"
+ )
+ ),
+ len(c.subject)
+ )
+ ],
+ [
+ "Issuer",
+ urwid.BoxAdapter(
+ urwid.ListBox(
+ common.format_keyvals(
+ c.issuer, key="highlight", val="text"
+ )
+ ),
+ len(c.issuer)
+ )
+ ]
]
- text.extend(common.format_keyvals(parts, key="key", val="text", indent=4))
+ if c.altnames:
+ parts.append(
+ [
+ "Alt names",
+ ", ".join(c.altnames)
+ ]
+ )
+ text.extend(
+ common.format_keyvals(parts, key="key", val="text", indent=4)
+ )
- parts = []
+ if cc:
+ text.append(urwid.Text([("head", "Client Connection:")]))
- parts.append(["Client conn. established", utils.format_timestamp_with_milli(cc.timestamp_start) if (cc and cc.timestamp_start) else "active"])
- parts.append(["Server conn. initiated", utils.format_timestamp_with_milli(sc.timestamp_start) if sc else "active" ])
- parts.append(["Server conn. TCP handshake", utils.format_timestamp_with_milli(sc.timestamp_tcp_setup) if (sc and sc.timestamp_tcp_setup) else "active"])
- if sc.ssl_established:
- parts.append(["Server conn. SSL handshake", utils.format_timestamp_with_milli(sc.timestamp_ssl_setup) if sc.timestamp_ssl_setup else "active"])
- parts.append(["Client conn. SSL handshake", utils.format_timestamp_with_milli(cc.timestamp_ssl_setup) if (cc and cc.timestamp_ssl_setup) else "active"])
- parts.append(["First request byte", utils.format_timestamp_with_milli(req.timestamp_start)])
- parts.append(["Request complete", utils.format_timestamp_with_milli(req.timestamp_end) if req.timestamp_end else "active"])
- parts.append(["First response byte", utils.format_timestamp_with_milli(resp.timestamp_start) if resp else "active"])
- parts.append(["Response complete", utils.format_timestamp_with_milli(resp.timestamp_end) if (resp and resp.timestamp_end) else "active"])
+ parts = [
+ ["Address", "%s:%s" % cc.address()],
+ # ["Requests", "%s"%cc.requestcount],
+ ]
- # sort operations by timestamp
- parts = sorted(parts, key=lambda p: p[1])
+ text.extend(
+ common.format_keyvals(parts, key="key", val="text", indent=4)
+ )
- text.append(urwid.Text([("head", "Timing:")]))
- text.extend(common.format_keyvals(parts, key="key", val="text", indent=4))
- return text
+ parts = []
+
+ parts.append(
+ [
+ "Client conn. established",
+ maybe_timestamp(cc, "timestamp_start")
+ ]
+ )
+ parts.append(
+ [
+ "Server conn. initiated",
+ maybe_timestamp(sc, "timestamp_start")
+ ]
+ )
+ parts.append(
+ [
+ "Server conn. TCP handshake",
+ maybe_timestamp(sc, "timestamp_tcp_setup")
+ ]
+ )
+ if sc.ssl_established:
+ parts.append(
+ [
+ "Server conn. SSL handshake",
+ maybe_timestamp(sc, "timestamp_ssl_setup")
+ ]
+ )
+ parts.append(
+ [
+ "Client conn. SSL handshake",
+ maybe_timestamp(cc, "timestamp_ssl_setup")
+ ]
+ )
+ parts.append(
+ [
+ "First request byte",
+ maybe_timestamp(req, "timestamp_start")
+ ]
+ )
+ parts.append(
+ [
+ "Request complete",
+ maybe_timestamp(req, "timestamp_end")
+ ]
+ )
+ parts.append(
+ [
+ "First response byte",
+ maybe_timestamp(resp, "timestamp_start")
+ ]
+ )
+ parts.append(
+ [
+ "Response complete",
+ maybe_timestamp(resp, "timestamp_end")
+ ]
+ )
+
+ # sort operations by timestamp
+ parts = sorted(parts, key=lambda p: p[1])
+
+ text.append(urwid.Text([("head", "Timing:")]))
+ text.extend(common.format_keyvals(parts, key="key", val="text", indent=4))
+ return searchable.Searchable(state, text)
diff --git a/libmproxy/console/flowlist.py b/libmproxy/console/flowlist.py
index 5d8ad942..fd071569 100644
--- a/libmproxy/console/flowlist.py
+++ b/libmproxy/console/flowlist.py
@@ -1,7 +1,7 @@
from __future__ import absolute_import
import urwid
from netlib import http
-from . import common
+from . import common, signals
def _mkhelp():
@@ -15,10 +15,10 @@ def _mkhelp():
("D", "duplicate flow"),
("e", "toggle eventlog"),
("F", "toggle follow flow list"),
- ("g", "copy flow to clipboard"),
("l", "set limit filter pattern"),
("L", "load saved flows"),
("n", "create a new request"),
+ ("P", "copy flow to clipboard"),
("r", "replay request"),
("V", "revert changes to request"),
("w", "save flows "),
@@ -47,6 +47,10 @@ class EventListBox(urwid.ListBox):
if key == "C":
self.master.clear_events()
key = None
+ elif key == "G":
+ self.set_focus(0)
+ elif key == "g":
+ self.set_focus(len(self.master.eventlist) - 1)
return urwid.ListBox.keypress(self, size, key)
@@ -72,7 +76,8 @@ class BodyPile(urwid.Pile):
def keypress(self, size, key):
if key == "tab":
- self.focus_position = (self.focus_position + 1)%len(self.widget_list)
+ self.focus_position = (
+ self.focus_position + 1) % len(self.widget_list)
if self.focus_position == 1:
self.widget_list[1].header = self.active_header
else:
@@ -111,17 +116,15 @@ class ConnectionItem(urwid.WidgetWrap):
def save_flows_prompt(self, k):
if k == "a":
- self.master.path_prompt(
- "Save all flows to: ",
- self.state.last_saveload,
- self.master.save_flows
+ signals.status_prompt_path.send(
+ prompt = "Save all flows to",
+ callback = self.master.save_flows
)
else:
- self.master.path_prompt(
- "Save this flow to: ",
- self.state.last_saveload,
- self.master.save_one_flow,
- self.flow
+ signals.status_prompt_path.send(
+ prompt = "Save this flow to",
+ callback = self.master.save_one_flow,
+ args = (self.flow,)
)
def stop_server_playback_prompt(self, a):
@@ -150,64 +153,65 @@ class ConnectionItem(urwid.WidgetWrap):
self.master.options.replay_ignore_host
)
else:
- self.master.path_prompt(
- "Server replay path: ",
- self.state.last_saveload,
- self.master.server_playback_path
+ signals.status_prompt_path.send(
+ prompt = "Server replay path",
+ callback = self.master.server_playback_path
)
- def keypress(self, (maxcol,), key):
+ def keypress(self, xxx_todo_changeme, key):
+ (maxcol,) = xxx_todo_changeme
key = common.shortcuts(key)
if key == "a":
self.flow.accept_intercept(self.master)
- self.master.sync_list_view()
+ signals.flowlist_change.send(self)
elif key == "d":
self.flow.kill(self.master)
self.state.delete_flow(self.flow)
- self.master.sync_list_view()
+ signals.flowlist_change.send(self)
elif key == "D":
f = self.master.duplicate_flow(self.flow)
self.master.view_flow(f)
elif key == "r":
r = self.master.replay_request(self.flow)
if r:
- self.master.statusbar.message(r)
- self.master.sync_list_view()
+ signals.status_message.send(message=r)
+ signals.flowlist_change.send(self)
elif key == "S":
if not self.master.server_playback:
- self.master.prompt_onekey(
- "Server Replay",
- (
+ signals.status_prompt_onekey.send(
+ prompt = "Server Replay",
+ keys = (
("all flows", "a"),
("this flow", "t"),
("file", "f"),
),
- self.server_replay_prompt,
+ callback = self.server_replay_prompt,
)
else:
- self.master.prompt_onekey(
- "Stop current server replay?",
- (
+ signals.status_prompt_onekey.send(
+ prompt = "Stop current server replay?",
+ keys = (
("yes", "y"),
("no", "n"),
),
- self.stop_server_playback_prompt,
+ callback = self.stop_server_playback_prompt,
)
elif key == "V":
if not self.flow.modified():
- self.master.statusbar.message("Flow not modified.")
+ signals.status_message.send(message="Flow not modified.")
return
self.state.revert(self.flow)
- self.master.sync_list_view()
- self.master.statusbar.message("Reverted.")
+ signals.flowlist_change.send(self)
+ signals.status_message.send(message="Reverted.")
elif key == "w":
- self.master.prompt_onekey(
- "Save",
- (
+ signals.status_prompt_onekey.send(
+ self,
+ prompt = "Save",
+ keys = (
("all flows", "a"),
("this flow", "t"),
),
- self.save_flows_prompt,
+ callback = self.save_flows_prompt,
)
elif key == "X":
self.flow.kill(self.master)
@@ -215,13 +219,12 @@ class ConnectionItem(urwid.WidgetWrap):
if self.flow.request:
self.master.view_flow(self.flow)
elif key == "|":
- self.master.path_prompt(
- "Send flow to script: ",
- self.state.last_script,
- self.master.run_script_once,
- self.flow
+ signals.status_prompt_path.send(
+ prompt = "Send flow to script",
+ callback = self.master.run_script_once,
+ args = (self.flow,)
)
- elif key == "g":
+ elif key == "P":
common.ask_copy_part("a", self.flow, self.master, self.state)
elif key == "b":
common.ask_save_body(None, self.master, self.state, self.flow)
@@ -232,8 +235,10 @@ class ConnectionItem(urwid.WidgetWrap):
class FlowListWalker(urwid.ListWalker):
def __init__(self, master, state):
self.master, self.state = master, state
- if self.state.flow_count():
- self.set_focus(0)
+ signals.flowlist_change.connect(self.sig_flowlist_change)
+
+ def sig_flowlist_change(self, sender):
+ self._modified()
def get_focus(self):
f, i = self.state.get_focus()
@@ -258,7 +263,10 @@ class FlowListWalker(urwid.ListWalker):
class FlowListBox(urwid.ListBox):
def __init__(self, master):
self.master = master
- urwid.ListBox.__init__(self, master.flow_list_walker)
+ urwid.ListBox.__init__(
+ self,
+ FlowListWalker(master, master.state)
+ )
def get_method_raw(self, k):
if k:
@@ -266,7 +274,12 @@ class FlowListBox(urwid.ListBox):
def get_method(self, k):
if k == "e":
- self.master.prompt("Method:", "", self.get_method_raw)
+ signals.status_prompt.send(
+ self,
+ prompt = "Method",
+ text = "",
+ callback = self.get_method_raw
+ )
else:
method = ""
for i in common.METHOD_OPTIONS:
@@ -275,17 +288,17 @@ class FlowListBox(urwid.ListBox):
self.get_url(method)
def get_url(self, method):
- self.master.prompt(
- "URL:",
- "http://www.example.com/",
- self.new_request,
- method
+ signals.status_prompt.send(
+ prompt = "URL",
+ text = "http://www.example.com/",
+ callback = self.new_request,
+ args = (method,)
)
def new_request(self, url, method):
parts = http.parse_url(str(url))
if not parts:
- self.master.statusbar.message("Invalid Url")
+ signals.status_message.send(message="Invalid Url")
return
scheme, host, port, path = parts
f = self.master.create_request(method, scheme, host, port, path)
@@ -295,28 +308,34 @@ class FlowListBox(urwid.ListBox):
key = common.shortcuts(key)
if key == "A":
self.master.accept_all()
- self.master.sync_list_view()
+ signals.flowlist_change.send(self)
elif key == "C":
self.master.clear_flows()
elif key == "e":
self.master.toggle_eventlog()
+ elif key == "G":
+ self.master.state.set_focus(0)
+ signals.flowlist_change.send(self)
+ elif key == "g":
+ self.master.state.set_focus(self.master.state.flow_count())
+ signals.flowlist_change.send(self)
elif key == "l":
- self.master.prompt(
- "Limit: ",
- self.master.state.limit_txt,
- self.master.set_limit
+ signals.status_prompt.send(
+ prompt = "Limit",
+ text = self.master.state.limit_txt,
+ callback = self.master.set_limit
)
elif key == "L":
- self.master.path_prompt(
- "Load flows: ",
- self.master.state.last_saveload,
- self.master.load_flows_callback
+ signals.status_prompt_path.send(
+ self,
+ prompt = "Load flows",
+ callback = self.master.load_flows_callback
)
elif key == "n":
- self.master.prompt_onekey(
- "Method",
- common.METHOD_OPTIONS,
- self.get_method
+ signals.status_prompt_onekey.send(
+ prompt = "Method",
+ keys = common.METHOD_OPTIONS,
+ callback = self.get_method
)
elif key == "F":
self.master.toggle_follow_flows()
@@ -324,10 +343,10 @@ class FlowListBox(urwid.ListBox):
if self.master.stream:
self.master.stop_stream()
else:
- self.master.path_prompt(
- "Stream flows to: ",
- self.master.state.last_saveload,
- self.master.start_stream_to_path
+ signals.status_prompt_path.send(
+ self,
+ prompt = "Stream flows to",
+ callback = self.master.start_stream_to_path
)
else:
return urwid.ListBox.keypress(self, size, key)
diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py
index 89e75aad..43a40d69 100644
--- a/libmproxy/console/flowview.py
+++ b/libmproxy/console/flowview.py
@@ -1,12 +1,16 @@
from __future__ import absolute_import
-import os, sys, copy
+import os
+import sys
import urwid
-from . import common, grideditor, contentview
-from .. import utils, flow, controller
+from netlib import odict
+from . import common, grideditor, contentview, signals, searchable, tabs
+from . import flowdetailview
+from .. import utils, controller
from ..protocol.http import HTTPRequest, HTTPResponse, CONTENT_MISSING, decoded
-class SearchError(Exception): pass
+class SearchError(Exception):
+ pass
def _mkhelp():
@@ -19,58 +23,58 @@ def _mkhelp():
("D", "duplicate flow"),
("e", "edit request/response"),
("f", "load full body data"),
- ("g", "copy response(content/headers) to clipboard"),
("m", "change body display mode for this entity"),
- (None,
- common.highlight_key("automatic", "a") +
- [("text", ": automatic detection")]
- ),
- (None,
- common.highlight_key("hex", "e") +
- [("text", ": Hex")]
- ),
- (None,
- common.highlight_key("html", "h") +
- [("text", ": HTML")]
- ),
- (None,
- common.highlight_key("image", "i") +
- [("text", ": Image")]
- ),
- (None,
- common.highlight_key("javascript", "j") +
- [("text", ": JavaScript")]
- ),
- (None,
- common.highlight_key("json", "s") +
- [("text", ": JSON")]
- ),
- (None,
- common.highlight_key("urlencoded", "u") +
- [("text", ": URL-encoded data")]
- ),
- (None,
- common.highlight_key("raw", "r") +
- [("text", ": raw data")]
- ),
- (None,
- common.highlight_key("xml", "x") +
- [("text", ": XML")]
- ),
+ (None,
+ common.highlight_key("automatic", "a") +
+ [("text", ": automatic detection")]
+ ),
+ (None,
+ common.highlight_key("hex", "e") +
+ [("text", ": Hex")]
+ ),
+ (None,
+ common.highlight_key("html", "h") +
+ [("text", ": HTML")]
+ ),
+ (None,
+ common.highlight_key("image", "i") +
+ [("text", ": Image")]
+ ),
+ (None,
+ common.highlight_key("javascript", "j") +
+ [("text", ": JavaScript")]
+ ),
+ (None,
+ common.highlight_key("json", "s") +
+ [("text", ": JSON")]
+ ),
+ (None,
+ common.highlight_key("urlencoded", "u") +
+ [("text", ": URL-encoded data")]
+ ),
+ (None,
+ common.highlight_key("raw", "r") +
+ [("text", ": raw data")]
+ ),
+ (None,
+ common.highlight_key("xml", "x") +
+ [("text", ": XML")]
+ ),
("M", "change default body display mode"),
("p", "previous flow"),
+ ("P", "copy response(content/headers) to clipboard"),
("r", "replay request"),
("V", "revert changes to request"),
("v", "view body in external viewer"),
("w", "save all flows matching current limit"),
("W", "save this flow"),
("x", "delete body"),
- ("X", "view flow details"),
("z", "encode/decode a request/response"),
- ("tab", "toggle request/response view"),
+ ("tab", "next tab"),
+ ("h, l", "previous tab, next tab"),
("space", "next flow"),
("|", "run script on this flow"),
- ("/", "search in response body (case sensitive)"),
+ ("/", "search (case sensitive)"),
("n", "repeat search forward"),
("N", "repeat search backwards"),
]
@@ -87,417 +91,171 @@ footer = [
class FlowViewHeader(urwid.WidgetWrap):
def __init__(self, master, f):
self.master, self.flow = master, f
- self._w = common.format_flow(f, False, extended=True, padding=0, hostheader=self.master.showhost)
-
- def refresh_flow(self, f):
- if f == self.flow:
- self._w = common.format_flow(f, False, extended=True, padding=0, hostheader=self.master.showhost)
-
+ self._w = common.format_flow(
+ f,
+ False,
+ extended=True,
+ padding=0,
+ hostheader=self.master.showhost
+ )
+ signals.flow_change.connect(self.sig_flow_change)
+
+ def sig_flow_change(self, sender, flow):
+ if flow == self.flow:
+ self._w = common.format_flow(
+ flow,
+ False,
+ extended=True,
+ padding=0,
+ hostheader=self.master.showhost
+ )
-class CallbackCache:
- @utils.LRUCache(200)
- def _callback(self, method, *args, **kwargs):
- return getattr(self.obj, method)(*args, **kwargs)
- def callback(self, obj, method, *args, **kwargs):
- # obj varies!
- self.obj = obj
- return self._callback(method, *args, **kwargs)
-cache = CallbackCache()
+cache = utils.LRUCache(200)
+TAB_REQ = 0
+TAB_RESP = 1
-class FlowView(urwid.WidgetWrap):
- REQ = 0
- RESP = 1
+class FlowView(tabs.Tabs):
highlight_color = "focusfield"
- def __init__(self, master, state, flow):
+ def __init__(self, master, state, flow, tab_offset):
self.master, self.state, self.flow = master, state, flow
+ tabs.Tabs.__init__(self,
+ [
+ (self.tab_request, self.view_request),
+ (self.tab_response, self.view_response),
+ (self.tab_details, self.view_details),
+ ],
+ tab_offset
+ )
+ self.show()
self.last_displayed_body = None
- if self.state.view_flow_mode == common.VIEW_FLOW_RESPONSE:
- self.view_response()
- else:
- self.view_request()
+ signals.flow_change.connect(self.sig_flow_change)
- def _cached_content_view(self, viewmode, hdrItems, content, limit, is_request):
- return contentview.get_content_view(viewmode, hdrItems, content, limit, self.master.add_event, is_request)
+ def tab_request(self):
+ if self.flow.intercepted and not self.flow.reply.acked and not self.flow.response:
+ return "Request intercepted"
+ else:
+ return "Request"
- def content_view(self, viewmode, conn):
- full = self.state.get_flow_setting(
- self.flow,
- (self.state.view_flow_mode, "fullcontents"),
- False
- )
- if full:
- limit = sys.maxint
+ def tab_response(self):
+ if self.flow.intercepted and not self.flow.reply.acked and self.flow.response:
+ return "Response intercepted"
else:
- limit = contentview.VIEW_CUTOFF
- description, text_objects = cache.callback(
- self, "_cached_content_view",
- viewmode,
- tuple(tuple(i) for i in conn.headers.lst),
- conn.content,
- limit,
- isinstance(conn, HTTPRequest)
- )
- return (description, text_objects)
+ return "Response"
- def cont_view_handle_missing(self, conn, viewmode):
- if conn.content == CONTENT_MISSING:
- msg, body = "", [urwid.Text([("error", "[content missing]")])]
- else:
- msg, body = self.content_view(viewmode, conn)
+ def tab_details(self):
+ return "Detail"
+
+ def view_request(self):
+ return self.conn_text(self.flow.request)
+
+ def view_response(self):
+ return self.conn_text(self.flow.response)
+
+ def view_details(self):
+ return flowdetailview.flowdetails(self.state, self.flow)
+ def sig_flow_change(self, sender, flow):
+ if flow == self.flow:
+ self.show()
+
+ def content_view(self, viewmode, conn):
+ if conn.content == CONTENT_MISSING:
+ msg, body = "", [urwid.Text([("error", "[content missing]")])]
return (msg, body)
+ else:
+ full = self.state.get_flow_setting(
+ self.flow,
+ (self.tab_offset, "fullcontents"),
+ False
+ )
+ if full:
+ limit = sys.maxsize
+ else:
+ limit = contentview.VIEW_CUTOFF
+ description, text_objects = cache.get(
+ contentview.get_content_view,
+ viewmode,
+ tuple(tuple(i) for i in conn.headers.lst),
+ conn.content,
+ limit,
+ self.master.add_event,
+ isinstance(conn, HTTPRequest)
+ )
+ return (description, text_objects)
- def viewmode_get(self, override):
+ def viewmode_get(self):
+ override = self.state.get_flow_setting(
+ self.flow,
+ (self.tab_offset, "prettyview")
+ )
return self.state.default_body_view if override is None else override
- def override_get(self):
- return self.state.get_flow_setting(self.flow,
- (self.state.view_flow_mode, "prettyview"))
-
- def conn_text_raw(self, conn):
- """
- Based on a request/response, conn, returns the elements for
- display.
- """
- headers = common.format_keyvals(
- [(h+":", v) for (h, v) in conn.headers.lst],
+ def conn_text(self, conn):
+ if conn:
+ txt = common.format_keyvals(
+ [(h + ":", v) for (h, v) in conn.headers.lst],
key = "header",
val = "text"
)
- override = self.override_get()
- viewmode = self.viewmode_get(override)
- msg, body = self.cont_view_handle_missing(conn, viewmode)
- return headers, msg, body
-
- def conn_text_merge(self, headers, msg, body):
- """
- Grabs what is returned by conn_text_raw and merges them all
- toghether, mainly used by conn_text and search
- """
- override = self.override_get()
- viewmode = self.viewmode_get(override)
-
- cols = [urwid.Text(
- [
- ("heading", msg),
- ]
- )
- ]
+ viewmode = self.viewmode_get()
+ msg, body = self.content_view(viewmode, conn)
- if override is not None:
- cols.append(urwid.Text([
+ cols = [
+ urwid.Text(
+ [
+ ("heading", msg),
+ ]
+ )
+ ]
+ cols.append(
+ urwid.Text(
+ [
" ",
('heading', "["),
('heading_key', "m"),
- ('heading', (":%s]"%viewmode.name)),
+ ('heading', (":%s]" % viewmode.name)),
],
align="right"
)
)
+ title = urwid.AttrWrap(urwid.Columns(cols), "heading")
- title = urwid.AttrWrap(urwid.Columns(cols), "heading")
- headers.append(title)
- headers.extend(body)
-
- return headers
-
- def conn_text(self, conn):
- """
- Same as conn_text_raw, but returns result wrapped in a listbox ready for usage.
- """
- headers, msg, body = self.conn_text_raw(conn)
- merged = self.conn_text_merge(headers, msg, body)
- return urwid.ListBox(merged)
-
- def _tab(self, content, attr):
- p = urwid.Text(content)
- p = urwid.Padding(p, align="left", width=("relative", 100))
- p = urwid.AttrWrap(p, attr)
- return p
-
- def wrap_body(self, active, body):
- parts = []
-
- if self.flow.intercepted and not self.flow.reply.acked and not self.flow.response:
- qt = "Request intercepted"
- else:
- qt = "Request"
- if active == common.VIEW_FLOW_REQUEST:
- parts.append(self._tab(qt, "heading"))
+ txt.append(title)
+ txt.extend(body)
else:
- parts.append(self._tab(qt, "heading_inactive"))
-
- if self.flow.intercepted and not self.flow.reply.acked and self.flow.response:
- st = "Response intercepted"
- else:
- st = "Response"
- if active == common.VIEW_FLOW_RESPONSE:
- parts.append(self._tab(st, "heading"))
- else:
- parts.append(self._tab(st, "heading_inactive"))
-
- h = urwid.Columns(parts)
- f = urwid.Frame(
- body,
- header=h
+ txt = [
+ urwid.Text(""),
+ urwid.Text(
+ [
+ ("highlight", "No response. Press "),
+ ("key", "e"),
+ ("highlight", " and edit any aspect to add one."),
+ ]
)
- return f
-
- def search_wrapped_around(self, last_find_line, last_search_index, backwards):
- """
- returns true if search wrapped around the bottom.
- """
-
- current_find_line = self.state.get_flow_setting(self.flow,
- "last_find_line")
- current_search_index = self.state.get_flow_setting(self.flow,
- "last_search_index")
-
- if not backwards:
- message = "search hit BOTTOM, continuing at TOP"
- if current_find_line <= last_find_line:
- return True, message
- elif current_find_line == last_find_line:
- if current_search_index <= last_search_index:
- return True, message
- else:
- message = "search hit TOP, continuing at BOTTOM"
- if current_find_line >= last_find_line:
- return True, message
- elif current_find_line == last_find_line:
- if current_search_index >= last_search_index:
- return True, message
-
- return False, ""
-
- def search_again(self, backwards=False):
- """
- runs the previous search again, forwards or backwards.
- """
- last_search_string = self.state.get_flow_setting(self.flow, "last_search_string")
- if last_search_string:
- message = self.search(last_search_string, backwards)
- if message:
- self.master.statusbar.message(message)
- else:
- message = "no previous searches have been made"
- self.master.statusbar.message(message)
-
- return message
-
- def search(self, search_string, backwards=False):
- """
- similar to view_response or view_request, but instead of just
- displaying the conn, it highlights a word that the user is
- searching for and handles all the logic surrounding that.
- """
-
- if not search_string:
- search_string = self.state.get_flow_setting(self.flow,
- "last_search_string")
- if not search_string:
- return
-
- if self.state.view_flow_mode == common.VIEW_FLOW_REQUEST:
- text = self.flow.request
- const = common.VIEW_FLOW_REQUEST
- else:
- text = self.flow.response
- const = common.VIEW_FLOW_RESPONSE
- if not self.flow.response:
- return "no response to search in"
-
- last_find_line = self.state.get_flow_setting(self.flow,
- "last_find_line")
- last_search_index = self.state.get_flow_setting(self.flow,
- "last_search_index")
-
- # generate the body, highlight the words and get focus
- headers, msg, body = self.conn_text_raw(text)
- try:
- body, focus_position = self.search_highlight_text(body, search_string, backwards=backwards)
- except SearchError:
- return "Search not supported in this view."
-
- if focus_position == None:
- # no results found.
- return "no matches for '%s'" % search_string
-
- # UI stuff.
- merged = self.conn_text_merge(headers, msg, body)
- list_box = urwid.ListBox(merged)
- list_box.set_focus(focus_position + 2)
- self._w = self.wrap_body(const, list_box)
- self.master.statusbar.redraw()
-
- self.last_displayed_body = list_box
-
- wrapped, wrapped_message = self.search_wrapped_around(last_find_line, last_search_index, backwards)
-
- if wrapped:
- return wrapped_message
-
- def search_get_start(self, search_string):
- start_line = 0
- start_index = 0
- last_search_string = self.state.get_flow_setting(self.flow, "last_search_string")
- if search_string == last_search_string:
- start_line = self.state.get_flow_setting(self.flow, "last_find_line")
- start_index = self.state.get_flow_setting(self.flow,
- "last_search_index")
-
- if start_index == None:
- start_index = 0
- else:
- start_index += len(search_string)
-
- if start_line == None:
- start_line = 0
-
- else:
- self.state.add_flow_setting(self.flow, "last_search_string",
- search_string)
-
- return (start_line, start_index)
-
- def search_get_range(self, len_text_objects, start_line, backwards):
- if not backwards:
- loop_range = xrange(start_line, len_text_objects)
- else:
- loop_range = xrange(start_line, -1, -1)
-
- return loop_range
-
- def search_find(self, text, search_string, start_index, backwards):
- if backwards == False:
- find_index = text.find(search_string, start_index)
- else:
- if start_index != 0:
- start_index -= len(search_string)
- else:
- start_index = None
-
- find_index = text.rfind(search_string, 0, start_index)
-
- return find_index
-
- def search_highlight_text(self, text_objects, search_string, looping = False, backwards = False):
- start_line, start_index = self.search_get_start(search_string)
- i = start_line
-
- found = False
- text_objects = copy.deepcopy(text_objects)
- loop_range = self.search_get_range(len(text_objects), start_line, backwards)
- for i in loop_range:
- text_object = text_objects[i]
-
- try:
- text, style = text_object.get_text()
- except AttributeError:
- raise SearchError()
-
- if i != start_line:
- start_index = 0
-
- find_index = self.search_find(text, search_string, start_index, backwards)
-
- if find_index != -1:
- new_text = self.search_highlight_object(text, find_index, search_string)
- text_objects[i] = new_text
-
- found = True
- self.state.add_flow_setting(self.flow, "last_search_index",
- find_index)
- self.state.add_flow_setting(self.flow, "last_find_line", i)
-
- break
-
- # handle search WRAP
- if found:
- focus_pos = i
- else :
- if looping:
- focus_pos = None
- else:
- if not backwards:
- self.state.add_flow_setting(self.flow, "last_search_index", 0)
- self.state.add_flow_setting(self.flow, "last_find_line", 0)
- else:
- self.state.add_flow_setting(self.flow, "last_search_index", None)
- self.state.add_flow_setting(self.flow, "last_find_line", len(text_objects) - 1)
-
- text_objects, focus_pos = self.search_highlight_text(text_objects,
- search_string, looping=True, backwards=backwards)
-
- return text_objects, focus_pos
-
- def search_highlight_object(self, text_object, find_index, search_string):
- """
- just a little abstraction
- """
- before = text_object[:find_index]
- after = text_object[find_index+len(search_string):]
-
- new_text = urwid.Text(
- [
- before,
- (self.highlight_color, search_string),
- after,
]
- )
-
- return new_text
-
- def view_request(self):
- self.state.view_flow_mode = common.VIEW_FLOW_REQUEST
- body = self.conn_text(self.flow.request)
- self._w = self.wrap_body(common.VIEW_FLOW_REQUEST, body)
- self.master.statusbar.redraw()
-
- def view_response(self):
- self.state.view_flow_mode = common.VIEW_FLOW_RESPONSE
- if self.flow.response:
- body = self.conn_text(self.flow.response)
- else:
- body = urwid.ListBox(
- [
- urwid.Text(""),
- urwid.Text(
- [
- ("highlight", "No response. Press "),
- ("key", "e"),
- ("highlight", " and edit any aspect to add one."),
- ]
- )
- ]
- )
- self._w = self.wrap_body(common.VIEW_FLOW_RESPONSE, body)
- self.master.statusbar.redraw()
-
- def refresh_flow(self, c=None):
- if c == self.flow:
- if self.state.view_flow_mode == common.VIEW_FLOW_RESPONSE and self.flow.response:
- self.view_response()
- else:
- self.view_request()
+ return searchable.Searchable(self.state, txt)
def set_method_raw(self, m):
if m:
self.flow.request.method = m
- self.master.refresh_flow(self.flow)
+ signals.flow_change.send(self, flow = self.flow)
def edit_method(self, m):
if m == "e":
- self.master.prompt_edit("Method", self.flow.request.method, self.set_method_raw)
+ signals.status_prompt.send(
+ prompt = "Method",
+ text = self.flow.request.method,
+ callback = self.set_method_raw
+ )
else:
for i in common.METHOD_OPTIONS:
if i[1] == m:
self.flow.request.method = i[0].upper()
- self.master.refresh_flow(self.flow)
+ signals.flow_change.send(self, flow = self.flow)
def set_url(self, url):
request = self.flow.request
@@ -505,7 +263,7 @@ class FlowView(urwid.WidgetWrap):
request.url = str(url)
except ValueError:
return "Invalid URL."
- self.master.refresh_flow(self.flow)
+ signals.flow_change.send(self, flow = self.flow)
def set_resp_code(self, code):
response = self.flow.response
@@ -514,87 +272,161 @@ class FlowView(urwid.WidgetWrap):
except ValueError:
return None
import BaseHTTPServer
- if BaseHTTPServer.BaseHTTPRequestHandler.responses.has_key(int(code)):
- response.msg = BaseHTTPServer.BaseHTTPRequestHandler.responses[int(code)][0]
- self.master.refresh_flow(self.flow)
+ if int(code) in BaseHTTPServer.BaseHTTPRequestHandler.responses:
+ response.msg = BaseHTTPServer.BaseHTTPRequestHandler.responses[
+ int(code)][0]
+ signals.flow_change.send(self, flow = self.flow)
def set_resp_msg(self, msg):
response = self.flow.response
response.msg = msg
- self.master.refresh_flow(self.flow)
+ signals.flow_change.send(self, flow = self.flow)
def set_headers(self, lst, conn):
- conn.headers = flow.ODictCaseless(lst)
+ conn.headers = odict.ODictCaseless(lst)
+ signals.flow_change.send(self, flow = self.flow)
def set_query(self, lst, conn):
- conn.set_query(flow.ODict(lst))
+ conn.set_query(odict.ODict(lst))
+ signals.flow_change.send(self, flow = self.flow)
def set_path_components(self, lst, conn):
- conn.set_path_components([i[0] for i in lst])
+ conn.set_path_components(lst)
+ signals.flow_change.send(self, flow = self.flow)
def set_form(self, lst, conn):
- conn.set_form_urlencoded(flow.ODict(lst))
+ conn.set_form_urlencoded(odict.ODict(lst))
+ signals.flow_change.send(self, flow = self.flow)
def edit_form(self, conn):
self.master.view_grideditor(
- grideditor.URLEncodedFormEditor(self.master, conn.get_form_urlencoded().lst, self.set_form, conn)
+ grideditor.URLEncodedFormEditor(
+ self.master,
+ conn.get_form_urlencoded().lst,
+ self.set_form,
+ conn
+ )
)
def edit_form_confirm(self, key, conn):
if key == "y":
self.edit_form(conn)
+ def set_cookies(self, lst, conn):
+ od = odict.ODict(lst)
+ conn.set_cookies(od)
+ signals.flow_change.send(self, flow = self.flow)
+
+ def set_setcookies(self, data, conn):
+ conn.set_cookies(data)
+ signals.flow_change.send(self, flow = self.flow)
+
def edit(self, part):
- if self.state.view_flow_mode == common.VIEW_FLOW_REQUEST:
+ if self.tab_offset == TAB_REQ:
message = self.flow.request
else:
if not self.flow.response:
self.flow.response = HTTPResponse(
self.flow.request.httpversion,
- 200, "OK", flow.ODictCaseless(), ""
+ 200, "OK", odict.ODictCaseless(), ""
)
self.flow.response.reply = controller.DummyReply()
message = self.flow.response
self.flow.backup()
+ if message == self.flow.request and part == "c":
+ self.master.view_grideditor(
+ grideditor.CookieEditor(
+ self.master,
+ message.get_cookies().lst,
+ self.set_cookies,
+ message
+ )
+ )
+ if message == self.flow.response and part == "c":
+ self.master.view_grideditor(
+ grideditor.SetCookieEditor(
+ self.master,
+ message.get_cookies(),
+ self.set_setcookies,
+ message
+ )
+ )
if part == "r":
with decoded(message):
- # Fix an issue caused by some editors when editing a request/response body.
- # Many editors make it hard to save a file without a terminating newline on the last
- # line. When editing message bodies, this can cause problems. For now, I just
- # strip the newlines off the end of the body when we return from an editor.
+ # Fix an issue caused by some editors when editing a
+ # request/response body. Many editors make it hard to save a
+ # file without a terminating newline on the last line. When
+ # editing message bodies, this can cause problems. For now, I
+ # just strip the newlines off the end of the body when we return
+ # from an editor.
c = self.master.spawn_editor(message.content or "")
message.content = c.rstrip("\n")
elif part == "f":
if not message.get_form_urlencoded() and message.content:
- self.master.prompt_onekey(
- "Existing body is not a URL-encoded form. Clear and edit?",
- [
+ signals.status_prompt_onekey.send(
+ prompt = "Existing body is not a URL-encoded form. Clear and edit?",
+ keys = [
("yes", "y"),
("no", "n"),
],
- self.edit_form_confirm,
- message
+ callback = self.edit_form_confirm,
+ args = (message,)
)
else:
self.edit_form(message)
elif part == "h":
- self.master.view_grideditor(grideditor.HeaderEditor(self.master, message.headers.lst, self.set_headers, message))
+ self.master.view_grideditor(
+ grideditor.HeaderEditor(
+ self.master,
+ message.headers.lst,
+ self.set_headers,
+ message
+ )
+ )
elif part == "p":
p = message.get_path_components()
- p = [[i] for i in p]
- self.master.view_grideditor(grideditor.PathEditor(self.master, p, self.set_path_components, message))
+ self.master.view_grideditor(
+ grideditor.PathEditor(
+ self.master,
+ p,
+ self.set_path_components,
+ message
+ )
+ )
elif part == "q":
- self.master.view_grideditor(grideditor.QueryEditor(self.master, message.get_query().lst, self.set_query, message))
- elif part == "u" and self.state.view_flow_mode == common.VIEW_FLOW_REQUEST:
- self.master.prompt_edit("URL", message.url, self.set_url)
- elif part == "m" and self.state.view_flow_mode == common.VIEW_FLOW_REQUEST:
- self.master.prompt_onekey("Method", common.METHOD_OPTIONS, self.edit_method)
- elif part == "c" and self.state.view_flow_mode == common.VIEW_FLOW_RESPONSE:
- self.master.prompt_edit("Code", str(message.code), self.set_resp_code)
- elif part == "m" and self.state.view_flow_mode == common.VIEW_FLOW_RESPONSE:
- self.master.prompt_edit("Message", message.msg, self.set_resp_msg)
- self.master.refresh_flow(self.flow)
+ self.master.view_grideditor(
+ grideditor.QueryEditor(
+ self.master,
+ message.get_query().lst,
+ self.set_query, message
+ )
+ )
+ elif part == "u":
+ signals.status_prompt.send(
+ prompt = "URL",
+ text = message.url,
+ callback = self.set_url
+ )
+ elif part == "m":
+ signals.status_prompt_onekey.send(
+ prompt = "Method",
+ keys = common.METHOD_OPTIONS,
+ callback = self.edit_method
+ )
+ elif part == "o":
+ signals.status_prompt.send(
+ prompt = "Code",
+ text = str(message.code),
+ callback = self.set_resp_code
+ )
+ elif part == "m":
+ signals.status_prompt.send(
+ prompt = "Message",
+ text = message.msg,
+ callback = self.set_resp_msg
+ )
+ signals.flow_change.send(self, flow = self.flow)
def _view_nextprev_flow(self, np, flow):
try:
@@ -606,9 +438,10 @@ class FlowView(urwid.WidgetWrap):
else:
new_flow, new_idx = self.state.get_prev(idx)
if new_flow is None:
- self.master.statusbar.message("No more flows!")
- return
- self.master.view_flow(new_flow)
+ signals.status_message.send(message="No more flows!")
+ else:
+ signals.pop_view_state.send(self)
+ self.master.view_flow(new_flow, self.tab_offset)
def view_next_flow(self, flow):
return self._view_nextprev_flow("next", flow)
@@ -619,42 +452,38 @@ class FlowView(urwid.WidgetWrap):
def change_this_display_mode(self, t):
self.state.add_flow_setting(
self.flow,
- (self.state.view_flow_mode, "prettyview"),
+ (self.tab_offset, "prettyview"),
contentview.get_by_shortcut(t)
)
- self.master.refresh_flow(self.flow)
+ signals.flow_change.send(self, flow = self.flow)
def delete_body(self, t):
if t == "m":
val = CONTENT_MISSING
else:
val = None
- if self.state.view_flow_mode == common.VIEW_FLOW_REQUEST:
+ if self.tab_offset == TAB_REQ:
self.flow.request.content = val
else:
self.flow.response.content = val
- self.master.refresh_flow(self.flow)
+ signals.flow_change.send(self, flow = self.flow)
def keypress(self, size, key):
+ key = super(self.__class__, self).keypress(size, key)
+
if key == " ":
self.view_next_flow(self.flow)
return
key = common.shortcuts(key)
- if self.state.view_flow_mode == common.VIEW_FLOW_REQUEST:
+ if self.tab_offset == TAB_REQ:
conn = self.flow.request
- else:
+ elif self.tab_offset == TAB_RESP:
conn = self.flow.response
+ else:
+ conn = None
- if key == "q":
- self.master.view_flowlist()
- key = None
- elif key == "tab":
- if self.state.view_flow_mode == common.VIEW_FLOW_REQUEST:
- self.view_response()
- else:
- self.view_request()
- elif key in ("up", "down", "page up", "page down"):
+ if key in ("up", "down", "page up", "page down"):
# Why doesn't this just work??
self._w.keypress(size, key)
elif key == "a":
@@ -663,15 +492,10 @@ class FlowView(urwid.WidgetWrap):
elif key == "A":
self.master.accept_all()
self.master.view_flow(self.flow)
- elif key == "b":
- if self.state.view_flow_mode == common.VIEW_FLOW_REQUEST:
- common.ask_save_body("q", self.master, self.state, self.flow)
- else:
- common.ask_save_body("s", self.master, self.state, self.flow)
elif key == "d":
if self.state.flow_count() == 1:
self.master.view_flowlist()
- elif self.state.view.index(self.flow) == len(self.state.view)-1:
+ elif self.state.view.index(self.flow) == len(self.state.view) - 1:
self.view_prev_flow(self.flow)
else:
self.view_next_flow(self.flow)
@@ -681,134 +505,143 @@ class FlowView(urwid.WidgetWrap):
elif key == "D":
f = self.master.duplicate_flow(self.flow)
self.master.view_flow(f)
- self.master.statusbar.message("Duplicated.")
- elif key == "e":
- if self.state.view_flow_mode == common.VIEW_FLOW_REQUEST:
- self.master.prompt_onekey(
- "Edit request",
- (
- ("query", "q"),
- ("path", "p"),
- ("url", "u"),
- ("header", "h"),
- ("form", "f"),
- ("raw body", "r"),
- ("method", "m"),
- ),
- self.edit
- )
- else:
- self.master.prompt_onekey(
- "Edit response",
- (
- ("code", "c"),
- ("message", "m"),
- ("header", "h"),
- ("raw body", "r"),
- ),
- self.edit
- )
- key = None
- elif key == "f":
- self.master.statusbar.message("Loading all body data...")
- self.state.add_flow_setting(
- self.flow,
- (self.state.view_flow_mode, "fullcontents"),
- True
- )
- self.master.refresh_flow(self.flow)
- self.master.statusbar.message("")
- elif key == "g":
- if self.state.view_flow_mode == common.VIEW_FLOW_REQUEST:
- scope = "q"
- else:
- scope = "s"
- common.ask_copy_part(scope, self.flow, self.master, self.state)
- elif key == "m":
- p = list(contentview.view_prompts)
- p.insert(0, ("Clear", "C"))
- self.master.prompt_onekey(
- "Display mode",
- p,
- self.change_this_display_mode
- )
- key = None
+ signals.status_message.send(message="Duplicated.")
elif key == "p":
self.view_prev_flow(self.flow)
elif key == "r":
r = self.master.replay_request(self.flow)
if r:
- self.master.statusbar.message(r)
- self.master.refresh_flow(self.flow)
+ signals.status_message.send(message=r)
+ signals.flow_change.send(self, flow = self.flow)
elif key == "V":
if not self.flow.modified():
- self.master.statusbar.message("Flow not modified.")
+ signals.status_message.send(message="Flow not modified.")
return
self.state.revert(self.flow)
- self.master.refresh_flow(self.flow)
- self.master.statusbar.message("Reverted.")
+ signals.flow_change.send(self, flow = self.flow)
+ signals.status_message.send(message="Reverted.")
elif key == "W":
- self.master.path_prompt(
- "Save this flow: ",
- self.state.last_saveload,
- self.master.save_one_flow,
- self.flow
+ signals.status_prompt_path.send(
+ prompt = "Save this flow",
+ callback = self.master.save_one_flow,
+ args = (self.flow,)
)
- elif key == "v":
- if conn and conn.content:
- t = conn.headers["content-type"] or [None]
- t = t[0]
- if os.environ.has_key("EDITOR") or os.environ.has_key("PAGER"):
- self.master.spawn_external_viewer(conn.content, t)
- else:
- self.master.statusbar.message("Error! Set $EDITOR or $PAGER.")
elif key == "|":
- self.master.path_prompt(
- "Send flow to script: ", self.state.last_script,
- self.master.run_script_once, self.flow
+ signals.status_prompt_path.send(
+ prompt = "Send flow to script",
+ callback = self.master.run_script_once,
+ args = (self.flow,)
)
- elif key == "x":
- self.master.prompt_onekey(
- "Delete body",
- (
- ("completely", "c"),
- ("mark as missing", "m"),
- ),
- self.delete_body
+
+ if not conn and key in set(list("befgmxvz")):
+ signals.status_message.send(
+ message = "Tab to the request or response",
+ expire = 1
)
- key = None
- elif key == "X":
- self.master.view_flowdetails(self.flow)
- elif key == "z":
- if conn:
+ elif conn:
+ if key == "b":
+ if self.tab_offset == TAB_REQ:
+ common.ask_save_body(
+ "q", self.master, self.state, self.flow
+ )
+ else:
+ common.ask_save_body(
+ "s", self.master, self.state, self.flow
+ )
+ elif key == "e":
+ if self.tab_offset == TAB_REQ:
+ signals.status_prompt_onekey.send(
+ prompt = "Edit request",
+ keys = (
+ ("cookies", "c"),
+ ("query", "q"),
+ ("path", "p"),
+ ("url", "u"),
+ ("header", "h"),
+ ("form", "f"),
+ ("raw body", "r"),
+ ("method", "m"),
+ ),
+ callback = self.edit
+ )
+ else:
+ signals.status_prompt_onekey.send(
+ prompt = "Edit response",
+ keys = (
+ ("cookies", "c"),
+ ("code", "o"),
+ ("message", "m"),
+ ("header", "h"),
+ ("raw body", "r"),
+ ),
+ callback = self.edit
+ )
+ key = None
+ elif key == "f":
+ signals.status_message.send(message="Loading all body data...")
+ self.state.add_flow_setting(
+ self.flow,
+ (self.tab_offset, "fullcontents"),
+ True
+ )
+ signals.flow_change.send(self, flow = self.flow)
+ signals.status_message.send(message="")
+ elif key == "P":
+ if self.tab_offset == TAB_REQ:
+ scope = "q"
+ else:
+ scope = "s"
+ common.ask_copy_part(scope, self.flow, self.master, self.state)
+ elif key == "m":
+ p = list(contentview.view_prompts)
+ p.insert(0, ("Clear", "C"))
+ signals.status_prompt_onekey.send(
+ self,
+ prompt = "Display mode",
+ keys = p,
+ callback = self.change_this_display_mode
+ )
+ key = None
+ elif key == "x":
+ signals.status_prompt_onekey.send(
+ prompt = "Delete body",
+ keys = (
+ ("completely", "c"),
+ ("mark as missing", "m"),
+ ),
+ callback = self.delete_body
+ )
+ key = None
+ elif key == "v":
+ if conn.content:
+ t = conn.headers["content-type"] or [None]
+ t = t[0]
+ if "EDITOR" in os.environ or "PAGER" in os.environ:
+ self.master.spawn_external_viewer(conn.content, t)
+ else:
+ signals.status_message.send(
+ message = "Error! Set $EDITOR or $PAGER."
+ )
+ elif key == "z":
self.flow.backup()
e = conn.headers.get_first("content-encoding", "identity")
if e != "identity":
if not conn.decode():
- self.master.statusbar.message("Could not decode - invalid data?")
+ signals.status_message.send(
+ message = "Could not decode - invalid data?"
+ )
else:
- self.master.prompt_onekey(
- "Select encoding: ",
- (
+ signals.status_prompt_onekey.send(
+ prompt = "Select encoding: ",
+ keys = (
("gzip", "z"),
("deflate", "d"),
),
- self.encode_callback,
- conn
+ callback = self.encode_callback,
+ args = (conn,)
)
- self.master.refresh_flow(self.flow)
- elif key == "/":
- last_search_string = self.state.get_flow_setting(self.flow, "last_search_string")
- search_prompt = "Search body ["+last_search_string+"]: " if last_search_string else "Search body: "
- self.master.prompt(search_prompt,
- None,
- self.search)
- elif key == "n":
- self.search_again(backwards=False)
- elif key == "N":
- self.search_again(backwards=True)
- else:
- return key
+ signals.flow_change.send(self, flow = self.flow)
+ return key
def encode_callback(self, key, conn):
encoding_map = {
@@ -816,4 +649,4 @@ class FlowView(urwid.WidgetWrap):
"d": "deflate",
}
conn.encode(encoding_map[key])
- self.master.refresh_flow(self.flow)
+ signals.flow_change.send(self, flow = self.flow)
diff --git a/libmproxy/console/grideditor.py b/libmproxy/console/grideditor.py
index fe3df509..b20e54e4 100644
--- a/libmproxy/console/grideditor.py
+++ b/libmproxy/console/grideditor.py
@@ -5,31 +5,99 @@ import re
import os
import urwid
-from . import common
+from . import common, signals
from .. import utils, filt, script
-from netlib import http_uastrings
+from netlib import http_uastrings, http_cookies, odict
-footer = [
+FOOTER = [
('heading_key', "enter"), ":edit ",
('heading_key', "q"), ":back ",
]
-footer_editing = [
+FOOTER_EDITING = [
('heading_key', "esc"), ":stop editing ",
]
-class SText(urwid.WidgetWrap):
- def __init__(self, txt, focused, error):
+class TextColumn:
+ subeditor = None
+
+ def __init__(self, heading):
+ self.heading = heading
+
+ def text(self, obj):
+ return SEscaped(obj or "")
+
+ def blank(self):
+ return ""
+
+ def keypress(self, key, editor):
+ if key == "r":
+ if editor.walker.get_current_value() is not None:
+ signals.status_prompt_path.send(
+ self,
+ prompt = "Read file",
+ callback = editor.read_file
+ )
+ elif key == "R":
+ if editor.walker.get_current_value() is not None:
+ signals.status_prompt_path.send(
+ editor,
+ prompt = "Read unescaped file",
+ callback = editor.read_file,
+ args = (True,)
+ )
+ elif key == "e":
+ o = editor.walker.get_current_value()
+ if o is not None:
+ n = editor.master.spawn_editor(o.encode("string-escape"))
+ n = utils.clean_hanging_newline(n)
+ editor.walker.set_current_value(n, False)
+ editor.walker._modified()
+ elif key in ["enter"]:
+ editor.walker.start_edit()
+ else:
+ return key
+
+
+class SubgridColumn:
+ def __init__(self, heading, subeditor):
+ self.heading = heading
+ self.subeditor = subeditor
+
+ def text(self, obj):
+ p = http_cookies._format_pairs(obj, sep="\n")
+ return urwid.Text(p)
+
+ def blank(self):
+ return []
+
+ def keypress(self, key, editor):
+ if key in "rRe":
+ signals.status_message.send(
+ self,
+ message = "Press enter to edit this field.",
+ expire = 1000
+ )
+ return
+ elif key in ["enter"]:
+ editor.master.view_grideditor(
+ self.subeditor(
+ editor.master,
+ editor.walker.get_current_value(),
+ editor.set_subeditor_value,
+ editor.walker.focus,
+ editor.walker.focus_col
+ )
+ )
+ else:
+ return key
+
+
+class SEscaped(urwid.WidgetWrap):
+ def __init__(self, txt):
txt = txt.encode("string-escape")
w = urwid.Text(txt, wrap="any")
- if focused:
- if error:
- w = urwid.AttrWrap(w, "focusfield_error")
- else:
- w = urwid.AttrWrap(w, "focusfield")
- elif error:
- w = urwid.AttrWrap(w, "field_error")
urwid.WidgetWrap.__init__(self, w)
def get_text(self):
@@ -50,7 +118,7 @@ class SEdit(urwid.WidgetWrap):
urwid.WidgetWrap.__init__(self, w)
def get_text(self):
- return self._w.get_text()[0]
+ return self._w.get_text()[0].strip()
def selectable(self):
return True
@@ -67,9 +135,15 @@ class GridRow(urwid.WidgetWrap):
self.editing = SEdit(v)
self.fields.append(self.editing)
else:
- self.fields.append(
- SText(v, True if focused == i else False, i in errors)
- )
+ w = self.editor.columns[i].text(v)
+ if focused == i:
+ if i in errors:
+ w = urwid.AttrWrap(w, "focusfield_error")
+ else:
+ w = urwid.AttrWrap(w, "focusfield")
+ elif i in errors:
+ w = urwid.AttrWrap(w, "field_error")
+ self.fields.append(w)
fspecs = self.fields[:]
if len(self.fields) > 1:
@@ -101,6 +175,7 @@ class GridWalker(urwid.ListWalker):
and errors is a set with an entry of each offset in rows that is an
error.
"""
+
def __init__(self, lst, editor):
self.lst = [(i, set([])) for i in lst]
self.editor = editor
@@ -125,31 +200,43 @@ class GridWalker(urwid.ListWalker):
try:
val = val.decode("string-escape")
except ValueError:
- self.editor.master.statusbar.message(
- "Invalid Python-style string encoding.", 1000
+ signals.status_message.send(
+ self,
+ message = "Invalid Python-style string encoding.",
+ expire = 1000
)
return
errors = self.lst[self.focus][1]
emsg = self.editor.is_error(self.focus_col, val)
if emsg:
- self.editor.master.statusbar.message(emsg, 1000)
+ signals.status_message.send(message = emsg, expire = 1)
errors.add(self.focus_col)
else:
errors.discard(self.focus_col)
-
- row = list(self.lst[self.focus][0])
- row[self.focus_col] = val
- self.lst[self.focus] = [tuple(row), errors]
+ self.set_value(val, self.focus, self.focus_col, errors)
+
+ def set_value(self, val, focus, focus_col, errors=None):
+ if not errors:
+ errors = set([])
+ row = list(self.lst[focus][0])
+ row[focus_col] = val
+ self.lst[focus] = [tuple(row), errors]
+ self._modified()
def delete_focus(self):
if self.lst:
del self.lst[self.focus]
- self.focus = min(len(self.lst)-1, self.focus)
+ self.focus = min(len(self.lst) - 1, self.focus)
self._modified()
def _insert(self, pos):
self.focus = pos
- self.lst.insert(self.focus, [[""]*self.editor.columns, set([])])
+ self.lst.insert(
+ self.focus,
+ [
+ [c.blank() for c in self.editor.columns], set([])
+ ]
+ )
self.focus_col = 0
self.start_edit()
@@ -160,16 +247,17 @@ class GridWalker(urwid.ListWalker):
return self._insert(min(self.focus + 1, len(self.lst)))
def start_edit(self):
- if self.lst:
+ col = self.editor.columns[self.focus_col]
+ if self.lst and not col.subeditor:
self.editing = GridRow(
self.focus_col, True, self.editor, self.lst[self.focus]
)
- self.editor.master.statusbar.update(footer_editing)
+ self.editor.master.loop.widget.footer.update(FOOTER_EDITING)
self._modified()
def stop_edit(self):
if self.editing:
- self.editor.master.statusbar.update(footer)
+ self.editor.master.loop.widget.footer.update(FOOTER)
self.set_current_value(self.editing.get_edit_value(), False)
self.editing = False
self._modified()
@@ -179,14 +267,14 @@ class GridWalker(urwid.ListWalker):
self._modified()
def right(self):
- self.focus_col = min(self.focus_col + 1, self.editor.columns-1)
+ self.focus_col = min(self.focus_col + 1, len(self.editor.columns) - 1)
self._modified()
def tab_next(self):
self.stop_edit()
- if self.focus_col < self.editor.columns-1:
+ if self.focus_col < len(self.editor.columns) - 1:
self.focus_col += 1
- elif self.focus != len(self.lst)-1:
+ elif self.focus != len(self.lst) - 1:
self.focus_col = 0
self.focus += 1
self._modified()
@@ -207,16 +295,17 @@ class GridWalker(urwid.ListWalker):
def set_focus(self, focus):
self.stop_edit()
self.focus = focus
+ self._modified()
def get_next(self, pos):
- if pos+1 >= len(self.lst):
+ if pos + 1 >= len(self.lst):
return None, None
- return GridRow(None, False, self.editor, self.lst[pos+1]), pos+1
+ return GridRow(None, False, self.editor, self.lst[pos + 1]), pos + 1
def get_prev(self, pos):
- if pos-1 < 0:
+ if pos - 1 < 0:
return None, None
- return GridRow(None, False, self.editor, self.lst[pos-1]), pos-1
+ return GridRow(None, False, self.editor, self.lst[pos - 1]), pos - 1
class GridListBox(urwid.ListBox):
@@ -231,17 +320,16 @@ FIRST_WIDTH_MIN = 20
class GridEditor(urwid.WidgetWrap):
title = None
columns = None
- headings = None
def __init__(self, master, value, callback, *cb_args, **cb_kwargs):
- value = copy.deepcopy(value)
+ value = self.data_in(copy.deepcopy(value))
self.master, self.value, self.callback = master, value, callback
self.cb_args, self.cb_kwargs = cb_args, cb_kwargs
first_width = 20
if value:
for r in value:
- assert len(r) == self.columns
+ assert len(r) == len(self.columns)
first_width = max(len(r), first_width)
self.first_width = min(first_width, FIRST_WIDTH_MAX)
@@ -250,9 +338,9 @@ class GridEditor(urwid.WidgetWrap):
title = urwid.AttrWrap(title, "heading")
headings = []
- for i, h in enumerate(self.headings):
- c = urwid.Text(h)
- if i == 0 and len(self.headings) > 1:
+ for i, col in enumerate(self.columns):
+ c = urwid.Text(col.heading)
+ if i == 0 and len(self.columns) > 1:
headings.append(("fixed", first_width + 2, c))
else:
headings.append(c)
@@ -268,7 +356,7 @@ class GridEditor(urwid.WidgetWrap):
self.lb,
header = urwid.Pile([title, h])
)
- self.master.statusbar.update("")
+ self.master.loop.widget.footer.update("")
self.show_empty_msg()
def show_empty_msg(self):
@@ -300,9 +388,12 @@ class GridEditor(urwid.WidgetWrap):
d = file(p, "rb").read()
self.walker.set_current_value(d, unescaped)
self.walker._modified()
- except IOError, v:
+ except IOError as v:
return str(v)
+ def set_subeditor_value(self, val, focus, focus_col):
+ self.walker.set_value(val, focus, focus_col)
+
def keypress(self, size, key):
if self.walker.editing:
if key in ["esc"]:
@@ -317,13 +408,18 @@ class GridEditor(urwid.WidgetWrap):
return None
key = common.shortcuts(key)
+ column = self.columns[self.walker.focus_col]
if key in ["q", "esc"]:
res = []
for i in self.walker.lst:
- if not i[1] and any([x.strip() for x in i[0]]):
+ if not i[1] and any([x for x in i[0]]):
res.append(i[0])
- self.callback(res, *self.cb_args, **self.cb_kwargs)
- self.master.pop_view()
+ self.callback(self.data_out(res), *self.cb_args, **self.cb_kwargs)
+ signals.pop_view_state.send(self)
+ elif key == "G":
+ self.walker.set_focus(0)
+ elif key == "g":
+ self.walker.set_focus(len(self.walker.lst) - 1)
elif key in ["h", "left"]:
self.walker.left()
elif key in ["l", "right"]:
@@ -336,26 +432,22 @@ class GridEditor(urwid.WidgetWrap):
self.walker.insert()
elif key == "d":
self.walker.delete_focus()
- elif key == "r":
- if self.walker.get_current_value() is not None:
- self.master.path_prompt("Read file: ", "", self.read_file)
- elif key == "R":
- if self.walker.get_current_value() is not None:
- self.master.path_prompt(
- "Read unescaped file: ", "", self.read_file, True
- )
- elif key == "e":
- o = self.walker.get_current_value()
- if o is not None:
- n = self.master.spawn_editor(o.encode("string-escape"))
- n = utils.clean_hanging_newline(n)
- self.walker.set_current_value(n, False)
- self.walker._modified()
- elif key in ["enter"]:
- self.walker.start_edit()
- elif not self.handle_key(key):
+ elif column.keypress(key, self) and not self.handle_key(key):
return self._w.keypress(size, key)
+ def data_out(self, data):
+ """
+ Called on raw list data, before data is returned through the
+ callback.
+ """
+ return data
+
+ def data_in(self, data):
+ """
+ Called to prepare provided data.
+ """
+ return data
+
def is_error(self, col, val):
"""
Return False, or a string error message.
@@ -373,10 +465,10 @@ class GridEditor(urwid.WidgetWrap):
("a", "add row after cursor"),
("d", "delete row"),
("e", "spawn external editor on current field"),
- ("q", "return to flow view"),
+ ("q", "save changes and exit editor"),
("r", "read value from file"),
("R", "read unescaped value from file"),
- ("esc", "return to flow view/exit field edit mode"),
+ ("esc", "save changes and exit editor"),
("tab", "next field"),
("enter", "edit field"),
]
@@ -396,14 +488,18 @@ class GridEditor(urwid.WidgetWrap):
class QueryEditor(GridEditor):
title = "Editing query"
- columns = 2
- headings = ("Key", "Value")
+ columns = [
+ TextColumn("Key"),
+ TextColumn("Value")
+ ]
class HeaderEditor(GridEditor):
title = "Editing headers"
- columns = 2
- headings = ("Key", "Value")
+ columns = [
+ TextColumn("Key"),
+ TextColumn("Value")
+ ]
def make_help(self):
h = GridEditor.make_help(self)
@@ -431,24 +527,29 @@ class HeaderEditor(GridEditor):
def handle_key(self, key):
if key == "U":
- self.master.prompt_onekey(
- "Add User-Agent header:",
- [(i[0], i[1]) for i in http_uastrings.UASTRINGS],
- self.set_user_agent,
+ signals.status_prompt_onekey.send(
+ prompt = "Add User-Agent header:",
+ keys = [(i[0], i[1]) for i in http_uastrings.UASTRINGS],
+ callback = self.set_user_agent,
)
return True
class URLEncodedFormEditor(GridEditor):
title = "Editing URL-encoded form"
- columns = 2
- headings = ("Key", "Value")
+ columns = [
+ TextColumn("Key"),
+ TextColumn("Value")
+ ]
class ReplaceEditor(GridEditor):
title = "Editing replacement patterns"
- columns = 3
- headings = ("Filter", "Regex", "Replacement")
+ columns = [
+ TextColumn("Filter"),
+ TextColumn("Regex"),
+ TextColumn("Replacement"),
+ ]
def is_error(self, col, val):
if col == 0:
@@ -464,8 +565,11 @@ class ReplaceEditor(GridEditor):
class SetHeadersEditor(GridEditor):
title = "Editing header set patterns"
- columns = 3
- headings = ("Filter", "Header", "Value")
+ columns = [
+ TextColumn("Filter"),
+ TextColumn("Header"),
+ TextColumn("Value"),
+ ]
def is_error(self, col, val):
if col == 0:
@@ -500,39 +604,105 @@ class SetHeadersEditor(GridEditor):
def handle_key(self, key):
if key == "U":
- self.master.prompt_onekey(
- "Add User-Agent header:",
- [(i[0], i[1]) for i in http_uastrings.UASTRINGS],
- self.set_user_agent,
+ signals.status_prompt_onekey.send(
+ prompt = "Add User-Agent header:",
+ keys = [(i[0], i[1]) for i in http_uastrings.UASTRINGS],
+ callback = self.set_user_agent,
)
return True
class PathEditor(GridEditor):
title = "Editing URL path components"
- columns = 1
- headings = ("Component",)
+ columns = [
+ TextColumn("Component"),
+ ]
+
+ def data_in(self, data):
+ return [[i] for i in data]
+
+ def data_out(self, data):
+ return [i[0] for i in data]
class ScriptEditor(GridEditor):
title = "Editing scripts"
- columns = 1
- headings = ("Command",)
+ columns = [
+ TextColumn("Command"),
+ ]
def is_error(self, col, val):
try:
script.Script.parse_command(val)
- except script.ScriptError, v:
+ except script.ScriptError as v:
return str(v)
class HostPatternEditor(GridEditor):
title = "Editing host patterns"
- columns = 1
- headings = ("Regex (matched on hostname:port / ip:port)",)
+ columns = [
+ TextColumn("Regex (matched on hostname:port / ip:port)")
+ ]
def is_error(self, col, val):
try:
re.compile(val, re.IGNORECASE)
except re.error as e:
return "Invalid regex: %s" % str(e)
+
+ def data_in(self, data):
+ return [[i] for i in data]
+
+ def data_out(self, data):
+ return [i[0] for i in data]
+
+
+class CookieEditor(GridEditor):
+ title = "Editing request Cookie header"
+ columns = [
+ TextColumn("Name"),
+ TextColumn("Value"),
+ ]
+
+
+class CookieAttributeEditor(GridEditor):
+ title = "Editing Set-Cookie attributes"
+ columns = [
+ TextColumn("Name"),
+ TextColumn("Value"),
+ ]
+
+ def data_out(self, data):
+ ret = []
+ for i in data:
+ if not i[1]:
+ ret.append([i[0], None])
+ else:
+ ret.append(i)
+ return ret
+
+
+class SetCookieEditor(GridEditor):
+ title = "Editing response SetCookie header"
+ columns = [
+ TextColumn("Name"),
+ TextColumn("Value"),
+ SubgridColumn("Attributes", CookieAttributeEditor),
+ ]
+
+ def data_in(self, data):
+ flattened = []
+ for k, v in data.items():
+ flattened.append([k, v[0], v[1].lst])
+ return flattened
+
+ def data_out(self, data):
+ vals = []
+ for i in data:
+ vals.append(
+ [
+ i[0],
+ [i[1], odict.ODictCaseless(i[2])]
+ ]
+ )
+ return odict.ODict(vals)
diff --git a/libmproxy/console/help.py b/libmproxy/console/help.py
index 6bb49a92..4e81a566 100644
--- a/libmproxy/console/help.py
+++ b/libmproxy/console/help.py
@@ -2,18 +2,17 @@ from __future__ import absolute_import
import urwid
-from . import common
+from . import common, signals
from .. import filt, version
footer = [
- ("heading", 'mitmproxy v%s '%version.VERSION),
+ ("heading", 'mitmproxy v%s ' % version.VERSION),
('heading_key', "q"), ":back ",
]
class HelpView(urwid.ListBox):
- def __init__(self, master, help_context, state):
- self.master, self.state = master, state
+ def __init__(self, help_context):
self.help_context = help_context or []
urwid.ListBox.__init__(
self,
@@ -29,101 +28,26 @@ class HelpView(urwid.ListBox):
keys = [
("j, k", "down, up"),
("h, l", "left, right (in some contexts)"),
+ ("g, G", "go to end, beginning"),
("space", "page down"),
("pg up/down", "page up/down"),
("arrows", "up, down, left, right"),
]
- text.extend(common.format_keyvals(keys, key="key", val="text", indent=4))
+ text.extend(
+ common.format_keyvals(
+ keys,
+ key="key",
+ val="text",
+ indent=4))
text.append(urwid.Text([("head", "\n\nGlobal keys:\n")]))
keys = [
("c", "client replay"),
- ("H", "edit global header set patterns"),
- ("I", "set ignore pattern"),
("i", "set interception pattern"),
- ("M", "change global default display mode"),
- (None,
- common.highlight_key("automatic", "a") +
- [("text", ": automatic detection")]
- ),
- (None,
- common.highlight_key("hex", "e") +
- [("text", ": Hex")]
- ),
- (None,
- common.highlight_key("html", "h") +
- [("text", ": HTML")]
- ),
- (None,
- common.highlight_key("image", "i") +
- [("text", ": Image")]
- ),
- (None,
- common.highlight_key("javascript", "j") +
- [("text", ": JavaScript")]
- ),
- (None,
- common.highlight_key("json", "s") +
- [("text", ": JSON")]
- ),
- (None,
- common.highlight_key("css", "c") +
- [("text", ": CSS")]
- ),
- (None,
- common.highlight_key("urlencoded", "u") +
- [("text", ": URL-encoded data")]
- ),
- (None,
- common.highlight_key("raw", "r") +
- [("text", ": raw data")]
- ),
- (None,
- common.highlight_key("xml", "x") +
- [("text", ": XML")]
- ),
- (None,
- common.highlight_key("wbxml", "w") +
- [("text", ": WBXML")]
- ),
- (None,
- common.highlight_key("amf", "f") +
- [("text", ": AMF (requires PyAMF)")]
- ),
- ("o", "toggle options:"),
- (None,
- common.highlight_key("anticache", "a") +
- [("text", ": prevent cached responses")]
- ),
- (None,
- common.highlight_key("anticomp", "c") +
- [("text", ": prevent compressed responses")]
- ),
- (None,
- common.highlight_key("showhost", "h") +
- [("text", ": use Host header for URL display")]
- ),
- (None,
- common.highlight_key("killextra", "k") +
- [("text", ": kill requests not part of server replay")]
- ),
- (None,
- common.highlight_key("norefresh", "n") +
- [("text", ": disable server replay response refresh")]
- ),
- (None,
- common.highlight_key("upstream certs", "u") +
- [("text", ": sniff cert info from upstream server")]
- ),
-
- ("q", "quit / return to flow list"),
+ ("o", "options"),
+ ("q", "quit / return to previous page"),
("Q", "quit without confirm prompt"),
- ("R", "edit replacement patterns"),
- ("s", "add/remove scripts"),
("S", "server replay"),
- ("t", "set sticky cookie expression"),
- ("T", "set tcp proxying pattern"),
- ("u", "set sticky auth expression"),
]
text.extend(
common.format_keyvals(keys, key="key", val="text", indent=4)
@@ -133,15 +57,15 @@ class HelpView(urwid.ListBox):
f = []
for i in filt.filt_unary:
f.append(
- ("~%s"%i.code, i.help)
+ ("~%s" % i.code, i.help)
)
for i in filt.filt_rex:
f.append(
- ("~%s regex"%i.code, i.help)
+ ("~%s regex" % i.code, i.help)
)
for i in filt.filt_int:
f.append(
- ("~%s int"%i.code, i.help)
+ ("~%s int" % i.code, i.help)
)
f.sort()
f.extend(
@@ -156,7 +80,7 @@ class HelpView(urwid.ListBox):
text.append(
urwid.Text(
- [
+ [
"\n",
("text", " Regexes are Python-style.\n"),
("text", " Regexes can be specified as quoted strings.\n"),
@@ -164,13 +88,13 @@ class HelpView(urwid.ListBox):
("text", " Expressions with no operators are regex matches against URL.\n"),
("text", " Default binary operator is &.\n"),
("head", "\n Examples:\n"),
- ]
+ ]
)
)
examples = [
- ("google\.com", "Url containing \"google.com"),
- ("~q ~b test", "Requests where body contains \"test\""),
- ("!(~q & ~t \"text/html\")", "Anything but requests with a text/html content type."),
+ ("google\.com", "Url containing \"google.com"),
+ ("~q ~b test", "Requests where body contains \"test\""),
+ ("!(~q & ~t \"text/html\")", "Anything but requests with a text/html content type."),
]
text.extend(
common.format_keyvals(examples, key="key", val="text", indent=4)
@@ -180,11 +104,12 @@ class HelpView(urwid.ListBox):
def keypress(self, size, key):
key = common.shortcuts(key)
if key == "q":
- self.master.statusbar = self.state[0]
- self.master.body = self.state[1]
- self.master.header = self.state[2]
- self.master.loop.widget = self.master.make_view()
+ signals.pop_view_state.send(self)
return None
elif key == "?":
key = None
+ elif key == "G":
+ self.set_focus(0)
+ elif key == "g":
+ self.set_focus(len(self.body.contents))
return urwid.ListBox.keypress(self, size, key)
diff --git a/libmproxy/console/options.py b/libmproxy/console/options.py
new file mode 100644
index 00000000..58a4d469
--- /dev/null
+++ b/libmproxy/console/options.py
@@ -0,0 +1,269 @@
+import urwid
+
+from . import common, signals, grideditor, contentview
+from . import select, palettes
+
+footer = [
+ ('heading_key', "enter/space"), ":toggle ",
+ ('heading_key', "C"), ":clear all ",
+]
+
+
+def _mkhelp():
+ text = []
+ keys = [
+ ("enter/space", "activate option"),
+ ("C", "clear all options"),
+ ]
+ text.extend(common.format_keyvals(keys, key="key", val="text", indent=4))
+ return text
+help_context = _mkhelp()
+
+
+class Options(urwid.WidgetWrap):
+ def __init__(self, master):
+ self.master = master
+ self.lb = select.Select(
+ [
+ select.Heading("Traffic Manipulation"),
+ select.Option(
+ "Header Set Patterns",
+ "H",
+ lambda: master.setheaders.count(),
+ self.setheaders
+ ),
+ select.Option(
+ "Ignore Patterns",
+ "I",
+ lambda: master.server.config.check_ignore,
+ self.ignorepatterns
+ ),
+ select.Option(
+ "Replacement Patterns",
+ "R",
+ lambda: master.replacehooks.count(),
+ self.replacepatterns
+ ),
+ select.Option(
+ "Scripts",
+ "S",
+ lambda: master.scripts,
+ self.scripts
+ ),
+
+ select.Heading("Interface"),
+ select.Option(
+ "Default Display Mode",
+ "M",
+ self.has_default_displaymode,
+ self.default_displaymode
+ ),
+ select.Option(
+ "Palette",
+ "P",
+ lambda: self.master.palette != palettes.DEFAULT,
+ self.palette
+ ),
+ select.Option(
+ "Show Host",
+ "w",
+ lambda: master.showhost,
+ self.toggle_showhost
+ ),
+
+ select.Heading("Network"),
+ select.Option(
+ "No Upstream Certs",
+ "U",
+ lambda: master.server.config.no_upstream_cert,
+ self.toggle_upstream_cert
+ ),
+ select.Option(
+ "TCP Proxying",
+ "T",
+ lambda: master.server.config.check_tcp,
+ self.tcp_proxy
+ ),
+
+ select.Heading("Utility"),
+ select.Option(
+ "Anti-Cache",
+ "a",
+ lambda: master.anticache,
+ self.toggle_anticache
+ ),
+ select.Option(
+ "Anti-Compression",
+ "o",
+ lambda: master.anticomp,
+ self.toggle_anticomp
+ ),
+ select.Option(
+ "Kill Extra",
+ "x",
+ lambda: master.killextra,
+ self.toggle_killextra
+ ),
+ select.Option(
+ "No Refresh",
+ "f",
+ lambda: not master.refresh_server_playback,
+ self.toggle_refresh_server_playback
+ ),
+ select.Option(
+ "Sticky Auth",
+ "A",
+ lambda: master.stickyauth_txt,
+ self.sticky_auth
+ ),
+ select.Option(
+ "Sticky Cookies",
+ "t",
+ lambda: master.stickycookie_txt,
+ self.sticky_cookie
+ ),
+ ]
+ )
+ title = urwid.Text("Options")
+ title = urwid.Padding(title, align="left", width=("relative", 100))
+ title = urwid.AttrWrap(title, "heading")
+ self._w = urwid.Frame(
+ self.lb,
+ header = title
+ )
+ self.master.loop.widget.footer.update("")
+ signals.update_settings.connect(self.sig_update_settings)
+
+ def sig_update_settings(self, sender):
+ self.lb.walker._modified()
+
+ def keypress(self, size, key):
+ if key == "C":
+ self.clearall()
+ return None
+ return super(self.__class__, self).keypress(size, key)
+
+ def clearall(self):
+ self.master.anticache = False
+ self.master.anticomp = False
+ self.master.killextra = False
+ self.master.showhost = False
+ self.master.refresh_server_playback = True
+ self.master.server.config.no_upstream_cert = False
+ self.master.setheaders.clear()
+ self.master.replacehooks.clear()
+ self.master.set_ignore_filter([])
+ self.master.set_tcp_filter([])
+ self.master.scripts = []
+ self.master.set_stickyauth(None)
+ self.master.set_stickycookie(None)
+ self.master.state.default_body_view = contentview.get("Auto")
+
+ signals.update_settings.send(self)
+ signals.status_message.send(
+ message = "All select.Options cleared",
+ expire = 1
+ )
+
+ def toggle_anticache(self):
+ self.master.anticache = not self.master.anticache
+
+ def toggle_anticomp(self):
+ self.master.anticomp = not self.master.anticomp
+
+ def toggle_killextra(self):
+ self.master.killextra = not self.master.killextra
+
+ def toggle_showhost(self):
+ self.master.showhost = not self.master.showhost
+
+ def toggle_refresh_server_playback(self):
+ self.master.refresh_server_playback = not self.master.refresh_server_playback
+
+ def toggle_upstream_cert(self):
+ self.master.server.config.no_upstream_cert = not self.master.server.config.no_upstream_cert
+ signals.update_settings.send(self)
+
+ def setheaders(self):
+ def _set(*args, **kwargs):
+ self.master.setheaders.set(*args, **kwargs)
+ signals.update_settings.send(self)
+ self.master.view_grideditor(
+ grideditor.SetHeadersEditor(
+ self.master,
+ self.master.setheaders.get_specs(),
+ _set
+ )
+ )
+
+ def ignorepatterns(self):
+ def _set(ignore):
+ self.master.set_ignore_filter(ignore)
+ signals.update_settings.send(self)
+ self.master.view_grideditor(
+ grideditor.HostPatternEditor(
+ self.master,
+ self.master.get_ignore_filter(),
+ _set
+ )
+ )
+
+ def replacepatterns(self):
+ def _set(*args, **kwargs):
+ self.master.replacehooks.set(*args, **kwargs)
+ signals.update_settings.send(self)
+ self.master.view_grideditor(
+ grideditor.ReplaceEditor(
+ self.master,
+ self.master.replacehooks.get_specs(),
+ _set
+ )
+ )
+
+ def scripts(self):
+ self.master.view_grideditor(
+ grideditor.ScriptEditor(
+ self.master,
+ [[i.command] for i in self.master.scripts],
+ self.master.edit_scripts
+ )
+ )
+
+ def default_displaymode(self):
+ signals.status_prompt_onekey.send(
+ prompt = "Global default display mode",
+ keys = contentview.view_prompts,
+ callback = self.master.change_default_display_mode
+ )
+
+ def has_default_displaymode(self):
+ return self.master.state.default_body_view.name != "Auto"
+
+ def tcp_proxy(self):
+ def _set(tcp):
+ self.master.set_tcp_filter(tcp)
+ signals.update_settings.send(self)
+ self.master.view_grideditor(
+ grideditor.HostPatternEditor(
+ self.master,
+ self.master.get_tcp_filter(),
+ _set
+ )
+ )
+
+ def sticky_auth(self):
+ signals.status_prompt.send(
+ prompt = "Sticky auth filter",
+ text = self.master.stickyauth_txt,
+ callback = self.master.set_stickyauth
+ )
+
+ def sticky_cookie(self):
+ signals.status_prompt.send(
+ prompt = "Sticky cookie filter",
+ text = self.master.stickycookie_txt,
+ callback = self.master.set_stickycookie
+ )
+
+ def palette(self):
+ self.master.view_palette_picker()
diff --git a/libmproxy/console/palettepicker.py b/libmproxy/console/palettepicker.py
new file mode 100644
index 00000000..7e2c10cd
--- /dev/null
+++ b/libmproxy/console/palettepicker.py
@@ -0,0 +1,81 @@
+import urwid
+
+from . import select, common, palettes, signals
+
+footer = [
+ ('heading_key', "enter/space"), ":select",
+]
+
+
+def _mkhelp():
+ text = []
+ keys = [
+ ("enter/space", "select"),
+ ]
+ text.extend(common.format_keyvals(keys, key="key", val="text", indent=4))
+ return text
+help_context = _mkhelp()
+
+
+class PalettePicker(urwid.WidgetWrap):
+ def __init__(self, master):
+ self.master = master
+ low, high = [], []
+ for k, v in palettes.palettes.items():
+ if v.high:
+ high.append(k)
+ else:
+ low.append(k)
+ high.sort()
+ low.sort()
+
+ options = [
+ select.Heading("High Colour")
+ ]
+
+ def mkopt(name):
+ return select.Option(
+ i,
+ None,
+ lambda: self.master.palette == name,
+ lambda: self.select(name)
+ )
+
+ for i in high:
+ options.append(mkopt(i))
+ options.append(select.Heading("Low Colour"))
+ for i in low:
+ options.append(mkopt(i))
+
+ options.extend(
+ [
+ select.Heading("Options"),
+ select.Option(
+ "Transparent",
+ "T",
+ lambda: master.palette_transparent,
+ self.toggle_palette_transparent
+ )
+ ]
+ )
+
+ self.lb = select.Select(options)
+ title = urwid.Text("Palettes")
+ title = urwid.Padding(title, align="left", width=("relative", 100))
+ title = urwid.AttrWrap(title, "heading")
+ self._w = urwid.Frame(
+ self.lb,
+ header = title
+ )
+ signals.update_settings.connect(self.sig_update_settings)
+
+ def sig_update_settings(self, sender):
+ self.lb.walker._modified()
+
+ def select(self, name):
+ self.master.set_palette(name)
+
+ def toggle_palette_transparent(self):
+ self.master.palette_transparent = not self.master.palette_transparent
+ self.master.set_palette(self.master.palette)
+ signals.update_settings.send(self)
diff --git a/libmproxy/console/palettes.py b/libmproxy/console/palettes.py
index cfb2702c..ea3d1b62 100644
--- a/libmproxy/console/palettes.py
+++ b/libmproxy/console/palettes.py
@@ -1,4 +1,3 @@
-
# Low-color themes should ONLY use the standard foreground and background
# colours listed here:
#
@@ -6,9 +5,9 @@
#
-
class Palette:
_fields = [
+ 'background',
'title',
# Status bar & heading
@@ -17,6 +16,10 @@ class Palette:
# Help
'key', 'head', 'text',
+ # Options
+ 'option_selected', 'option_active', 'option_active_selected',
+ 'option_selected_key',
+
# List and Connections
'method', 'focus',
'code_200', 'code_300', 'code_400', 'code_500', 'code_other',
@@ -31,15 +34,33 @@ class Palette:
]
high = None
- def palette(self):
+ def palette(self, transparent):
l = []
+ highback, lowback = None, None
+ if not transparent:
+ if self.high and self.high.get("background"):
+ highback = self.high["background"][1]
+ lowback = self.low["background"][1]
+
for i in self._fields:
- v = [i]
- v.extend(self.low[i])
- if self.high and i in self.high:
- v.append(None)
- v.extend(self.high[i])
- l.append(tuple(v))
+ if transparent and i == "background":
+ l.append(["background", "default", "default"])
+ else:
+ v = [i]
+ low = list(self.low[i])
+ if lowback and low[1] == "default":
+ low[1] = lowback
+ v.extend(low)
+ if self.high and i in self.high:
+ v.append(None)
+ high = list(self.high[i])
+ if highback and high[1] == "default":
+ high[1] = highback
+ v.extend(high)
+ elif highback and self.low[i][1] == "default":
+ high = [None, low[0], highback]
+ v.extend(high)
+ l.append(tuple(v))
return l
@@ -48,18 +69,25 @@ class LowDark(Palette):
Low-color dark background
"""
low = dict(
+ background = ('white', 'black'),
title = ('white,bold', 'default'),
# Status bar & heading
- heading = ('light gray', 'dark blue'),
+ heading = ('white', 'dark blue'),
heading_key = ('light cyan', 'dark blue'),
- heading_inactive = ('white', 'dark gray'),
+ heading_inactive = ('dark gray', 'light gray'),
# Help
key = ('light cyan', 'default'),
head = ('white,bold', 'default'),
text = ('light gray', 'default'),
+ # Options
+ option_selected = ('black', 'light gray'),
+ option_selected_key = ('light cyan', 'light gray'),
+ option_active = ('light red', 'default'),
+ option_active_selected = ('light red', 'light gray'),
+
# List and Connections
method = ('dark cyan', 'default'),
focus = ('yellow', 'default'),
@@ -92,6 +120,10 @@ class Dark(LowDark):
high = dict(
heading_inactive = ('g58', 'g11'),
intercept = ('#f60', 'default'),
+
+ option_selected = ('g85', 'g45'),
+ option_selected_key = ('light cyan', 'g50'),
+ option_active_selected = ('light red', 'g50'),
)
@@ -100,18 +132,25 @@ class LowLight(Palette):
Low-color light background
"""
low = dict(
- title = ('dark magenta,bold', 'light blue'),
+ background = ('black', 'white'),
+ title = ('dark magenta', 'default'),
# Status bar & heading
- heading = ('light gray', 'dark blue'),
- heading_key = ('light cyan', 'dark blue'),
+ heading = ('white', 'black'),
+ heading_key = ('dark blue', 'black'),
heading_inactive = ('black', 'light gray'),
# Help
- key = ('dark blue,bold', 'default'),
- head = ('black,bold', 'default'),
+ key = ('dark blue', 'default'),
+ head = ('black', 'default'),
text = ('dark gray', 'default'),
+ # Options
+ option_selected = ('black', 'light gray'),
+ option_selected_key = ('dark blue', 'light gray'),
+ option_active = ('light red', 'default'),
+ option_active_selected = ('light red', 'light gray'),
+
# List and Connections
method = ('dark cyan', 'default'),
focus = ('black', 'default'),
@@ -142,10 +181,15 @@ class LowLight(Palette):
class Light(LowLight):
high = dict(
+ background = ('black', 'g100'),
heading = ('g99', '#08f'),
heading_key = ('#0ff,bold', '#08f'),
heading_inactive = ('g35', 'g85'),
replay = ('#0a0,bold', 'default'),
+
+ option_selected = ('black', 'g85'),
+ option_selected_key = ('dark blue', 'g85'),
+ option_active_selected = ('light red', 'g85'),
)
@@ -155,10 +199,10 @@ sol_base03 = "h234"
sol_base02 = "h235"
sol_base01 = "h240"
sol_base00 = "h241"
-sol_base0 = "h244"
-sol_base1 = "h245"
-sol_base2 = "h254"
-sol_base3 = "h230"
+sol_base0 = "h244"
+sol_base1 = "h245"
+sol_base2 = "h254"
+sol_base3 = "h230"
sol_yellow = "h136"
sol_orange = "h166"
sol_red = "h160"
@@ -167,9 +211,12 @@ sol_violet = "h61"
sol_blue = "h33"
sol_cyan = "h37"
sol_green = "h64"
+
+
class SolarizedLight(LowLight):
high = dict(
- title = (sol_blue, 'default'),
+ background = (sol_base00, sol_base3),
+ title = (sol_cyan, 'default'),
text = (sol_base00, 'default'),
# Status bar & heading
@@ -181,6 +228,12 @@ class SolarizedLight(LowLight):
key = (sol_blue, 'default',),
head = (sol_base00, 'default'),
+ # Options
+ option_selected = (sol_base03, sol_base2),
+ option_selected_key = (sol_blue, sol_base2),
+ option_active = (sol_orange, 'default'),
+ option_active_selected = (sol_orange, sol_base2),
+
# List and Connections
method = (sol_cyan, 'default'),
focus = (sol_base01, 'default'),
@@ -193,7 +246,7 @@ class SolarizedLight(LowLight):
error = (sol_red, 'default'),
- header = (sol_base01, 'default'),
+ header = (sol_blue, 'default'),
highlight = (sol_base01, 'default'),
intercept = (sol_red, 'default',),
replay = (sol_green, 'default',),
@@ -211,17 +264,24 @@ class SolarizedLight(LowLight):
class SolarizedDark(LowDark):
high = dict(
+ background = (sol_base2, sol_base03),
title = (sol_blue, 'default'),
- text = (sol_base0, 'default'),
+ text = (sol_base1, 'default'),
# Status bar & heading
- heading = (sol_base03, sol_base1),
- heading_key = (sol_blue+",bold", sol_base1),
+ heading = (sol_base2, sol_base01),
+ heading_key = (sol_blue + ",bold", sol_base01),
heading_inactive = (sol_base1, sol_base02),
# Help
key = (sol_blue, 'default',),
- head = (sol_base00, 'default'),
+ head = (sol_base2, 'default'),
+
+ # Options
+ option_selected = (sol_base03, sol_base00),
+ option_selected_key = (sol_blue, sol_base00),
+ option_active = (sol_orange, 'default'),
+ option_active_selected = (sol_orange, sol_base00),
# List and Connections
method = (sol_cyan, 'default'),
@@ -235,7 +295,7 @@ class SolarizedDark(LowDark):
error = (sol_red, 'default'),
- header = (sol_base01, 'default'),
+ header = (sol_blue, 'default'),
highlight = (sol_base01, 'default'),
intercept = (sol_red, 'default',),
replay = (sol_green, 'default',),
@@ -251,6 +311,7 @@ class SolarizedDark(LowDark):
)
+DEFAULT = "dark"
palettes = {
"lowlight": LowLight(),
"lowdark": LowDark(),
diff --git a/libmproxy/console/pathedit.py b/libmproxy/console/pathedit.py
new file mode 100644
index 00000000..dccec14a
--- /dev/null
+++ b/libmproxy/console/pathedit.py
@@ -0,0 +1,69 @@
+import glob
+import os.path
+
+import urwid
+
+
+class _PathCompleter:
+ def __init__(self, _testing=False):
+ """
+ _testing: disables reloading of the lookup table to make testing
+ possible.
+ """
+ self.lookup, self.offset = None, None
+ self.final = None
+ self._testing = _testing
+
+ def reset(self):
+ self.lookup = None
+ self.offset = -1
+
+ def complete(self, txt):
+ """
+ Returns the next completion for txt, or None if there is no
+ completion.
+ """
+ path = os.path.expanduser(txt)
+ if not self.lookup:
+ if not self._testing:
+ # Lookup is a set of (display value, actual value) tuples.
+ self.lookup = []
+ if os.path.isdir(path):
+ files = glob.glob(os.path.join(path, "*"))
+ prefix = txt
+ else:
+ files = glob.glob(path + "*")
+ prefix = os.path.dirname(txt)
+ prefix = prefix or "./"
+ for f in files:
+ display = os.path.join(prefix, os.path.basename(f))
+ if os.path.isdir(f):
+ display += "/"
+ self.lookup.append((display, f))
+ if not self.lookup:
+ self.final = path
+ return path
+ self.lookup.sort()
+ self.offset = -1
+ self.lookup.append((txt, txt))
+ self.offset += 1
+ if self.offset >= len(self.lookup):
+ self.offset = 0
+ ret = self.lookup[self.offset]
+ self.final = ret[1]
+ return ret[0]
+
+
+class PathEdit(urwid.Edit, _PathCompleter):
+ def __init__(self, *args, **kwargs):
+ urwid.Edit.__init__(self, *args, **kwargs)
+ _PathCompleter.__init__(self)
+
+ def keypress(self, size, key):
+ if key == "tab":
+ comp = self.complete(self.get_edit_text())
+ self.set_edit_text(comp)
+ self.set_edit_pos(len(comp))
+ else:
+ self.reset()
+ return urwid.Edit.keypress(self, size, key)
diff --git a/libmproxy/console/searchable.py b/libmproxy/console/searchable.py
new file mode 100644
index 00000000..627d595d
--- /dev/null
+++ b/libmproxy/console/searchable.py
@@ -0,0 +1,91 @@
+import urwid
+
+from . import signals
+
+
+class Highlight(urwid.AttrMap):
+ def __init__(self, t):
+ urwid.AttrMap.__init__(
+ self,
+ urwid.Text(t.text),
+ "focusfield",
+ )
+ self.backup = t
+
+
+class Searchable(urwid.ListBox):
+ def __init__(self, state, contents):
+ self.walker = urwid.SimpleFocusListWalker(contents)
+ urwid.ListBox.__init__(self, self.walker)
+ self.state = state
+ self.search_offset = 0
+ self.current_highlight = None
+ self.search_term = None
+
+ def keypress(self, size, key):
+ if key == "/":
+ signals.status_prompt.send(
+ prompt = "Search for",
+ text = "",
+ callback = self.set_search
+ )
+ elif key == "n":
+ self.find_next(False)
+ elif key == "N":
+ self.find_next(True)
+ elif key == "G":
+ self.set_focus(0)
+ self.walker._modified()
+ elif key == "g":
+ self.set_focus(len(self.walker) - 1)
+ self.walker._modified()
+ else:
+ return super(self.__class__, self).keypress(size, key)
+
+ def set_search(self, text):
+ self.state.last_search = text
+ self.search_term = text or None
+ self.find_next(False)
+
+ def set_highlight(self, offset):
+ if self.current_highlight is not None:
+ old = self.body[self.current_highlight]
+ self.body[self.current_highlight] = old.backup
+ if offset is None:
+ self.current_highlight = None
+ else:
+ self.body[offset] = Highlight(self.body[offset])
+ self.current_highlight = offset
+
+ def get_text(self, w):
+ if isinstance(w, urwid.Text):
+ return w.text
+ elif isinstance(w, Highlight):
+ return w.backup.text
+ else:
+ return None
+
+ def find_next(self, backwards):
+ if not self.search_term:
+ if self.state.last_search:
+ self.search_term = self.state.last_search
+ else:
+ self.set_highlight(None)
+ return
+ # Start search at focus + 1
+ if backwards:
+ rng = xrange(len(self.body) - 1, -1, -1)
+ else:
+ rng = xrange(1, len(self.body) + 1)
+ for i in rng:
+ off = (self.focus_position + i) % len(self.body)
+ w = self.body[off]
+ txt = self.get_text(w)
+ if txt and self.search_term in txt:
+ self.set_highlight(off)
+ self.set_focus(off, coming_from="above")
+ self.body._modified()
+ return
+ else:
+ self.set_highlight(None)
+ signals.status_message.send(message="Search not found.", expire=1)
diff --git a/libmproxy/console/select.py b/libmproxy/console/select.py
new file mode 100644
index 00000000..bf96a785
--- /dev/null
+++ b/libmproxy/console/select.py
@@ -0,0 +1,115 @@
+import urwid
+
+from . import common
+
+
+class _OptionWidget(urwid.WidgetWrap):
+ def __init__(self, option, text, shortcut, active, focus):
+ self.option = option
+ textattr = "text"
+ keyattr = "key"
+ if focus and active:
+ textattr = "option_active_selected"
+ keyattr = "option_selected_key"
+ elif focus:
+ textattr = "option_selected"
+ keyattr = "option_selected_key"
+ elif active:
+ textattr = "option_active"
+ if shortcut:
+ text = common.highlight_key(
+ text,
+ shortcut,
+ textattr = textattr,
+ keyattr = keyattr
+ )
+ opt = urwid.Text(text, align="left")
+ opt = urwid.AttrWrap(opt, textattr)
+ opt = urwid.Padding(opt, align = "center", width = 40)
+ urwid.WidgetWrap.__init__(self, opt)
+
+ def keypress(self, size, key):
+ return key
+
+ def selectable(self):
+ return True
+
+
+class OptionWalker(urwid.ListWalker):
+ def __init__(self, options):
+ urwid.ListWalker.__init__(self)
+ self.options = options
+ self.focus = 0
+
+ def set_focus(self, pos):
+ self.focus = pos
+
+ def get_focus(self):
+ return self.options[self.focus].render(True), self.focus
+
+ def get_next(self, pos):
+ if pos >= len(self.options) - 1:
+ return None, None
+ return self.options[pos + 1].render(False), pos + 1
+
+ def get_prev(self, pos):
+ if pos <= 0:
+ return None, None
+ return self.options[pos - 1].render(False), pos - 1
+
+
+class Heading:
+ def __init__(self, text):
+ self.text = text
+
+ def render(self, focus):
+ opt = urwid.Text("\n" + self.text, align="left")
+ opt = urwid.AttrWrap(opt, "title")
+ opt = urwid.Padding(opt, align = "center", width = 40)
+ return opt
+
+
+_neg = lambda: False
+
+
+class Option:
+ def __init__(self, text, shortcut, getstate=None, activate=None):
+ self.text = text
+ self.shortcut = shortcut
+ self.getstate = getstate or _neg
+ self.activate = activate or _neg
+
+ def render(self, focus):
+ return _OptionWidget(
+ self,
+ self.text,
+ self.shortcut,
+ self.getstate(),
+ focus)
+
+
+class Select(urwid.ListBox):
+ def __init__(self, options):
+ self.walker = OptionWalker(options)
+ urwid.ListBox.__init__(
+ self,
+ self.walker
+ )
+ self.options = options
+ self.keymap = {}
+ for i in options:
+ if hasattr(i, "shortcut") and i.shortcut:
+ if i.shortcut in self.keymap:
+ raise ValueError("Duplicate shortcut key: %s" % i.shortcut)
+ self.keymap[i.shortcut] = i
+
+ def keypress(self, size, key):
+ if key == "enter" or key == " ":
+ self.get_focus()[0].option.activate()
+ return None
+ key = common.shortcuts(key)
+ if key in self.keymap:
+ self.keymap[key].activate()
+ self.set_focus(self.options.index(self.keymap[key]))
+ return None
+ return super(self.__class__, self).keypress(size, key)
diff --git a/libmproxy/console/signals.py b/libmproxy/console/signals.py
new file mode 100644
index 00000000..c1bcf201
--- /dev/null
+++ b/libmproxy/console/signals.py
@@ -0,0 +1,32 @@
+import blinker
+
+# Show a status message in the action bar
+status_message = blinker.Signal()
+
+# Prompt for input
+status_prompt = blinker.Signal()
+
+# Prompt for a path
+status_prompt_path = blinker.Signal()
+
+# Prompt for a single keystroke
+status_prompt_onekey = blinker.Signal()
+
+# Call a callback in N seconds
+call_in = blinker.Signal()
+
+# Focus the body, footer or header of the main window
+focus = blinker.Signal()
+
+# Fired when settings change
+update_settings = blinker.Signal()
+
+# Fired when a flow changes
+flow_change = blinker.Signal()
+
+# Fired when the flow list or focus changes
+flowlist_change = blinker.Signal()
+
+# Pop and push view state onto a stack
+pop_view_state = blinker.Signal()
+push_view_state = blinker.Signal()
diff --git a/libmproxy/console/statusbar.py b/libmproxy/console/statusbar.py
new file mode 100644
index 00000000..7eb2131b
--- /dev/null
+++ b/libmproxy/console/statusbar.py
@@ -0,0 +1,254 @@
+import os.path
+
+import urwid
+
+import netlib.utils
+from . import pathedit, signals, common
+from .. import utils
+
+
+class ActionBar(urwid.WidgetWrap):
+ def __init__(self):
+ urwid.WidgetWrap.__init__(self, None)
+ self.clear()
+ signals.status_message.connect(self.sig_message)
+ signals.status_prompt.connect(self.sig_prompt)
+ signals.status_prompt_path.connect(self.sig_path_prompt)
+ signals.status_prompt_onekey.connect(self.sig_prompt_onekey)
+
+ self.last_path = ""
+
+ self.prompting = False
+ self.onekey = False
+ self.pathprompt = False
+
+ def sig_message(self, sender, message, expire=None):
+ w = urwid.Text(message)
+ self._w = w
+ if expire:
+ def cb(*args):
+ if w == self._w:
+ self.clear()
+ signals.call_in.send(seconds=expire, callback=cb)
+
+ def prep_prompt(self, p):
+ return p.strip() + ": "
+
+ def sig_prompt(self, sender, prompt, text, callback, args=()):
+ signals.focus.send(self, section="footer")
+ self._w = urwid.Edit(self.prep_prompt(prompt), text or "")
+ self.prompting = (callback, args)
+
+ def sig_path_prompt(self, sender, prompt, callback, args=()):
+ signals.focus.send(self, section="footer")
+ self._w = pathedit.PathEdit(
+ self.prep_prompt(prompt),
+ os.path.dirname(self.last_path)
+ )
+ self.pathprompt = True
+ self.prompting = (callback, args)
+
+ def sig_prompt_onekey(self, sender, prompt, keys, callback, args=()):
+ """
+ Keys are a set of (word, key) tuples. The appropriate key in the
+ word is highlighted.
+ """
+ signals.focus.send(self, section="footer")
+ prompt = [prompt, " ("]
+ mkup = []
+ for i, e in enumerate(keys):
+ mkup.extend(common.highlight_key(e[0], e[1]))
+ if i < len(keys) - 1:
+ mkup.append(",")
+ prompt.extend(mkup)
+ prompt.append(")? ")
+ self.onekey = set(i[1] for i in keys)
+ self._w = urwid.Edit(prompt, "")
+ self.prompting = (callback, args)
+
+ def selectable(self):
+ return True
+
+ def keypress(self, size, k):
+ if self.prompting:
+ if k == "esc":
+ self.prompt_done()
+ elif self.onekey:
+ if k == "enter":
+ self.prompt_done()
+ elif k in self.onekey:
+ self.prompt_execute(k)
+ elif k == "enter":
+ self.prompt_execute(self._w.get_edit_text())
+ else:
+ if common.is_keypress(k):
+ self._w.keypress(size, k)
+ else:
+ return k
+
+ def clear(self):
+ self._w = urwid.Text("")
+
+ def prompt_done(self):
+ self.prompting = False
+ self.onekey = False
+ self.pathprompt = False
+ signals.status_message.send(message="")
+ signals.focus.send(self, section="body")
+
+ def prompt_execute(self, txt):
+ if self.pathprompt:
+ self.last_path = txt
+ p, args = self.prompting
+ self.prompt_done()
+ msg = p(txt, *args)
+ if msg:
+ signals.status_message.send(message=msg, expire=1)
+
+
+class StatusBar(urwid.WidgetWrap):
+ def __init__(self, master, helptext):
+ self.master, self.helptext = master, helptext
+ self.ab = ActionBar()
+ self.ib = urwid.WidgetWrap(urwid.Text(""))
+ self._w = urwid.Pile([self.ib, self.ab])
+ signals.update_settings.connect(self.sig_update_settings)
+ signals.flowlist_change.connect(self.sig_update_settings)
+ self.redraw()
+
+ def sig_update_settings(self, sender):
+ self.redraw()
+
+ def keypress(self, *args, **kwargs):
+ return self.ab.keypress(*args, **kwargs)
+
+ def get_status(self):
+ r = []
+
+ if self.master.setheaders.count():
+ r.append("[")
+ r.append(("heading_key", "H"))
+ r.append("eaders]")
+ if self.master.replacehooks.count():
+ r.append("[")
+ r.append(("heading_key", "R"))
+ r.append("eplacing]")
+ if self.master.client_playback:
+ r.append("[")
+ r.append(("heading_key", "cplayback"))
+ r.append(":%s to go]" % self.master.client_playback.count())
+ if self.master.server_playback:
+ r.append("[")
+ r.append(("heading_key", "splayback"))
+ if self.master.nopop:
+ r.append(":%s in file]" % self.master.server_playback.count())
+ else:
+ r.append(":%s to go]" % self.master.server_playback.count())
+ if self.master.get_ignore_filter():
+ r.append("[")
+ r.append(("heading_key", "I"))
+ r.append("gnore:%d]" % len(self.master.get_ignore_filter()))
+ if self.master.get_tcp_filter():
+ r.append("[")
+ r.append(("heading_key", "T"))
+ r.append("CP:%d]" % len(self.master.get_tcp_filter()))
+ if self.master.state.intercept_txt:
+ r.append("[")
+ r.append(("heading_key", "i"))
+ r.append(":%s]" % self.master.state.intercept_txt)
+ if self.master.state.limit_txt:
+ r.append("[")
+ r.append(("heading_key", "l"))
+ r.append(":%s]" % self.master.state.limit_txt)
+ if self.master.stickycookie_txt:
+ r.append("[")
+ r.append(("heading_key", "t"))
+ r.append(":%s]" % self.master.stickycookie_txt)
+ if self.master.stickyauth_txt:
+ r.append("[")
+ r.append(("heading_key", "u"))
+ r.append(":%s]" % self.master.stickyauth_txt)
+ if self.master.state.default_body_view.name != "Auto":
+ r.append("[")
+ r.append(("heading_key", "M"))
+ r.append(":%s]" % self.master.state.default_body_view.name)
+
+ opts = []
+ if self.master.anticache:
+ opts.append("anticache")
+ if self.master.anticomp:
+ opts.append("anticomp")
+ if self.master.showhost:
+ opts.append("showhost")
+ if not self.master.refresh_server_playback:
+ opts.append("norefresh")
+ if self.master.killextra:
+ opts.append("killextra")
+ if self.master.server.config.no_upstream_cert:
+ opts.append("no-upstream-cert")
+ if self.master.state.follow_focus:
+ opts.append("following")
+ if self.master.stream_large_bodies:
+ opts.append(
+ "stream:%s" % netlib.utils.pretty_size(
+ self.master.stream_large_bodies.max_size
+ )
+ )
+
+ if opts:
+ r.append("[%s]" % (":".join(opts)))
+
+ if self.master.server.config.mode in ["reverse", "upstream"]:
+ dst = self.master.server.config.mode.dst
+ scheme = "https" if dst[0] else "http"
+ if dst[1] != dst[0]:
+ scheme += "2https" if dst[1] else "http"
+ r.append("[dest:%s]" % utils.unparse_url(scheme, *dst[2:]))
+ if self.master.scripts:
+ r.append("[")
+ r.append(("heading_key", "s"))
+ r.append("cripts:%s]" % len(self.master.scripts))
+ # r.append("[lt:%0.3f]"%self.master.looptime)
+
+ if self.master.stream:
+ r.append("[W:%s]" % self.master.stream_path)
+
+ return r
+
+ def redraw(self):
+ fc = self.master.state.flow_count()
+ if self.master.state.focus is None:
+ offset = 0
+ else:
+ offset = min(self.master.state.focus + 1, fc)
+ t = [
+ ('heading', ("[%s/%s]" % (offset, fc)).ljust(9))
+ ]
+
+ if self.master.server.bound:
+ host = self.master.server.address.host
+ if host == "0.0.0.0":
+ host = "*"
+ boundaddr = "[%s:%s]" % (host, self.master.server.address.port)
+ else:
+ boundaddr = ""
+ t.extend(self.get_status())
+ status = urwid.AttrWrap(urwid.Columns([
+ urwid.Text(t),
+ urwid.Text(
+ [
+ self.helptext,
+ boundaddr
+ ],
+ align="right"
+ ),
+ ]), "heading")
+ self.ib._w = status
+
+ def update(self, text):
+ self.helptext = text
+ self.redraw()
+ self.master.loop.draw_screen()
+
+ def selectable(self):
+ return True
diff --git a/libmproxy/console/tabs.py b/libmproxy/console/tabs.py
new file mode 100644
index 00000000..953f6b12
--- /dev/null
+++ b/libmproxy/console/tabs.py
@@ -0,0 +1,39 @@
+import urwid
+
+
+class Tabs(urwid.WidgetWrap):
+ def __init__(self, tabs, tab_offset=0):
+ urwid.WidgetWrap.__init__(self, "")
+ self.tab_offset = tab_offset
+ self.tabs = tabs
+ self.show()
+
+ def _tab(self, content, attr):
+ p = urwid.Text(content, align="center")
+ p = urwid.Padding(p, align="center", width=("relative", 100))
+ p = urwid.AttrWrap(p, attr)
+ return p
+
+ def keypress(self, size, key):
+ if key in ["tab", "l"]:
+ self.tab_offset = (self.tab_offset + 1) % (len(self.tabs))
+ self.show()
+ elif key == "h":
+ self.tab_offset = (self.tab_offset - 1) % (len(self.tabs))
+ self.show()
+ return self._w.keypress(size, key)
+
+ def show(self):
+ headers = []
+ for i in range(len(self.tabs)):
+ txt = self.tabs[i][0]()
+ if i == self.tab_offset:
+ headers.append(self._tab(txt, "heading"))
+ else:
+ headers.append(self._tab(txt, "heading_inactive"))
+ headers = urwid.Columns(headers, dividechars=1)
+ self._w = urwid.Frame(
+ body = self.tabs[self.tab_offset][1](),
+ header = headers
+ )
+ self._w.set_focus("body")
diff --git a/libmproxy/console/window.py b/libmproxy/console/window.py
new file mode 100644
index 00000000..d64e83df
--- /dev/null
+++ b/libmproxy/console/window.py
@@ -0,0 +1,72 @@
+import urwid
+from . import signals
+
+
+class Window(urwid.Frame):
+ def __init__(self, master, body, header, footer, helpctx):
+ urwid.Frame.__init__(
+ self,
+ urwid.AttrWrap(body, "background"),
+ header = urwid.AttrWrap(header, "background") if header else None,
+ footer = urwid.AttrWrap(footer, "background") if footer else None
+ )
+ self.master = master
+ self.helpctx = helpctx
+ signals.focus.connect(self.sig_focus)
+
+ def sig_focus(self, sender, section):
+ self.focus_position = section
+
+ def keypress(self, size, k):
+ k = super(self.__class__, self).keypress(size, k)
+ if k == "?":
+ self.master.view_help(self.helpctx)
+ elif k == "c":
+ if not self.master.client_playback:
+ signals.status_prompt_path.send(
+ self,
+ prompt = "Client replay",
+ callback = self.master.client_playback_path
+ )
+ else:
+ signals.status_prompt_onekey.send(
+ self,
+ prompt = "Stop current client replay?",
+ keys = (
+ ("yes", "y"),
+ ("no", "n"),
+ ),
+ callback = self.master.stop_client_playback_prompt,
+ )
+ elif k == "i":
+ signals.status_prompt.send(
+ self,
+ prompt = "Intercept filter",
+ text = self.master.state.intercept_txt,
+ callback = self.master.set_intercept
+ )
+ elif k == "o":
+ self.master.view_options()
+ elif k == "Q":
+ raise urwid.ExitMainLoop
+ elif k == "q":
+ signals.pop_view_state.send(self)
+ elif k == "S":
+ if not self.master.server_playback:
+ signals.status_prompt_path.send(
+ self,
+ prompt = "Server replay path",
+ callback = self.master.server_playback_path
+ )
+ else:
+ signals.status_prompt_onekey.send(
+ self,
+ prompt = "Stop current server replay?",
+ keys = (
+ ("yes", "y"),
+ ("no", "n"),
+ ),
+ callback = self.master.stop_server_playback_prompt,
+ )
+ else:
+ return k
diff --git a/libmproxy/controller.py b/libmproxy/controller.py
index 9ca89184..98a3aec7 100644
--- a/libmproxy/controller.py
+++ b/libmproxy/controller.py
@@ -1,11 +1,14 @@
from __future__ import absolute_import
-import Queue, threading
+import Queue
+import threading
+
class DummyReply:
"""
A reply object that does nothing. Useful when we need an object to seem
like it has a channel, and during testing.
"""
+
def __init__(self):
self.acked = False
@@ -19,6 +22,7 @@ class Reply:
This object is used to respond to the message through the return
channel.
"""
+
def __init__(self, obj):
self.obj = obj
self.q = Queue.Queue()
@@ -67,11 +71,13 @@ class Slave(threading.Thread):
Slaves get a channel end-point through which they can send messages to
the master.
"""
+
def __init__(self, channel, server):
self.channel, self.server = channel, server
self.server.set_channel(channel)
threading.Thread.__init__(self)
- self.name = "SlaveThread (%s:%s)" % (self.server.address.host, self.server.address.port)
+ self.name = "SlaveThread (%s:%s)" % (
+ self.server.address.host, self.server.address.port)
def run(self):
self.server.serve_forever()
@@ -81,6 +87,7 @@ class Master(object):
"""
Masters get and respond to messages from slaves.
"""
+
def __init__(self, server):
"""
server may be None if no server is needed.
diff --git a/libmproxy/dump.py b/libmproxy/dump.py
index c464644c..ee8c65a0 100644
--- a/libmproxy/dump.py
+++ b/libmproxy/dump.py
@@ -53,7 +53,7 @@ class Options(object):
def str_response(resp):
- r = "%s %s"%(resp.code, resp.msg)
+ r = "%s %s" % (resp.code, resp.msg)
if resp.is_replay:
r = "[replay] " + r
return r
@@ -64,7 +64,7 @@ def str_request(f, showhost):
c = f.client_conn.address.host
else:
c = "[replay]"
- r = "%s %s %s"%(c, f.request.method, f.request.pretty_url(showhost))
+ r = "%s %s %s" % (c, f.request.method, f.request.pretty_url(showhost))
if f.request.stickycookie:
r = "[stickycookie] " + r
return r
@@ -102,7 +102,7 @@ class DumpMaster(flow.FlowMaster):
try:
f = file(path, options.outfile[1])
self.start_stream(f, self.filt)
- except IOError, v:
+ except IOError as v:
raise DumpError(v.strerror)
if options.replacements:
@@ -140,7 +140,7 @@ class DumpMaster(flow.FlowMaster):
if options.rfile:
try:
self.load_flows_file(options.rfile)
- except flow.FlowReadError, v:
+ except flow.FlowReadError as v:
self.add_event("Flow file corrupted.", "error")
raise DumpError(v)
@@ -171,7 +171,7 @@ class DumpMaster(flow.FlowMaster):
def _print_message(self, message):
if self.o.flow_detail >= 2:
- print(self.indent(4, message.headers), file=self.outfile)
+ print(self.indent(4, message.headers.format()), file=self.outfile)
if self.o.flow_detail >= 3:
if message.content == http.CONTENT_MISSING:
print(self.indent(4, "(content missing)"), file=self.outfile)
@@ -181,12 +181,18 @@ class DumpMaster(flow.FlowMaster):
if not utils.isBin(content):
try:
jsn = json.loads(content)
- print(self.indent(4, json.dumps(jsn, indent=2)), file=self.outfile)
+ print(
+ self.indent(
+ 4,
+ json.dumps(
+ jsn,
+ indent=2)),
+ file=self.outfile)
except ValueError:
print(self.indent(4, content), file=self.outfile)
else:
d = netlib.utils.hexdump(content)
- d = "\n".join("%s\t%s %s"%i for i in d)
+ d = "\n".join("%s\t%s %s" % i for i in d)
print(self.indent(4, d), file=self.outfile)
if self.o.flow_detail >= 2:
print("", file=self.outfile)
@@ -207,8 +213,13 @@ class DumpMaster(flow.FlowMaster):
if f.response.content == http.CONTENT_MISSING:
sz = "(content missing)"
else:
- sz = utils.pretty_size(len(f.response.content))
- print(" << %s %s" % (str_response(f.response), sz), file=self.outfile)
+ sz = netlib.utils.pretty_size(len(f.response.content))
+ print(
+ " << %s %s" %
+ (str_response(
+ f.response),
+ sz),
+ file=self.outfile)
self._print_message(f.response)
if f.error:
diff --git a/libmproxy/encoding.py b/libmproxy/encoding.py
index 0fd90870..f107eb5f 100644
--- a/libmproxy/encoding.py
+++ b/libmproxy/encoding.py
@@ -3,12 +3,14 @@
"""
from __future__ import absolute_import
import cStringIO
-import gzip, zlib
+import gzip
+import zlib
__ALL__ = ["ENCODINGS"]
ENCODINGS = set(["identity", "gzip", "deflate"])
+
def decode(e, content):
encoding_map = {
"identity": identity,
@@ -19,6 +21,7 @@ def decode(e, content):
return None
return encoding_map[e](content)
+
def encode(e, content):
encoding_map = {
"identity": identity,
@@ -29,6 +32,7 @@ def encode(e, content):
return None
return encoding_map[e](content)
+
def identity(content):
"""
Returns content unchanged. Identity is the default value of
@@ -36,6 +40,7 @@ def identity(content):
"""
return content
+
def decode_gzip(content):
gfile = gzip.GzipFile(fileobj=cStringIO.StringIO(content))
try:
@@ -43,6 +48,7 @@ def decode_gzip(content):
except (IOError, EOFError):
return None
+
def encode_gzip(content):
s = cStringIO.StringIO()
gf = gzip.GzipFile(fileobj=s, mode='wb')
@@ -50,6 +56,7 @@ def encode_gzip(content):
gf.close()
return s.getvalue()
+
def decode_deflate(content):
"""
Returns decompressed data for DEFLATE. Some servers may respond with
@@ -67,6 +74,7 @@ def decode_deflate(content):
except zlib.error:
return None
+
def encode_deflate(content):
"""
Returns compressed content, always including zlib header and checksum.
diff --git a/libmproxy/filt.py b/libmproxy/filt.py
index 40b2f6c9..3081eb94 100644
--- a/libmproxy/filt.py
+++ b/libmproxy/filt.py
@@ -32,16 +32,17 @@
rex Equivalent to ~u rex
"""
from __future__ import absolute_import
-import re, sys
+import re
+import sys
from .contrib import pyparsing as pp
from .protocol.http import decoded
class _Token:
def dump(self, indent=0, fp=sys.stdout):
- print >> fp, "\t"*indent, self.__class__.__name__,
+ print >> fp, "\t" * indent, self.__class__.__name__,
if hasattr(self, "expr"):
- print >> fp, "(%s)"%self.expr,
+ print >> fp, "(%s)" % self.expr,
print >> fp
@@ -54,6 +55,7 @@ class _Action(_Token):
class FErr(_Action):
code = "e"
help = "Match error"
+
def __call__(self, f):
return True if f.error else False
@@ -61,6 +63,7 @@ class FErr(_Action):
class FReq(_Action):
code = "q"
help = "Match request with no response"
+
def __call__(self, f):
if not f.response:
return True
@@ -69,6 +72,7 @@ class FReq(_Action):
class FResp(_Action):
code = "s"
help = "Match response"
+
def __call__(self, f):
return True if f.response else False
@@ -79,7 +83,7 @@ class _Rex(_Action):
try:
self.re = re.compile(self.expr)
except:
- raise ValueError, "Cannot compile expression."
+ raise ValueError("Cannot compile expression.")
def _check_content_type(expr, o):
@@ -100,6 +104,7 @@ class FAsset(_Action):
"image/.*",
"application/x-shockwave-flash"
]
+
def __call__(self, f):
if f.response:
for i in self.ASSET_TYPES:
@@ -111,6 +116,7 @@ class FAsset(_Action):
class FContentType(_Rex):
code = "t"
help = "Content-type header"
+
def __call__(self, f):
if _check_content_type(self.expr, f.request):
return True
@@ -122,6 +128,7 @@ class FContentType(_Rex):
class FRequestContentType(_Rex):
code = "tq"
help = "Request Content-Type header"
+
def __call__(self, f):
return _check_content_type(self.expr, f.request)
@@ -129,6 +136,7 @@ class FRequestContentType(_Rex):
class FResponseContentType(_Rex):
code = "ts"
help = "Response Content-Type header"
+
def __call__(self, f):
if f.response:
return _check_content_type(self.expr, f.response)
@@ -138,6 +146,7 @@ class FResponseContentType(_Rex):
class FHead(_Rex):
code = "h"
help = "Header"
+
def __call__(self, f):
if f.request.headers.match_re(self.expr):
return True
@@ -149,6 +158,7 @@ class FHead(_Rex):
class FHeadRequest(_Rex):
code = "hq"
help = "Request header"
+
def __call__(self, f):
if f.request.headers.match_re(self.expr):
return True
@@ -157,6 +167,7 @@ class FHeadRequest(_Rex):
class FHeadResponse(_Rex):
code = "hs"
help = "Response header"
+
def __call__(self, f):
if f.response and f.response.headers.match_re(self.expr):
return True
@@ -165,6 +176,7 @@ class FHeadResponse(_Rex):
class FBod(_Rex):
code = "b"
help = "Body"
+
def __call__(self, f):
if f.request and f.request.content:
with decoded(f.request):
@@ -180,6 +192,7 @@ class FBod(_Rex):
class FBodRequest(_Rex):
code = "bq"
help = "Request body"
+
def __call__(self, f):
if f.request and f.request.content:
with decoded(f.request):
@@ -190,6 +203,7 @@ class FBodRequest(_Rex):
class FBodResponse(_Rex):
code = "bs"
help = "Response body"
+
def __call__(self, f):
if f.response and f.response.content:
with decoded(f.response):
@@ -200,6 +214,7 @@ class FBodResponse(_Rex):
class FMethod(_Rex):
code = "m"
help = "Method"
+
def __call__(self, f):
return bool(re.search(self.expr, f.request.method, re.IGNORECASE))
@@ -207,6 +222,7 @@ class FMethod(_Rex):
class FDomain(_Rex):
code = "d"
help = "Domain"
+
def __call__(self, f):
return bool(re.search(self.expr, f.request.host, re.IGNORECASE))
@@ -215,6 +231,7 @@ class FUrl(_Rex):
code = "u"
help = "URL"
# FUrl is special, because it can be "naked".
+
@classmethod
def make(klass, s, loc, toks):
if len(toks) > 1:
@@ -233,6 +250,7 @@ class _Int(_Action):
class FCode(_Int):
code = "c"
help = "HTTP response code"
+
def __call__(self, f):
if f.response and f.response.code == self.num:
return True
@@ -243,9 +261,9 @@ class FAnd(_Token):
self.lst = lst
def dump(self, indent=0, fp=sys.stdout):
- print >> fp, "\t"*indent, self.__class__.__name__
+ print >> fp, "\t" * indent, self.__class__.__name__
for i in self.lst:
- i.dump(indent+1, fp)
+ i.dump(indent + 1, fp)
def __call__(self, f):
return all(i(f) for i in self.lst)
@@ -256,9 +274,9 @@ class FOr(_Token):
self.lst = lst
def dump(self, indent=0, fp=sys.stdout):
- print >> fp, "\t"*indent, self.__class__.__name__
+ print >> fp, "\t" * indent, self.__class__.__name__
for i in self.lst:
- i.dump(indent+1, fp)
+ i.dump(indent + 1, fp)
def __call__(self, f):
return any(i(f) for i in self.lst)
@@ -269,7 +287,7 @@ class FNot(_Token):
self.itm = itm[0]
def dump(self, indent=0, fp=sys.stdout):
- print >> fp, "\t"*indent, self.__class__.__name__
+ print >> fp, "\t" * indent, self.__class__.__name__
self.itm.dump(indent + 1, fp)
def __call__(self, f):
@@ -299,26 +317,28 @@ filt_rex = [
filt_int = [
FCode
]
+
+
def _make():
# Order is important - multi-char expressions need to come before narrow
# ones.
parts = []
for klass in filt_unary:
- f = pp.Literal("~%s"%klass.code)
+ f = pp.Literal("~%s" % klass.code)
f.setParseAction(klass.make)
parts.append(f)
- simplerex = "".join(c for c in pp.printables if c not in "()~'\"")
+ simplerex = "".join(c for c in pp.printables if c not in "()~'\"")
rex = pp.Word(simplerex) |\
- pp.QuotedString("\"", escChar='\\') |\
- pp.QuotedString("'", escChar='\\')
+ pp.QuotedString("\"", escChar='\\') |\
+ pp.QuotedString("'", escChar='\\')
for klass in filt_rex:
- f = pp.Literal("~%s"%klass.code) + rex.copy()
+ f = pp.Literal("~%s" % klass.code) + rex.copy()
f.setParseAction(klass.make)
parts.append(f)
for klass in filt_int:
- f = pp.Literal("~%s"%klass.code) + pp.Word(pp.nums)
+ f = pp.Literal("~%s" % klass.code) + pp.Word(pp.nums)
f.setParseAction(klass.make)
parts.append(f)
@@ -328,14 +348,20 @@ def _make():
parts.append(f)
atom = pp.MatchFirst(parts)
- expr = pp.operatorPrecedence(
- atom,
- [
- (pp.Literal("!").suppress(), 1, pp.opAssoc.RIGHT, lambda x: FNot(*x)),
- (pp.Literal("&").suppress(), 2, pp.opAssoc.LEFT, lambda x: FAnd(*x)),
- (pp.Literal("|").suppress(), 2, pp.opAssoc.LEFT, lambda x: FOr(*x)),
- ]
- )
+ expr = pp.operatorPrecedence(atom,
+ [(pp.Literal("!").suppress(),
+ 1,
+ pp.opAssoc.RIGHT,
+ lambda x: FNot(*x)),
+ (pp.Literal("&").suppress(),
+ 2,
+ pp.opAssoc.LEFT,
+ lambda x: FAnd(*x)),
+ (pp.Literal("|").suppress(),
+ 2,
+ pp.opAssoc.LEFT,
+ lambda x: FOr(*x)),
+ ])
expr = pp.OneOrMore(expr)
return expr.setParseAction(lambda x: FAnd(x) if len(x) != 1 else x)
bnf = _make()
@@ -355,15 +381,15 @@ def parse(s):
help = []
for i in filt_unary:
help.append(
- ("~%s"%i.code, i.help)
+ ("~%s" % i.code, i.help)
)
for i in filt_rex:
help.append(
- ("~%s regex"%i.code, i.help)
+ ("~%s regex" % i.code, i.help)
)
for i in filt_int:
help.append(
- ("~%s int"%i.code, i.help)
+ ("~%s int" % i.code, i.help)
)
help.sort()
help.extend(
@@ -373,4 +399,4 @@ help.extend(
("|", "or"),
("(...)", "grouping"),
]
-) \ No newline at end of file
+)
diff --git a/libmproxy/flow.py b/libmproxy/flow.py
index 8343c183..6154e3d7 100644
--- a/libmproxy/flow.py
+++ b/libmproxy/flow.py
@@ -17,9 +17,6 @@ from .proxy.config import HostMatcher
from .proxy.connection import ClientConnection, ServerConnection
import urlparse
-ODict = odict.ODict
-ODictCaseless = odict.ODictCaseless
-
class AppRegistry:
def __init__(self):
@@ -165,7 +162,8 @@ class StreamLargeBodies(object):
r.headers, is_request, flow.request.method, code
)
if not (0 <= expected_size <= self.max_size):
- r.stream = r.stream or True # r.stream may already be a callable, which we want to preserve.
+ # r.stream may already be a callable, which we want to preserve.
+ r.stream = r.stream or True
class ClientPlaybackState:
@@ -203,8 +201,16 @@ class ClientPlaybackState:
class ServerPlaybackState:
- def __init__(self, headers, flows, exit, nopop, ignore_params, ignore_content,
- ignore_payload_params, ignore_host):
+ def __init__(
+ self,
+ headers,
+ flows,
+ exit,
+ nopop,
+ ignore_params,
+ ignore_content,
+ ignore_payload_params,
+ ignore_host):
"""
headers: Case-insensitive list of request headers that should be
included in request-response matching.
@@ -232,7 +238,7 @@ class ServerPlaybackState:
r = flow.request
_, _, path, _, query, _ = urlparse.urlparse(r.url)
- queriesArray = urlparse.parse_qsl(query)
+ queriesArray = urlparse.parse_qsl(query, keep_blank_values=True)
key = [
str(r.port),
@@ -242,7 +248,7 @@ class ServerPlaybackState:
]
if not self.ignore_content:
- form_contents = r.get_form_urlencoded()
+ form_contents = r.get_form()
if self.ignore_payload_params and form_contents:
key.extend(
p for p in form_contents
@@ -271,7 +277,7 @@ class ServerPlaybackState:
# to prevent a mismatch between unicode/non-unicode.
v = [str(x) for x in v]
hdrs.append((i, v))
- key.append(repr(hdrs))
+ key.append(hdrs)
return hashlib.sha256(repr(key)).digest()
def next_flow(self, request):
@@ -462,8 +468,9 @@ class FlowStore(FlowList):
Notifies the state that a flow has been updated.
The flow must be present in the state.
"""
- for view in self.views:
- view._update(f)
+ if f in self:
+ for view in self.views:
+ view._update(f)
def _remove(self, f):
"""
@@ -534,7 +541,8 @@ class State(object):
def flow_count(self):
return len(self.flows)
- # TODO: All functions regarding flows that don't cause side-effects should be moved into FlowStore.
+ # TODO: All functions regarding flows that don't cause side-effects should
+ # be moved into FlowStore.
def index(self, f):
return self.flows.index(f)
@@ -593,6 +601,10 @@ class State(object):
def accept_all(self, master):
self.flows.accept_all(master)
+ def backup(self, f):
+ f.backup()
+ self.update_flow(f)
+
def revert(self, f):
f.revert()
self.update_flow(f)
@@ -658,7 +670,7 @@ class FlowMaster(controller.Master):
"""
try:
s = script.Script(command, self)
- except script.ScriptError, v:
+ except script.ScriptError as v:
return v.args[0]
self.scripts.append(s)
@@ -722,8 +734,17 @@ class FlowMaster(controller.Master):
def stop_client_playback(self):
self.client_playback = None
- def start_server_playback(self, flows, kill, headers, exit, nopop, ignore_params,
- ignore_content, ignore_payload_params, ignore_host):
+ def start_server_playback(
+ self,
+ flows,
+ kill,
+ headers,
+ exit,
+ nopop,
+ ignore_params,
+ ignore_content,
+ ignore_payload_params,
+ ignore_host):
"""
flows: List of flows.
kill: Boolean, should we kill requests not part of the replay?
@@ -732,9 +753,15 @@ class FlowMaster(controller.Master):
ignore_payload_params: list of content params to ignore in server replay
ignore_host: true if request host should be ignored in server replay
"""
- self.server_playback = ServerPlaybackState(headers, flows, exit, nopop,
- ignore_params, ignore_content,
- ignore_payload_params, ignore_host)
+ self.server_playback = ServerPlaybackState(
+ headers,
+ flows,
+ exit,
+ nopop,
+ ignore_params,
+ ignore_content,
+ ignore_payload_params,
+ ignore_host)
self.kill_nonreplay = kill
def stop_server_playback(self):
@@ -771,6 +798,8 @@ class FlowMaster(controller.Master):
if all(e):
self.shutdown()
self.client_playback.tick(self)
+ if self.client_playback.done():
+ self.client_playback = None
return super(FlowMaster, self).tick(q, timeout)
@@ -780,25 +809,38 @@ class FlowMaster(controller.Master):
def create_request(self, method, scheme, host, port, path):
"""
this method creates a new artificial and minimalist request also adds it to flowlist
- """
+ """
c = ClientConnection.from_state(dict(
- address=dict(address=(host, port), use_ipv6=False),
- clientcert=None
- ))
+ address=dict(address=(host, port), use_ipv6=False),
+ clientcert=None
+ ))
s = ServerConnection.from_state(dict(
- address=dict(address=(host, port), use_ipv6=False),
- state=[],
- source_address=None, #source_address=dict(address=(host, port), use_ipv6=False),
- cert=None,
- sni=host,
- ssl_established=True
- ))
- f = http.HTTPFlow(c,s);
+ address=dict(address=(host, port), use_ipv6=False),
+ state=[],
+ source_address=None,
+ # source_address=dict(address=(host, port), use_ipv6=False),
+ cert=None,
+ sni=host,
+ ssl_established=True
+ ))
+ f = http.HTTPFlow(c, s)
headers = ODictCaseless()
-
- req = http.HTTPRequest("absolute", method, scheme, host, port, path, (1, 1), headers, None,
- None, None, None)
+
+ req = http.HTTPRequest(
+ "absolute",
+ method,
+ scheme,
+ host,
+ port,
+ path,
+ (1,
+ 1),
+ headers,
+ None,
+ None,
+ None,
+ None)
f.request = req
return self.load_flow(f)
@@ -809,7 +851,8 @@ class FlowMaster(controller.Master):
if self.server and self.server.config.mode == "reverse":
f.request.host, f.request.port = self.server.config.mode.dst[2:]
- f.request.scheme = "https" if self.server.config.mode.dst[1] else "http"
+ f.request.scheme = "https" if self.server.config.mode.dst[
+ 1] else "http"
f.reply = controller.DummyReply()
if f.request:
@@ -836,7 +879,7 @@ class FlowMaster(controller.Master):
try:
f = file(path, "rb")
freader = FlowReader(f)
- except IOError, v:
+ except IOError as v:
raise FlowReadError(v.strerror)
return self.load_flows(freader)
@@ -877,7 +920,8 @@ class FlowMaster(controller.Master):
f.backup()
f.request.is_replay = True
if f.request.content:
- f.request.headers["Content-Length"] = [str(len(f.request.content))]
+ f.request.headers[
+ "Content-Length"] = [str(len(f.request.content))]
f.response = None
f.error = None
self.process_new_request(f)
@@ -1028,7 +1072,7 @@ class FlowReader:
"""
off = 0
try:
- while 1:
+ while True:
data = tnetstring.load(self.fo)
if tuple(data["version"][:2]) != version.IVERSION[:2]:
v = ".".join(str(i) for i in data["version"])
@@ -1037,7 +1081,7 @@ class FlowReader:
)
off = self.fo.tell()
yield handle.protocols[data["type"]]["flow"].from_state(data)
- except ValueError, v:
+ except ValueError as v:
# Error is due to EOF
if self.fo.tell() == off and self.fo.read() == '':
return
diff --git a/libmproxy/main.py b/libmproxy/main.py
index e5b7f56b..73e6c62b 100644
--- a/libmproxy/main.py
+++ b/libmproxy/main.py
@@ -70,27 +70,29 @@ def get_server(dummy_server, options):
else:
try:
return ProxyServer(options)
- except ProxyServerError, v:
+ except ProxyServerError as v:
print(str(v), file=sys.stderr)
sys.exit(1)
-def mitmproxy(): # pragma: nocover
+def mitmproxy(args=None): # pragma: nocover
from . import console
check_versions()
assert_utf8_env()
parser = cmdline.mitmproxy()
- options = parser.parse_args()
+ options = parser.parse_args(args)
if options.quiet:
options.verbose = 0
proxy_config = process_proxy_options(parser, options)
console_options = console.Options(**cmdline.get_common_options(options))
console_options.palette = options.palette
+ console_options.palette_transparent = options.palette_transparent
console_options.eventlog = options.eventlog
console_options.intercept = options.intercept
+ console_options.limit = options.limit
server = get_server(console_options.no_server, proxy_config)
@@ -101,13 +103,13 @@ def mitmproxy(): # pragma: nocover
pass
-def mitmdump(): # pragma: nocover
+def mitmdump(args=None): # pragma: nocover
from . import dump
check_versions()
parser = cmdline.mitmdump()
- options = parser.parse_args()
+ options = parser.parse_args(args)
if options.quiet:
options.verbose = 0
options.flow_detail = 0
@@ -135,13 +137,13 @@ def mitmdump(): # pragma: nocover
pass
-def mitmweb(): # pragma: nocover
+def mitmweb(args=None): # pragma: nocover
from . import web
check_versions()
parser = cmdline.mitmweb()
- options = parser.parse_args()
+ options = parser.parse_args(args)
if options.quiet:
options.verbose = 0
diff --git a/libmproxy/onboarding/app.py b/libmproxy/onboarding/app.py
index 37f05e96..6edd74b1 100644
--- a/libmproxy/onboarding/app.py
+++ b/libmproxy/onboarding/app.py
@@ -45,7 +45,10 @@ class PEM(tornado.web.RequestHandler):
def get(self):
p = os.path.join(self.request.master.server.config.cadir, self.filename)
self.set_header("Content-Type", "application/x-x509-ca-cert")
- self.set_header("Content-Disposition", "inline; filename={}".format(self.filename))
+ self.set_header(
+ "Content-Disposition",
+ "inline; filename={}".format(
+ self.filename))
with open(p, "rb") as f:
self.write(f.read())
@@ -59,7 +62,10 @@ class P12(tornado.web.RequestHandler):
def get(self):
p = os.path.join(self.request.master.server.config.cadir, self.filename)
self.set_header("Content-Type", "application/x-pkcs12")
- self.set_header("Content-Disposition", "inline; filename={}".format(self.filename))
+ self.set_header(
+ "Content-Disposition",
+ "inline; filename={}".format(
+ self.filename))
with open(p, "rb") as f:
self.write(f.read())
@@ -78,7 +84,6 @@ application = tornado.web.Application(
}
),
],
- #debug=True
+ # debug=True
)
mapp = Adapter(application)
-
diff --git a/libmproxy/platform/__init__.py b/libmproxy/platform/__init__.py
index 1b2cf909..e1ff7c47 100644
--- a/libmproxy/platform/__init__.py
+++ b/libmproxy/platform/__init__.py
@@ -8,9 +8,9 @@ if sys.platform == "linux2":
elif sys.platform == "darwin":
from . import osx
resolver = osx.Resolver
-elif sys.platform == "freebsd10":
+elif sys.platform.startswith("freebsd"):
from . import osx
resolver = osx.Resolver
elif sys.platform == "win32":
from . import windows
- resolver = windows.Resolver \ No newline at end of file
+ resolver = windows.Resolver
diff --git a/libmproxy/platform/linux.py b/libmproxy/platform/linux.py
index d5cfec90..e60a9950 100644
--- a/libmproxy/platform/linux.py
+++ b/libmproxy/platform/linux.py
@@ -1,4 +1,5 @@
-import socket, struct
+import socket
+import struct
# Python socket module does not have this constant
SO_ORIGINAL_DST = 80
diff --git a/libmproxy/platform/osx.py b/libmproxy/platform/osx.py
index 810e5e5f..c5922850 100644
--- a/libmproxy/platform/osx.py
+++ b/libmproxy/platform/osx.py
@@ -21,6 +21,7 @@ class Resolver(object):
peer = csock.getpeername()
stxt = subprocess.check_output(self.STATECMD, stderr=subprocess.STDOUT)
if "sudo: a password is required" in stxt:
- raise RuntimeError("Insufficient privileges to access pfctl. "
- "See http://mitmproxy.org/doc/transparent/osx.html for details.")
+ raise RuntimeError(
+ "Insufficient privileges to access pfctl. "
+ "See http://mitmproxy.org/doc/transparent/osx.html for details.")
return pf.lookup(peer[0], peer[1], stxt)
diff --git a/libmproxy/platform/pf.py b/libmproxy/platform/pf.py
index 8c2f4678..97a4c192 100644
--- a/libmproxy/platform/pf.py
+++ b/libmproxy/platform/pf.py
@@ -13,7 +13,7 @@ def lookup(address, port, s):
if "ESTABLISHED:ESTABLISHED" in i and spec in i:
s = i.split()
if len(s) > 4:
- if sys.platform == "freebsd10":
+ if sys.platform.startswith("freebsd"):
# strip parentheses for FreeBSD pfctl
s = s[3][1:-1].split(":")
else:
@@ -21,4 +21,4 @@ def lookup(address, port, s):
if len(s) == 2:
return s[0], int(s[1])
- raise RuntimeError("Could not resolve original destination.") \ No newline at end of file
+ raise RuntimeError("Could not resolve original destination.")
diff --git a/libmproxy/platform/windows.py b/libmproxy/platform/windows.py
index 98bfebcf..09a4422f 100644
--- a/libmproxy/platform/windows.py
+++ b/libmproxy/platform/windows.py
@@ -197,9 +197,12 @@ class TransparentProxy(object):
self.driver = WinDivert()
self.driver.register()
- self.request_filter = custom_filter or " or ".join(("tcp.DstPort == %d" % p) for p in redirect_ports)
+ self.request_filter = custom_filter or " or ".join(
+ ("tcp.DstPort == %d" %
+ p) for p in redirect_ports)
self.request_forward_handle = None
- self.request_forward_thread = threading.Thread(target=self.request_forward)
+ self.request_forward_thread = threading.Thread(
+ target=self.request_forward)
self.request_forward_thread.daemon = True
self.addr_pid_map = dict()
@@ -235,17 +238,25 @@ class TransparentProxy(object):
# Block all ICMP requests (which are sent on Windows by default).
# In layman's terms: If we don't do this, our proxy machine tells the client that it can directly connect to the
# real gateway if they are on the same network.
- self.icmp_handle = self.driver.open_handle(filter="icmp", layer=Layer.NETWORK, flags=Flag.DROP)
-
- self.response_handle = self.driver.open_handle(filter=self.response_filter, layer=Layer.NETWORK)
+ self.icmp_handle = self.driver.open_handle(
+ filter="icmp",
+ layer=Layer.NETWORK,
+ flags=Flag.DROP)
+
+ self.response_handle = self.driver.open_handle(
+ filter=self.response_filter,
+ layer=Layer.NETWORK)
self.response_thread.start()
if self.mode == "forward" or self.mode == "both":
- self.request_forward_handle = self.driver.open_handle(filter=self.request_filter,
- layer=Layer.NETWORK_FORWARD)
+ self.request_forward_handle = self.driver.open_handle(
+ filter=self.request_filter,
+ layer=Layer.NETWORK_FORWARD)
self.request_forward_thread.start()
if self.mode == "local" or self.mode == "both":
- self.request_local_handle = self.driver.open_handle(filter=self.request_filter, layer=Layer.NETWORK)
+ self.request_local_handle = self.driver.open_handle(
+ filter=self.request_filter,
+ layer=Layer.NETWORK)
self.request_local_thread.start()
def shutdown(self):
@@ -266,14 +277,17 @@ class TransparentProxy(object):
try:
raw_packet, metadata = handle.recv()
return self.driver.parse_packet(raw_packet), metadata
- except WindowsError, e:
+ except WindowsError as e:
if e.winerror == 995:
return None, None
else:
raise
def fetch_pids(self):
- ret = windll.iphlpapi.GetTcpTable2(byref(self.tcptable2), byref(self.tcptable2_size), 0)
+ ret = windll.iphlpapi.GetTcpTable2(
+ byref(
+ self.tcptable2), byref(
+ self.tcptable2_size), 0)
if ret == ERROR_INSUFFICIENT_BUFFER:
self.tcptable2 = MIB_TCPTABLE2(self.tcptable2_size.value)
self.fetch_pids()
@@ -299,7 +313,8 @@ class TransparentProxy(object):
self.fetch_pids()
# If this fails, we most likely have a connection from an external client to
- # a local server on 80/443. In this, case we always want to proxy the request.
+ # a local server on 80/443. In this, case we always want to proxy
+ # the request.
pid = self.addr_pid_map.get(client, None)
if pid not in self.trusted_pids:
@@ -325,7 +340,8 @@ class TransparentProxy(object):
server = (packet.dst_addr, packet.dst_port)
if client in self.client_server_map:
- del self.client_server_map[client] # Force re-add to mark as "newest" entry in the dict.
+ # Force re-add to mark as "newest" entry in the dict.
+ del self.client_server_map[client]
while len(self.client_server_map) > self.connection_cache_size:
self.client_server_map.popitem(False)
@@ -335,7 +351,8 @@ class TransparentProxy(object):
metadata.direction = Direction.INBOUND
packet = self.driver.update_packet_checksums(packet)
- # Use any handle thats on the NETWORK layer - request_local may be unavailable.
+ # Use any handle thats on the NETWORK layer - request_local may be
+ # unavailable.
self.response_handle.send((packet.raw, metadata))
def response(self):
@@ -361,15 +378,32 @@ class TransparentProxy(object):
if __name__ == "__main__":
- parser = configargparse.ArgumentParser(description="Windows Transparent Proxy")
- parser.add_argument('--mode', choices=['forward', 'local', 'both'], default="both",
- help='redirection operation mode: "forward" to only redirect forwarded packets, '
- '"local" to only redirect packets originating from the local machine')
+ parser = configargparse.ArgumentParser(
+ description="Windows Transparent Proxy")
+ parser.add_argument(
+ '--mode',
+ choices=[
+ 'forward',
+ 'local',
+ 'both'],
+ default="both",
+ help='redirection operation mode: "forward" to only redirect forwarded packets, '
+ '"local" to only redirect packets originating from the local machine')
group = parser.add_mutually_exclusive_group()
- group.add_argument("--redirect-ports", nargs="+", type=int, default=[80, 443], metavar="80",
- help="ports that should be forwarded to the proxy")
- group.add_argument("--custom-filter", default=None, metavar="WINDIVERT_FILTER",
- help="Custom WinDivert interception rule.")
+ group.add_argument(
+ "--redirect-ports",
+ nargs="+",
+ type=int,
+ default=[
+ 80,
+ 443],
+ metavar="80",
+ help="ports that should be forwarded to the proxy")
+ group.add_argument(
+ "--custom-filter",
+ default=None,
+ metavar="WINDIVERT_FILTER",
+ help="Custom WinDivert interception rule.")
parser.add_argument("--proxy-addr", default=False,
help="Proxy Server Address")
parser.add_argument("--proxy-port", type=int, default=8080,
diff --git a/libmproxy/protocol/__init__.py b/libmproxy/protocol/__init__.py
index f5d6a2d0..bbc20dba 100644
--- a/libmproxy/protocol/__init__.py
+++ b/libmproxy/protocol/__init__.py
@@ -1 +1 @@
-from .primitives import * \ No newline at end of file
+from .primitives import *
diff --git a/libmproxy/protocol/handle.py b/libmproxy/protocol/handle.py
index 100c7368..49cb3c1b 100644
--- a/libmproxy/protocol/handle.py
+++ b/libmproxy/protocol/handle.py
@@ -6,6 +6,7 @@ protocols = {
'tcp': dict(handler=tcp.TCPHandler)
}
+
def protocol_handler(protocol):
"""
@type protocol: str
@@ -14,4 +15,6 @@ def protocol_handler(protocol):
if protocol in protocols:
return protocols[protocol]["handler"]
- raise NotImplementedError("Unknown Protocol: %s" % protocol) # pragma: nocover \ No newline at end of file
+ raise NotImplementedError(
+ "Unknown Protocol: %s" %
+ protocol) # pragma: nocover
diff --git a/libmproxy/protocol/http.py b/libmproxy/protocol/http.py
index 49310ec3..91e74567 100644
--- a/libmproxy/protocol/http.py
+++ b/libmproxy/protocol/http.py
@@ -6,15 +6,16 @@ import time
import copy
from email.utils import parsedate_tz, formatdate, mktime_tz
import threading
-from netlib import http, tcp, http_status
+from netlib import http, tcp, http_status, http_cookies
import netlib.utils
-from netlib.odict import ODict, ODictCaseless
+from netlib import odict
from .tcp import TCPHandler
from .primitives import KILL, ProtocolHandler, Flow, Error
from ..proxy.connection import ServerConnection
from .. import encoding, utils, controller, stateobject, proxy
HDR_FORM_URLENCODED = "application/x-www-form-urlencoded"
+HDR_FORM_MULTIPART = "multipart/form-data"
CONTENT_MISSING = 0
@@ -22,19 +23,6 @@ class KillSignal(Exception):
pass
-def get_line(fp):
- """
- Get a line, possibly preceded by a blank.
- """
- line = fp.readline()
- if line == "\r\n" or line == "\n":
- # Possible leftover from previous message
- line = fp.readline()
- if line == "":
- raise tcp.NetLibDisconnect()
- return line
-
-
def send_connect_request(conn, host, port, update_state=True):
upstream_request = HTTPRequest(
"authority",
@@ -44,7 +32,7 @@ def send_connect_request(conn, host, port, update_state=True):
port,
None,
(1, 1),
- ODictCaseless(),
+ odict.ODictCaseless(),
""
)
conn.send(upstream_request.assemble())
@@ -99,7 +87,7 @@ class HTTPMessage(stateobject.StateObject):
timestamp_end=None):
self.httpversion = httpversion
self.headers = headers
- """@type: ODictCaseless"""
+ """@type: odict.ODictCaseless"""
self.content = content
self.timestamp_start = timestamp_start
@@ -107,7 +95,7 @@ class HTTPMessage(stateobject.StateObject):
_stateobject_attributes = dict(
httpversion=tuple,
- headers=ODictCaseless,
+ headers=odict.ODictCaseless,
content=str,
timestamp_start=float,
timestamp_end=float
@@ -119,6 +107,8 @@ class HTTPMessage(stateobject.StateObject):
if short:
if self.content:
ret["contentLength"] = len(self.content)
+ elif self.content == CONTENT_MISSING:
+ ret["contentLength"] = None
else:
ret["contentLength"] = 0
return ret
@@ -239,7 +229,7 @@ class HTTPRequest(HTTPMessage):
httpversion: HTTP version tuple, e.g. (1,1)
- headers: ODictCaseless object
+ headers: odict.ODictCaseless object
content: Content of the request, None, or CONTENT_MISSING if there
is content associated, but not present. CONTENT_MISSING evaluates
@@ -277,7 +267,7 @@ class HTTPRequest(HTTPMessage):
timestamp_end=None,
form_out=None
):
- assert isinstance(headers, ODictCaseless) or not headers
+ assert isinstance(headers, odict.ODictCaseless) or not headers
HTTPMessage.__init__(
self,
httpversion,
@@ -315,7 +305,18 @@ class HTTPRequest(HTTPMessage):
@classmethod
def from_state(cls, state):
- f = cls(None, None, None, None, None, None, None, None, None, None, None)
+ f = cls(
+ None,
+ None,
+ None,
+ None,
+ None,
+ None,
+ None,
+ None,
+ None,
+ None,
+ None)
f.load_state(state)
return f
@@ -325,78 +326,56 @@ class HTTPRequest(HTTPMessage):
)
@classmethod
- def from_stream(cls, rfile, include_body=True, body_size_limit=None):
+ def from_stream(
+ cls,
+ rfile,
+ include_body=True,
+ body_size_limit=None,
+ wfile=None):
"""
Parse an HTTP request from a file stream
+
+ Args:
+ rfile (file): Input file to read from
+ include_body (bool): Read response body as well
+ body_size_limit (bool): Maximum body size
+ wfile (file): If specified, HTTP Expect headers are handled automatically.
+ by writing a HTTP 100 CONTINUE response to the stream.
+
+ Returns:
+ HTTPRequest: The HTTP request
+
+ Raises:
+ HttpError: If the input is invalid.
"""
- httpversion, host, port, scheme, method, path, headers, content, timestamp_start, timestamp_end = (
- None, None, None, None, None, None, None, None, None, None)
+ timestamp_start, timestamp_end = None, None
timestamp_start = utils.timestamp()
-
if hasattr(rfile, "reset_timestamps"):
rfile.reset_timestamps()
- request_line = get_line(rfile)
+ req = http.read_request(
+ rfile,
+ include_body = include_body,
+ body_size_limit = body_size_limit,
+ wfile = wfile
+ )
if hasattr(rfile, "first_byte_timestamp"):
# more accurate timestamp_start
timestamp_start = rfile.first_byte_timestamp
- request_line_parts = http.parse_init(request_line)
- if not request_line_parts:
- raise http.HttpError(
- 400,
- "Bad HTTP request line: %s" % repr(request_line)
- )
- method, path, httpversion = request_line_parts
-
- if path == '*' or path.startswith("/"):
- form_in = "relative"
- if not netlib.utils.isascii(path):
- raise http.HttpError(
- 400,
- "Bad HTTP request line: %s" % repr(request_line)
- )
- elif method.upper() == 'CONNECT':
- form_in = "authority"
- r = http.parse_init_connect(request_line)
- if not r:
- raise http.HttpError(
- 400,
- "Bad HTTP request line: %s" % repr(request_line)
- )
- host, port, _ = r
- path = None
- else:
- form_in = "absolute"
- r = http.parse_init_proxy(request_line)
- if not r:
- raise http.HttpError(
- 400,
- "Bad HTTP request line: %s" % repr(request_line)
- )
- _, scheme, host, port, path, _ = r
-
- headers = http.read_headers(rfile)
- if headers is None:
- raise http.HttpError(400, "Invalid headers")
-
- if include_body:
- content = http.read_http_body(rfile, headers, body_size_limit,
- method, None, True)
- timestamp_end = utils.timestamp()
-
+ timestamp_end = utils.timestamp()
return HTTPRequest(
- form_in,
- method,
- scheme,
- host,
- port,
- path,
- httpversion,
- headers,
- content,
+ req.form_in,
+ req.method,
+ req.scheme,
+ req.host,
+ req.port,
+ req.path,
+ req.httpversion,
+ req.headers,
+ req.content,
timestamp_start,
timestamp_end
)
@@ -440,11 +419,12 @@ class HTTPRequest(HTTPMessage):
self.host,
self.port)]
- # If content is defined (i.e. not None or CONTENT_MISSING), we always add a content-length header.
+ # If content is defined (i.e. not None or CONTENT_MISSING), we always
+ # add a content-length header.
if self.content or self.content == "":
headers["Content-Length"] = [str(len(self.content))]
- return str(headers)
+ return headers.format()
def _assemble_head(self, form=None):
return "%s\r\n%s\r\n" % (
@@ -497,9 +477,9 @@ class HTTPRequest(HTTPMessage):
decode appropriately.
"""
if self.headers["accept-encoding"]:
- self.headers["accept-encoding"] = [', '.join(
- e for e in encoding.ENCODINGS if e in self.headers["accept-encoding"][0]
- )]
+ self.headers["accept-encoding"] = [
+ ', '.join(
+ e for e in encoding.ENCODINGS if e in self.headers["accept-encoding"][0])]
def update_host_header(self):
"""
@@ -507,15 +487,42 @@ class HTTPRequest(HTTPMessage):
"""
self.headers["Host"] = [self.host]
+ def get_form(self):
+ """
+ Retrieves the URL-encoded or multipart form data, returning an ODict object.
+ Returns an empty ODict if there is no data or the content-type
+ indicates non-form data.
+ """
+ if self.content:
+ if self.headers.in_any("content-type", HDR_FORM_URLENCODED, True):
+ return self.get_form_urlencoded()
+ elif self.headers.in_any("content-type", HDR_FORM_MULTIPART, True):
+ return self.get_form_multipart()
+ return odict.ODict([])
+
def get_form_urlencoded(self):
"""
Retrieves the URL-encoded form data, returning an ODict object.
Returns an empty ODict if there is no data or the content-type
indicates non-form data.
"""
- if self.content and self.headers.in_any("content-type", HDR_FORM_URLENCODED, True):
- return ODict(utils.urldecode(self.content))
- return ODict([])
+ if self.content and self.headers.in_any(
+ "content-type",
+ HDR_FORM_URLENCODED,
+ True):
+ return odict.ODict(utils.urldecode(self.content))
+ return odict.ODict([])
+
+ def get_form_multipart(self):
+ if self.content and self.headers.in_any(
+ "content-type",
+ HDR_FORM_MULTIPART,
+ True):
+ return odict.ODict(
+ utils.multipartdecode(
+ self.headers,
+ self.content))
+ return odict.ODict([])
def set_form_urlencoded(self, odict):
"""
@@ -556,8 +563,8 @@ class HTTPRequest(HTTPMessage):
"""
_, _, _, _, query, _ = urlparse.urlparse(self.url)
if query:
- return ODict(utils.urldecode(query))
- return ODict([])
+ return odict.ODict(utils.urldecode(query))
+ return odict.ODict([])
def set_query(self, odict):
"""
@@ -588,8 +595,10 @@ class HTTPRequest(HTTPMessage):
host = self.headers.get_first("host")
if not host:
host = self.host
- host = host.encode("idna")
- return host
+ if host:
+ return host.encode("idna")
+ else:
+ return None
def pretty_url(self, hostheader):
if self.form_out == "authority": # upstream proxy mode
@@ -625,15 +634,22 @@ class HTTPRequest(HTTPMessage):
self.scheme, self.host, self.port, self.path = parts
def get_cookies(self):
- cookie_headers = self.headers.get("cookie")
- if not cookie_headers:
- return None
+ """
+
+ Returns a possibly empty netlib.odict.ODict object.
+ """
+ ret = odict.ODict()
+ for i in self.headers["cookie"]:
+ ret.extend(http_cookies.parse_cookie_header(i))
+ return ret
- cookies = []
- for header in cookie_headers:
- pairs = [pair.partition("=") for pair in header.split(';')]
- cookies.extend((pair[0], (pair[2], {})) for pair in pairs)
- return dict(cookies)
+ def set_cookies(self, odict):
+ """
+ Takes an netlib.odict.ODict object. Over-writes any existing Cookie
+ headers.
+ """
+ v = http_cookies.format_cookie_header(odict)
+ self.headers["Cookie"] = [v]
def replace(self, pattern, repl, *args, **kwargs):
"""
@@ -674,9 +690,16 @@ class HTTPResponse(HTTPMessage):
timestamp_end: Timestamp indicating when request transmission ended
"""
- def __init__(self, httpversion, code, msg, headers, content, timestamp_start=None,
- timestamp_end=None):
- assert isinstance(headers, ODictCaseless) or headers is None
+ def __init__(
+ self,
+ httpversion,
+ code,
+ msg,
+ headers,
+ content,
+ timestamp_start=None,
+ timestamp_end=None):
+ assert isinstance(headers, odict.ODictCaseless) or headers is None
HTTPMessage.__init__(
self,
httpversion,
@@ -706,7 +729,10 @@ class HTTPResponse(HTTPMessage):
return f
def __repr__(self):
- size = utils.pretty_size(len(self.content)) if self.content else "content missing"
+ if self.content:
+ size = netlib.utils.pretty_size(len(self.content))
+ else:
+ size = "content missing"
return "<HTTPResponse: {code} {msg} ({contenttype}, {size})>".format(
code=self.code,
msg=self.msg,
@@ -717,7 +743,12 @@ class HTTPResponse(HTTPMessage):
)
@classmethod
- def from_stream(cls, rfile, request_method, include_body=True, body_size_limit=None):
+ def from_stream(
+ cls,
+ rfile,
+ request_method,
+ include_body=True,
+ body_size_limit=None):
"""
Parse an HTTP response from a file stream
"""
@@ -767,11 +798,12 @@ class HTTPResponse(HTTPMessage):
if not preserve_transfer_encoding:
del headers['Transfer-Encoding']
- # If content is defined (i.e. not None or CONTENT_MISSING), we always add a content-length header.
+ # If content is defined (i.e. not None or CONTENT_MISSING), we always
+ # add a content-length header.
if self.content or self.content == "":
headers["Content-Length"] = [str(len(self.content))]
- return str(headers)
+ return headers.format()
def _assemble_head(self, preserve_transfer_encoding=False):
return '%s\r\n%s\r\n' % (
@@ -850,20 +882,39 @@ class HTTPResponse(HTTPMessage):
self.headers["set-cookie"] = c
def get_cookies(self):
- cookie_headers = self.headers.get("set-cookie")
- if not cookie_headers:
- return None
+ """
+ Get the contents of all Set-Cookie headers.
+
+ Returns a possibly empty ODict, where keys are cookie name strings,
+ and values are [value, attr] lists. Value is a string, and attr is
+ an ODictCaseless containing cookie attributes. Within attrs, unary
+ attributes (e.g. HTTPOnly) are indicated by a Null value.
+ """
+ ret = []
+ for header in self.headers["set-cookie"]:
+ v = http_cookies.parse_set_cookie_header(header)
+ if v:
+ name, value, attrs = v
+ ret.append([name, [value, attrs]])
+ return odict.ODict(ret)
+
+ def set_cookies(self, odict):
+ """
+ Set the Set-Cookie headers on this response, over-writing existing
+ headers.
- cookies = []
- for header in cookie_headers:
- pairs = [pair.partition("=") for pair in header.split(';')]
- cookie_name = pairs[0][0] # the key of the first key/value pairs
- cookie_value = pairs[0][2] # the value of the first key/value pairs
- cookie_parameters = {
- key.strip().lower(): value.strip() for key, sep, value in pairs[1:]
- }
- cookies.append((cookie_name, (cookie_value, cookie_parameters)))
- return dict(cookies)
+ Accepts an ODict of the same format as that returned by get_cookies.
+ """
+ values = []
+ for i in odict.lst:
+ values.append(
+ http_cookies.format_set_cookie_header(
+ i[0],
+ i[1][0],
+ i[1][1]
+ )
+ )
+ self.headers["Set-Cookie"] = values
class HTTPFlow(Flow):
@@ -996,7 +1047,7 @@ class HTTPHandler(ProtocolHandler):
include_body=False
)
break
- except (tcp.NetLibError, http.HttpErrorConnClosed), v:
+ except (tcp.NetLibError, http.HttpErrorConnClosed) as v:
self.c.log(
"error in server communication: %s" % repr(v),
level="debug"
@@ -1041,7 +1092,8 @@ class HTTPHandler(ProtocolHandler):
try:
req = HTTPRequest.from_stream(
self.c.client_conn.rfile,
- body_size_limit=self.c.config.body_size_limit
+ body_size_limit=self.c.config.body_size_limit,
+ wfile=self.c.client_conn.wfile
)
except tcp.NetLibError:
# don't throw an error for disconnects that happen
@@ -1066,7 +1118,8 @@ class HTTPHandler(ProtocolHandler):
if request_reply is None or request_reply == KILL:
raise KillSignal()
- self.process_server_address(flow) # The inline script may have changed request.host
+ # The inline script may have changed request.host
+ self.process_server_address(flow)
if isinstance(request_reply, HTTPResponse):
flow.response = request_reply
@@ -1077,7 +1130,9 @@ class HTTPHandler(ProtocolHandler):
# we can safely set it as the final attribute value here.
flow.server_conn = self.c.server_conn
- self.c.log("response", "debug", [flow.response._assemble_first_line()])
+ self.c.log(
+ "response", "debug", [
+ flow.response._assemble_first_line()])
response_reply = self.c.channel.ask("response", flow)
if response_reply is None or response_reply == KILL:
raise KillSignal()
@@ -1104,7 +1159,8 @@ class HTTPHandler(ProtocolHandler):
}
)
)
- if not self.process_connect_request((flow.request.host, flow.request.port)):
+ if not self.process_connect_request(
+ (flow.request.host, flow.request.port)):
return False
# If the user has changed the target server on this connection,
@@ -1117,7 +1173,7 @@ class HTTPHandler(ProtocolHandler):
http.HttpError,
proxy.ProxyError,
tcp.NetLibError,
- ), e:
+ ) as e:
self.handle_error(e, flow)
except KillSignal:
self.c.log("Connection killed", "info")
@@ -1213,7 +1269,8 @@ class HTTPHandler(ProtocolHandler):
# Determine .scheme, .host and .port attributes
# For absolute-form requests, they are directly given in the request.
# For authority-form requests, we only need to determine the request scheme.
- # For relative-form requests, we need to determine host and port as well.
+ # For relative-form requests, we need to determine host and port as
+ # well.
if not request.scheme:
request.scheme = "https" if flow.server_conn and flow.server_conn.ssl_established else "http"
if not request.host:
@@ -1240,7 +1297,8 @@ class HTTPHandler(ProtocolHandler):
flow.server_conn = self.c.server_conn
self.c.establish_server_connection()
self.c.client_conn.send(
- 'HTTP/1.1 200 Connection established\r\n' +
+ ('HTTP/%s.%s 200 ' % (request.httpversion[0], request.httpversion[1])) +
+ 'Connection established\r\n' +
'Content-Length: 0\r\n' +
('Proxy-agent: %s\r\n' % self.c.config.server_version) +
'\r\n'
@@ -1304,7 +1362,8 @@ class HTTPHandler(ProtocolHandler):
)
if needs_server_change:
- # force create new connection to the proxy server to reset state
+ # force create new connection to the proxy server to reset
+ # state
self.live.change_server(self.c.server_conn.address, force=True)
if ssl:
send_connect_request(
@@ -1314,8 +1373,9 @@ class HTTPHandler(ProtocolHandler):
)
self.c.establish_ssl(server=True)
else:
- # If we're not in upstream mode, we just want to update the host and
- # possibly establish TLS. This is a no op if the addresses match.
+ # If we're not in upstream mode, we just want to update the host
+ # and possibly establish TLS. This is a no op if the addresses
+ # match.
self.live.change_server(address, ssl=ssl)
flow.server_conn = self.c.server_conn
@@ -1323,8 +1383,8 @@ class HTTPHandler(ProtocolHandler):
def send_response_to_client(self, flow):
if not flow.response.stream:
# no streaming:
- # we already received the full response from the server and can send
- # it to the client straight away.
+ # we already received the full response from the server and can
+ # send it to the client straight away.
self.c.client_conn.send(flow.response.assemble())
else:
# streaming:
@@ -1356,14 +1416,21 @@ class HTTPHandler(ProtocolHandler):
semantics. Returns True, if so.
"""
close_connection = (
- http.connection_close(flow.request.httpversion, flow.request.headers) or
- http.connection_close(flow.response.httpversion, flow.response.headers) or
- http.expected_http_body_size(flow.response.headers, False, flow.request.method,
- flow.response.code) == -1)
+ http.connection_close(
+ flow.request.httpversion,
+ flow.request.headers) or http.connection_close(
+ flow.response.httpversion,
+ flow.response.headers) or http.expected_http_body_size(
+ flow.response.headers,
+ False,
+ flow.request.method,
+ flow.response.code) == -1)
if close_connection:
if flow.request.form_in == "authority" and flow.response.code == 200:
- # Workaround for https://github.com/mitmproxy/mitmproxy/issues/313:
- # Some proxies (e.g. Charles) send a CONNECT response with HTTP/1.0 and no Content-Length header
+ # Workaround for
+ # https://github.com/mitmproxy/mitmproxy/issues/313: Some
+ # proxies (e.g. Charles) send a CONNECT response with HTTP/1.0
+ # and no Content-Length header
pass
else:
return True
@@ -1385,14 +1452,16 @@ class HTTPHandler(ProtocolHandler):
self.expected_form_out = "relative"
self.skip_authentication = True
- # In practice, nobody issues a CONNECT request to send unencrypted HTTP requests afterwards.
- # If we don't delegate to TCP mode, we should always negotiate a SSL connection.
+ # In practice, nobody issues a CONNECT request to send unencrypted
+ # HTTP requests afterwards. If we don't delegate to TCP mode, we
+ # should always negotiate a SSL connection.
#
- # FIXME:
- # Turns out the previous statement isn't entirely true. Chrome on Windows CONNECTs to :80
- # if an explicit proxy is configured and a websocket connection should be established.
- # We don't support websocket at the moment, so it fails anyway, but we should come up with
- # a better solution to this if we start to support WebSockets.
+ # FIXME: Turns out the previous statement isn't entirely true.
+ # Chrome on Windows CONNECTs to :80 if an explicit proxy is
+ # configured and a websocket connection should be established. We
+ # don't support websocket at the moment, so it fails anyway, but we
+ # should come up with a better solution to this if we start to
+ # support WebSockets.
should_establish_ssl = (
address.port in self.c.config.ssl_ports
or
@@ -1400,12 +1469,18 @@ class HTTPHandler(ProtocolHandler):
)
if should_establish_ssl:
- self.c.log("Received CONNECT request to SSL port. Upgrading to SSL...", "debug")
+ self.c.log(
+ "Received CONNECT request to SSL port. "
+ "Upgrading to SSL...", "debug"
+ )
self.c.establish_ssl(server=True, client=True)
self.c.log("Upgrade to SSL completed.", "debug")
if self.c.config.check_tcp(address):
- self.c.log("Generic TCP mode for host: %s:%s" % address(), "info")
+ self.c.log(
+ "Generic TCP mode for host: %s:%s" % address(),
+ "info"
+ )
TCPHandler(self.c).handle_messages()
return False
@@ -1426,7 +1501,8 @@ class RequestReplayThread(threading.Thread):
def __init__(self, config, flow, masterq, should_exit):
"""
- masterqueue can be a queue or None, if no scripthooks should be processed.
+ masterqueue can be a queue or None, if no scripthooks should be
+ processed.
"""
self.config, self.flow = config, flow
if masterq:
@@ -1452,12 +1528,17 @@ class RequestReplayThread(threading.Thread):
if not self.flow.response:
# In all modes, we directly connect to the server displayed
if self.config.mode == "upstream":
- server_address = self.config.mode.get_upstream_server(self.flow.client_conn)[2:]
+ server_address = self.config.mode.get_upstream_server(
+ self.flow.client_conn
+ )[2:]
server = ServerConnection(server_address)
server.connect()
if r.scheme == "https":
send_connect_request(server, r.host, r.port)
- server.establish_ssl(self.config.clientcerts, sni=self.flow.server_conn.sni)
+ server.establish_ssl(
+ self.config.clientcerts,
+ sni=self.flow.server_conn.sni
+ )
r.form_out = "relative"
else:
r.form_out = "absolute"
@@ -1466,12 +1547,18 @@ class RequestReplayThread(threading.Thread):
server = ServerConnection(server_address)
server.connect()
if r.scheme == "https":
- server.establish_ssl(self.config.clientcerts, sni=self.flow.server_conn.sni)
+ server.establish_ssl(
+ self.config.clientcerts,
+ sni=self.flow.server_conn.sni
+ )
r.form_out = "relative"
server.send(r.assemble())
self.flow.server_conn = server
- self.flow.response = HTTPResponse.from_stream(server.rfile, r.method,
- body_size_limit=self.config.body_size_limit)
+ self.flow.response = HTTPResponse.from_stream(
+ server.rfile,
+ r.method,
+ body_size_limit=self.config.body_size_limit
+ )
if self.channel:
response_reply = self.channel.ask("response", self.flow)
if response_reply is None or response_reply == KILL:
@@ -1481,7 +1568,8 @@ class RequestReplayThread(threading.Thread):
if self.channel:
self.channel.ask("error", self.flow)
except KillSignal:
- # KillSignal should only be raised if there's a channel in the first place.
+ # KillSignal should only be raised if there's a channel in the
+ # first place.
self.channel.tell("log", proxy.Log("Connection killed", "info"))
finally:
r.form_out = form_out_backup
diff --git a/libmproxy/protocol/primitives.py b/libmproxy/protocol/primitives.py
index f9c22e1a..2f8ea3e0 100644
--- a/libmproxy/protocol/primitives.py
+++ b/libmproxy/protocol/primitives.py
@@ -24,6 +24,7 @@ class Error(stateobject.StateObject):
msg: Message describing the error
timestamp: Seconds since the epoch
"""
+
def __init__(self, msg, timestamp=None):
"""
@type msg: str
@@ -59,6 +60,7 @@ class Flow(stateobject.StateObject):
A Flow is a collection of objects representing a single transaction.
This class is usually subclassed for each protocol, e.g. HTTPFlow.
"""
+
def __init__(self, type, client_conn, server_conn, live=None):
self.type = type
self.id = str(uuid.uuid4())
@@ -165,12 +167,12 @@ class Flow(stateobject.StateObject):
master.handle_accept_intercept(self)
-
class ProtocolHandler(object):
"""
A ProtocolHandler implements an application-layer protocol, e.g. HTTP.
See: libmproxy.protocol.http.HTTPHandler
"""
+
def __init__(self, c):
self.c = c
"""@type: libmproxy.proxy.server.ConnectionHandler"""
@@ -209,13 +211,20 @@ class LiveConnection(object):
interface with a live connection, without exposing the internals
of the ConnectionHandler.
"""
+
def __init__(self, c):
self.c = c
"""@type: libmproxy.proxy.server.ConnectionHandler"""
self._backup_server_conn = None
"""@type: libmproxy.proxy.connection.ServerConnection"""
- def change_server(self, address, ssl=None, sni=None, force=False, persistent_change=False):
+ def change_server(
+ self,
+ address,
+ ssl=None,
+ sni=None,
+ force=False,
+ persistent_change=False):
"""
Change the server connection to the specified address.
@returns:
diff --git a/libmproxy/protocol/tcp.py b/libmproxy/protocol/tcp.py
index 5314b577..0feb77c6 100644
--- a/libmproxy/protocol/tcp.py
+++ b/libmproxy/protocol/tcp.py
@@ -79,7 +79,8 @@ class TCPHandler(ProtocolHandler):
),
"info"
)
- # Do not use dst.connection.send here, which may raise OpenSSL-specific errors.
+ # Do not use dst.connection.send here, which may raise
+ # OpenSSL-specific errors.
dst.send(contents)
else:
# socket.socket.send supports raw bytearrays/memoryviews
diff --git a/libmproxy/proxy/config.py b/libmproxy/proxy/config.py
index dfde2958..3f579669 100644
--- a/libmproxy/proxy/config.py
+++ b/libmproxy/proxy/config.py
@@ -81,16 +81,27 @@ class ProxyConfig:
self.check_tcp = HostMatcher(tcp_hosts)
self.authenticator = authenticator
self.cadir = os.path.expanduser(cadir)
- self.certstore = certutils.CertStore.from_store(self.cadir, CONF_BASENAME)
+ self.certstore = certutils.CertStore.from_store(
+ self.cadir,
+ CONF_BASENAME)
for spec, cert in certs:
self.certstore.add_cert_file(spec, cert)
self.certforward = certforward
- self.openssl_method_client, self.openssl_options_client = version_to_openssl(ssl_version_client)
- self.openssl_method_server, self.openssl_options_server = version_to_openssl(ssl_version_server)
+ self.openssl_method_client, self.openssl_options_client = version_to_openssl(
+ ssl_version_client)
+ self.openssl_method_server, self.openssl_options_server = version_to_openssl(
+ ssl_version_server)
self.ssl_ports = ssl_ports
-sslversion_choices = ("all", "secure", "SSLv2", "SSLv3", "TLSv1", "TLSv1_1", "TLSv1_2")
+sslversion_choices = (
+ "all",
+ "secure",
+ "SSLv2",
+ "SSLv3",
+ "TLSv1",
+ "TLSv1_1",
+ "TLSv1_2")
def version_to_openssl(version):
@@ -119,7 +130,8 @@ def process_proxy_options(parser, options):
if options.transparent_proxy:
c += 1
if not platform.resolver:
- return parser.error("Transparent mode not supported on this platform.")
+ return parser.error(
+ "Transparent mode not supported on this platform.")
mode = "transparent"
if options.socks_proxy:
c += 1
@@ -133,28 +145,33 @@ def process_proxy_options(parser, options):
mode = "upstream"
upstream_server = options.upstream_proxy
if c > 1:
- return parser.error("Transparent, SOCKS5, reverse and upstream proxy mode "
- "are mutually exclusive.")
+ return parser.error(
+ "Transparent, SOCKS5, reverse and upstream proxy mode "
+ "are mutually exclusive.")
if options.clientcerts:
options.clientcerts = os.path.expanduser(options.clientcerts)
- if not os.path.exists(options.clientcerts) or not os.path.isdir(options.clientcerts):
+ if not os.path.exists(
+ options.clientcerts) or not os.path.isdir(
+ options.clientcerts):
return parser.error(
- "Client certificate directory does not exist or is not a directory: %s" % options.clientcerts
- )
+ "Client certificate directory does not exist or is not a directory: %s" %
+ options.clientcerts)
if (options.auth_nonanonymous or options.auth_singleuser or options.auth_htpasswd):
if options.auth_singleuser:
if len(options.auth_singleuser.split(':')) != 2:
- return parser.error("Invalid single-user specification. Please use the format username:password")
+ return parser.error(
+ "Invalid single-user specification. Please use the format username:password")
username, password = options.auth_singleuser.split(':')
password_manager = http_auth.PassManSingleUser(username, password)
elif options.auth_nonanonymous:
password_manager = http_auth.PassManNonAnon()
elif options.auth_htpasswd:
try:
- password_manager = http_auth.PassManHtpasswd(options.auth_htpasswd)
- except ValueError, v:
+ password_manager = http_auth.PassManHtpasswd(
+ options.auth_htpasswd)
+ except ValueError as v:
return parser.error(v.message)
authenticator = http_auth.BasicProxyAuth(password_manager, "mitmproxy")
else:
@@ -203,15 +220,18 @@ def process_proxy_options(parser, options):
def ssl_option_group(parser):
group = parser.add_argument_group("SSL")
group.add_argument(
- "--cert", dest='certs', default=[], type=str,
- metavar="SPEC", action="append",
+ "--cert",
+ dest='certs',
+ default=[],
+ type=str,
+ metavar="SPEC",
+ action="append",
help='Add an SSL certificate. SPEC is of the form "[domain=]path". '
- 'The domain may include a wildcard, and is equal to "*" if not specified. '
- 'The file at path is a certificate in PEM format. If a private key is included in the PEM, '
- 'it is used, else the default key in the conf dir is used. '
- 'The PEM file should contain the full certificate chain, with the leaf certificate as the first entry. '
- 'Can be passed multiple times.'
- )
+ 'The domain may include a wildcard, and is equal to "*" if not specified. '
+ 'The file at path is a certificate in PEM format. If a private key is included in the PEM, '
+ 'it is used, else the default key in the conf dir is used. '
+ 'The PEM file should contain the full certificate chain, with the leaf certificate as the first entry. '
+ 'Can be passed multiple times.')
group.add_argument(
"--cert-forward", action="store_true",
dest="certforward", default=False,
@@ -238,11 +258,15 @@ def ssl_option_group(parser):
help="Don't connect to upstream server to look up certificate details."
)
group.add_argument(
- "--ssl-port", action="append", type=int, dest="ssl_ports", default=list(TRANSPARENT_SSL_PORTS),
+ "--ssl-port",
+ action="append",
+ type=int,
+ dest="ssl_ports",
+ default=list(TRANSPARENT_SSL_PORTS),
metavar="PORT",
help="Can be passed multiple times. Specify destination ports which are assumed to be SSL. "
- "Defaults to %s." % str(TRANSPARENT_SSL_PORTS)
- )
+ "Defaults to %s." %
+ str(TRANSPARENT_SSL_PORTS))
group.add_argument(
"--ssl-version-client", dest="ssl_version_client",
default="secure", action="store",
diff --git a/libmproxy/proxy/connection.py b/libmproxy/proxy/connection.py
index 1eeae16f..5219023b 100644
--- a/libmproxy/proxy/connection.py
+++ b/libmproxy/proxy/connection.py
@@ -7,7 +7,9 @@ from .. import stateobject, utils
class ClientConnection(tcp.BaseHandler, stateobject.StateObject):
def __init__(self, client_connection, address, server):
- if client_connection: # Eventually, this object is restored from state. We don't have a connection then.
+ # Eventually, this object is restored from state. We don't have a
+ # connection then.
+ if client_connection:
tcp.BaseHandler.__init__(self, client_connection, address, server)
else:
self.connection = None
@@ -39,15 +41,18 @@ class ClientConnection(tcp.BaseHandler, stateobject.StateObject):
def get_state(self, short=False):
d = super(ClientConnection, self).get_state(short)
d.update(
- address={"address": self.address(), "use_ipv6": self.address.use_ipv6},
- clientcert=self.cert.to_pem() if self.clientcert else None
- )
+ address={
+ "address": self.address(),
+ "use_ipv6": self.address.use_ipv6},
+ clientcert=self.cert.to_pem() if self.clientcert else None)
return d
def load_state(self, state):
super(ClientConnection, self).load_state(state)
- self.address = tcp.Address(**state["address"]) if state["address"] else None
- self.clientcert = certutils.SSLCert.from_pem(state["clientcert"]) if state["clientcert"] else None
+ self.address = tcp.Address(
+ **state["address"]) if state["address"] else None
+ self.clientcert = certutils.SSLCert.from_pem(
+ state["clientcert"]) if state["clientcert"] else None
def copy(self):
return copy.copy(self)
@@ -114,7 +119,7 @@ class ServerConnection(tcp.TCPClient, stateobject.StateObject):
address={"address": self.address(),
"use_ipv6": self.address.use_ipv6},
source_address= ({"address": self.source_address(),
- "use_ipv6": self.source_address.use_ipv6} if self.source_address else None),
+ "use_ipv6": self.source_address.use_ipv6} if self.source_address else None),
cert=self.cert.to_pem() if self.cert else None
)
return d
@@ -122,9 +127,12 @@ class ServerConnection(tcp.TCPClient, stateobject.StateObject):
def load_state(self, state):
super(ServerConnection, self).load_state(state)
- self.address = tcp.Address(**state["address"]) if state["address"] else None
- self.source_address = tcp.Address(**state["source_address"]) if state["source_address"] else None
- self.cert = certutils.SSLCert.from_pem(state["cert"]) if state["cert"] else None
+ self.address = tcp.Address(
+ **state["address"]) if state["address"] else None
+ self.source_address = tcp.Address(
+ **state["source_address"]) if state["source_address"] else None
+ self.cert = certutils.SSLCert.from_pem(
+ state["cert"]) if state["cert"] else None
@classmethod
def from_state(cls, state):
@@ -147,7 +155,9 @@ class ServerConnection(tcp.TCPClient, stateobject.StateObject):
def establish_ssl(self, clientcerts, sni, **kwargs):
clientcert = None
if clientcerts:
- path = os.path.join(clientcerts, self.address.host.encode("idna")) + ".pem"
+ path = os.path.join(
+ clientcerts,
+ self.address.host.encode("idna")) + ".pem"
if os.path.exists(path):
clientcert = path
self.convert_to_ssl(cert=clientcert, sni=sni, **kwargs)
diff --git a/libmproxy/proxy/primitives.py b/libmproxy/proxy/primitives.py
index c0ae424d..9e7dae9a 100644
--- a/libmproxy/proxy/primitives.py
+++ b/libmproxy/proxy/primitives.py
@@ -1,6 +1,7 @@
from __future__ import absolute_import
from netlib import socks
+
class ProxyError(Exception):
def __init__(self, code, message, headers=None):
super(ProxyError, self).__init__(message)
@@ -61,7 +62,7 @@ class TransparentProxyMode(ProxyMode):
def get_upstream_server(self, client_conn):
try:
dst = self.resolver.original_addr(client_conn.connection)
- except Exception, e:
+ except Exception as e:
raise ProxyError(502, "Transparent mode failure: %s" % str(e))
if dst[1] in self.sslports:
@@ -87,7 +88,9 @@ class Socks5ProxyMode(ProxyMode):
guess = ""
raise socks.SocksError(
socks.REP.GENERAL_SOCKS_SERVER_FAILURE,
- guess + "Invalid SOCKS version. Expected 0x05, got 0x%x" % msg.ver)
+ guess +
+ "Invalid SOCKS version. Expected 0x05, got 0x%x" %
+ msg.ver)
def get_upstream_server(self, client_conn):
try:
@@ -117,13 +120,15 @@ class Socks5ProxyMode(ProxyMode):
"mitmproxy only supports SOCKS5 CONNECT."
)
- # We do not connect here yet, as the clientconnect event has not been handled yet.
+ # We do not connect here yet, as the clientconnect event has not
+ # been handled yet.
connect_reply = socks.Message(
socks.VERSION.SOCKS5,
socks.REP.SUCCEEDED,
socks.ATYP.DOMAINNAME,
- client_conn.address # dummy value, we don't have an upstream connection yet.
+ # dummy value, we don't have an upstream connection yet.
+ client_conn.address
)
connect_reply.to_file(client_conn.wfile)
client_conn.wfile.flush()
@@ -161,4 +166,4 @@ class UpstreamProxyMode(_ConstDestinationProxyMode):
class Log:
def __init__(self, msg, level="info"):
self.msg = msg
- self.level = level \ No newline at end of file
+ self.level = level
diff --git a/libmproxy/proxy/server.py b/libmproxy/proxy/server.py
index a72f9aba..e1587df1 100644
--- a/libmproxy/proxy/server.py
+++ b/libmproxy/proxy/server.py
@@ -34,7 +34,7 @@ class ProxyServer(tcp.TCPServer):
self.config = config
try:
tcp.TCPServer.__init__(self, (config.host, config.port))
- except socket.error, v:
+ except socket.error as v:
raise ProxyServerError('Error starting proxy server: ' + repr(v))
self.channel = None
@@ -46,16 +46,30 @@ class ProxyServer(tcp.TCPServer):
self.channel = channel
def handle_client_connection(self, conn, client_address):
- h = ConnectionHandler(self.config, conn, client_address, self, self.channel)
+ h = ConnectionHandler(
+ self.config,
+ conn,
+ client_address,
+ self,
+ self.channel)
h.handle()
h.finish()
class ConnectionHandler:
- def __init__(self, config, client_connection, client_address, server, channel):
+ def __init__(
+ self,
+ config,
+ client_connection,
+ client_address,
+ server,
+ channel):
self.config = config
"""@type: libmproxy.proxy.config.ProxyConfig"""
- self.client_conn = ClientConnection(client_connection, client_address, server)
+ self.client_conn = ClientConnection(
+ client_connection,
+ client_address,
+ server)
"""@type: libmproxy.proxy.connection.ClientConnection"""
self.server_conn = None
"""@type: libmproxy.proxy.connection.ServerConnection"""
@@ -70,17 +84,23 @@ class ConnectionHandler:
# Can we already identify the target server and connect to it?
client_ssl, server_ssl = False, False
conn_kwargs = dict()
- upstream_info = self.config.mode.get_upstream_server(self.client_conn)
+ upstream_info = self.config.mode.get_upstream_server(
+ self.client_conn)
if upstream_info:
self.set_server_address(upstream_info[2:])
client_ssl, server_ssl = upstream_info[:2]
if self.config.check_ignore(self.server_conn.address):
- self.log("Ignore host: %s:%s" % self.server_conn.address(), "info")
+ self.log(
+ "Ignore host: %s:%s" %
+ self.server_conn.address(),
+ "info")
self.conntype = "tcp"
conn_kwargs["log"] = False
client_ssl, server_ssl = False, False
else:
- pass # No upstream info from the metadata: upstream info in the protocol (e.g. HTTP absolute-form)
+ # No upstream info from the metadata: upstream info in the
+ # protocol (e.g. HTTP absolute-form)
+ pass
self.channel.ask("clientconnect", self)
@@ -92,11 +112,17 @@ class ConnectionHandler:
self.establish_ssl(client=client_ssl, server=server_ssl)
if self.config.check_tcp(self.server_conn.address):
- self.log("Generic TCP mode for host: %s:%s" % self.server_conn.address(), "info")
+ self.log(
+ "Generic TCP mode for host: %s:%s" %
+ self.server_conn.address(),
+ "info")
self.conntype = "tcp"
# Delegate handling to the protocol handler
- protocol_handler(self.conntype)(self, **conn_kwargs).handle_messages()
+ protocol_handler(
+ self.conntype)(
+ self,
+ **conn_kwargs).handle_messages()
self.log("clientdisconnect", "info")
self.channel.tell("clientdisconnect", self)
@@ -104,7 +130,8 @@ class ConnectionHandler:
except ProxyError as e:
protocol_handler(self.conntype)(self, **conn_kwargs).handle_error(e)
except Exception:
- import traceback, sys
+ import traceback
+ import sys
self.log(traceback.format_exc(), "error")
print >> sys.stderr, traceback.format_exc()
@@ -112,7 +139,8 @@ class ConnectionHandler:
print >> sys.stderr, "Please lodge a bug report at: https://github.com/mitmproxy/mitmproxy"
finally:
# Make sure that we close the server connection in any case.
- # The client connection is closed by the ProxyServer and does not have be handled here.
+ # The client connection is closed by the ProxyServer and does not
+ # have be handled here.
self.del_server_connection()
def del_server_connection(self):
@@ -122,8 +150,10 @@ class ConnectionHandler:
if self.server_conn and self.server_conn.connection:
self.server_conn.finish()
self.server_conn.close()
- self.log("serverdisconnect", "debug", ["%s:%s" % (self.server_conn.address.host,
- self.server_conn.address.port)])
+ self.log(
+ "serverdisconnect", "debug", [
+ "%s:%s" %
+ (self.server_conn.address.host, self.server_conn.address.port)])
self.channel.tell("serverdisconnect", self)
self.server_conn = None
@@ -141,7 +171,9 @@ class ConnectionHandler:
if self.server_conn:
self.del_server_connection()
- self.log("Set new server address: %s:%s" % (address.host, address.port), "debug")
+ self.log(
+ "Set new server address: %s:%s" %
+ (address.host, address.port), "debug")
self.server_conn = ServerConnection(address)
def establish_server_connection(self, ask=True):
@@ -155,12 +187,16 @@ class ConnectionHandler:
"""
if self.server_conn.connection:
return
- self.log("serverconnect", "debug", ["%s:%s" % self.server_conn.address()[:2]])
+ self.log(
+ "serverconnect", "debug", [
+ "%s:%s" %
+ self.server_conn.address()[
+ :2]])
if ask:
self.channel.ask("serverconnect", self)
try:
self.server_conn.connect()
- except tcp.NetLibError, v:
+ except tcp.NetLibError as v:
raise ProxyError(502, v)
def establish_ssl(self, client=False, server=False, sni=None):
@@ -237,7 +273,8 @@ class ConnectionHandler:
self.server_conn.state = state
# Receiving new_sni where had_ssl is False is a weird case that happens when the workaround for
- # https://github.com/mitmproxy/mitmproxy/issues/427 is active. In this case, we want to establish SSL as well.
+ # https://github.com/mitmproxy/mitmproxy/issues/427 is active. In this
+ # case, we want to establish SSL as well.
if had_ssl or new_sni:
self.establish_ssl(server=True, sni=sni)
@@ -246,8 +283,10 @@ class ConnectionHandler:
def log(self, msg, level, subs=()):
msg = [
- "%s:%s: %s" % (self.client_conn.address.host, self.client_conn.address.port, msg)
- ]
+ "%s:%s: %s" %
+ (self.client_conn.address.host,
+ self.client_conn.address.port,
+ msg)]
for i in subs:
msg.append(" -> " + i)
msg = "\n".join(msg)
@@ -255,11 +294,13 @@ class ConnectionHandler:
def find_cert(self):
if self.config.certforward and self.server_conn.ssl_established:
- return self.server_conn.cert, self.config.certstore.gen_pkey(self.server_conn.cert), None
+ return self.server_conn.cert, self.config.certstore.gen_pkey(
+ self.server_conn.cert), None
else:
host = self.server_conn.address.host
sans = []
- if self.server_conn.ssl_established and (not self.config.no_upstream_cert):
+ if self.server_conn.ssl_established and (
+ not self.config.no_upstream_cert):
upstream_cert = self.server_conn.cert
sans.extend(upstream_cert.altnames)
if upstream_cert.cn:
@@ -291,8 +332,11 @@ class ConnectionHandler:
# - We established SSL with the server previously
# - We initially wanted to establish SSL with the server,
# but the server refused to negotiate without SNI.
- if self.server_conn.ssl_established or hasattr(self.server_conn, "may_require_sni"):
- self.server_reconnect(sni) # reconnect to upstream server with SNI
+ if self.server_conn.ssl_established or hasattr(
+ self.server_conn,
+ "may_require_sni"):
+ # reconnect to upstream server with SNI
+ self.server_reconnect(sni)
# Now, change client context to reflect changed certificate:
cert, key, chain_file = self.find_cert()
new_context = self.client_conn.create_ssl_context(
@@ -308,4 +352,7 @@ class ConnectionHandler:
# make dang sure it doesn't happen.
except: # pragma: no cover
import traceback
- self.log("Error in handle_sni:\r\n" + traceback.format_exc(), "error")
+ self.log(
+ "Error in handle_sni:\r\n" +
+ traceback.format_exc(),
+ "error")
diff --git a/libmproxy/script.py b/libmproxy/script.py
index be226004..4c550342 100644
--- a/libmproxy/script.py
+++ b/libmproxy/script.py
@@ -1,7 +1,11 @@
from __future__ import absolute_import
-import os, traceback, threading, shlex
+import os
+import traceback
+import threading
+import shlex
from . import controller
+
class ScriptError(Exception):
pass
@@ -56,6 +60,7 @@ class Script:
s = Script(argv, master)
s.load()
"""
+
def __init__(self, command, master):
self.command = command
self.argv = self.parse_command(command)
@@ -73,9 +78,11 @@ class Script:
args = shlex.split(command)
args[0] = os.path.expanduser(args[0])
if not os.path.exists(args[0]):
- raise ScriptError(("Script file not found: %s.\r\n"
- "If you script path contains spaces, "
- "make sure to wrap it in additional quotes, e.g. -s \"'./foo bar/baz.py' --args\".") % args[0])
+ raise ScriptError(
+ ("Script file not found: %s.\r\n"
+ "If you script path contains spaces, "
+ "make sure to wrap it in additional quotes, e.g. -s \"'./foo bar/baz.py' --args\".") %
+ args[0])
elif not os.path.isfile(args[0]):
raise ScriptError("Not a file: %s" % args[0])
return args
@@ -90,7 +97,7 @@ class Script:
ns = {}
try:
execfile(self.argv[0], ns, ns)
- except Exception, v:
+ except Exception as v:
raise ScriptError(traceback.format_exc(v))
self.ns = ns
r = self.run("start", self.argv)
@@ -114,7 +121,7 @@ class Script:
if f:
try:
return (True, f(self.ctx, *args, **kwargs))
- except Exception, v:
+ except Exception as v:
return (False, (v, traceback.format_exc(v)))
else:
return (False, None)
@@ -133,7 +140,7 @@ class ReplyProxy(object):
return
self.original_reply(*args, **kwargs)
- def __getattr__ (self, k):
+ def __getattr__(self, k):
return getattr(self.original_reply, k)
@@ -145,7 +152,8 @@ def _handle_concurrent_reply(fn, o, *args, **kwargs):
def run():
fn(*args, **kwargs)
- reply_proxy() # If the script did not call .reply(), we have to do it now.
+ # If the script did not call .reply(), we have to do it now.
+ reply_proxy()
ScriptThread(target=run).start()
@@ -154,8 +162,15 @@ class ScriptThread(threading.Thread):
def concurrent(fn):
- if fn.func_name in ("request", "response", "error", "clientconnect", "serverconnect", "clientdisconnect"):
+ if fn.func_name in (
+ "request",
+ "response",
+ "error",
+ "clientconnect",
+ "serverconnect",
+ "clientdisconnect"):
def _concurrent(ctx, obj):
_handle_concurrent_reply(fn, obj, ctx, obj)
return _concurrent
- raise NotImplementedError("Concurrent decorator not supported for this method.")
+ raise NotImplementedError(
+ "Concurrent decorator not supported for this method.")
diff --git a/libmproxy/tnetstring.py b/libmproxy/tnetstring.py
index 91b3ba2a..c5c185c6 100644
--- a/libmproxy/tnetstring.py
+++ b/libmproxy/tnetstring.py
@@ -72,13 +72,14 @@ __ver_major__ = 0
__ver_minor__ = 2
__ver_patch__ = 0
__ver_sub__ = ""
-__version__ = "%d.%d.%d%s" % (__ver_major__,__ver_minor__,__ver_patch__,__ver_sub__)
+__version__ = "%d.%d.%d%s" % (
+ __ver_major__, __ver_minor__, __ver_patch__, __ver_sub__)
from collections import deque
-def dumps(value,encoding=None):
+def dumps(value, encoding=None):
"""dumps(object,encoding=None) -> string
This function dumps a python object as a tnetstring.
@@ -90,21 +91,21 @@ def dumps(value,encoding=None):
# consider the _gdumps() function instead; it's a standard top-down
# generator that's simpler to understand but much less efficient.
q = deque()
- _rdumpq(q,0,value,encoding)
+ _rdumpq(q, 0, value, encoding)
return "".join(q)
-def dump(value,file,encoding=None):
+def dump(value, file, encoding=None):
"""dump(object,file,encoding=None)
This function dumps a python object as a tnetstring and writes it to
the given file.
"""
- file.write(dumps(value,encoding))
+ file.write(dumps(value, encoding))
file.flush()
-def _rdumpq(q,size,value,encoding=None):
+def _rdumpq(q, size, value, encoding=None):
"""Dump value as a tnetstring, to a deque instance, last chunks first.
This function generates the tnetstring representation of the given value,
@@ -129,7 +130,7 @@ def _rdumpq(q,size,value,encoding=None):
if value is False:
write("5:false!")
return size + 8
- if isinstance(value,(int,long)):
+ if isinstance(value, (int, long)):
data = str(value)
ldata = len(data)
span = str(ldata)
@@ -138,7 +139,7 @@ def _rdumpq(q,size,value,encoding=None):
write(":")
write(span)
return size + 2 + len(span) + ldata
- if isinstance(value,(float,)):
+ if isinstance(value, (float,)):
# Use repr() for float rather than str().
# It round-trips more accurately.
# Probably unnecessary in later python versions that
@@ -151,7 +152,7 @@ def _rdumpq(q,size,value,encoding=None):
write(":")
write(span)
return size + 2 + len(span) + ldata
- if isinstance(value,str):
+ if isinstance(value, str):
lvalue = len(value)
span = str(lvalue)
write(",")
@@ -159,26 +160,26 @@ def _rdumpq(q,size,value,encoding=None):
write(":")
write(span)
return size + 2 + len(span) + lvalue
- if isinstance(value,(list,tuple,)):
+ if isinstance(value, (list, tuple,)):
write("]")
init_size = size = size + 1
for item in reversed(value):
- size = _rdumpq(q,size,item,encoding)
+ size = _rdumpq(q, size, item, encoding)
span = str(size - init_size)
write(":")
write(span)
return size + 1 + len(span)
- if isinstance(value,dict):
+ if isinstance(value, dict):
write("}")
init_size = size = size + 1
- for (k,v) in value.iteritems():
- size = _rdumpq(q,size,v,encoding)
- size = _rdumpq(q,size,k,encoding)
+ for (k, v) in value.iteritems():
+ size = _rdumpq(q, size, v, encoding)
+ size = _rdumpq(q, size, k, encoding)
span = str(size - init_size)
write(":")
write(span)
return size + 1 + len(span)
- if isinstance(value,unicode):
+ if isinstance(value, unicode):
if encoding is None:
raise ValueError("must specify encoding to dump unicode strings")
value = value.encode(encoding)
@@ -192,7 +193,7 @@ def _rdumpq(q,size,value,encoding=None):
raise ValueError("unserializable object")
-def _gdumps(value,encoding):
+def _gdumps(value, encoding):
"""Generate fragments of value dumped as a tnetstring.
This is the naive dumping algorithm, implemented as a generator so that
@@ -207,24 +208,24 @@ def _gdumps(value,encoding):
yield "4:true!"
elif value is False:
yield "5:false!"
- elif isinstance(value,(int,long)):
+ elif isinstance(value, (int, long)):
data = str(value)
yield str(len(data))
yield ":"
yield data
yield "#"
- elif isinstance(value,(float,)):
+ elif isinstance(value, (float,)):
data = repr(value)
yield str(len(data))
yield ":"
yield data
yield "^"
- elif isinstance(value,(str,)):
+ elif isinstance(value, (str,)):
yield str(len(value))
yield ":"
yield value
yield ","
- elif isinstance(value,(list,tuple,)):
+ elif isinstance(value, (list, tuple,)):
sub = []
for item in value:
sub.extend(_gdumps(item))
@@ -233,9 +234,9 @@ def _gdumps(value,encoding):
yield ":"
yield sub
yield "]"
- elif isinstance(value,(dict,)):
+ elif isinstance(value, (dict,)):
sub = []
- for (k,v) in value.iteritems():
+ for (k, v) in value.iteritems():
sub.extend(_gdumps(k))
sub.extend(_gdumps(v))
sub = "".join(sub)
@@ -243,7 +244,7 @@ def _gdumps(value,encoding):
yield ":"
yield sub
yield "}"
- elif isinstance(value,(unicode,)):
+ elif isinstance(value, (unicode,)):
if encoding is None:
raise ValueError("must specify encoding to dump unicode strings")
value = value.encode(encoding)
@@ -255,7 +256,7 @@ def _gdumps(value,encoding):
raise ValueError("unserializable object")
-def loads(string,encoding=None):
+def loads(string, encoding=None):
"""loads(string,encoding=None) -> object
This function parses a tnetstring into a python object.
@@ -263,10 +264,10 @@ def loads(string,encoding=None):
# No point duplicating effort here. In the C-extension version,
# loads() is measurably faster then pop() since it can avoid
# the overhead of building a second string.
- return pop(string,encoding)[0]
+ return pop(string, encoding)[0]
-def load(file,encoding=None):
+def load(file, encoding=None):
"""load(file,encoding=None) -> object
This function reads a tnetstring from a file and parses it into a
@@ -324,21 +325,20 @@ def load(file,encoding=None):
if type == "]":
l = []
while data:
- (item,data) = pop(data,encoding)
+ (item, data) = pop(data, encoding)
l.append(item)
return l
if type == "}":
d = {}
while data:
- (key,data) = pop(data,encoding)
- (val,data) = pop(data,encoding)
+ (key, data) = pop(data, encoding)
+ (val, data) = pop(data, encoding)
d[key] = val
return d
raise ValueError("unknown type tag")
-
-def pop(string,encoding=None):
+def pop(string, encoding=None):
"""pop(string,encoding=None) -> (object, remain)
This function parses a tnetstring into a python object.
@@ -347,12 +347,12 @@ def pop(string,encoding=None):
"""
# Parse out data length, type and remaining string.
try:
- (dlen,rest) = string.split(":",1)
+ (dlen, rest) = string.split(":", 1)
dlen = int(dlen)
except ValueError:
raise ValueError("not a tnetstring: missing or invalid length prefix")
try:
- (data,type,remain) = (rest[:dlen],rest[dlen],rest[dlen+1:])
+ (data, type, remain) = (rest[:dlen], rest[dlen], rest[dlen + 1:])
except IndexError:
# This fires if len(rest) < dlen, meaning we don't need
# to further validate that data is the right length.
@@ -360,40 +360,40 @@ def pop(string,encoding=None):
# Parse the data based on the type tag.
if type == ",":
if encoding is not None:
- return (data.decode(encoding),remain)
- return (data,remain)
+ return (data.decode(encoding), remain)
+ return (data, remain)
if type == "#":
try:
- return (int(data),remain)
+ return (int(data), remain)
except ValueError:
raise ValueError("not a tnetstring: invalid integer literal")
if type == "^":
try:
- return (float(data),remain)
+ return (float(data), remain)
except ValueError:
raise ValueError("not a tnetstring: invalid float literal")
if type == "!":
if data == "true":
- return (True,remain)
+ return (True, remain)
elif data == "false":
- return (False,remain)
+ return (False, remain)
else:
raise ValueError("not a tnetstring: invalid boolean literal")
if type == "~":
if data:
raise ValueError("not a tnetstring: invalid null literal")
- return (None,remain)
+ return (None, remain)
if type == "]":
l = []
while data:
- (item,data) = pop(data,encoding)
+ (item, data) = pop(data, encoding)
l.append(item)
- return (l,remain)
+ return (l, remain)
if type == "}":
d = {}
while data:
- (key,data) = pop(data,encoding)
- (val,data) = pop(data,encoding)
+ (key, data) = pop(data, encoding)
+ (val, data) = pop(data, encoding)
d[key] = val
- return (d,remain)
+ return (d, remain)
raise ValueError("unknown type tag")
diff --git a/libmproxy/utils.py b/libmproxy/utils.py
index 51f2dc26..a29a53f5 100644
--- a/libmproxy/utils.py
+++ b/libmproxy/utils.py
@@ -1,8 +1,14 @@
from __future__ import absolute_import
-import os, datetime, urllib, re
-import time, functools, cgi
+import os
+import datetime
+import urllib
+import re
+import time
+import functools
+import cgi
import json
+
def timestamp():
"""
Returns a serializable UTC timestamp.
@@ -69,20 +75,33 @@ def urlencode(s):
return urllib.urlencode(s, False)
-def pretty_size(size):
- suffixes = [
- ("B", 2**10),
- ("kB", 2**20),
- ("MB", 2**30),
- ]
- for suf, lim in suffixes:
- if size >= lim:
- continue
- else:
- x = round(size/float(lim/2**10), 2)
- if x == int(x):
- x = int(x)
- return str(x) + suf
+def multipartdecode(hdrs, content):
+ """
+ Takes a multipart boundary encoded string and returns list of (key, value) tuples.
+ """
+ v = hdrs.get_first("content-type")
+ if v:
+ v = parse_content_type(v)
+ if not v:
+ return []
+ boundary = v[2].get("boundary")
+ if not boundary:
+ return []
+
+ rx = re.compile(r'\bname="([^"]+)"')
+ r = []
+
+ for i in content.split("--" + boundary):
+ parts = i.splitlines()
+ if len(parts) > 1 and parts[0][0:2] != "--":
+ match = rx.search(parts[1])
+ if match:
+ key = match.group(1)
+ value = "".join(parts[3 + parts[2:].index(""):])
+ r.append((key, value))
+ return r
+ return []
+
def pretty_duration(secs):
formatters = [
@@ -94,8 +113,9 @@ def pretty_duration(secs):
for limit, formatter in formatters:
if secs >= limit:
return formatter.format(secs)
- #less than 1 sec
- return "{:.0f}ms".format(secs*1000)
+ # less than 1 sec
+ return "{:.0f}ms".format(secs * 1000)
+
class Data:
def __init__(self, name):
@@ -112,47 +132,41 @@ class Data:
"""
fullpath = os.path.join(self.dirname, path)
if not os.path.exists(fullpath):
- raise ValueError, "dataPath: %s does not exist."%fullpath
+ raise ValueError("dataPath: %s does not exist." % fullpath)
return fullpath
pkg_data = Data(__name__)
class LRUCache:
"""
- A decorator that implements a self-expiring LRU cache for class
- methods (not functions!).
-
- Cache data is tracked as attributes on the object itself. There is
- therefore a separate cache for each object instance.
+ A simple LRU cache for generated values.
"""
+
def __init__(self, size=100):
self.size = size
+ self.cache = {}
+ self.cacheList = []
+
+ def get(self, gen, *args):
+ """
+ gen: A (presumably expensive) generator function. The identity of
+ gen is NOT taken into account by the cache.
+ *args: A list of immutable arguments, used to establish identiy by
+ *the cache, and passed to gen to generate values.
+ """
+ if args in self.cache:
+ self.cacheList.remove(args)
+ self.cacheList.insert(0, args)
+ return self.cache[args]
+ else:
+ ret = gen(*args)
+ self.cacheList.insert(0, args)
+ self.cache[args] = ret
+ if len(self.cacheList) > self.size:
+ d = self.cacheList.pop()
+ self.cache.pop(d)
+ return ret
- def __call__(self, f):
- cacheName = "_cached_%s"%f.__name__
- cacheListName = "_cachelist_%s"%f.__name__
- size = self.size
-
- @functools.wraps(f)
- def wrap(self, *args):
- if not hasattr(self, cacheName):
- setattr(self, cacheName, {})
- setattr(self, cacheListName, [])
- cache = getattr(self, cacheName)
- cacheList = getattr(self, cacheListName)
- if cache.has_key(args):
- cacheList.remove(args)
- cacheList.insert(0, args)
- return cache[args]
- else:
- ret = f(self, *args)
- cacheList.insert(0, args)
- cache[args] = ret
- if len(cacheList) > size:
- d = cacheList.pop()
- cache.pop(d)
- return ret
- return wrap
def parse_content_type(c):
"""
@@ -188,14 +202,14 @@ def hostport(scheme, host, port):
if (port, scheme) in [(80, "http"), (443, "https")]:
return host
else:
- return "%s:%s"%(host, port)
+ return "%s:%s" % (host, port)
def unparse_url(scheme, host, port, path=""):
"""
Returns a URL string, constructed from the specified compnents.
"""
- return "%s://%s%s"%(scheme, hostport(scheme, host, port), path)
+ return "%s://%s%s" % (scheme, hostport(scheme, host, port), path)
def clean_hanging_newline(t):
@@ -236,7 +250,7 @@ def parse_size(s):
try:
return int(s) * mult
except ValueError:
- raise ValueError("Invalid size specification: %s"%s)
+ raise ValueError("Invalid size specification: %s" % s)
def safe_subn(pattern, repl, target, *args, **kwargs):
diff --git a/libmproxy/version.py b/libmproxy/version.py
index babc5ec5..6d802202 100644
--- a/libmproxy/version.py
+++ b/libmproxy/version.py
@@ -1,4 +1,4 @@
-IVERSION = (0, 11, 4)
+IVERSION = (0, 12, 1)
VERSION = ".".join(str(i) for i in IVERSION)
MINORVERSION = ".".join(str(i) for i in IVERSION[:2])
NAME = "mitmproxy"
diff --git a/libmproxy/web/__init__.py b/libmproxy/web/__init__.py
index 173ddf9f..a0af7315 100644
--- a/libmproxy/web/__init__.py
+++ b/libmproxy/web/__init__.py
@@ -79,6 +79,7 @@ class WebState(flow.State):
data=[]
)
+
class Options(object):
attributes = [
"app",
@@ -128,11 +129,13 @@ class WebMaster(flow.FlowMaster):
if options.rfile:
try:
self.load_flows_file(options.rfile)
- except flow.FlowReadError, v:
+ except flow.FlowReadError as v:
self.add_event(
- "Could not read flow file: %s"%v,
+ "Could not read flow file: %s" % v,
"error"
)
+ if self.options.app:
+ self.start_app(self.options.app_host, self.options.app_port)
def tick(self):
flow.FlowMaster.tick(self, self.masterq, timeout=0)
@@ -154,7 +157,8 @@ class WebMaster(flow.FlowMaster):
self.shutdown()
def _process_flow(self, f):
- if self.state.intercept and self.state.intercept(f) and not f.request.is_replay:
+ if self.state.intercept and self.state.intercept(
+ f) and not f.request.is_replay:
f.intercept(self)
else:
f.reply()
@@ -173,4 +177,4 @@ class WebMaster(flow.FlowMaster):
def add_event(self, e, level="info"):
super(WebMaster, self).add_event(e, level)
- self.state.add_event(e, level) \ No newline at end of file
+ self.state.add_event(e, level)
diff --git a/libmproxy/web/app.py b/libmproxy/web/app.py
index 2fc849f9..29ae9e7a 100644
--- a/libmproxy/web/app.py
+++ b/libmproxy/web/app.py
@@ -26,6 +26,13 @@ class RequestHandler(tornado.web.RequestHandler):
)
@property
+ def json(self):
+ if not self.request.headers.get(
+ "Content-Type").startswith("application/json"):
+ return None
+ return json.loads(self.request.body)
+
+ @property
def state(self):
return self.application.master.state
@@ -61,8 +68,10 @@ class FiltHelp(RequestHandler):
commands=filt.help
))
+
class WebSocketEventBroadcaster(tornado.websocket.WebSocketHandler):
- connections = None # raise an error if inherited class doesn't specify its own instance.
+ # raise an error if inherited class doesn't specify its own instance.
+ connections = None
def open(self):
self.connections.add(self)
@@ -111,6 +120,42 @@ class FlowHandler(RequestHandler):
self.flow.kill(self.master)
self.state.delete_flow(self.flow)
+ def put(self, flow_id):
+ flow = self.flow
+ flow.backup()
+ for a, b in self.json.iteritems():
+
+ if a == "request":
+ request = flow.request
+ for k, v in b.iteritems():
+ if k in ["method", "scheme", "host", "path"]:
+ setattr(request, k, str(v))
+ elif k == "port":
+ request.port = int(v)
+ elif k == "httpversion":
+ request.httpversion = tuple(int(x) for x in v)
+ elif k == "headers":
+ request.headers.load_state(v)
+ else:
+ print "Warning: Unknown update {}.{}: {}".format(a, k, v)
+
+ elif a == "response":
+ response = flow.response
+ for k, v in b.iteritems():
+ if k == "msg":
+ response.msg = str(v)
+ elif k == "code":
+ response.code = int(v)
+ elif k == "httpversion":
+ response.httpversion = tuple(int(x) for x in v)
+ elif k == "headers":
+ response.headers.load_state(v)
+ else:
+ print "Warning: Unknown update {}.{}: {}".format(a, k, v)
+ else:
+ print "Warning: Unknown update {}: {}".format(a, b)
+ self.state.update_flow(flow)
+
class DuplicateFlow(RequestHandler):
def post(self, flow_id):
@@ -124,6 +169,10 @@ class RevertFlow(RequestHandler):
class ReplayFlow(RequestHandler):
def post(self, flow_id):
+ self.flow.backup()
+ self.flow.response = None
+ self.state.update_flow(self.flow)
+
r = self.master.replay_request(self.flow)
if r:
raise APIError(400, r)
@@ -176,18 +225,12 @@ class Settings(RequestHandler):
)
))
- def put(self, *update, **kwargs):
+ def put(self):
update = {}
- for k, v in self.request.arguments.iteritems():
- if len(v) != 1:
- print("Warning: Unknown length for setting {}: {}".format(k, v))
- continue
-
- if k == "_xsrf":
- continue
- elif k == "intercept":
- self.state.set_intercept(v[0])
- update[k] = v[0]
+ for k, v in self.json.iteritems():
+ if k == "intercept":
+ self.state.set_intercept(v)
+ update[k] = v
else:
print("Warning: Unknown setting {}: {}".format(k, v))
diff --git a/libmproxy/web/static/app.css b/libmproxy/web/static/app.css
index 4f24ddd9..a7dc4f00 100644
--- a/libmproxy/web/static/app.css
+++ b/libmproxy/web/static/app.css
@@ -50,6 +50,7 @@ body,
#container {
display: flex;
flex-direction: column;
+ outline: none;
}
#container > header,
#container > footer,
@@ -60,7 +61,6 @@ body,
flex: 1 1 auto;
display: flex;
flex-direction: row;
- outline: 0;
}
.main-view.vertical {
flex-direction: column;
@@ -145,7 +145,7 @@ header .menu {
padding-left: 2.5px;
padding-right: 2.5px;
}
-@media (min-width: 992px) {
+@media (min-width: 768px) {
.filter-input {
float: left;
width: 25%;
@@ -155,6 +155,7 @@ header .menu {
top: 27px;
display: block;
max-width: none;
+ opacity: 0.9;
}
.filter-input .popover .popover-content {
max-height: 500px;
@@ -271,7 +272,12 @@ header .menu {
}
.flow-detail {
width: 100%;
- overflow: auto;
+ overflow-x: auto;
+ overflow-y: scroll;
+ /*.request .response-line,
+ .response .request-line {
+ opacity: 0.7;
+ }*/
}
.flow-detail nav {
background-color: #F2F2F2;
@@ -290,6 +296,25 @@ header .menu {
max-height: 100px;
overflow-y: auto;
}
+.flow-detail .request-line {
+ margin-bottom: 2px;
+}
+.flow-detail hr {
+ margin: 0 0 5px;
+}
+.inline-input {
+ margin: 0 -5px;
+ padding: 0 5px;
+}
+.inline-input[contenteditable] {
+ background-color: rgba(255, 255, 255, 0.2);
+}
+.inline-input[contenteditable].has-warning {
+ color: #ffb8b8;
+}
+.view-options {
+ margin-top: 10px;
+}
.flow-detail table {
font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
width: 100%;
@@ -306,6 +331,9 @@ header .menu {
width: 50%;
padding-right: 1em;
}
+.header-table td {
+ line-height: 1.3em;
+}
.header-table .header-name {
width: 33%;
padding-right: 1em;
@@ -316,6 +344,38 @@ header .menu {
text-overflow: ellipsis;
white-space: nowrap;
}
+.flowview-image {
+ text-align: center;
+}
+.flowview-image img {
+ max-width: 100%;
+ max-height: 100%;
+}
+.prompt-dialog {
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ position: fixed;
+ z-index: 100;
+ background-color: rgba(0, 0, 0, 0.1);
+}
+.prompt-content {
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ height: 25px;
+ padding: 2px 5px;
+ background-color: white;
+ box-shadow: 0 -1px 3px lightgray;
+}
+.prompt-content .option {
+ cursor: pointer;
+}
+.prompt-content .option:not(:last-child)::after {
+ content: ", ";
+}
.eventlog {
height: 200px;
flex: 0 0 auto;
diff --git a/libmproxy/web/static/app.js b/libmproxy/web/static/app.js
index dae10a34..b9767153 100644
--- a/libmproxy/web/static/app.js
+++ b/libmproxy/web/static/app.js
@@ -303,6 +303,8 @@ function isUndefined(arg) {
},{}],2:[function(require,module,exports){
var $ = require("jquery");
+var _ = require("lodash");
+var AppDispatcher = require("./dispatcher.js").AppDispatcher;
var ActionTypes = {
// Connection
@@ -347,7 +349,8 @@ var SettingsActions = {
$.ajax({
type: "PUT",
url: "/settings",
- data: settings
+ contentType: 'application/json',
+ data: JSON.stringify(settings)
});
/*
@@ -398,11 +401,22 @@ var FlowActions = {
revert: function(flow){
$.post("/flows/" + flow.id + "/revert");
},
- update: function (flow) {
+ update: function (flow, nextProps) {
+ /*
+ //Facebook Flux: We do an optimistic update on the client already.
+ var nextFlow = _.cloneDeep(flow);
+ _.merge(nextFlow, nextProps);
AppDispatcher.dispatchViewAction({
type: ActionTypes.FLOW_STORE,
cmd: StoreCmds.UPDATE,
- data: flow
+ data: nextFlow
+ });
+ */
+ $.ajax({
+ type: "PUT",
+ url: "/flows/" + flow.id,
+ contentType: 'application/json',
+ data: JSON.stringify(nextProps)
});
},
clear: function(){
@@ -411,7 +425,7 @@ var FlowActions = {
};
var Query = {
- FILTER: "f",
+ SEARCH: "s",
HIGHLIGHT: "h",
SHOW_EVENTLOG: "e"
};
@@ -421,20 +435,26 @@ module.exports = {
ConnectionActions: ConnectionActions,
FlowActions: FlowActions,
StoreCmds: StoreCmds,
+ SettingsActions: SettingsActions,
+ EventLogActions: EventLogActions,
Query: Query
};
-},{"jquery":"jquery"}],3:[function(require,module,exports){
-
+},{"./dispatcher.js":21,"jquery":"jquery","lodash":"lodash"}],3:[function(require,module,exports){
var React = require("react");
var ReactRouter = require("react-router");
var $ = require("jquery");
var Connection = require("./connection");
var proxyapp = require("./components/proxyapp.js");
+var EventLogActions = require("./actions.js").EventLogActions;
$(function () {
window.ws = new Connection("/updates");
+ window.onerror = function (msg) {
+ EventLogActions.add_event(msg);
+ };
+
ReactRouter.run(proxyapp.routes, function (Handler, state) {
React.render(React.createElement(Handler, null), document.body);
});
@@ -442,7 +462,7 @@ $(function () {
-},{"./components/proxyapp.js":12,"./connection":14,"jquery":"jquery","react":"react","react-router":"react-router"}],4:[function(require,module,exports){
+},{"./actions.js":2,"./components/proxyapp.js":18,"./connection":20,"jquery":"jquery","react":"react","react-router":"react-router"}],4:[function(require,module,exports){
var React = require("react");
var ReactRouter = require("react-router");
var _ = require("lodash");
@@ -452,8 +472,8 @@ var AutoScrollMixin = {
componentWillUpdate: function () {
var node = this.getDOMNode();
this._shouldScrollBottom = (
- node.scrollTop !== 0 &&
- node.scrollTop + node.clientHeight === node.scrollHeight
+ node.scrollTop !== 0 &&
+ node.scrollTop + node.clientHeight === node.scrollHeight
);
},
componentDidUpdate: function () {
@@ -474,34 +494,79 @@ var StickyHeadMixin = {
}
};
+var SettingsState = {
+ contextTypes: {
+ settingsStore: React.PropTypes.object.isRequired
+ },
+ getInitialState: function () {
+ return {
+ settings: this.context.settingsStore.dict
+ };
+ },
+ componentDidMount: function () {
+ this.context.settingsStore.addListener("recalculate", this.onSettingsChange);
+ },
+ componentWillUnmount: function () {
+ this.context.settingsStore.removeListener("recalculate", this.onSettingsChange);
+ },
+ onSettingsChange: function () {
+ this.setState({
+ settings: this.context.settingsStore.dict
+ });
+ },
+};
+
+
+var ChildFocus = {
+ contextTypes: {
+ returnFocus: React.PropTypes.func
+ },
+ returnFocus: function(){
+ React.findDOMNode(this).blur();
+ window.getSelection().removeAllRanges();
+ this.context.returnFocus();
+ }
+};
+
var Navigation = _.extend({}, ReactRouter.Navigation, {
setQuery: function (dict) {
- var q = this.context.getCurrentQuery();
- for(var i in dict){
- if(dict.hasOwnProperty(i)){
+ var q = this.context.router.getCurrentQuery();
+ for (var i in dict) {
+ if (dict.hasOwnProperty(i)) {
q[i] = dict[i] || undefined; //falsey values shall be removed.
}
}
- q._ = "_"; // workaround for https://github.com/rackt/react-router/pull/957
- this.replaceWith(this.context.getCurrentPath(), this.context.getCurrentParams(), q);
+ this.replaceWith(this.context.router.getCurrentPath(), this.context.router.getCurrentParams(), q);
},
- replaceWith: function(routeNameOrPath, params, query) {
- if(routeNameOrPath === undefined){
- routeNameOrPath = this.context.getCurrentPath();
+ replaceWith: function (routeNameOrPath, params, query) {
+ if (routeNameOrPath === undefined) {
+ routeNameOrPath = this.context.router.getCurrentPath();
}
- if(params === undefined){
- params = this.context.getCurrentParams();
+ if (params === undefined) {
+ params = this.context.router.getCurrentParams();
}
- if(query === undefined) {
- query = this.context.getCurrentQuery();
+ if (query === undefined) {
+ query = this.context.router.getCurrentQuery();
}
- // FIXME: react-router is just broken.
- ReactRouter.Navigation.replaceWith.call(this, routeNameOrPath, params, query);
+ this.context.router.replaceWith(routeNameOrPath, params, query);
+ }
+});
+
+// react-router is fairly good at changing its API regularly.
+// We keep the old method for now - if it should turn out that their changes are permanent,
+// we may remove this mixin and access react-router directly again.
+var RouterState = _.extend({}, ReactRouter.State, {
+ getQuery: function () {
+ // For whatever reason, react-router always returns the same object, which makes comparing
+ // the current props with nextProps impossible. As a workaround, we just clone the query object.
+ return _.clone(this.context.router.getCurrentQuery());
+ },
+ getParams: function () {
+ return _.clone(this.context.router.getCurrentParams());
}
});
-_.extend(Navigation.contextTypes, ReactRouter.State.contextTypes);
var Splitter = React.createClass({displayName: "Splitter",
getDefaultProps: function () {
@@ -609,19 +674,218 @@ var Splitter = React.createClass({displayName: "Splitter",
});
module.exports = {
- State: ReactRouter.State, // keep here - react-router is pretty buggy, we may need workarounds in the future.
+ ChildFocus: ChildFocus,
+ RouterState: RouterState,
Navigation: Navigation,
StickyHeadMixin: StickyHeadMixin,
AutoScrollMixin: AutoScrollMixin,
- Splitter: Splitter
+ Splitter: Splitter,
+ SettingsState: SettingsState
};
},{"lodash":"lodash","react":"react","react-router":"react-router"}],5:[function(require,module,exports){
var React = require("react");
var common = require("./common.js");
+var utils = require("../utils.js");
+
+var contentToHtml = function (content) {
+ return _.escape(content);
+};
+var nodeToContent = function (node) {
+ return node.textContent;
+};
+
+/*
+Basic Editor Functionality
+ */
+var EditorBase = React.createClass({displayName: "EditorBase",
+ propTypes: {
+ content: React.PropTypes.string.isRequired,
+ onDone: React.PropTypes.func.isRequired,
+ contentToHtml: React.PropTypes.func,
+ nodeToContent: React.PropTypes.func, // content === nodeToContent( Node<innerHTML=contentToHtml(content)> )
+ submitOnEnter: React.PropTypes.bool,
+ className: React.PropTypes.string,
+ tag: React.PropTypes.string
+ },
+ getDefaultProps: function () {
+ return {
+ contentToHtml: contentToHtml,
+ nodeToContent: nodeToContent,
+ submitOnEnter: true,
+ className: "",
+ tag: "div"
+ };
+ },
+ getInitialState: function () {
+ return {
+ editable: false
+ };
+ },
+ render: function () {
+ var className = "inline-input " + this.props.className;
+ var html = {__html: this.props.contentToHtml(this.props.content)};
+ var Tag = this.props.tag;
+ return React.createElement(Tag, React.__spread({},
+ this.props,
+ {tabIndex: "0",
+ className: className,
+ contentEditable: this.state.editable || undefined, // workaround: use undef instead of false to remove attr
+ onFocus: this.onFocus,
+ onBlur: this._stop,
+ onKeyDown: this.onKeyDown,
+ onInput: this.onInput,
+ onPaste: this.onPaste,
+ dangerouslySetInnerHTML: html})
+ );
+ },
+ onPaste: function(e){
+ e.preventDefault();
+ var content = e.clipboardData.getData("text/plain");
+ document.execCommand("insertHTML", false, content);
+ },
+ onFocus: function (e) {
+ this.setState({editable: true}, function () {
+ React.findDOMNode(this).focus();
+ var range = document.createRange();
+ range.selectNodeContents(this.getDOMNode());
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+ });
+ this.props.onFocus && this.props.onFocus(e);
+ },
+ stop: function () {
+ // a stop would cause a blur as a side-effect.
+ // but a blur event must trigger a stop as well.
+ // to fix this, make stop = blur and do the actual stop in the onBlur handler.
+ React.findDOMNode(this).blur();
+ },
+ _stop: function (e) {
+ window.getSelection().removeAllRanges(); //make sure that selection is cleared on blur
+ var node = React.findDOMNode(this);
+ var content = this.props.nodeToContent(node);
+ this.setState({editable: false});
+ this.props.onDone(content);
+ this.props.onBlur && this.props.onBlur(e);
+ },
+ cancel: function () {
+ React.findDOMNode(this).innerHTML = this.props.contentToHtml(this.props.content);
+ this.stop();
+ },
+ onKeyDown: function (e) {
+ e.stopPropagation();
+ switch (e.keyCode) {
+ case utils.Key.ESC:
+ e.preventDefault();
+ this.cancel();
+ break;
+ case utils.Key.ENTER:
+ if (this.props.submitOnEnter && !e.shiftKey) {
+ e.preventDefault();
+ this.stop();
+ }
+ break;
+ default:
+ break;
+ }
+ },
+ onInput: function () {
+ var node = React.findDOMNode(this);
+ var content = this.props.nodeToContent(node);
+ this.props.onInput && this.props.onInput(content);
+ }
+});
+
+/*
+Add Validation to EditorBase
+ */
+var ValidateEditor = React.createClass({displayName: "ValidateEditor",
+ propTypes: {
+ content: React.PropTypes.string.isRequired,
+ onDone: React.PropTypes.func.isRequired,
+ onInput: React.PropTypes.func,
+ isValid: React.PropTypes.func,
+ className: React.PropTypes.string,
+ },
+ getInitialState: function(){
+ return {
+ currentContent: this.props.content
+ };
+ },
+ componentWillReceiveProps: function(){
+ this.setState({currentContent: this.props.content});
+ },
+ onInput: function(content){
+ this.setState({currentContent: content});
+ this.props.onInput && this.props.onInput(content);
+ },
+ render: function () {
+ var className = this.props.className || "";
+ if (this.props.isValid) {
+ if (this.props.isValid(this.state.currentContent)) {
+ className += " has-success";
+ } else {
+ className += " has-warning"
+ }
+ }
+ return React.createElement(EditorBase, React.__spread({},
+ this.props,
+ {ref: "editor",
+ className: className,
+ onDone: this.onDone,
+ onInput: this.onInput})
+ );
+ },
+ onDone: function (content) {
+ if(this.props.isValid && !this.props.isValid(content)){
+ this.refs.editor.cancel();
+ content = this.props.content;
+ }
+ this.props.onDone(content);
+ }
+});
+
+/*
+Text Editor with mitmweb-specific convenience features
+ */
+var ValueEditor = React.createClass({displayName: "ValueEditor",
+ mixins: [common.ChildFocus],
+ propTypes: {
+ content: React.PropTypes.string.isRequired,
+ onDone: React.PropTypes.func.isRequired,
+ inline: React.PropTypes.bool,
+ },
+ render: function () {
+ var tag = this.props.inline ? "span" : "div";
+ return React.createElement(ValidateEditor, React.__spread({},
+ this.props,
+ {onBlur: this.onBlur,
+ tag: tag})
+ );
+ },
+ focus: function () {
+ React.findDOMNode(this).focus();
+ },
+ onBlur: function(e){
+ if(!e.relatedTarget){
+ this.returnFocus();
+ }
+ this.props.onBlur && this.props.onBlur(e);
+ }
+});
+
+module.exports = {
+ ValueEditor: ValueEditor
+};
+
+},{"../utils.js":26,"./common.js":4,"react":"react"}],6:[function(require,module,exports){
+var React = require("react");
+var common = require("./common.js");
var Query = require("../actions.js").Query;
var VirtualScrollMixin = require("./virtualscroll.js");
var views = require("../store/view.js");
+var _ = require("lodash");
var LogMessage = React.createClass({displayName: "LogMessage",
render: function () {
@@ -639,7 +903,7 @@ var LogMessage = React.createClass({displayName: "LogMessage",
}
return (
React.createElement("div", null,
- indicator, " ", entry.message
+ indicator, " ", entry.message
)
);
},
@@ -649,46 +913,36 @@ var LogMessage = React.createClass({displayName: "LogMessage",
});
var EventLogContents = React.createClass({displayName: "EventLogContents",
+ contextTypes: {
+ eventStore: React.PropTypes.object.isRequired
+ },
mixins: [common.AutoScrollMixin, VirtualScrollMixin],
getInitialState: function () {
- return {
- log: []
- };
- },
- componentWillMount: function () {
- this.openView(this.props.eventStore);
- },
- componentWillUnmount: function () {
- this.closeView();
- },
- openView: function (store) {
- var view = new views.StoreView(store, function (entry) {
+ var filterFn = function (entry) {
return this.props.filter[entry.level];
- }.bind(this));
- this.setState({
- view: view
- });
-
+ };
+ var view = new views.StoreView(this.context.eventStore, filterFn.bind(this));
view.addListener("add", this.onEventLogChange);
view.addListener("recalculate", this.onEventLogChange);
+
+ return {
+ view: view
+ };
},
- closeView: function () {
+ componentWillUnmount: function () {
this.state.view.close();
},
+ filter: function (entry) {
+ return this.props.filter[entry.level];
+ },
onEventLogChange: function () {
- this.setState({
- log: this.state.view.list
- });
+ this.forceUpdate();
},
componentWillReceiveProps: function (nextProps) {
if (nextProps.filter !== this.props.filter) {
this.props.filter = nextProps.filter; // Dirty: Make sure that view filter sees the update.
this.state.view.recalculate();
}
- if (nextProps.eventStore !== this.props.eventStore) {
- this.closeView();
- this.openView(nextProps.eventStore);
- }
},
getDefaultProps: function () {
return {
@@ -701,12 +955,13 @@ var EventLogContents = React.createClass({displayName: "EventLogContents",
return React.createElement(LogMessage, {key: elem.id, entry: elem});
},
render: function () {
- var rows = this.renderRows(this.state.log);
+ var entries = this.state.view.list;
+ var rows = this.renderRows(entries);
return React.createElement("pre", {onScroll: this.onScroll},
- this.getPlaceholderTop(this.state.log.length),
+ this.getPlaceholderTop(entries.length),
rows,
- this.getPlaceholderBottom(this.state.log.length)
+ this.getPlaceholderBottom(entries.length)
);
}
});
@@ -767,7 +1022,7 @@ var EventLog = React.createClass({displayName: "EventLog",
)
),
- React.createElement(EventLogContents, {filter: this.state.filter, eventStore: this.props.eventStore})
+ React.createElement(EventLogContents, {filter: this.state.filter})
)
);
}
@@ -775,408 +1030,7 @@ var EventLog = React.createClass({displayName: "EventLog",
module.exports = EventLog;
-},{"../actions.js":2,"../store/view.js":19,"./common.js":4,"./virtualscroll.js":13,"react":"react"}],6:[function(require,module,exports){
-var React = require("react");
-var _ = require("lodash");
-
-var common = require("./common.js");
-var actions = require("../actions.js");
-var flowutils = require("../flow/utils.js");
-var toputils = require("../utils.js");
-
-var NavAction = React.createClass({displayName: "NavAction",
- onClick: function (e) {
- e.preventDefault();
- this.props.onClick();
- },
- render: function () {
- return (
- React.createElement("a", {title: this.props.title,
- href: "#",
- className: "nav-action",
- onClick: this.onClick},
- React.createElement("i", {className: "fa fa-fw " + this.props.icon})
- )
- );
- }
-});
-
-var FlowDetailNav = React.createClass({displayName: "FlowDetailNav",
- render: function () {
- var flow = this.props.flow;
-
- var tabs = this.props.tabs.map(function (e) {
- var str = e.charAt(0).toUpperCase() + e.slice(1);
- var className = this.props.active === e ? "active" : "";
- var onClick = function (event) {
- this.props.selectTab(e);
- event.preventDefault();
- }.bind(this);
- return React.createElement("a", {key: e,
- href: "#",
- className: className,
- onClick: onClick}, str);
- }.bind(this));
-
- var acceptButton = null;
- if(flow.intercepted){
- acceptButton = React.createElement(NavAction, {title: "[a]ccept intercepted flow", icon: "fa-play", onClick: actions.FlowActions.accept.bind(null, flow)});
- }
- var revertButton = null;
- if(flow.modified){
- revertButton = React.createElement(NavAction, {title: "revert changes to flow [V]", icon: "fa-history", onClick: actions.FlowActions.revert.bind(null, flow)});
- }
-
- return (
- React.createElement("nav", {ref: "head", className: "nav-tabs nav-tabs-sm"},
- tabs,
- React.createElement(NavAction, {title: "[d]elete flow", icon: "fa-trash", onClick: actions.FlowActions.delete.bind(null, flow)}),
- React.createElement(NavAction, {title: "[D]uplicate flow", icon: "fa-copy", onClick: actions.FlowActions.duplicate.bind(null, flow)}),
- React.createElement(NavAction, {disabled: true, title: "[r]eplay flow", icon: "fa-repeat", onClick: actions.FlowActions.replay.bind(null, flow)}),
- acceptButton,
- revertButton
- )
- );
- }
-});
-
-var Headers = React.createClass({displayName: "Headers",
- render: function () {
- var rows = this.props.message.headers.map(function (header, i) {
- return (
- React.createElement("tr", {key: i},
- React.createElement("td", {className: "header-name"}, header[0] + ":"),
- React.createElement("td", {className: "header-value"}, header[1])
- )
- );
- });
- return (
- React.createElement("table", {className: "header-table"},
- React.createElement("tbody", null,
- rows
- )
- )
- );
- }
-});
-
-var FlowDetailRequest = React.createClass({displayName: "FlowDetailRequest",
- render: function () {
- var flow = this.props.flow;
- var first_line = [
- flow.request.method,
- flowutils.RequestUtils.pretty_url(flow.request),
- "HTTP/" + flow.request.httpversion.join(".")
- ].join(" ");
- var content = null;
- if (flow.request.contentLength > 0) {
- content = "Request Content Size: " + toputils.formatSize(flow.request.contentLength);
- } else {
- content = React.createElement("div", {className: "alert alert-info"}, "No Content");
- }
-
- //TODO: Styling
-
- return (
- React.createElement("section", null,
- React.createElement("div", {className: "first-line"}, first_line ),
- React.createElement(Headers, {message: flow.request}),
- React.createElement("hr", null),
- content
- )
- );
- }
-});
-
-var FlowDetailResponse = React.createClass({displayName: "FlowDetailResponse",
- render: function () {
- var flow = this.props.flow;
- var first_line = [
- "HTTP/" + flow.response.httpversion.join("."),
- flow.response.code,
- flow.response.msg
- ].join(" ");
- var content = null;
- if (flow.response.contentLength > 0) {
- content = "Response Content Size: " + toputils.formatSize(flow.response.contentLength);
- } else {
- content = React.createElement("div", {className: "alert alert-info"}, "No Content");
- }
-
- //TODO: Styling
-
- return (
- React.createElement("section", null,
- React.createElement("div", {className: "first-line"}, first_line ),
- React.createElement(Headers, {message: flow.response}),
- React.createElement("hr", null),
- content
- )
- );
- }
-});
-
-var FlowDetailError = React.createClass({displayName: "FlowDetailError",
- render: function () {
- var flow = this.props.flow;
- return (
- React.createElement("section", null,
- React.createElement("div", {className: "alert alert-warning"},
- flow.error.msg,
- React.createElement("div", null,
- React.createElement("small", null, toputils.formatTimeStamp(flow.error.timestamp) )
- )
- )
- )
- );
- }
-});
-
-var TimeStamp = React.createClass({displayName: "TimeStamp",
- render: function () {
-
- if (!this.props.t) {
- //should be return null, but that triggers a React bug.
- return React.createElement("tr", null);
- }
-
- var ts = toputils.formatTimeStamp(this.props.t);
-
- var delta;
- if (this.props.deltaTo) {
- delta = toputils.formatTimeDelta(1000 * (this.props.t - this.props.deltaTo));
- delta = React.createElement("span", {className: "text-muted"}, "(" + delta + ")");
- } else {
- delta = null;
- }
-
- return React.createElement("tr", null,
- React.createElement("td", null, this.props.title + ":"),
- React.createElement("td", null, ts, " ", delta)
- );
- }
-});
-
-var ConnectionInfo = React.createClass({displayName: "ConnectionInfo",
-
- render: function () {
- var conn = this.props.conn;
- var address = conn.address.address.join(":");
-
- var sni = React.createElement("tr", {key: "sni"}); //should be null, but that triggers a React bug.
- if (conn.sni) {
- sni = React.createElement("tr", {key: "sni"},
- React.createElement("td", null,
- React.createElement("abbr", {title: "TLS Server Name Indication"}, "TLS SNI:")
- ),
- React.createElement("td", null, conn.sni)
- );
- }
- return (
- React.createElement("table", {className: "connection-table"},
- React.createElement("tbody", null,
- React.createElement("tr", {key: "address"},
- React.createElement("td", null, "Address:"),
- React.createElement("td", null, address)
- ),
- sni
- )
- )
- );
- }
-});
-
-var CertificateInfo = React.createClass({displayName: "CertificateInfo",
- render: function () {
- //TODO: We should fetch human-readable certificate representation
- // from the server
- var flow = this.props.flow;
- var client_conn = flow.client_conn;
- var server_conn = flow.server_conn;
-
- var preStyle = {maxHeight: 100};
- return (
- React.createElement("div", null,
- client_conn.cert ? React.createElement("h4", null, "Client Certificate") : null,
- client_conn.cert ? React.createElement("pre", {style: preStyle}, client_conn.cert) : null,
-
- server_conn.cert ? React.createElement("h4", null, "Server Certificate") : null,
- server_conn.cert ? React.createElement("pre", {style: preStyle}, server_conn.cert) : null
- )
- );
- }
-});
-
-var Timing = React.createClass({displayName: "Timing",
- render: function () {
- var flow = this.props.flow;
- var sc = flow.server_conn;
- var cc = flow.client_conn;
- var req = flow.request;
- var resp = flow.response;
-
- var timestamps = [
- {
- title: "Server conn. initiated",
- t: sc.timestamp_start,
- deltaTo: req.timestamp_start
- }, {
- title: "Server conn. TCP handshake",
- t: sc.timestamp_tcp_setup,
- deltaTo: req.timestamp_start
- }, {
- title: "Server conn. SSL handshake",
- t: sc.timestamp_ssl_setup,
- deltaTo: req.timestamp_start
- }, {
- title: "Client conn. established",
- t: cc.timestamp_start,
- deltaTo: req.timestamp_start
- }, {
- title: "Client conn. SSL handshake",
- t: cc.timestamp_ssl_setup,
- deltaTo: req.timestamp_start
- }, {
- title: "First request byte",
- t: req.timestamp_start,
- }, {
- title: "Request complete",
- t: req.timestamp_end,
- deltaTo: req.timestamp_start
- }
- ];
-
- if (flow.response) {
- timestamps.push(
- {
- title: "First response byte",
- t: resp.timestamp_start,
- deltaTo: req.timestamp_start
- }, {
- title: "Response complete",
- t: resp.timestamp_end,
- deltaTo: req.timestamp_start
- }
- );
- }
-
- //Add unique key for each row.
- timestamps.forEach(function (e) {
- e.key = e.title;
- });
-
- timestamps = _.sortBy(timestamps, 't');
-
- var rows = timestamps.map(function (e) {
- return React.createElement(TimeStamp, React.__spread({}, e));
- });
-
- return (
- React.createElement("div", null,
- React.createElement("h4", null, "Timing"),
- React.createElement("table", {className: "timing-table"},
- React.createElement("tbody", null,
- rows
- )
- )
- )
- );
- }
-});
-
-var FlowDetailConnectionInfo = React.createClass({displayName: "FlowDetailConnectionInfo",
- render: function () {
- var flow = this.props.flow;
- var client_conn = flow.client_conn;
- var server_conn = flow.server_conn;
- return (
- React.createElement("section", null,
-
- React.createElement("h4", null, "Client Connection"),
- React.createElement(ConnectionInfo, {conn: client_conn}),
-
- React.createElement("h4", null, "Server Connection"),
- React.createElement(ConnectionInfo, {conn: server_conn}),
-
- React.createElement(CertificateInfo, {flow: flow}),
-
- React.createElement(Timing, {flow: flow})
-
- )
- );
- }
-});
-
-var allTabs = {
- request: FlowDetailRequest,
- response: FlowDetailResponse,
- error: FlowDetailError,
- details: FlowDetailConnectionInfo
-};
-
-var FlowDetail = React.createClass({displayName: "FlowDetail",
- mixins: [common.StickyHeadMixin, common.Navigation, common.State],
- getTabs: function (flow) {
- var tabs = [];
- ["request", "response", "error"].forEach(function (e) {
- if (flow[e]) {
- tabs.push(e);
- }
- });
- tabs.push("details");
- return tabs;
- },
- nextTab: function (i) {
- var tabs = this.getTabs(this.props.flow);
- var currentIndex = tabs.indexOf(this.getParams().detailTab);
- // JS modulo operator doesn't correct negative numbers, make sure that we are positive.
- var nextIndex = (currentIndex + i + tabs.length) % tabs.length;
- this.selectTab(tabs[nextIndex]);
- },
- selectTab: function (panel) {
- this.replaceWith(
- "flow",
- {
- flowId: this.getParams().flowId,
- detailTab: panel
- }
- );
- },
- render: function () {
- var flow = this.props.flow;
- var tabs = this.getTabs(flow);
- var active = this.getParams().detailTab;
-
- if (!_.contains(tabs, active)) {
- if (active === "response" && flow.error) {
- active = "error";
- } else if (active === "error" && flow.response) {
- active = "response";
- } else {
- active = tabs[0];
- }
- this.selectTab(active);
- }
-
- var Tab = allTabs[active];
- return (
- React.createElement("div", {className: "flow-detail", onScroll: this.adjustHead},
- React.createElement(FlowDetailNav, {ref: "head",
- flow: flow,
- tabs: tabs,
- active: active,
- selectTab: this.selectTab}),
- React.createElement(Tab, {flow: flow})
- )
- );
- }
-});
-
-module.exports = {
- FlowDetail: FlowDetail
-};
-
-},{"../actions.js":2,"../flow/utils.js":17,"../utils.js":20,"./common.js":4,"lodash":"lodash","react":"react"}],7:[function(require,module,exports){
+},{"../actions.js":2,"../store/view.js":25,"./common.js":4,"./virtualscroll.js":19,"lodash":"lodash","react":"react"}],7:[function(require,module,exports){
var React = require("react");
var RequestUtils = require("../flow/utils.js").RequestUtils;
var ResponseUtils = require("../flow/utils.js").ResponseUtils;
@@ -1195,7 +1049,7 @@ var TLSColumn = React.createClass({displayName: "TLSColumn",
},
render: function () {
var flow = this.props.flow;
- var ssl = (flow.request.scheme == "https");
+ var ssl = (flow.request.scheme === "https");
var classes;
if (ssl) {
classes = "col-tls col-tls-https";
@@ -1223,7 +1077,7 @@ var IconColumn = React.createClass({displayName: "IconColumn",
var contentType = ResponseUtils.getContentType(flow.response);
//TODO: We should assign a type to the flow somewhere else.
- if (flow.response.code == 304) {
+ if (flow.response.code === 304) {
icon = "resource-icon-not-modified";
} else if (300 <= flow.response.code && flow.response.code < 400) {
icon = "resource-icon-redirect";
@@ -1379,7 +1233,7 @@ var all_columns = [
module.exports = all_columns;
-},{"../flow/utils.js":17,"../utils.js":20,"react":"react"}],8:[function(require,module,exports){
+},{"../flow/utils.js":23,"../utils.js":26,"react":"react"}],8:[function(require,module,exports){
var React = require("react");
var common = require("./common.js");
var utils = require("../utils.js");
@@ -1490,33 +1344,25 @@ var ROW_HEIGHT = 32;
var FlowTable = React.createClass({displayName: "FlowTable",
mixins: [common.StickyHeadMixin, common.AutoScrollMixin, VirtualScrollMixin],
+ contextTypes: {
+ view: React.PropTypes.object.isRequired
+ },
getInitialState: function () {
return {
columns: flowtable_columns
};
},
- _listen: function(view){
- if(!view){
- return;
- }
- view.addListener("add", this.onChange);
- view.addListener("update", this.onChange);
- view.addListener("remove", this.onChange);
- view.addListener("recalculate", this.onChange);
- },
componentWillMount: function () {
- this._listen(this.props.view);
+ this.context.view.addListener("add", this.onChange);
+ this.context.view.addListener("update", this.onChange);
+ this.context.view.addListener("remove", this.onChange);
+ this.context.view.addListener("recalculate", this.onChange);
},
- componentWillReceiveProps: function (nextProps) {
- if (nextProps.view !== this.props.view) {
- if (this.props.view) {
- this.props.view.removeListener("add");
- this.props.view.removeListener("update");
- this.props.view.removeListener("remove");
- this.props.view.removeListener("recalculate");
- }
- this._listen(nextProps.view);
- }
+ componentWillUnmount: function(){
+ this.context.view.removeListener("add", this.onChange);
+ this.context.view.removeListener("update", this.onChange);
+ this.context.view.removeListener("remove", this.onChange);
+ this.context.view.removeListener("recalculate", this.onChange);
},
getDefaultProps: function () {
return {
@@ -1532,7 +1378,7 @@ var FlowTable = React.createClass({displayName: "FlowTable",
},
scrollIntoView: function (flow) {
this.scrollRowIntoView(
- this.props.view.index(flow),
+ this.context.view.index(flow),
this.refs.body.getDOMNode().offsetTop
);
},
@@ -1540,8 +1386,8 @@ var FlowTable = React.createClass({displayName: "FlowTable",
var selected = (flow === this.props.selected);
var highlighted =
(
- this.props.view._highlight &&
- this.props.view._highlight[flow.id]
+ this.context.view._highlight &&
+ this.context.view._highlight[flow.id]
);
return React.createElement(FlowRow, {key: flow.id,
@@ -1554,9 +1400,7 @@ var FlowTable = React.createClass({displayName: "FlowTable",
);
},
render: function () {
- //console.log("render flowtable", this.state.start, this.state.stop, this.props.selected);
- var flows = this.props.view ? this.props.view.list : [];
-
+ var flows = this.context.view.list;
var rows = this.renderRows(flows);
return (
@@ -1579,16 +1423,960 @@ var FlowTable = React.createClass({displayName: "FlowTable",
module.exports = FlowTable;
-},{"../utils.js":20,"./common.js":4,"./flowtable-columns.js":7,"./virtualscroll.js":13,"lodash":"lodash","react":"react"}],9:[function(require,module,exports){
+},{"../utils.js":26,"./common.js":4,"./flowtable-columns.js":7,"./virtualscroll.js":19,"lodash":"lodash","react":"react"}],9:[function(require,module,exports){
+var React = require("react");
+var _ = require("lodash");
+
+var MessageUtils = require("../../flow/utils.js").MessageUtils;
+var utils = require("../../utils.js");
+
+var image_regex = /^image\/(png|jpe?g|gif|vnc.microsoft.icon|x-icon)$/i;
+var ViewImage = React.createClass({displayName: "ViewImage",
+ statics: {
+ matches: function (message) {
+ return image_regex.test(MessageUtils.getContentType(message));
+ }
+ },
+ render: function () {
+ var url = MessageUtils.getContentURL(this.props.flow, this.props.message);
+ return React.createElement("div", {className: "flowview-image"},
+ React.createElement("img", {src: url, alt: "preview", className: "img-thumbnail"})
+ );
+ }
+});
+
+var RawMixin = {
+ getInitialState: function () {
+ return {
+ content: undefined,
+ request: undefined
+ }
+ },
+ requestContent: function (nextProps) {
+ if (this.state.request) {
+ this.state.request.abort();
+ }
+ var request = MessageUtils.getContent(nextProps.flow, nextProps.message);
+ this.setState({
+ content: undefined,
+ request: request
+ });
+ request.done(function (data) {
+ this.setState({content: data});
+ }.bind(this)).fail(function (jqXHR, textStatus, errorThrown) {
+ if (textStatus === "abort") {
+ return;
+ }
+ this.setState({content: "AJAX Error: " + textStatus + "\r\n" + errorThrown});
+ }.bind(this)).always(function () {
+ this.setState({request: undefined});
+ }.bind(this));
+
+ },
+ componentWillMount: function () {
+ this.requestContent(this.props);
+ },
+ componentWillReceiveProps: function (nextProps) {
+ if (nextProps.message !== this.props.message) {
+ this.requestContent(nextProps);
+ }
+ },
+ componentWillUnmount: function () {
+ if (this.state.request) {
+ this.state.request.abort();
+ }
+ },
+ render: function () {
+ if (!this.state.content) {
+ return React.createElement("div", {className: "text-center"},
+ React.createElement("i", {className: "fa fa-spinner fa-spin"})
+ );
+ }
+ return this.renderContent();
+ }
+};
+
+var ViewRaw = React.createClass({displayName: "ViewRaw",
+ mixins: [RawMixin],
+ statics: {
+ matches: function (message) {
+ return true;
+ }
+ },
+ renderContent: function () {
+ return React.createElement("pre", null, this.state.content);
+ }
+});
+
+var json_regex = /^application\/json$/i;
+var ViewJSON = React.createClass({displayName: "ViewJSON",
+ mixins: [RawMixin],
+ statics: {
+ matches: function (message) {
+ return json_regex.test(MessageUtils.getContentType(message));
+ }
+ },
+ renderContent: function () {
+ var json = this.state.content;
+ try {
+ json = JSON.stringify(JSON.parse(json), null, 2);
+ } catch (e) {
+ }
+ return React.createElement("pre", null, json);
+ }
+});
+
+var ViewAuto = React.createClass({displayName: "ViewAuto",
+ statics: {
+ matches: function () {
+ return false; // don't match itself
+ },
+ findView: function (message) {
+ for (var i = 0; i < all.length; i++) {
+ if (all[i].matches(message)) {
+ return all[i];
+ }
+ }
+ return all[all.length - 1];
+ }
+ },
+ render: function () {
+ var View = ViewAuto.findView(this.props.message);
+ return React.createElement(View, React.__spread({}, this.props));
+ }
+});
+
+var all = [ViewAuto, ViewImage, ViewJSON, ViewRaw];
+
+
+var ContentEmpty = React.createClass({displayName: "ContentEmpty",
+ render: function () {
+ var message_name = this.props.flow.request === this.props.message ? "request" : "response";
+ return React.createElement("div", {className: "alert alert-info"}, "No ", message_name, " content.");
+ }
+});
+
+var ContentMissing = React.createClass({displayName: "ContentMissing",
+ render: function () {
+ var message_name = this.props.flow.request === this.props.message ? "Request" : "Response";
+ return React.createElement("div", {className: "alert alert-info"}, message_name, " content missing.");
+ }
+});
+
+var TooLarge = React.createClass({displayName: "TooLarge",
+ statics: {
+ isTooLarge: function (message) {
+ var max_mb = ViewImage.matches(message) ? 10 : 0.2;
+ return message.contentLength > 1024 * 1024 * max_mb;
+ }
+ },
+ render: function () {
+ var size = utils.formatSize(this.props.message.contentLength);
+ return React.createElement("div", {className: "alert alert-warning"},
+ React.createElement("button", {onClick: this.props.onClick, className: "btn btn-xs btn-warning pull-right"}, "Display anyway"),
+ size, " content size."
+ );
+ }
+});
+
+var ViewSelector = React.createClass({displayName: "ViewSelector",
+ render: function () {
+ var views = [];
+ for (var i = 0; i < all.length; i++) {
+ var view = all[i];
+ var className = "btn btn-default";
+ if (view === this.props.active) {
+ className += " active";
+ }
+ var text;
+ if (view === ViewAuto) {
+ text = "auto: " + ViewAuto.findView(this.props.message).displayName.toLowerCase().replace("view", "");
+ } else {
+ text = view.displayName.toLowerCase().replace("view", "");
+ }
+ views.push(
+ React.createElement("button", {
+ key: view.displayName,
+ onClick: this.props.selectView.bind(null, view),
+ className: className},
+ text
+ )
+ );
+ }
+
+ return React.createElement("div", {className: "view-selector btn-group btn-group-xs"}, views);
+ }
+});
+
+var ContentView = React.createClass({displayName: "ContentView",
+ getInitialState: function () {
+ return {
+ displayLarge: false,
+ View: ViewAuto
+ };
+ },
+ propTypes: {
+ // It may seem a bit weird at the first glance:
+ // Every view takes the flow and the message as props, e.g.
+ // <Auto flow={flow} message={flow.request}/>
+ flow: React.PropTypes.object.isRequired,
+ message: React.PropTypes.object.isRequired,
+ },
+ selectView: function (view) {
+ this.setState({
+ View: view
+ });
+ },
+ displayLarge: function () {
+ this.setState({displayLarge: true});
+ },
+ componentWillReceiveProps: function (nextProps) {
+ if (nextProps.message !== this.props.message) {
+ this.setState(this.getInitialState());
+ }
+ },
+ render: function () {
+ var message = this.props.message;
+ if (message.contentLength === 0) {
+ return React.createElement(ContentEmpty, React.__spread({}, this.props));
+ } else if (message.contentLength === null) {
+ return React.createElement(ContentMissing, React.__spread({}, this.props));
+ } else if (!this.state.displayLarge && TooLarge.isTooLarge(message)) {
+ return React.createElement(TooLarge, React.__spread({}, this.props, {onClick: this.displayLarge}));
+ }
+
+ var downloadUrl = MessageUtils.getContentURL(this.props.flow, message);
+
+ return React.createElement("div", null,
+ React.createElement(this.state.View, React.__spread({}, this.props)),
+ React.createElement("div", {className: "view-options text-center"},
+ React.createElement(ViewSelector, {selectView: this.selectView, active: this.state.View, message: message}),
+ " ",
+ React.createElement("a", {className: "btn btn-default btn-xs", href: downloadUrl},
+ React.createElement("i", {className: "fa fa-download"})
+ )
+ )
+ );
+ }
+});
+
+module.exports = ContentView;
+
+},{"../../flow/utils.js":23,"../../utils.js":26,"lodash":"lodash","react":"react"}],10:[function(require,module,exports){
+var React = require("react");
+var _ = require("lodash");
+
+var utils = require("../../utils.js");
+
+var TimeStamp = React.createClass({displayName: "TimeStamp",
+ render: function () {
+
+ if (!this.props.t) {
+ //should be return null, but that triggers a React bug.
+ return React.createElement("tr", null);
+ }
+
+ var ts = utils.formatTimeStamp(this.props.t);
+
+ var delta;
+ if (this.props.deltaTo) {
+ delta = utils.formatTimeDelta(1000 * (this.props.t - this.props.deltaTo));
+ delta = React.createElement("span", {className: "text-muted"}, "(" + delta + ")");
+ } else {
+ delta = null;
+ }
+
+ return React.createElement("tr", null,
+ React.createElement("td", null, this.props.title + ":"),
+ React.createElement("td", null, ts, " ", delta)
+ );
+ }
+});
+
+var ConnectionInfo = React.createClass({displayName: "ConnectionInfo",
+
+ render: function () {
+ var conn = this.props.conn;
+ var address = conn.address.address.join(":");
+
+ var sni = React.createElement("tr", {key: "sni"}); //should be null, but that triggers a React bug.
+ if (conn.sni) {
+ sni = React.createElement("tr", {key: "sni"},
+ React.createElement("td", null,
+ React.createElement("abbr", {title: "TLS Server Name Indication"}, "TLS SNI:")
+ ),
+ React.createElement("td", null, conn.sni)
+ );
+ }
+ return (
+ React.createElement("table", {className: "connection-table"},
+ React.createElement("tbody", null,
+ React.createElement("tr", {key: "address"},
+ React.createElement("td", null, "Address:"),
+ React.createElement("td", null, address)
+ ),
+ sni
+ )
+ )
+ );
+ }
+});
+
+var CertificateInfo = React.createClass({displayName: "CertificateInfo",
+ render: function () {
+ //TODO: We should fetch human-readable certificate representation
+ // from the server
+ var flow = this.props.flow;
+ var client_conn = flow.client_conn;
+ var server_conn = flow.server_conn;
+
+ var preStyle = {maxHeight: 100};
+ return (
+ React.createElement("div", null,
+ client_conn.cert ? React.createElement("h4", null, "Client Certificate") : null,
+ client_conn.cert ? React.createElement("pre", {style: preStyle}, client_conn.cert) : null,
+
+ server_conn.cert ? React.createElement("h4", null, "Server Certificate") : null,
+ server_conn.cert ? React.createElement("pre", {style: preStyle}, server_conn.cert) : null
+ )
+ );
+ }
+});
+
+var Timing = React.createClass({displayName: "Timing",
+ render: function () {
+ var flow = this.props.flow;
+ var sc = flow.server_conn;
+ var cc = flow.client_conn;
+ var req = flow.request;
+ var resp = flow.response;
+
+ var timestamps = [
+ {
+ title: "Server conn. initiated",
+ t: sc.timestamp_start,
+ deltaTo: req.timestamp_start
+ }, {
+ title: "Server conn. TCP handshake",
+ t: sc.timestamp_tcp_setup,
+ deltaTo: req.timestamp_start
+ }, {
+ title: "Server conn. SSL handshake",
+ t: sc.timestamp_ssl_setup,
+ deltaTo: req.timestamp_start
+ }, {
+ title: "Client conn. established",
+ t: cc.timestamp_start,
+ deltaTo: req.timestamp_start
+ }, {
+ title: "Client conn. SSL handshake",
+ t: cc.timestamp_ssl_setup,
+ deltaTo: req.timestamp_start
+ }, {
+ title: "First request byte",
+ t: req.timestamp_start,
+ }, {
+ title: "Request complete",
+ t: req.timestamp_end,
+ deltaTo: req.timestamp_start
+ }
+ ];
+
+ if (flow.response) {
+ timestamps.push(
+ {
+ title: "First response byte",
+ t: resp.timestamp_start,
+ deltaTo: req.timestamp_start
+ }, {
+ title: "Response complete",
+ t: resp.timestamp_end,
+ deltaTo: req.timestamp_start
+ }
+ );
+ }
+
+ //Add unique key for each row.
+ timestamps.forEach(function (e) {
+ e.key = e.title;
+ });
+
+ timestamps = _.sortBy(timestamps, 't');
+
+ var rows = timestamps.map(function (e) {
+ return React.createElement(TimeStamp, React.__spread({}, e));
+ });
+
+ return (
+ React.createElement("div", null,
+ React.createElement("h4", null, "Timing"),
+ React.createElement("table", {className: "timing-table"},
+ React.createElement("tbody", null,
+ rows
+ )
+ )
+ )
+ );
+ }
+});
+
+var Details = React.createClass({displayName: "Details",
+ render: function () {
+ var flow = this.props.flow;
+ var client_conn = flow.client_conn;
+ var server_conn = flow.server_conn;
+ return (
+ React.createElement("section", null,
+
+ React.createElement("h4", null, "Client Connection"),
+ React.createElement(ConnectionInfo, {conn: client_conn}),
+
+ React.createElement("h4", null, "Server Connection"),
+ React.createElement(ConnectionInfo, {conn: server_conn}),
+
+ React.createElement(CertificateInfo, {flow: flow}),
+
+ React.createElement(Timing, {flow: flow})
+
+ )
+ );
+ }
+});
+
+module.exports = Details;
+
+},{"../../utils.js":26,"lodash":"lodash","react":"react"}],11:[function(require,module,exports){
var React = require("react");
+var _ = require("lodash");
+
+var common = require("../common.js");
+var Nav = require("./nav.js");
+var Messages = require("./messages.js");
+var Details = require("./details.js");
+var Prompt = require("../prompt.js");
+
+
+var allTabs = {
+ request: Messages.Request,
+ response: Messages.Response,
+ error: Messages.Error,
+ details: Details
+};
+
+var FlowView = React.createClass({displayName: "FlowView",
+ mixins: [common.StickyHeadMixin, common.Navigation, common.RouterState],
+ getInitialState: function () {
+ return {
+ prompt: false
+ };
+ },
+ getTabs: function (flow) {
+ var tabs = [];
+ ["request", "response", "error"].forEach(function (e) {
+ if (flow[e]) {
+ tabs.push(e);
+ }
+ });
+ tabs.push("details");
+ return tabs;
+ },
+ nextTab: function (i) {
+ var tabs = this.getTabs(this.props.flow);
+ var currentIndex = tabs.indexOf(this.getActive());
+ // JS modulo operator doesn't correct negative numbers, make sure that we are positive.
+ var nextIndex = (currentIndex + i + tabs.length) % tabs.length;
+ this.selectTab(tabs[nextIndex]);
+ },
+ selectTab: function (panel) {
+ this.replaceWith(
+ "flow",
+ {
+ flowId: this.getParams().flowId,
+ detailTab: panel
+ }
+ );
+ },
+ getActive: function(){
+ return this.getParams().detailTab;
+ },
+ promptEdit: function () {
+ var options;
+ switch(this.getActive()){
+ case "request":
+ options = [
+ "method",
+ "url",
+ {text:"http version", key:"v"},
+ "header"
+ /*, "content"*/];
+ break;
+ case "response":
+ options = [
+ {text:"http version", key:"v"},
+ "code",
+ "message",
+ "header"
+ /*, "content"*/];
+ break;
+ case "details":
+ return;
+ default:
+ throw "Unknown tab for edit: " + this.getActive();
+ }
+
+ this.setState({
+ prompt: {
+ done: function (k) {
+ this.setState({prompt: false});
+ if(k){
+ this.refs.tab.edit(k);
+ }
+ }.bind(this),
+ options: options
+ }
+ });
+ },
+ render: function () {
+ var flow = this.props.flow;
+ var tabs = this.getTabs(flow);
+ var active = this.getActive();
+
+ if (!_.contains(tabs, active)) {
+ if (active === "response" && flow.error) {
+ active = "error";
+ } else if (active === "error" && flow.response) {
+ active = "response";
+ } else {
+ active = tabs[0];
+ }
+ this.selectTab(active);
+ }
+
+ var prompt = null;
+ if (this.state.prompt) {
+ prompt = React.createElement(Prompt, React.__spread({}, this.state.prompt));
+ }
+
+ var Tab = allTabs[active];
+ return (
+ React.createElement("div", {className: "flow-detail", onScroll: this.adjustHead},
+ React.createElement(Nav, {ref: "head",
+ flow: flow,
+ tabs: tabs,
+ active: active,
+ selectTab: this.selectTab}),
+ React.createElement(Tab, {ref: "tab", flow: flow}),
+ prompt
+ )
+ );
+ }
+});
+
+module.exports = FlowView;
+
+},{"../common.js":4,"../prompt.js":17,"./details.js":10,"./messages.js":12,"./nav.js":13,"lodash":"lodash","react":"react"}],12:[function(require,module,exports){
+var React = require("react");
+var _ = require("lodash");
+
+var common = require("../common.js");
+var actions = require("../../actions.js");
+var flowutils = require("../../flow/utils.js");
+var utils = require("../../utils.js");
+var ContentView = require("./contentview.js");
+var ValueEditor = require("../editor.js").ValueEditor;
+
+var Headers = React.createClass({displayName: "Headers",
+ propTypes: {
+ onChange: React.PropTypes.func.isRequired,
+ message: React.PropTypes.object.isRequired
+ },
+ onChange: function (row, col, val) {
+ var nextHeaders = _.cloneDeep(this.props.message.headers);
+ nextHeaders[row][col] = val;
+ if (!nextHeaders[row][0] && !nextHeaders[row][1]) {
+ // do not delete last row
+ if (nextHeaders.length === 1) {
+ nextHeaders[0][0] = "Name";
+ nextHeaders[0][1] = "Value";
+ } else {
+ nextHeaders.splice(row, 1);
+ // manually move selection target if this has been the last row.
+ if (row === nextHeaders.length) {
+ this._nextSel = (row - 1) + "-value";
+ }
+ }
+ }
+ this.props.onChange(nextHeaders);
+ },
+ edit: function () {
+ this.refs["0-key"].focus();
+ },
+ onTab: function (row, col, e) {
+ var headers = this.props.message.headers;
+ if (row === headers.length - 1 && col === 1) {
+ e.preventDefault();
+
+ var nextHeaders = _.cloneDeep(this.props.message.headers);
+ nextHeaders.push(["Name", "Value"]);
+ this.props.onChange(nextHeaders);
+ this._nextSel = (row + 1) + "-key";
+ }
+ },
+ componentDidUpdate: function () {
+ if (this._nextSel && this.refs[this._nextSel]) {
+ this.refs[this._nextSel].focus();
+ this._nextSel = undefined;
+ }
+ },
+ onRemove: function (row, col, e) {
+ if (col === 1) {
+ e.preventDefault();
+ this.refs[row + "-key"].focus();
+ } else if (row > 0) {
+ e.preventDefault();
+ this.refs[(row - 1) + "-value"].focus();
+ }
+ },
+ render: function () {
+
+ var rows = this.props.message.headers.map(function (header, i) {
+
+ var kEdit = React.createElement(HeaderEditor, {
+ ref: i + "-key",
+ content: header[0],
+ onDone: this.onChange.bind(null, i, 0),
+ onRemove: this.onRemove.bind(null, i, 0),
+ onTab: this.onTab.bind(null, i, 0)});
+ var vEdit = React.createElement(HeaderEditor, {
+ ref: i + "-value",
+ content: header[1],
+ onDone: this.onChange.bind(null, i, 1),
+ onRemove: this.onRemove.bind(null, i, 1),
+ onTab: this.onTab.bind(null, i, 1)});
+ return (
+ React.createElement("tr", {key: i},
+ React.createElement("td", {className: "header-name"}, kEdit, ":"),
+ React.createElement("td", {className: "header-value"}, vEdit)
+ )
+ );
+ }.bind(this));
+ return (
+ React.createElement("table", {className: "header-table"},
+ React.createElement("tbody", null,
+ rows
+ )
+ )
+ );
+ }
+});
+
+var HeaderEditor = React.createClass({displayName: "HeaderEditor",
+ render: function () {
+ return React.createElement(ValueEditor, React.__spread({ref: "input"}, this.props, {onKeyDown: this.onKeyDown, inline: true}));
+ },
+ focus: function () {
+ this.getDOMNode().focus();
+ },
+ onKeyDown: function (e) {
+ switch (e.keyCode) {
+ case utils.Key.BACKSPACE:
+ var s = window.getSelection().getRangeAt(0);
+ if (s.startOffset === 0 && s.endOffset === 0) {
+ this.props.onRemove(e);
+ }
+ break;
+ case utils.Key.TAB:
+ if (!e.shiftKey) {
+ this.props.onTab(e);
+ }
+ break;
+ }
+ }
+});
+
+var RequestLine = React.createClass({displayName: "RequestLine",
+ render: function () {
+ var flow = this.props.flow;
+ var url = flowutils.RequestUtils.pretty_url(flow.request);
+ var httpver = "HTTP/" + flow.request.httpversion.join(".");
+
+ return React.createElement("div", {className: "first-line request-line"},
+ React.createElement(ValueEditor, {
+ ref: "method",
+ content: flow.request.method,
+ onDone: this.onMethodChange,
+ inline: true}),
+ " ",
+ React.createElement(ValueEditor, {
+ ref: "url",
+ content: url,
+ onDone: this.onUrlChange,
+ isValid: this.isValidUrl,
+ inline: true}),
+ " ",
+ React.createElement(ValueEditor, {
+ ref: "httpVersion",
+ content: httpver,
+ onDone: this.onHttpVersionChange,
+ isValid: flowutils.isValidHttpVersion,
+ inline: true})
+ )
+ },
+ isValidUrl: function (url) {
+ var u = flowutils.parseUrl(url);
+ return !!u.host;
+ },
+ onMethodChange: function (nextMethod) {
+ actions.FlowActions.update(
+ this.props.flow,
+ {request: {method: nextMethod}}
+ );
+ },
+ onUrlChange: function (nextUrl) {
+ var props = flowutils.parseUrl(nextUrl);
+ props.path = props.path || "";
+ actions.FlowActions.update(
+ this.props.flow,
+ {request: props}
+ );
+ },
+ onHttpVersionChange: function (nextVer) {
+ var ver = flowutils.parseHttpVersion(nextVer);
+ actions.FlowActions.update(
+ this.props.flow,
+ {request: {httpversion: ver}}
+ );
+ }
+});
+
+var ResponseLine = React.createClass({displayName: "ResponseLine",
+ render: function () {
+ var flow = this.props.flow;
+ var httpver = "HTTP/" + flow.response.httpversion.join(".");
+ return React.createElement("div", {className: "first-line response-line"},
+ React.createElement(ValueEditor, {
+ ref: "httpVersion",
+ content: httpver,
+ onDone: this.onHttpVersionChange,
+ isValid: flowutils.isValidHttpVersion,
+ inline: true}),
+ " ",
+ React.createElement(ValueEditor, {
+ ref: "code",
+ content: flow.response.code + "",
+ onDone: this.onCodeChange,
+ isValid: this.isValidCode,
+ inline: true}),
+ " ",
+ React.createElement(ValueEditor, {
+ ref: "msg",
+ content: flow.response.msg,
+ onDone: this.onMsgChange,
+ inline: true})
+ );
+ },
+ isValidCode: function (code) {
+ return /^\d+$/.test(code);
+ },
+ onHttpVersionChange: function (nextVer) {
+ var ver = flowutils.parseHttpVersion(nextVer);
+ actions.FlowActions.update(
+ this.props.flow,
+ {response: {httpversion: ver}}
+ );
+ },
+ onMsgChange: function (nextMsg) {
+ actions.FlowActions.update(
+ this.props.flow,
+ {response: {msg: nextMsg}}
+ );
+ },
+ onCodeChange: function (nextCode) {
+ nextCode = parseInt(nextCode);
+ actions.FlowActions.update(
+ this.props.flow,
+ {response: {code: nextCode}}
+ );
+ }
+});
+
+var Request = React.createClass({displayName: "Request",
+ render: function () {
+ var flow = this.props.flow;
+ return (
+ React.createElement("section", {className: "request"},
+ React.createElement(RequestLine, {ref: "requestLine", flow: flow}),
+ /*<ResponseLine flow={flow}/>*/
+ React.createElement(Headers, {ref: "headers", message: flow.request, onChange: this.onHeaderChange}),
+ React.createElement("hr", null),
+ React.createElement(ContentView, {flow: flow, message: flow.request})
+ )
+ );
+ },
+ edit: function (k) {
+ switch (k) {
+ case "m":
+ this.refs.requestLine.refs.method.focus();
+ break;
+ case "u":
+ this.refs.requestLine.refs.url.focus();
+ break;
+ case "v":
+ this.refs.requestLine.refs.httpVersion.focus();
+ break;
+ case "h":
+ this.refs.headers.edit();
+ break;
+ default:
+ throw "Unimplemented: " + k;
+ }
+ },
+ onHeaderChange: function (nextHeaders) {
+ actions.FlowActions.update(this.props.flow, {
+ request: {
+ headers: nextHeaders
+ }
+ });
+ }
+});
+
+var Response = React.createClass({displayName: "Response",
+ render: function () {
+ var flow = this.props.flow;
+ return (
+ React.createElement("section", {className: "response"},
+ /*<RequestLine flow={flow}/>*/
+ React.createElement(ResponseLine, {ref: "responseLine", flow: flow}),
+ React.createElement(Headers, {ref: "headers", message: flow.response, onChange: this.onHeaderChange}),
+ React.createElement("hr", null),
+ React.createElement(ContentView, {flow: flow, message: flow.response})
+ )
+ );
+ },
+ edit: function (k) {
+ switch (k) {
+ case "c":
+ this.refs.responseLine.refs.code.focus();
+ break;
+ case "m":
+ this.refs.responseLine.refs.msg.focus();
+ break;
+ case "v":
+ this.refs.responseLine.refs.httpVersion.focus();
+ break;
+ case "h":
+ this.refs.headers.edit();
+ break;
+ default:
+ throw "Unimplemented: " + k;
+ }
+ },
+ onHeaderChange: function (nextHeaders) {
+ actions.FlowActions.update(this.props.flow, {
+ response: {
+ headers: nextHeaders
+ }
+ });
+ }
+});
+
+var Error = React.createClass({displayName: "Error",
+ render: function () {
+ var flow = this.props.flow;
+ return (
+ React.createElement("section", null,
+ React.createElement("div", {className: "alert alert-warning"},
+ flow.error.msg,
+ React.createElement("div", null,
+ React.createElement("small", null, utils.formatTimeStamp(flow.error.timestamp) )
+ )
+ )
+ )
+ );
+ }
+});
+
+module.exports = {
+ Request: Request,
+ Response: Response,
+ Error: Error
+};
+
+},{"../../actions.js":2,"../../flow/utils.js":23,"../../utils.js":26,"../common.js":4,"../editor.js":5,"./contentview.js":9,"lodash":"lodash","react":"react"}],13:[function(require,module,exports){
+var React = require("react");
+
+var actions = require("../../actions.js");
+
+var NavAction = React.createClass({displayName: "NavAction",
+ onClick: function (e) {
+ e.preventDefault();
+ this.props.onClick();
+ },
+ render: function () {
+ return (
+ React.createElement("a", {title: this.props.title,
+ href: "#",
+ className: "nav-action",
+ onClick: this.onClick},
+ React.createElement("i", {className: "fa fa-fw " + this.props.icon})
+ )
+ );
+ }
+});
+
+var Nav = React.createClass({displayName: "Nav",
+ render: function () {
+ var flow = this.props.flow;
+
+ var tabs = this.props.tabs.map(function (e) {
+ var str = e.charAt(0).toUpperCase() + e.slice(1);
+ var className = this.props.active === e ? "active" : "";
+ var onClick = function (event) {
+ this.props.selectTab(e);
+ event.preventDefault();
+ }.bind(this);
+ return React.createElement("a", {key: e,
+ href: "#",
+ className: className,
+ onClick: onClick}, str);
+ }.bind(this));
+
+ var acceptButton = null;
+ if(flow.intercepted){
+ acceptButton = React.createElement(NavAction, {title: "[a]ccept intercepted flow", icon: "fa-play", onClick: actions.FlowActions.accept.bind(null, flow)});
+ }
+ var revertButton = null;
+ if(flow.modified){
+ revertButton = React.createElement(NavAction, {title: "revert changes to flow [V]", icon: "fa-history", onClick: actions.FlowActions.revert.bind(null, flow)});
+ }
+
+ return (
+ React.createElement("nav", {ref: "head", className: "nav-tabs nav-tabs-sm"},
+ tabs,
+ React.createElement(NavAction, {title: "[d]elete flow", icon: "fa-trash", onClick: actions.FlowActions.delete.bind(null, flow)}),
+ React.createElement(NavAction, {title: "[D]uplicate flow", icon: "fa-copy", onClick: actions.FlowActions.duplicate.bind(null, flow)}),
+ React.createElement(NavAction, {disabled: true, title: "[r]eplay flow", icon: "fa-repeat", onClick: actions.FlowActions.replay.bind(null, flow)}),
+ acceptButton,
+ revertButton
+ )
+ );
+ }
+});
+
+module.exports = Nav;
+
+},{"../../actions.js":2,"react":"react"}],14:[function(require,module,exports){
+var React = require("react");
+var common = require("./common.js");
var Footer = React.createClass({displayName: "Footer",
+ mixins: [common.SettingsState],
render: function () {
- var mode = this.props.settings.mode;
- var intercept = this.props.settings.intercept;
+ var mode = this.state.settings.mode;
+ var intercept = this.state.settings.intercept;
return (
React.createElement("footer", null,
- mode != "regular" ? React.createElement("span", {className: "label label-success"}, mode, " mode") : null,
+ mode && mode != "regular" ? React.createElement("span", {className: "label label-success"}, mode, " mode") : null,
" ",
intercept ? React.createElement("span", {className: "label label-success"}, "Intercept: ", intercept) : null
)
@@ -1598,7 +2386,7 @@ var Footer = React.createClass({displayName: "Footer",
module.exports = Footer;
-},{"react":"react"}],10:[function(require,module,exports){
+},{"./common.js":4,"react":"react"}],15:[function(require,module,exports){
var React = require("react");
var $ = require("jquery");
@@ -1651,6 +2439,7 @@ var FilterDocs = React.createClass({displayName: "FilterDocs",
}
});
var FilterInput = React.createClass({displayName: "FilterInput",
+ mixins: [common.ChildFocus],
getInitialState: function () {
// Consider both focus and mouseover for showing/hiding the tooltip,
// because onBlur of the input is triggered before the click on the tooltip
@@ -1715,11 +2504,13 @@ var FilterInput = React.createClass({displayName: "FilterInput",
// If closed using ESC/ENTER, hide the tooltip.
this.setState({mousefocus: false});
}
+ e.stopPropagation();
},
blur: function () {
this.refs.input.getDOMNode().blur();
+ this.returnFocus();
},
- focus: function () {
+ select: function () {
this.refs.input.getDOMNode().select();
},
render: function () {
@@ -1758,14 +2549,14 @@ var FilterInput = React.createClass({displayName: "FilterInput",
});
var MainMenu = React.createClass({displayName: "MainMenu",
- mixins: [common.Navigation, common.State],
+ mixins: [common.Navigation, common.RouterState, common.SettingsState],
statics: {
title: "Start",
route: "flows"
},
- onFilterChange: function (val) {
+ onSearchChange: function (val) {
var d = {};
- d[Query.FILTER] = val;
+ d[Query.SEARCH] = val;
this.setQuery(d);
},
onHighlightChange: function (val) {
@@ -1774,29 +2565,32 @@ var MainMenu = React.createClass({displayName: "MainMenu",
this.setQuery(d);
},
onInterceptChange: function (val) {
- SettingsActions.update({intercept: val});
+ actions.SettingsActions.update({intercept: val});
},
render: function () {
- var filter = this.getQuery()[Query.FILTER] || "";
+ var search = this.getQuery()[Query.SEARCH] || "";
var highlight = this.getQuery()[Query.HIGHLIGHT] || "";
- var intercept = this.props.settings.intercept || "";
+ var intercept = this.state.settings.intercept || "";
return (
React.createElement("div", null,
React.createElement("div", {className: "menu-row"},
React.createElement(FilterInput, {
- placeholder: "Filter",
- type: "filter",
+ ref: "search",
+ placeholder: "Search",
+ type: "search",
color: "black",
- value: filter,
- onChange: this.onFilterChange}),
+ value: search,
+ onChange: this.onSearchChange}),
React.createElement(FilterInput, {
+ ref: "highlight",
placeholder: "Highlight",
type: "tag",
color: "hsl(48, 100%, 50%)",
value: highlight,
onChange: this.onHighlightChange}),
React.createElement(FilterInput, {
+ ref: "intercept",
placeholder: "Intercept",
type: "pause",
color: "hsl(208, 56%, 53%)",
@@ -1815,7 +2609,7 @@ var ViewMenu = React.createClass({displayName: "ViewMenu",
title: "View",
route: "flows"
},
- mixins: [common.Navigation, common.State],
+ mixins: [common.Navigation, common.RouterState],
toggleEventLog: function () {
var d = {};
@@ -1957,15 +2751,17 @@ var Header = React.createClass({displayName: "Header",
},
render: function () {
var header = header_entries.map(function (entry, i) {
- var classes = React.addons.classSet({
- active: entry == this.state.active
- });
+ var className;
+ if (entry === this.state.active) {
+ className = "active";
+ } else {
+ className = "";
+ }
return (
React.createElement("a", {key: i,
href: "#",
- className: classes,
- onClick: this.handleClick.bind(this, entry)
- },
+ className: className,
+ onClick: this.handleClick.bind(this, entry)},
entry.title
)
);
@@ -1978,7 +2774,7 @@ var Header = React.createClass({displayName: "Header",
header
),
React.createElement("div", {className: "menu"},
- React.createElement(this.state.active, {settings: this.props.settings})
+ React.createElement(this.state.active, {ref: "active"})
)
)
);
@@ -1987,32 +2783,56 @@ var Header = React.createClass({displayName: "Header",
module.exports = {
- Header: Header
-}
+ Header: Header,
+ MainMenu: MainMenu
+};
-},{"../actions.js":2,"../filt/filt.js":16,"../utils.js":20,"./common.js":4,"jquery":"jquery","react":"react"}],11:[function(require,module,exports){
+},{"../actions.js":2,"../filt/filt.js":22,"../utils.js":26,"./common.js":4,"jquery":"jquery","react":"react"}],16:[function(require,module,exports){
var React = require("react");
-var common = require("./common.js");
var actions = require("../actions.js");
var Query = require("../actions.js").Query;
-var toputils = require("../utils.js");
+var utils = require("../utils.js");
var views = require("../store/view.js");
var Filt = require("../filt/filt.js");
-FlowTable = require("./flowtable.js");
-var flowdetail = require("./flowdetail.js");
+
+var common = require("./common.js");
+var FlowTable = require("./flowtable.js");
+var FlowView = require("./flowview/index.js");
var MainView = React.createClass({displayName: "MainView",
- mixins: [common.Navigation, common.State],
+ mixins: [common.Navigation, common.RouterState],
+ contextTypes: {
+ flowStore: React.PropTypes.object.isRequired,
+ },
+ childContextTypes: {
+ view: React.PropTypes.object.isRequired,
+ },
+ getChildContext: function () {
+ return {
+ view: this.state.view
+ };
+ },
getInitialState: function () {
+ var sortKeyFun = false;
+ var view = new views.StoreView(this.context.flowStore, this.getViewFilt(), sortKeyFun);
+ view.addListener("recalculate", this.onRecalculate);
+ view.addListener("add", this.onUpdate);
+ view.addListener("update", this.onUpdate);
+ view.addListener("remove", this.onUpdate);
+ view.addListener("remove", this.onRemove);
+
return {
- flows: [],
- sortKeyFun: false
+ view: view,
+ sortKeyFun: sortKeyFun
};
},
+ componentWillUnmount: function () {
+ this.state.view.close();
+ },
getViewFilt: function () {
try {
- var filt = Filt.parse(this.getQuery()[Query.FILTER] || "");
+ var filt = Filt.parse(this.getQuery()[Query.SEARCH] || "");
var highlightStr = this.getQuery()[Query.HIGHLIGHT];
var highlight = highlightStr ? Filt.parse(highlightStr) : false;
} catch (e) {
@@ -2028,29 +2848,12 @@ var MainView = React.createClass({displayName: "MainView",
};
},
componentWillReceiveProps: function (nextProps) {
- if (nextProps.flowStore !== this.props.flowStore) {
- this.closeView();
- this.openView(nextProps.flowStore);
- }
-
- var filterChanged = (this.props.query[Query.FILTER] !== nextProps.query[Query.FILTER]);
+ var filterChanged = (this.props.query[Query.SEARCH] !== nextProps.query[Query.SEARCH]);
var highlightChanged = (this.props.query[Query.HIGHLIGHT] !== nextProps.query[Query.HIGHLIGHT]);
if (filterChanged || highlightChanged) {
this.state.view.recalculate(this.getViewFilt(), this.state.sortKeyFun);
}
},
- openView: function (store) {
- var view = new views.StoreView(store, this.getViewFilt(), this.state.sortKeyFun);
- this.setState({
- view: view
- });
-
- view.addListener("recalculate", this.onRecalculate);
- view.addListener("add", this.onUpdate);
- view.addListener("update", this.onUpdate);
- view.addListener("remove", this.onUpdate);
- view.addListener("remove", this.onRemove);
- },
onRecalculate: function () {
this.forceUpdate();
var selected = this.getSelected();
@@ -2069,16 +2872,7 @@ var MainView = React.createClass({displayName: "MainView",
this.selectFlow(flow_to_select);
}
},
- closeView: function () {
- this.state.view.close();
- },
- componentWillMount: function () {
- this.openView(this.props.flowStore);
- },
- componentWillUnmount: function () {
- this.closeView();
- },
- setSortKeyFun: function(sortKeyFun){
+ setSortKeyFun: function (sortKeyFun) {
this.setState({
sortKeyFun: sortKeyFun
});
@@ -2102,7 +2896,7 @@ var MainView = React.createClass({displayName: "MainView",
var flows = this.state.view.list;
var index;
if (!this.getParams().flowId) {
- if (shift > 0) {
+ if (shift < 0) {
index = flows.length - 1;
} else {
index = 0;
@@ -2122,55 +2916,55 @@ var MainView = React.createClass({displayName: "MainView",
}
this.selectFlow(flows[index]);
},
- onKeyDown: function (e) {
+ onMainKeyDown: function (e) {
var flow = this.getSelected();
if (e.ctrlKey) {
return;
}
switch (e.keyCode) {
- case toputils.Key.K:
- case toputils.Key.UP:
+ case utils.Key.K:
+ case utils.Key.UP:
this.selectFlowRelative(-1);
break;
- case toputils.Key.J:
- case toputils.Key.DOWN:
+ case utils.Key.J:
+ case utils.Key.DOWN:
this.selectFlowRelative(+1);
break;
- case toputils.Key.SPACE:
- case toputils.Key.PAGE_DOWN:
+ case utils.Key.SPACE:
+ case utils.Key.PAGE_DOWN:
this.selectFlowRelative(+10);
break;
- case toputils.Key.PAGE_UP:
+ case utils.Key.PAGE_UP:
this.selectFlowRelative(-10);
break;
- case toputils.Key.END:
+ case utils.Key.END:
this.selectFlowRelative(+1e10);
break;
- case toputils.Key.HOME:
+ case utils.Key.HOME:
this.selectFlowRelative(-1e10);
break;
- case toputils.Key.ESC:
+ case utils.Key.ESC:
this.selectFlow(null);
break;
- case toputils.Key.H:
- case toputils.Key.LEFT:
+ case utils.Key.H:
+ case utils.Key.LEFT:
if (this.refs.flowDetails) {
this.refs.flowDetails.nextTab(-1);
}
break;
- case toputils.Key.L:
- case toputils.Key.TAB:
- case toputils.Key.RIGHT:
+ case utils.Key.L:
+ case utils.Key.TAB:
+ case utils.Key.RIGHT:
if (this.refs.flowDetails) {
this.refs.flowDetails.nextTab(+1);
}
break;
- case toputils.Key.C:
+ case utils.Key.C:
if (e.shiftKey) {
actions.FlowActions.clear();
}
break;
- case toputils.Key.D:
+ case utils.Key.D:
if (flow) {
if (e.shiftKey) {
actions.FlowActions.duplicate(flow);
@@ -2179,23 +2973,30 @@ var MainView = React.createClass({displayName: "MainView",
}
}
break;
- case toputils.Key.A:
+ case utils.Key.A:
if (e.shiftKey) {
actions.FlowActions.accept_all();
} else if (flow && flow.intercepted) {
actions.FlowActions.accept(flow);
}
break;
- case toputils.Key.R:
+ case utils.Key.R:
if (!e.shiftKey && flow) {
actions.FlowActions.replay(flow);
}
break;
- case toputils.Key.V:
+ case utils.Key.V:
if (e.shiftKey && flow && flow.modified) {
actions.FlowActions.revert(flow);
}
break;
+ case utils.Key.E:
+ if (this.refs.flowDetails) {
+ this.refs.flowDetails.promptEdit();
+ }
+ break;
+ case utils.Key.SHIFT:
+ break;
default:
console.debug("keydown", e.keyCode);
return;
@@ -2203,7 +3004,7 @@ var MainView = React.createClass({displayName: "MainView",
e.preventDefault();
},
getSelected: function () {
- return this.props.flowStore.get(this.getParams().flowId);
+ return this.context.flowStore.get(this.getParams().flowId);
},
render: function () {
var selected = this.getSelected();
@@ -2212,16 +3013,15 @@ var MainView = React.createClass({displayName: "MainView",
if (selected) {
details = [
React.createElement(common.Splitter, {key: "splitter"}),
- React.createElement(flowdetail.FlowDetail, {key: "flowDetails", ref: "flowDetails", flow: selected})
+ React.createElement(FlowView, {key: "flowDetails", ref: "flowDetails", flow: selected})
];
} else {
details = null;
}
return (
- React.createElement("div", {className: "main-view", onKeyDown: this.onKeyDown, tabIndex: "0"},
+ React.createElement("div", {className: "main-view"},
React.createElement(FlowTable, {ref: "flowTable",
- view: this.state.view,
selectFlow: this.selectFlow,
setSortKeyFun: this.setSortKeyFun,
selected: selected}),
@@ -2234,7 +3034,109 @@ var MainView = React.createClass({displayName: "MainView",
module.exports = MainView;
-},{"../actions.js":2,"../filt/filt.js":16,"../store/view.js":19,"../utils.js":20,"./common.js":4,"./flowdetail.js":6,"./flowtable.js":8,"react":"react"}],12:[function(require,module,exports){
+},{"../actions.js":2,"../filt/filt.js":22,"../store/view.js":25,"../utils.js":26,"./common.js":4,"./flowtable.js":8,"./flowview/index.js":11,"react":"react"}],17:[function(require,module,exports){
+var React = require("react");
+var _ = require("lodash");
+
+var utils = require("../utils.js");
+var common = require("./common.js");
+
+var Prompt = React.createClass({displayName: "Prompt",
+ mixins: [common.ChildFocus],
+ propTypes: {
+ options: React.PropTypes.array.isRequired,
+ done: React.PropTypes.func.isRequired,
+ prompt: React.PropTypes.string
+ },
+ componentDidMount: function () {
+ React.findDOMNode(this).focus();
+ },
+ onKeyDown: function (e) {
+ e.stopPropagation();
+ e.preventDefault();
+ var opts = this.getOptions();
+ for (var i = 0; i < opts.length; i++) {
+ var k = opts[i].key;
+ if (utils.Key[k.toUpperCase()] === e.keyCode) {
+ this.done(k);
+ return;
+ }
+ }
+ if (e.keyCode === utils.Key.ESC || e.keyCode === utils.Key.ENTER) {
+ this.done(false);
+ }
+ },
+ onClick: function (e) {
+ this.done(false);
+ },
+ done: function (ret) {
+ this.props.done(ret);
+ this.returnFocus();
+ },
+ getOptions: function () {
+ var opts = [];
+
+ var keyTaken = function (k) {
+ return _.includes(_.pluck(opts, "key"), k);
+ };
+
+ for (var i = 0; i < this.props.options.length; i++) {
+ var opt = this.props.options[i];
+ if (_.isString(opt)) {
+ var str = opt;
+ while (str.length > 0 && keyTaken(str[0])) {
+ str = str.substr(1);
+ }
+ opt = {
+ text: opt,
+ key: str[0]
+ };
+ }
+ if (!opt.text || !opt.key || keyTaken(opt.key)) {
+ throw "invalid options";
+ } else {
+ opts.push(opt);
+ }
+ }
+ return opts;
+ },
+ render: function () {
+ var opts = this.getOptions();
+ opts = _.map(opts, function (o) {
+ var prefix, suffix;
+ var idx = o.text.indexOf(o.key);
+ if (idx !== -1) {
+ prefix = o.text.substring(0, idx);
+ suffix = o.text.substring(idx + 1);
+
+ } else {
+ prefix = o.text + " (";
+ suffix = ")";
+ }
+ var onClick = function (e) {
+ this.done(o.key);
+ e.stopPropagation();
+ }.bind(this);
+ return React.createElement("span", {
+ key: o.key,
+ className: "option",
+ onClick: onClick},
+ prefix,
+ React.createElement("strong", {className: "text-primary"}, o.key), suffix
+ );
+ }.bind(this));
+ return React.createElement("div", {tabIndex: "0", onKeyDown: this.onKeyDown, onClick: this.onClick, className: "prompt-dialog"},
+ React.createElement("div", {className: "prompt-content"},
+ this.props.prompt || React.createElement("strong", null, "Select: "),
+ opts
+ )
+ );
+ }
+});
+
+module.exports = Prompt;
+
+},{"../utils.js":26,"./common.js":4,"lodash":"lodash","react":"react"}],18:[function(require,module,exports){
var React = require("react");
var ReactRouter = require("react-router");
var _ = require("lodash");
@@ -2246,6 +3148,7 @@ var header = require("./header.js");
var EventLog = require("./eventlog.js");
var store = require("../store/store.js");
var Query = require("../actions.js").Query;
+var Key = require("../utils.js").Key;
//TODO: Move out of here, just a stub.
@@ -2257,52 +3160,87 @@ var Reports = React.createClass({displayName: "Reports",
var ProxyAppMain = React.createClass({displayName: "ProxyAppMain",
- mixins: [common.State],
+ mixins: [common.RouterState],
+ childContextTypes: {
+ settingsStore: React.PropTypes.object.isRequired,
+ flowStore: React.PropTypes.object.isRequired,
+ eventStore: React.PropTypes.object.isRequired,
+ returnFocus: React.PropTypes.func.isRequired,
+ },
+ componentDidMount: function () {
+ this.focus();
+ },
+ getChildContext: function () {
+ return {
+ settingsStore: this.state.settingsStore,
+ flowStore: this.state.flowStore,
+ eventStore: this.state.eventStore,
+ returnFocus: this.focus,
+ };
+ },
getInitialState: function () {
var eventStore = new store.EventLogStore();
var flowStore = new store.FlowStore();
- var settings = new store.SettingsStore();
+ var settingsStore = new store.SettingsStore();
// Default Settings before fetch
- _.extend(settings.dict,{
- });
+ _.extend(settingsStore.dict, {});
return {
- settings: settings,
+ settingsStore: settingsStore,
flowStore: flowStore,
eventStore: eventStore
};
},
- componentDidMount: function () {
- this.state.settings.addListener("recalculate", this.onSettingsChange);
- window.app = this;
+ focus: function () {
+ React.findDOMNode(this).focus();
},
- componentWillUnmount: function () {
- this.state.settings.removeListener("recalculate", this.onSettingsChange);
+ getMainComponent: function () {
+ return this.refs.view.refs.__routeHandler__;
},
- onSettingsChange: function(){
- this.setState({
- settings: this.state.settings
- });
+ onKeydown: function (e) {
+
+ var selectFilterInput = function (name) {
+ var headerComponent = this.refs.header;
+ headerComponent.setState({active: header.MainMenu}, function () {
+ headerComponent.refs.active.refs[name].select();
+ });
+ }.bind(this);
+
+ switch (e.keyCode) {
+ case Key.I:
+ selectFilterInput("intercept");
+ break;
+ case Key.L:
+ selectFilterInput("search");
+ break;
+ case Key.H:
+ selectFilterInput("highlight");
+ break;
+ default:
+ var main = this.getMainComponent();
+ if (main.onMainKeyDown) {
+ main.onMainKeyDown(e);
+ }
+ return; // don't prevent default then
+ }
+ e.preventDefault();
},
render: function () {
var eventlog;
if (this.getQuery()[Query.SHOW_EVENTLOG]) {
eventlog = [
React.createElement(common.Splitter, {key: "splitter", axis: "y"}),
- React.createElement(EventLog, {key: "eventlog", eventStore: this.state.eventStore})
+ React.createElement(EventLog, {key: "eventlog"})
];
} else {
eventlog = null;
}
return (
- React.createElement("div", {id: "container"},
- React.createElement(header.Header, {settings: this.state.settings.dict}),
- React.createElement(RouteHandler, {
- settings: this.state.settings.dict,
- flowStore: this.state.flowStore,
- query: this.getQuery()}),
+ React.createElement("div", {id: "container", tabIndex: "0", onKeyDown: this.onKeydown},
+ React.createElement(header.Header, {ref: "header"}),
+ React.createElement(RouteHandler, {ref: "view", query: this.getQuery()}),
eventlog,
- React.createElement(Footer, {settings: this.state.settings.dict})
+ React.createElement(Footer, null)
)
);
}
@@ -2329,7 +3267,7 @@ module.exports = {
routes: routes
};
-},{"../actions.js":2,"../store/store.js":18,"./common.js":4,"./eventlog.js":5,"./footer.js":9,"./header.js":10,"./mainview.js":11,"lodash":"lodash","react":"react","react-router":"react-router"}],13:[function(require,module,exports){
+},{"../actions.js":2,"../store/store.js":24,"../utils.js":26,"./common.js":4,"./eventlog.js":6,"./footer.js":14,"./header.js":15,"./mainview.js":16,"lodash":"lodash","react":"react","react-router":"react-router"}],19:[function(require,module,exports){
var React = require("react");
var VirtualScrollMixin = {
@@ -2416,9 +3354,10 @@ var VirtualScrollMixin = {
module.exports = VirtualScrollMixin;
-},{"react":"react"}],14:[function(require,module,exports){
+},{"react":"react"}],20:[function(require,module,exports){
var actions = require("./actions.js");
+var AppDispatcher = require("./dispatcher.js").AppDispatcher;
function Connection(url) {
if (url[0] === "/") {
@@ -2435,18 +3374,18 @@ function Connection(url) {
};
ws.onerror = function () {
actions.ConnectionActions.error();
- EventLogActions.add_event("WebSocket connection error.");
+ actions.EventLogActions.add_event("WebSocket connection error.");
};
ws.onclose = function () {
actions.ConnectionActions.close();
- EventLogActions.add_event("WebSocket connection closed.");
+ actions.EventLogActions.add_event("WebSocket connection closed.");
};
return ws;
}
module.exports = Connection;
-},{"./actions.js":2}],15:[function(require,module,exports){
+},{"./actions.js":2,"./dispatcher.js":21}],21:[function(require,module,exports){
var flux = require("flux");
@@ -2456,7 +3395,7 @@ const PayloadSources = {
};
-AppDispatcher = new flux.Dispatcher();
+var AppDispatcher = new flux.Dispatcher();
AppDispatcher.dispatchViewAction = function (action) {
action.source = PayloadSources.VIEW;
this.dispatch(action);
@@ -2470,7 +3409,7 @@ module.exports = {
AppDispatcher: AppDispatcher
};
-},{"flux":"flux"}],16:[function(require,module,exports){
+},{"flux":"flux"}],22:[function(require,module,exports){
module.exports = (function() {
/*
* Generated by PEG.js 0.8.0.
@@ -4246,12 +5185,21 @@ module.exports = (function() {
};
})();
-},{"../flow/utils.js":17}],17:[function(require,module,exports){
+},{"../flow/utils.js":23}],23:[function(require,module,exports){
var _ = require("lodash");
+var $ = require("jquery");
+
+var defaultPorts = {
+ "http": 80,
+ "https": 443
+};
-var _MessageUtils = {
+var MessageUtils = {
getContentType: function (message) {
- return this.get_first_header(message, /^Content-Type$/i);
+ var ct = this.get_first_header(message, /^Content-Type$/i);
+ if(ct){
+ return ct.split(";")[0].trim();
+ }
},
get_first_header: function (message, regex) {
//FIXME: Cache Invalidation.
@@ -4283,15 +5231,22 @@ var _MessageUtils = {
}
}
return false;
+ },
+ getContentURL: function (flow, message) {
+ if (message === flow.request) {
+ message = "request";
+ } else if (message === flow.response) {
+ message = "response";
+ }
+ return "/flows/" + flow.id + "/" + message + "/content";
+ },
+ getContent: function (flow, message) {
+ var url = MessageUtils.getContentURL(flow, message);
+ return $.get(url);
}
};
-var defaultPorts = {
- "http": 80,
- "https": 443
-};
-
-var RequestUtils = _.extend(_MessageUtils, {
+var RequestUtils = _.extend(MessageUtils, {
pretty_host: function (request) {
//FIXME: Add hostheader
return request.host;
@@ -4305,16 +5260,64 @@ var RequestUtils = _.extend(_MessageUtils, {
}
});
-var ResponseUtils = _.extend(_MessageUtils, {});
+var ResponseUtils = _.extend(MessageUtils, {});
+
+var parseUrl_regex = /^(?:(https?):\/\/)?([^\/:]+)?(?::(\d+))?(\/.*)?$/i;
+var parseUrl = function (url) {
+ //there are many correct ways to parse a URL,
+ //however, a mitmproxy user may also wish to generate a not-so-correct URL. ;-)
+ var parts = parseUrl_regex.exec(url);
+ if(!parts){
+ return false;
+ }
+
+ var scheme = parts[1],
+ host = parts[2],
+ port = parseInt(parts[3]),
+ path = parts[4];
+ if (scheme) {
+ port = port || defaultPorts[scheme];
+ }
+ var ret = {};
+ if (scheme) {
+ ret.scheme = scheme;
+ }
+ if (host) {
+ ret.host = host;
+ }
+ if (port) {
+ ret.port = port;
+ }
+ if (path) {
+ ret.path = path;
+ }
+ return ret;
+};
+
+
+var isValidHttpVersion_regex = /^HTTP\/\d+(\.\d+)*$/i;
+var isValidHttpVersion = function (httpVersion) {
+ return isValidHttpVersion_regex.test(httpVersion);
+};
+
+var parseHttpVersion = function (httpVersion) {
+ httpVersion = httpVersion.replace("HTTP/", "").split(".");
+ return _.map(httpVersion, function (x) {
+ return parseInt(x);
+ });
+};
module.exports = {
ResponseUtils: ResponseUtils,
- RequestUtils: RequestUtils
-
-}
+ RequestUtils: RequestUtils,
+ MessageUtils: MessageUtils,
+ parseUrl: parseUrl,
+ parseHttpVersion: parseHttpVersion,
+ isValidHttpVersion: isValidHttpVersion
+};
-},{"lodash":"lodash"}],18:[function(require,module,exports){
+},{"jquery":"jquery","lodash":"lodash"}],24:[function(require,module,exports){
var _ = require("lodash");
var $ = require("jquery");
@@ -4497,7 +5500,7 @@ module.exports = {
FlowStore: FlowStore
};
-},{"../actions.js":2,"../dispatcher.js":15,"../utils.js":20,"events":1,"jquery":"jquery","lodash":"lodash"}],19:[function(require,module,exports){
+},{"../actions.js":2,"../dispatcher.js":21,"../utils.js":26,"events":1,"jquery":"jquery","lodash":"lodash"}],25:[function(require,module,exports){
var EventEmitter = require('events').EventEmitter;
var _ = require("lodash");
@@ -4514,8 +5517,6 @@ var default_filt = function (elem) {
function StoreView(store, filt, sortfun) {
EventEmitter.call(this);
- filt = filt || default_filt;
- sortfun = sortfun || default_sort;
this.store = store;
@@ -4537,12 +5538,13 @@ _.extend(StoreView.prototype, EventEmitter.prototype, {
this.store.removeListener("update", this.update);
this.store.removeListener("remove", this.remove);
this.store.removeListener("recalculate", this.recalculate);
+ this.removeAllListeners();
},
recalculate: function (filt, sortfun) {
- filt = filt || default_filt;
- sortfun = sortfun || default_sort;
+ filt = filt || this.filt || default_filt;
+ sortfun = sortfun || this.sortfun || default_sort;
filt = filt.bind(this);
- sortfun = sortfun.bind(this)
+ sortfun = sortfun.bind(this);
this.filt = filt;
this.sortfun = sortfun;
@@ -4615,9 +5617,14 @@ module.exports = {
StoreView: StoreView
};
-},{"../utils.js":20,"events":1,"lodash":"lodash"}],20:[function(require,module,exports){
+},{"../utils.js":26,"events":1,"lodash":"lodash"}],26:[function(require,module,exports){
var $ = require("jquery");
var _ = require("lodash");
+var actions = require("./actions.js");
+
+window.$ = $;
+window._ = _;
+window.React = require("react");
var Key = {
UP: 38,
@@ -4633,6 +5640,7 @@ var Key = {
TAB: 9,
SPACE: 32,
BACKSPACE: 8,
+ SHIFT: 16
};
// Add A-Z
for (var i = 65; i <= 90; i++) {
@@ -4644,17 +5652,17 @@ var formatSize = function (bytes) {
if (bytes === 0)
return "0";
var prefix = ["b", "kb", "mb", "gb", "tb"];
- for (var i = 0; i < prefix.length; i++){
- if (Math.pow(1024, i + 1) > bytes){
+ for (var i = 0; i < prefix.length; i++) {
+ if (Math.pow(1024, i + 1) > bytes) {
break;
}
}
var precision;
- if (bytes%Math.pow(1024, i) === 0)
+ if (bytes % Math.pow(1024, i) === 0)
precision = 0;
else
precision = 1;
- return (bytes/Math.pow(1024, i)).toFixed(precision) + prefix[i];
+ return (bytes / Math.pow(1024, i)).toFixed(precision) + prefix[i];
};
@@ -4676,21 +5684,20 @@ var formatTimeStamp = function (seconds) {
return ts.replace("T", " ").replace("Z", "");
};
-
// At some places, we need to sort strings alphabetically descending,
// but we can only provide a key function.
// This beauty "reverses" a JS string.
var end = String.fromCharCode(0xffff);
-function reverseString(s){
+function reverseString(s) {
return String.fromCharCode.apply(String,
- _.map(s.split(""), function (c) {
- return 0xffff - c.charCodeAt();
- })
- ) + end;
+ _.map(s.split(""), function (c) {
+ return 0xffff - c.charCodeAt(0);
+ })
+ ) + end;
}
function getCookie(name) {
- var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
+ var r = document.cookie.match(new RegExp("\\b" + name + "=([^;]*)\\b"));
return r ? r[1] : undefined;
}
var xsrf = $.param({_xsrf: getCookie("_xsrf")});
@@ -4698,19 +5705,22 @@ var xsrf = $.param({_xsrf: getCookie("_xsrf")});
//Tornado XSRF Protection.
$.ajaxPrefilter(function (options) {
if (["post", "put", "delete"].indexOf(options.type.toLowerCase()) >= 0 && options.url[0] === "/") {
- if (options.data) {
- options.data += ("&" + xsrf);
+ if(options.url.indexOf("?") === -1){
+ options.url += "?" + xsrf;
} else {
- options.data = xsrf;
+ options.url += "&" + xsrf;
}
}
});
// Log AJAX Errors
$(document).ajaxError(function (event, jqXHR, ajaxSettings, thrownError) {
+ if (thrownError === "abort") {
+ return;
+ }
var message = jqXHR.responseText;
- console.error(message, arguments);
- EventLogActions.add_event(thrownError + ": " + message);
- window.alert(message);
+ console.error(thrownError, message, arguments);
+ actions.EventLogActions.add_event(thrownError + ": " + message);
+ alert(message);
});
module.exports = {
@@ -4718,10 +5728,10 @@ module.exports = {
formatTimeDelta: formatTimeDelta,
formatTimeStamp: formatTimeStamp,
reverseString: reverseString,
- Key: Key
+ Key: Key,
};
-},{"jquery":"jquery","lodash":"lodash"}]},{},[3])
+},{"./actions.js":2,"jquery":"jquery","lodash":"lodash","react":"react"}]},{},[3])
//# sourceMappingURL=app.js.map \ No newline at end of file
diff --git a/libmproxy/web/static/vendor.css b/libmproxy/web/static/vendor.css
index 149372c8..a170c49a 100644
--- a/libmproxy/web/static/vendor.css
+++ b/libmproxy/web/static/vendor.css
@@ -945,12 +945,24 @@ th {
.glyphicon-bitcoin:before {
content: "\e227";
}
+.glyphicon-btc:before {
+ content: "\e227";
+}
+.glyphicon-xbt:before {
+ content: "\e227";
+}
.glyphicon-yen:before {
content: "\00a5";
}
+.glyphicon-jpy:before {
+ content: "\00a5";
+}
.glyphicon-ruble:before {
content: "\20bd";
}
+.glyphicon-rub:before {
+ content: "\20bd";
+}
.glyphicon-scale:before {
content: "\e230";
}
@@ -1147,6 +1159,9 @@ hr {
overflow: visible;
clip: auto;
}
+[role="button"] {
+ cursor: pointer;
+}
h1,
h2,
h3,
@@ -2548,10 +2563,13 @@ output {
.form-control[disabled],
.form-control[readonly],
fieldset[disabled] .form-control {
- cursor: not-allowed;
background-color: #eeeeee;
opacity: 1;
}
+.form-control[disabled],
+fieldset[disabled] .form-control {
+ cursor: not-allowed;
+}
textarea.form-control {
height: auto;
}
@@ -2618,6 +2636,7 @@ input[type="search"] {
}
.radio-inline,
.checkbox-inline {
+ position: relative;
display: inline-block;
padding-left: 20px;
margin-bottom: 0;
@@ -2654,6 +2673,7 @@ fieldset[disabled] .checkbox label {
padding-top: 7px;
padding-bottom: 7px;
margin-bottom: 0;
+ min-height: 34px;
}
.form-control-static.input-lg,
.form-control-static.input-sm {
@@ -2695,6 +2715,7 @@ select[multiple].form-group-sm .form-control {
padding: 5px 10px;
font-size: 12px;
line-height: 1.5;
+ min-height: 32px;
}
.input-lg {
height: 46px;
@@ -2731,6 +2752,7 @@ select[multiple].form-group-lg .form-control {
padding: 10px 16px;
font-size: 18px;
line-height: 1.3333333;
+ min-height: 38px;
}
.has-feedback {
position: relative;
@@ -3348,11 +3370,9 @@ input[type="button"].btn-block {
}
.collapse {
display: none;
- visibility: hidden;
}
.collapse.in {
display: block;
- visibility: visible;
}
tr.collapse.in {
display: table-row;
@@ -3377,7 +3397,7 @@ tbody.collapse.in {
height: 0;
margin-left: 2px;
vertical-align: middle;
- border-top: 4px solid;
+ border-top: 4px dashed;
border-right: 4px solid transparent;
border-left: 4px solid transparent;
}
@@ -4016,11 +4036,9 @@ select[multiple].input-group-sm > .input-group-btn > .btn {
}
.tab-content > .tab-pane {
display: none;
- visibility: hidden;
}
.tab-content > .active {
display: block;
- visibility: visible;
}
.nav-tabs .dropdown-menu {
margin-top: -1px;
@@ -4062,7 +4080,6 @@ select[multiple].input-group-sm > .input-group-btn > .btn {
}
.navbar-collapse.collapse {
display: block !important;
- visibility: visible !important;
height: auto !important;
padding-bottom: 0;
overflow: visible !important;
@@ -4791,7 +4808,8 @@ a.label:focus {
position: relative;
top: -1px;
}
-.btn-xs .badge {
+.btn-xs .badge,
+.btn-group-xs > .btn .badge {
top: 0;
padding: 1px 5px;
}
@@ -5614,10 +5632,10 @@ a.list-group-item-danger.active:focus {
width: 100%;
border: 0;
}
-.embed-responsive.embed-responsive-16by9 {
+.embed-responsive-16by9 {
padding-bottom: 56.25%;
}
-.embed-responsive.embed-responsive-4by3 {
+.embed-responsive-4by3 {
padding-bottom: 75%;
}
.well {
@@ -5678,7 +5696,7 @@ button.close {
right: 0;
bottom: 0;
left: 0;
- z-index: 1040;
+ z-index: 1050;
-webkit-overflow-scrolling: touch;
outline: 0;
}
@@ -5719,10 +5737,12 @@ button.close {
outline: 0;
}
.modal-backdrop {
- position: absolute;
+ position: fixed;
top: 0;
right: 0;
+ bottom: 0;
left: 0;
+ z-index: 1040;
background-color: #000000;
}
.modal-backdrop.fade {
@@ -5793,7 +5813,6 @@ button.close {
position: absolute;
z-index: 1070;
display: block;
- visibility: visible;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 12px;
font-weight: normal;
@@ -6316,7 +6335,6 @@ button.close {
}
.hidden {
display: none !important;
- visibility: hidden !important;
}
.affix {
position: fixed;
diff --git a/libmproxy/web/static/vendor.js b/libmproxy/web/static/vendor.js
index d98e50d9..1e83ac50 100644
--- a/libmproxy/web/static/vendor.js
+++ b/libmproxy/web/static/vendor.js
@@ -394,49 +394,432 @@ var invariant = function(condition, format, a, b, c, d, e, f) {
module.exports = invariant;
},{}],4:[function(require,module,exports){
-"use strict";
+module.exports = require('./lib/');
-/**
- * Represents a cancellation caused by navigating away
- * before the previous transition has fully resolved.
- */
-function Cancellation() {}
+},{"./lib/":5}],5:[function(require,module,exports){
+// Load modules
-module.exports = Cancellation;
-},{}],5:[function(require,module,exports){
-"use strict";
+var Stringify = require('./stringify');
+var Parse = require('./parse');
-var warning = require("react/lib/warning");
-var invariant = require("react/lib/invariant");
-function checkPropTypes(componentName, propTypes, props) {
- for (var propName in propTypes) {
- if (propTypes.hasOwnProperty(propName)) {
- var error = propTypes[propName](props, propName, componentName);
+// Declare internals
- if (error instanceof Error) warning(false, error.message);
+var internals = {};
+
+
+module.exports = {
+ stringify: Stringify,
+ parse: Parse
+};
+
+},{"./parse":6,"./stringify":7}],6:[function(require,module,exports){
+// Load modules
+
+var Utils = require('./utils');
+
+
+// Declare internals
+
+var internals = {
+ delimiter: '&',
+ depth: 5,
+ arrayLimit: 20,
+ parameterLimit: 1000
+};
+
+
+internals.parseValues = function (str, options) {
+
+ var obj = {};
+ var parts = str.split(options.delimiter, options.parameterLimit === Infinity ? undefined : options.parameterLimit);
+
+ for (var i = 0, il = parts.length; i < il; ++i) {
+ var part = parts[i];
+ var pos = part.indexOf(']=') === -1 ? part.indexOf('=') : part.indexOf(']=') + 1;
+
+ if (pos === -1) {
+ obj[Utils.decode(part)] = '';
+ }
+ else {
+ var key = Utils.decode(part.slice(0, pos));
+ var val = Utils.decode(part.slice(pos + 1));
+
+ if (Object.prototype.hasOwnProperty(key)) {
+ continue;
+ }
+
+ if (!obj.hasOwnProperty(key)) {
+ obj[key] = val;
+ }
+ else {
+ obj[key] = [].concat(obj[key]).concat(val);
+ }
+ }
}
- }
-}
-var Configuration = {
+ return obj;
+};
+
- statics: {
+internals.parseObject = function (chain, val, options) {
- validateProps: function validateProps(props) {
- checkPropTypes(this.displayName, this.propTypes, props);
+ if (!chain.length) {
+ return val;
}
- },
+ var root = chain.shift();
- render: function render() {
- invariant(false, "%s elements are for router configuration only and should not be rendered", this.constructor.displayName);
- }
+ var obj = {};
+ if (root === '[]') {
+ obj = [];
+ obj = obj.concat(internals.parseObject(chain, val, options));
+ }
+ else {
+ var cleanRoot = root[0] === '[' && root[root.length - 1] === ']' ? root.slice(1, root.length - 1) : root;
+ var index = parseInt(cleanRoot, 10);
+ var indexString = '' + index;
+ if (!isNaN(index) &&
+ root !== cleanRoot &&
+ indexString === cleanRoot &&
+ index >= 0 &&
+ index <= options.arrayLimit) {
+
+ obj = [];
+ obj[index] = internals.parseObject(chain, val, options);
+ }
+ else {
+ obj[cleanRoot] = internals.parseObject(chain, val, options);
+ }
+ }
+
+ return obj;
+};
+
+
+internals.parseKeys = function (key, val, options) {
+
+ if (!key) {
+ return;
+ }
+
+ // The regex chunks
+
+ var parent = /^([^\[\]]*)/;
+ var child = /(\[[^\[\]]*\])/g;
+
+ // Get the parent
+
+ var segment = parent.exec(key);
+
+ // Don't allow them to overwrite object prototype properties
+
+ if (Object.prototype.hasOwnProperty(segment[1])) {
+ return;
+ }
+
+ // Stash the parent if it exists
+
+ var keys = [];
+ if (segment[1]) {
+ keys.push(segment[1]);
+ }
+
+ // Loop through children appending to the array until we hit depth
+ var i = 0;
+ while ((segment = child.exec(key)) !== null && i < options.depth) {
+
+ ++i;
+ if (!Object.prototype.hasOwnProperty(segment[1].replace(/\[|\]/g, ''))) {
+ keys.push(segment[1]);
+ }
+ }
+
+ // If there's a remainder, just add whatever is left
+
+ if (segment) {
+ keys.push('[' + key.slice(segment.index) + ']');
+ }
+
+ return internals.parseObject(keys, val, options);
};
-module.exports = Configuration;
-},{"react/lib/invariant":182,"react/lib/warning":202}],6:[function(require,module,exports){
+
+module.exports = function (str, options) {
+
+ if (str === '' ||
+ str === null ||
+ typeof str === 'undefined') {
+
+ return {};
+ }
+
+ options = options || {};
+ options.delimiter = typeof options.delimiter === 'string' || Utils.isRegExp(options.delimiter) ? options.delimiter : internals.delimiter;
+ options.depth = typeof options.depth === 'number' ? options.depth : internals.depth;
+ options.arrayLimit = typeof options.arrayLimit === 'number' ? options.arrayLimit : internals.arrayLimit;
+ options.parameterLimit = typeof options.parameterLimit === 'number' ? options.parameterLimit : internals.parameterLimit;
+
+ var tempObj = typeof str === 'string' ? internals.parseValues(str, options) : str;
+ var obj = {};
+
+ // Iterate over the keys and setup the new object
+
+ var keys = Object.keys(tempObj);
+ for (var i = 0, il = keys.length; i < il; ++i) {
+ var key = keys[i];
+ var newObj = internals.parseKeys(key, tempObj[key], options);
+ obj = Utils.merge(obj, newObj);
+ }
+
+ return Utils.compact(obj);
+};
+
+},{"./utils":8}],7:[function(require,module,exports){
+// Load modules
+
+var Utils = require('./utils');
+
+
+// Declare internals
+
+var internals = {
+ delimiter: '&',
+ arrayPrefixGenerators: {
+ brackets: function (prefix, key) {
+ return prefix + '[]';
+ },
+ indices: function (prefix, key) {
+ return prefix + '[' + key + ']';
+ },
+ repeat: function (prefix, key) {
+ return prefix;
+ }
+ }
+};
+
+
+internals.stringify = function (obj, prefix, generateArrayPrefix) {
+
+ if (Utils.isBuffer(obj)) {
+ obj = obj.toString();
+ }
+ else if (obj instanceof Date) {
+ obj = obj.toISOString();
+ }
+ else if (obj === null) {
+ obj = '';
+ }
+
+ if (typeof obj === 'string' ||
+ typeof obj === 'number' ||
+ typeof obj === 'boolean') {
+
+ return [encodeURIComponent(prefix) + '=' + encodeURIComponent(obj)];
+ }
+
+ var values = [];
+
+ if (typeof obj === 'undefined') {
+ return values;
+ }
+
+ var objKeys = Object.keys(obj);
+ for (var i = 0, il = objKeys.length; i < il; ++i) {
+ var key = objKeys[i];
+ if (Array.isArray(obj)) {
+ values = values.concat(internals.stringify(obj[key], generateArrayPrefix(prefix, key), generateArrayPrefix));
+ }
+ else {
+ values = values.concat(internals.stringify(obj[key], prefix + '[' + key + ']', generateArrayPrefix));
+ }
+ }
+
+ return values;
+};
+
+
+module.exports = function (obj, options) {
+
+ options = options || {};
+ var delimiter = typeof options.delimiter === 'undefined' ? internals.delimiter : options.delimiter;
+
+ var keys = [];
+
+ if (typeof obj !== 'object' ||
+ obj === null) {
+
+ return '';
+ }
+
+ var arrayFormat;
+ if (options.arrayFormat in internals.arrayPrefixGenerators) {
+ arrayFormat = options.arrayFormat;
+ }
+ else if ('indices' in options) {
+ arrayFormat = options.indices ? 'indices' : 'repeat';
+ }
+ else {
+ arrayFormat = 'indices';
+ }
+
+ var generateArrayPrefix = internals.arrayPrefixGenerators[arrayFormat];
+
+ var objKeys = Object.keys(obj);
+ for (var i = 0, il = objKeys.length; i < il; ++i) {
+ var key = objKeys[i];
+ keys = keys.concat(internals.stringify(obj[key], key, generateArrayPrefix));
+ }
+
+ return keys.join(delimiter);
+};
+
+},{"./utils":8}],8:[function(require,module,exports){
+// Load modules
+
+
+// Declare internals
+
+var internals = {};
+
+
+exports.arrayToObject = function (source) {
+
+ var obj = {};
+ for (var i = 0, il = source.length; i < il; ++i) {
+ if (typeof source[i] !== 'undefined') {
+
+ obj[i] = source[i];
+ }
+ }
+
+ return obj;
+};
+
+
+exports.merge = function (target, source) {
+
+ if (!source) {
+ return target;
+ }
+
+ if (typeof source !== 'object') {
+ if (Array.isArray(target)) {
+ target.push(source);
+ }
+ else {
+ target[source] = true;
+ }
+
+ return target;
+ }
+
+ if (typeof target !== 'object') {
+ target = [target].concat(source);
+ return target;
+ }
+
+ if (Array.isArray(target) &&
+ !Array.isArray(source)) {
+
+ target = exports.arrayToObject(target);
+ }
+
+ var keys = Object.keys(source);
+ for (var k = 0, kl = keys.length; k < kl; ++k) {
+ var key = keys[k];
+ var value = source[key];
+
+ if (!target[key]) {
+ target[key] = value;
+ }
+ else {
+ target[key] = exports.merge(target[key], value);
+ }
+ }
+
+ return target;
+};
+
+
+exports.decode = function (str) {
+
+ try {
+ return decodeURIComponent(str.replace(/\+/g, ' '));
+ } catch (e) {
+ return str;
+ }
+};
+
+
+exports.compact = function (obj, refs) {
+
+ if (typeof obj !== 'object' ||
+ obj === null) {
+
+ return obj;
+ }
+
+ refs = refs || [];
+ var lookup = refs.indexOf(obj);
+ if (lookup !== -1) {
+ return refs[lookup];
+ }
+
+ refs.push(obj);
+
+ if (Array.isArray(obj)) {
+ var compacted = [];
+
+ for (var i = 0, il = obj.length; i < il; ++i) {
+ if (typeof obj[i] !== 'undefined') {
+ compacted.push(obj[i]);
+ }
+ }
+
+ return compacted;
+ }
+
+ var keys = Object.keys(obj);
+ for (i = 0, il = keys.length; i < il; ++i) {
+ var key = keys[i];
+ obj[key] = exports.compact(obj[key], refs);
+ }
+
+ return obj;
+};
+
+
+exports.isRegExp = function (obj) {
+ return Object.prototype.toString.call(obj) === '[object RegExp]';
+};
+
+
+exports.isBuffer = function (obj) {
+
+ if (obj === null ||
+ typeof obj === 'undefined') {
+
+ return false;
+ }
+
+ return !!(obj.constructor &&
+ obj.constructor.isBuffer &&
+ obj.constructor.isBuffer(obj));
+};
+
+},{}],9:[function(require,module,exports){
+"use strict";
+
+/**
+ * Represents a cancellation caused by navigating away
+ * before the previous transition has fully resolved.
+ */
+function Cancellation() {}
+
+module.exports = Cancellation;
+},{}],10:[function(require,module,exports){
"use strict";
var invariant = require("react/lib/invariant");
@@ -467,10 +850,10 @@ var History = {
};
module.exports = History;
-},{"react/lib/ExecutionEnvironment":64,"react/lib/invariant":182}],7:[function(require,module,exports){
+},{"react/lib/ExecutionEnvironment":62,"react/lib/invariant":191}],11:[function(require,module,exports){
"use strict";
-var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); };
+var _createClass = (function () { function defineProperties(target, props) { for (var key in props) { var prop = props[key]; prop.configurable = true; if (prop.value) prop.writable = true; } Object.defineProperties(target, props); } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } };
@@ -520,7 +903,7 @@ var Match = (function () {
this.routes = routes;
}
- _prototypeProperties(Match, {
+ _createClass(Match, null, {
findMatch: {
/**
@@ -537,9 +920,7 @@ var Match = (function () {
for (var i = 0, len = routes.length; match == null && i < len; ++i) match = deepSearch(routes[i], pathname, query);
return match;
- },
- writable: true,
- configurable: true
+ }
}
});
@@ -547,11 +928,20 @@ var Match = (function () {
})();
module.exports = Match;
-},{"./PathUtils":10}],8:[function(require,module,exports){
+},{"./PathUtils":13}],12:[function(require,module,exports){
"use strict";
+var warning = require("react/lib/warning");
var PropTypes = require("./PropTypes");
+function deprecatedMethod(routerMethodName, fn) {
+ return function () {
+ warning(false, "Router.Navigation is deprecated. Please use this.context.router." + routerMethodName + "() instead");
+
+ return fn.apply(this, arguments);
+ };
+}
+
/**
* A mixin for components that modify the URL.
*
@@ -559,11 +949,11 @@ var PropTypes = require("./PropTypes");
*
* var MyLink = React.createClass({
* mixins: [ Router.Navigation ],
- * handleClick: function (event) {
+ * handleClick(event) {
* event.preventDefault();
* this.transitionTo('aRoute', { the: 'params' }, { the: 'query' });
* },
- * render: function () {
+ * render() {
* return (
* <a onClick={this.handleClick}>Click me!</a>
* );
@@ -573,97 +963,62 @@ var PropTypes = require("./PropTypes");
var Navigation = {
contextTypes: {
- makePath: PropTypes.func.isRequired,
- makeHref: PropTypes.func.isRequired,
- transitionTo: PropTypes.func.isRequired,
- replaceWith: PropTypes.func.isRequired,
- goBack: PropTypes.func.isRequired
+ router: PropTypes.router.isRequired
},
/**
* Returns an absolute URL path created from the given route
* name, URL parameters, and query values.
*/
- makePath: function makePath(to, params, query) {
- return this.context.makePath(to, params, query);
- },
+ makePath: deprecatedMethod("makePath", function (to, params, query) {
+ return this.context.router.makePath(to, params, query);
+ }),
/**
* Returns a string that may safely be used as the href of a
* link to the route with the given name.
*/
- makeHref: function makeHref(to, params, query) {
- return this.context.makeHref(to, params, query);
- },
+ makeHref: deprecatedMethod("makeHref", function (to, params, query) {
+ return this.context.router.makeHref(to, params, query);
+ }),
/**
* Transitions to the URL specified in the arguments by pushing
* a new URL onto the history stack.
*/
- transitionTo: function transitionTo(to, params, query) {
- this.context.transitionTo(to, params, query);
- },
+ transitionTo: deprecatedMethod("transitionTo", function (to, params, query) {
+ this.context.router.transitionTo(to, params, query);
+ }),
/**
* Transitions to the URL specified in the arguments by replacing
* the current URL in the history stack.
*/
- replaceWith: function replaceWith(to, params, query) {
- this.context.replaceWith(to, params, query);
- },
+ replaceWith: deprecatedMethod("replaceWith", function (to, params, query) {
+ this.context.router.replaceWith(to, params, query);
+ }),
/**
* Transitions to the previous URL.
*/
- goBack: function goBack() {
- return this.context.goBack();
- }
+ goBack: deprecatedMethod("goBack", function () {
+ return this.context.router.goBack();
+ })
};
module.exports = Navigation;
-},{"./PropTypes":11}],9:[function(require,module,exports){
-"use strict";
-
-var PropTypes = require("./PropTypes");
-
-/**
- * Provides the router with context for Router.Navigation.
- */
-var NavigationContext = {
-
- childContextTypes: {
- makePath: PropTypes.func.isRequired,
- makeHref: PropTypes.func.isRequired,
- transitionTo: PropTypes.func.isRequired,
- replaceWith: PropTypes.func.isRequired,
- goBack: PropTypes.func.isRequired
- },
-
- getChildContext: function getChildContext() {
- return {
- makePath: this.constructor.makePath.bind(this.constructor),
- makeHref: this.constructor.makeHref.bind(this.constructor),
- transitionTo: this.constructor.transitionTo.bind(this.constructor),
- replaceWith: this.constructor.replaceWith.bind(this.constructor),
- goBack: this.constructor.goBack.bind(this.constructor)
- };
- }
-
-};
-
-module.exports = NavigationContext;
-},{"./PropTypes":11}],10:[function(require,module,exports){
+},{"./PropTypes":14,"react/lib/warning":212}],13:[function(require,module,exports){
"use strict";
var invariant = require("react/lib/invariant");
-var merge = require("qs/lib/utils").merge;
+var objectAssign = require("object-assign");
var qs = require("qs");
var paramCompileMatcher = /:([a-zA-Z_$][a-zA-Z0-9_$]*)|[*.()\[\]\\+|{}^$]/g;
var paramInjectMatcher = /:([a-zA-Z_$][a-zA-Z0-9_$?]*[?]?)|[*]/g;
var paramInjectTrailingSlashMatcher = /\/\/\?|\/\?\/|\/\?/g;
-var queryMatcher = /\?(.+)/;
+var queryMatcher = /\?(.*)$/;
var _compiledPatterns = {};
@@ -795,39 +1150,51 @@ var PathUtils = {
withQuery: function withQuery(path, query) {
var existingQuery = PathUtils.extractQuery(path);
- if (existingQuery) query = query ? merge(existingQuery, query) : existingQuery;
+ if (existingQuery) query = query ? objectAssign(existingQuery, query) : existingQuery;
- var queryString = qs.stringify(query, { indices: false });
+ var queryString = qs.stringify(query, { arrayFormat: "brackets" });
if (queryString) {
return PathUtils.withoutQuery(path) + "?" + queryString;
- }return path;
+ }return PathUtils.withoutQuery(path);
}
};
module.exports = PathUtils;
-},{"qs":38,"qs/lib/utils":42,"react/lib/invariant":182}],11:[function(require,module,exports){
+},{"object-assign":41,"qs":4,"react/lib/invariant":191}],14:[function(require,module,exports){
"use strict";
var assign = require("react/lib/Object.assign");
var ReactPropTypes = require("react").PropTypes;
+var Route = require("./Route");
-var PropTypes = assign({
+var PropTypes = assign({}, ReactPropTypes, {
/**
- * Requires that the value of a prop be falsy.
+ * Indicates that a prop should be falsy.
*/
falsy: function falsy(props, propName, componentName) {
if (props[propName]) {
return new Error("<" + componentName + "> may not have a \"" + propName + "\" prop");
}
- }
+ },
-}, ReactPropTypes);
+ /**
+ * Indicates that a prop should be a Route object.
+ */
+ route: ReactPropTypes.instanceOf(Route),
+
+ /**
+ * Indicates that a prop should be a Router object.
+ */
+ //router: ReactPropTypes.instanceOf(Router) // TODO
+ router: ReactPropTypes.func
+
+});
module.exports = PropTypes;
-},{"react":"react","react/lib/Object.assign":70}],12:[function(require,module,exports){
+},{"./Route":16,"react":"react","react/lib/Object.assign":69}],15:[function(require,module,exports){
"use strict";
/**
@@ -840,10 +1207,10 @@ function Redirect(to, params, query) {
}
module.exports = Redirect;
-},{}],13:[function(require,module,exports){
+},{}],16:[function(require,module,exports){
"use strict";
-var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); };
+var _createClass = (function () { function defineProperties(target, props) { for (var key in props) { var prop = props[key]; prop.configurable = true; if (prop.value) prop.writable = true; } Object.defineProperties(target, props); } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } };
@@ -869,7 +1236,33 @@ var Route = (function () {
this.handler = handler;
}
- _prototypeProperties(Route, {
+ _createClass(Route, {
+ appendChild: {
+
+ /**
+ * Appends the given route to this route's child routes.
+ */
+
+ value: function appendChild(route) {
+ invariant(route instanceof Route, "route.appendChild must use a valid Route");
+
+ if (!this.childRoutes) this.childRoutes = [];
+
+ this.childRoutes.push(route);
+ }
+ },
+ toString: {
+ value: function toString() {
+ var string = "<Route";
+
+ if (this.name) string += " name=\"" + this.name + "\"";
+
+ string += " path=\"" + this.path + "\">";
+
+ return string;
+ }
+ }
+ }, {
createRoute: {
/**
@@ -928,7 +1321,7 @@ var Route = (function () {
if (path && !(options.isDefault || options.isNotFound)) {
if (PathUtils.isAbsolute(path)) {
if (parentRoute) {
- invariant(parentRoute.paramNames.length === 0, "You cannot nest path \"%s\" inside \"%s\"; the parent requires URL parameters", path, parentRoute.path);
+ invariant(path === parentRoute.path || parentRoute.paramNames.length === 0, "You cannot nest path \"%s\" inside \"%s\"; the parent requires URL parameters", path, parentRoute.path);
}
} else if (parentRoute) {
// Relative paths extend their parent.
@@ -968,9 +1361,7 @@ var Route = (function () {
}
return route;
- },
- writable: true,
- configurable: true
+ }
},
createDefaultRoute: {
@@ -981,9 +1372,7 @@ var Route = (function () {
value: function createDefaultRoute(options) {
return Route.createRoute(assign({}, options, { isDefault: true }));
- },
- writable: true,
- configurable: true
+ }
},
createNotFoundRoute: {
@@ -994,9 +1383,7 @@ var Route = (function () {
value: function createNotFoundRoute(options) {
return Route.createRoute(assign({}, options, { isNotFound: true }));
- },
- writable: true,
- configurable: true
+ }
},
createRedirect: {
@@ -1020,39 +1407,7 @@ var Route = (function () {
transition.redirect(options.to, options.params || params, options.query || query);
}
}));
- },
- writable: true,
- configurable: true
- }
- }, {
- appendChild: {
-
- /**
- * Appends the given route to this route's child routes.
- */
-
- value: function appendChild(route) {
- invariant(route instanceof Route, "route.appendChild must use a valid Route");
-
- if (!this.childRoutes) this.childRoutes = [];
-
- this.childRoutes.push(route);
- },
- writable: true,
- configurable: true
- },
- toString: {
- value: function toString() {
- var string = "<Route";
-
- if (this.name) string += " name=\"" + this.name + "\"";
-
- string += " path=\"" + this.path + "\">";
-
- return string;
- },
- writable: true,
- configurable: true
+ }
}
});
@@ -1060,62 +1415,7 @@ var Route = (function () {
})();
module.exports = Route;
-},{"./PathUtils":10,"react/lib/Object.assign":70,"react/lib/invariant":182,"react/lib/warning":202}],14:[function(require,module,exports){
-"use strict";
-
-var React = require("react");
-var assign = require("react/lib/Object.assign");
-var PropTypes = require("./PropTypes");
-
-var REF_NAME = "__routeHandler__";
-
-var RouteHandlerMixin = {
-
- contextTypes: {
- getRouteAtDepth: PropTypes.func.isRequired,
- setRouteComponentAtDepth: PropTypes.func.isRequired,
- routeHandlers: PropTypes.array.isRequired
- },
-
- childContextTypes: {
- routeHandlers: PropTypes.array.isRequired
- },
-
- getChildContext: function getChildContext() {
- return {
- routeHandlers: this.context.routeHandlers.concat([this])
- };
- },
-
- componentDidMount: function componentDidMount() {
- this._updateRouteComponent(this.refs[REF_NAME]);
- },
-
- componentDidUpdate: function componentDidUpdate() {
- this._updateRouteComponent(this.refs[REF_NAME]);
- },
-
- componentWillUnmount: function componentWillUnmount() {
- this._updateRouteComponent(null);
- },
-
- _updateRouteComponent: function _updateRouteComponent(component) {
- this.context.setRouteComponentAtDepth(this.getRouteDepth(), component);
- },
-
- getRouteDepth: function getRouteDepth() {
- return this.context.routeHandlers.length;
- },
-
- createChildRouteHandler: function createChildRouteHandler(props) {
- var route = this.context.getRouteAtDepth(this.getRouteDepth());
- return route ? React.createElement(route.handler, assign({}, props || this.props, { ref: REF_NAME })) : null;
- }
-
-};
-
-module.exports = RouteHandlerMixin;
-},{"./PropTypes":11,"react":"react","react/lib/Object.assign":70}],15:[function(require,module,exports){
+},{"./PathUtils":13,"react/lib/Object.assign":69,"react/lib/invariant":191,"react/lib/warning":212}],17:[function(require,module,exports){
"use strict";
var invariant = require("react/lib/invariant");
@@ -1191,11 +1491,20 @@ var ScrollHistory = {
};
module.exports = ScrollHistory;
-},{"./getWindowScrollPosition":30,"react/lib/ExecutionEnvironment":64,"react/lib/invariant":182}],16:[function(require,module,exports){
+},{"./getWindowScrollPosition":32,"react/lib/ExecutionEnvironment":62,"react/lib/invariant":191}],18:[function(require,module,exports){
"use strict";
+var warning = require("react/lib/warning");
var PropTypes = require("./PropTypes");
+function deprecatedMethod(routerMethodName, fn) {
+ return function () {
+ warning(false, "Router.State is deprecated. Please use this.context.router." + routerMethodName + "() instead");
+
+ return fn.apply(this, arguments);
+ };
+}
+
/**
* A mixin for components that need to know the path, routes, URL
* params and query that are currently active.
@@ -1204,7 +1513,7 @@ var PropTypes = require("./PropTypes");
*
* var AboutLink = React.createClass({
* mixins: [ Router.State ],
- * render: function () {
+ * render() {
* var className = this.props.className;
*
* if (this.isActive('about'))
@@ -1217,158 +1526,56 @@ var PropTypes = require("./PropTypes");
var State = {
contextTypes: {
- getCurrentPath: PropTypes.func.isRequired,
- getCurrentRoutes: PropTypes.func.isRequired,
- getCurrentPathname: PropTypes.func.isRequired,
- getCurrentParams: PropTypes.func.isRequired,
- getCurrentQuery: PropTypes.func.isRequired,
- isActive: PropTypes.func.isRequired
+ router: PropTypes.router.isRequired
},
/**
* Returns the current URL path.
*/
- getPath: function getPath() {
- return this.context.getCurrentPath();
- },
-
- /**
- * Returns an array of the routes that are currently active.
- */
- getRoutes: function getRoutes() {
- return this.context.getCurrentRoutes();
- },
+ getPath: deprecatedMethod("getCurrentPath", function () {
+ return this.context.router.getCurrentPath();
+ }),
/**
* Returns the current URL path without the query string.
*/
- getPathname: function getPathname() {
- return this.context.getCurrentPathname();
- },
+ getPathname: deprecatedMethod("getCurrentPathname", function () {
+ return this.context.router.getCurrentPathname();
+ }),
/**
* Returns an object of the URL params that are currently active.
*/
- getParams: function getParams() {
- return this.context.getCurrentParams();
- },
+ getParams: deprecatedMethod("getCurrentParams", function () {
+ return this.context.router.getCurrentParams();
+ }),
/**
* Returns an object of the query params that are currently active.
*/
- getQuery: function getQuery() {
- return this.context.getCurrentQuery();
- },
-
- /**
- * A helper method to determine if a given route, params, and query
- * are active.
- */
- isActive: function isActive(to, params, query) {
- return this.context.isActive(to, params, query);
- }
-
-};
-
-module.exports = State;
-},{"./PropTypes":11}],17:[function(require,module,exports){
-"use strict";
-
-var assign = require("react/lib/Object.assign");
-var PropTypes = require("./PropTypes");
-var PathUtils = require("./PathUtils");
-
-function routeIsActive(activeRoutes, routeName) {
- return activeRoutes.some(function (route) {
- return route.name === routeName;
- });
-}
-
-function paramsAreActive(activeParams, params) {
- for (var property in params) if (String(activeParams[property]) !== String(params[property])) {
- return false;
- }return true;
-}
-
-function queryIsActive(activeQuery, query) {
- for (var property in query) if (String(activeQuery[property]) !== String(query[property])) {
- return false;
- }return true;
-}
-
-/**
- * Provides the router with context for Router.State.
- */
-var StateContext = {
-
- /**
- * Returns the current URL path + query string.
- */
- getCurrentPath: function getCurrentPath() {
- return this.state.path;
- },
-
- /**
- * Returns a read-only array of the currently active routes.
- */
- getCurrentRoutes: function getCurrentRoutes() {
- return this.state.routes.slice(0);
- },
-
- /**
- * Returns the current URL path without the query string.
- */
- getCurrentPathname: function getCurrentPathname() {
- return this.state.pathname;
- },
+ getQuery: deprecatedMethod("getCurrentQuery", function () {
+ return this.context.router.getCurrentQuery();
+ }),
/**
- * Returns a read-only object of the currently active URL parameters.
- */
- getCurrentParams: function getCurrentParams() {
- return assign({}, this.state.params);
- },
-
- /**
- * Returns a read-only object of the currently active query parameters.
+ * Returns an array of the routes that are currently active.
*/
- getCurrentQuery: function getCurrentQuery() {
- return assign({}, this.state.query);
- },
+ getRoutes: deprecatedMethod("getCurrentRoutes", function () {
+ return this.context.router.getCurrentRoutes();
+ }),
/**
- * Returns true if the given route, params, and query are active.
+ * A helper method to determine if a given route, params, and query
+ * are active.
*/
- isActive: function isActive(to, params, query) {
- if (PathUtils.isAbsolute(to)) {
- return to === this.state.path;
- }return routeIsActive(this.state.routes, to) && paramsAreActive(this.state.params, params) && (query == null || queryIsActive(this.state.query, query));
- },
-
- childContextTypes: {
- getCurrentPath: PropTypes.func.isRequired,
- getCurrentRoutes: PropTypes.func.isRequired,
- getCurrentPathname: PropTypes.func.isRequired,
- getCurrentParams: PropTypes.func.isRequired,
- getCurrentQuery: PropTypes.func.isRequired,
- isActive: PropTypes.func.isRequired
- },
-
- getChildContext: function getChildContext() {
- return {
- getCurrentPath: this.getCurrentPath,
- getCurrentRoutes: this.getCurrentRoutes,
- getCurrentPathname: this.getCurrentPathname,
- getCurrentParams: this.getCurrentParams,
- getCurrentQuery: this.getCurrentQuery,
- isActive: this.isActive
- };
- }
+ isActive: deprecatedMethod("isActive", function (to, params, query) {
+ return this.context.router.isActive(to, params, query);
+ })
};
-module.exports = StateContext;
-},{"./PathUtils":10,"./PropTypes":11,"react/lib/Object.assign":70}],18:[function(require,module,exports){
+module.exports = State;
+},{"./PropTypes":14,"react/lib/warning":212}],19:[function(require,module,exports){
"use strict";
/* jshint -W058 */
@@ -1444,7 +1651,7 @@ Transition.to = function (transition, routes, params, query, callback) {
};
module.exports = Transition;
-},{"./Cancellation":4,"./Redirect":12}],19:[function(require,module,exports){
+},{"./Cancellation":9,"./Redirect":15}],20:[function(require,module,exports){
"use strict";
/**
@@ -1470,7 +1677,7 @@ var LocationActions = {
};
module.exports = LocationActions;
-},{}],20:[function(require,module,exports){
+},{}],21:[function(require,module,exports){
"use strict";
var LocationActions = require("../actions/LocationActions");
@@ -1500,7 +1707,7 @@ var ImitateBrowserBehavior = {
};
module.exports = ImitateBrowserBehavior;
-},{"../actions/LocationActions":19}],21:[function(require,module,exports){
+},{"../actions/LocationActions":20}],22:[function(require,module,exports){
"use strict";
/**
@@ -1516,12 +1723,56 @@ var ScrollToTopBehavior = {
};
module.exports = ScrollToTopBehavior;
-},{}],22:[function(require,module,exports){
+},{}],23:[function(require,module,exports){
"use strict";
+var _createClass = (function () { function defineProperties(target, props) { for (var key in props) { var prop = props[key]; prop.configurable = true; if (prop.value) prop.writable = true; } Object.defineProperties(target, props); } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+var _inherits = function (subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; };
+
+var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } };
+
+/**
+ * This component is necessary to get around a context warning
+ * present in React 0.13.0. It sovles this by providing a separation
+ * between the "owner" and "parent" contexts.
+ */
+
var React = require("react");
-var Configuration = require("../Configuration");
+
+var ContextWrapper = (function (_React$Component) {
+ function ContextWrapper() {
+ _classCallCheck(this, ContextWrapper);
+
+ if (_React$Component != null) {
+ _React$Component.apply(this, arguments);
+ }
+ }
+
+ _inherits(ContextWrapper, _React$Component);
+
+ _createClass(ContextWrapper, {
+ render: {
+ value: function render() {
+ return this.props.children;
+ }
+ }
+ });
+
+ return ContextWrapper;
+})(React.Component);
+
+module.exports = ContextWrapper;
+},{"react":"react"}],24:[function(require,module,exports){
+"use strict";
+
+var _inherits = function (subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; };
+
+var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } };
+
var PropTypes = require("../PropTypes");
+var RouteHandler = require("./RouteHandler");
+var Route = require("./Route");
/**
* A <DefaultRoute> component is a special kind of <Route> that
@@ -1529,32 +1780,49 @@ var PropTypes = require("../PropTypes");
* Only one such route may be used at any given level in the
* route hierarchy.
*/
-var DefaultRoute = React.createClass({
-
- displayName: "DefaultRoute",
- mixins: [Configuration],
+var DefaultRoute = (function (_Route) {
+ function DefaultRoute() {
+ _classCallCheck(this, DefaultRoute);
- propTypes: {
- name: PropTypes.string,
- path: PropTypes.falsy,
- children: PropTypes.falsy,
- handler: PropTypes.func.isRequired
+ if (_Route != null) {
+ _Route.apply(this, arguments);
+ }
}
-});
+ _inherits(DefaultRoute, _Route);
+
+ return DefaultRoute;
+})(Route);
+
+// TODO: Include these in the above class definition
+// once we can use ES7 property initializers.
+// https://github.com/babel/babel/issues/619
+
+DefaultRoute.propTypes = {
+ name: PropTypes.string,
+ path: PropTypes.falsy,
+ children: PropTypes.falsy,
+ handler: PropTypes.func.isRequired
+};
+
+DefaultRoute.defaultProps = {
+ handler: RouteHandler
+};
module.exports = DefaultRoute;
-},{"../Configuration":5,"../PropTypes":11,"react":"react"}],23:[function(require,module,exports){
+},{"../PropTypes":14,"./Route":28,"./RouteHandler":29}],25:[function(require,module,exports){
"use strict";
+var _createClass = (function () { function defineProperties(target, props) { for (var key in props) { var prop = props[key]; prop.configurable = true; if (prop.value) prop.writable = true; } Object.defineProperties(target, props); } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+var _inherits = function (subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; };
+
+var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } };
+
var React = require("react");
-var classSet = require("react/lib/cx");
var assign = require("react/lib/Object.assign");
-var Navigation = require("../Navigation");
-var State = require("../State");
var PropTypes = require("../PropTypes");
-var Route = require("../Route");
function isLeftClickEvent(event) {
return event.button === 0;
@@ -1582,88 +1850,116 @@ function isModifiedEvent(event) {
*
* <Link to="showPost" params={{ postID: "123" }} query={{ show:true }}/>
*/
-var Link = React.createClass({
- displayName: "Link",
+var Link = (function (_React$Component) {
+ function Link() {
+ _classCallCheck(this, Link);
- mixins: [Navigation, State],
+ if (_React$Component != null) {
+ _React$Component.apply(this, arguments);
+ }
+ }
- propTypes: {
- activeClassName: PropTypes.string.isRequired,
- to: PropTypes.oneOfType([PropTypes.string, PropTypes.instanceOf(Route)]),
- params: PropTypes.object,
- query: PropTypes.object,
- activeStyle: PropTypes.object,
- onClick: PropTypes.func
- },
+ _inherits(Link, _React$Component);
- getDefaultProps: function getDefaultProps() {
- return {
- activeClassName: "active"
- };
- },
+ _createClass(Link, {
+ handleClick: {
+ value: function handleClick(event) {
+ var allowTransition = true;
+ var clickResult;
- handleClick: function handleClick(event) {
- var allowTransition = true;
- var clickResult;
+ if (this.props.onClick) clickResult = this.props.onClick(event);
- if (this.props.onClick) clickResult = this.props.onClick(event);
+ if (isModifiedEvent(event) || !isLeftClickEvent(event)) {
+ return;
+ }if (clickResult === false || event.defaultPrevented === true) allowTransition = false;
- if (isModifiedEvent(event) || !isLeftClickEvent(event)) {
- return;
- }if (clickResult === false || event.defaultPrevented === true) allowTransition = false;
+ event.preventDefault();
- event.preventDefault();
+ if (allowTransition) this.context.router.transitionTo(this.props.to, this.props.params, this.props.query);
+ }
+ },
+ getHref: {
- if (allowTransition) this.transitionTo(this.props.to, this.props.params, this.props.query);
- },
+ /**
+ * Returns the value of the "href" attribute to use on the DOM element.
+ */
- /**
- * Returns the value of the "href" attribute to use on the DOM element.
- */
- getHref: function getHref() {
- return this.makeHref(this.props.to, this.props.params, this.props.query);
- },
+ value: function getHref() {
+ return this.context.router.makeHref(this.props.to, this.props.params, this.props.query);
+ }
+ },
+ getClassName: {
- /**
- * Returns the value of the "class" attribute to use on the DOM element, which contains
- * the value of the activeClassName property when this <Link> is active.
- */
- getClassName: function getClassName() {
- var classNames = {};
+ /**
+ * Returns the value of the "class" attribute to use on the DOM element, which contains
+ * the value of the activeClassName property when this <Link> is active.
+ */
- if (this.props.className) classNames[this.props.className] = true;
+ value: function getClassName() {
+ var className = this.props.className;
- if (this.getActiveState()) classNames[this.props.activeClassName] = true;
+ if (this.getActiveState()) className += " " + this.props.activeClassName;
- return classSet(classNames);
- },
+ return className;
+ }
+ },
+ getActiveState: {
+ value: function getActiveState() {
+ return this.context.router.isActive(this.props.to, this.props.params, this.props.query);
+ }
+ },
+ render: {
+ value: function render() {
+ var props = assign({}, this.props, {
+ href: this.getHref(),
+ className: this.getClassName(),
+ onClick: this.handleClick.bind(this)
+ });
- getActiveState: function getActiveState() {
- return this.isActive(this.props.to, this.props.params, this.props.query);
- },
+ if (props.activeStyle && this.getActiveState()) props.style = props.activeStyle;
- render: function render() {
- var props = assign({}, this.props, {
- href: this.getHref(),
- className: this.getClassName(),
- onClick: this.handleClick
- });
+ return React.DOM.a(props, this.props.children);
+ }
+ }
+ });
- if (props.activeStyle && this.getActiveState()) props.style = props.activeStyle;
+ return Link;
+})(React.Component);
- return React.DOM.a(props, this.props.children);
- }
+// TODO: Include these in the above class definition
+// once we can use ES7 property initializers.
+// https://github.com/babel/babel/issues/619
-});
+Link.contextTypes = {
+ router: PropTypes.router.isRequired
+};
+
+Link.propTypes = {
+ activeClassName: PropTypes.string.isRequired,
+ to: PropTypes.oneOfType([PropTypes.string, PropTypes.route]).isRequired,
+ params: PropTypes.object,
+ query: PropTypes.object,
+ activeStyle: PropTypes.object,
+ onClick: PropTypes.func
+};
+
+Link.defaultProps = {
+ activeClassName: "active",
+ className: ""
+};
module.exports = Link;
-},{"../Navigation":8,"../PropTypes":11,"../Route":13,"../State":16,"react":"react","react/lib/Object.assign":70,"react/lib/cx":160}],24:[function(require,module,exports){
+},{"../PropTypes":14,"react":"react","react/lib/Object.assign":69}],26:[function(require,module,exports){
"use strict";
-var React = require("react");
-var Configuration = require("../Configuration");
+var _inherits = function (subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; };
+
+var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } };
+
var PropTypes = require("../PropTypes");
+var RouteHandler = require("./RouteHandler");
+var Route = require("./Route");
/**
* A <NotFoundRoute> is a special kind of <Route> that
@@ -1672,56 +1968,95 @@ var PropTypes = require("../PropTypes");
* Only one such route may be used at any given level in the
* route hierarchy.
*/
-var NotFoundRoute = React.createClass({
-
- displayName: "NotFoundRoute",
- mixins: [Configuration],
+var NotFoundRoute = (function (_Route) {
+ function NotFoundRoute() {
+ _classCallCheck(this, NotFoundRoute);
- propTypes: {
- name: PropTypes.string,
- path: PropTypes.falsy,
- children: PropTypes.falsy,
- handler: PropTypes.func.isRequired
+ if (_Route != null) {
+ _Route.apply(this, arguments);
+ }
}
-});
+ _inherits(NotFoundRoute, _Route);
+
+ return NotFoundRoute;
+})(Route);
+
+// TODO: Include these in the above class definition
+// once we can use ES7 property initializers.
+// https://github.com/babel/babel/issues/619
+
+NotFoundRoute.propTypes = {
+ name: PropTypes.string,
+ path: PropTypes.falsy,
+ children: PropTypes.falsy,
+ handler: PropTypes.func.isRequired
+};
+
+NotFoundRoute.defaultProps = {
+ handler: RouteHandler
+};
module.exports = NotFoundRoute;
-},{"../Configuration":5,"../PropTypes":11,"react":"react"}],25:[function(require,module,exports){
+},{"../PropTypes":14,"./Route":28,"./RouteHandler":29}],27:[function(require,module,exports){
"use strict";
-var React = require("react");
-var Configuration = require("../Configuration");
+var _inherits = function (subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; };
+
+var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } };
+
var PropTypes = require("../PropTypes");
+var Route = require("./Route");
/**
* A <Redirect> component is a special kind of <Route> that always
* redirects to another route when it matches.
*/
-var Redirect = React.createClass({
- displayName: "Redirect",
+var Redirect = (function (_Route) {
+ function Redirect() {
+ _classCallCheck(this, Redirect);
- mixins: [Configuration],
-
- propTypes: {
- path: PropTypes.string,
- from: PropTypes.string, // Alias for path.
- to: PropTypes.string,
- handler: PropTypes.falsy
+ if (_Route != null) {
+ _Route.apply(this, arguments);
+ }
}
-});
+ _inherits(Redirect, _Route);
+
+ return Redirect;
+})(Route);
+
+// TODO: Include these in the above class definition
+// once we can use ES7 property initializers.
+// https://github.com/babel/babel/issues/619
+
+Redirect.propTypes = {
+ path: PropTypes.string,
+ from: PropTypes.string, // Alias for path.
+ to: PropTypes.string,
+ handler: PropTypes.falsy
+};
+
+// Redirects should not have a default handler
+Redirect.defaultProps = {};
module.exports = Redirect;
-},{"../Configuration":5,"../PropTypes":11,"react":"react"}],26:[function(require,module,exports){
+},{"../PropTypes":14,"./Route":28}],28:[function(require,module,exports){
"use strict";
+var _createClass = (function () { function defineProperties(target, props) { for (var key in props) { var prop = props[key]; prop.configurable = true; if (prop.value) prop.writable = true; } Object.defineProperties(target, props); } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+var _inherits = function (subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; };
+
+var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } };
+
var React = require("react");
-var Configuration = require("../Configuration");
+var invariant = require("react/lib/invariant");
var PropTypes = require("../PropTypes");
var RouteHandler = require("./RouteHandler");
+
/**
* <Route> components specify components that are rendered to the page when the
* URL matches a given pattern.
@@ -1762,52 +2097,147 @@ var RouteHandler = require("./RouteHandler");
*
* If no handler is provided for the route, it will render a matched child route.
*/
-var Route = React.createClass({
- displayName: "Route",
+var Route = (function (_React$Component) {
+ function Route() {
+ _classCallCheck(this, Route);
- mixins: [Configuration],
+ if (_React$Component != null) {
+ _React$Component.apply(this, arguments);
+ }
+ }
- propTypes: {
- name: PropTypes.string,
- path: PropTypes.string,
- handler: PropTypes.func,
- ignoreScrollBehavior: PropTypes.bool
- },
+ _inherits(Route, _React$Component);
- getDefaultProps: function getDefaultProps() {
- return {
- handler: RouteHandler
- };
- }
+ _createClass(Route, {
+ render: {
+ value: function render() {
+ invariant(false, "%s elements are for router configuration only and should not be rendered", this.constructor.name);
+ }
+ }
+ });
-});
+ return Route;
+})(React.Component);
+
+// TODO: Include these in the above class definition
+// once we can use ES7 property initializers.
+// https://github.com/babel/babel/issues/619
+
+Route.propTypes = {
+ name: PropTypes.string,
+ path: PropTypes.string,
+ handler: PropTypes.func,
+ ignoreScrollBehavior: PropTypes.bool
+};
+
+Route.defaultProps = {
+ handler: RouteHandler
+};
module.exports = Route;
-},{"../Configuration":5,"../PropTypes":11,"./RouteHandler":27,"react":"react"}],27:[function(require,module,exports){
+},{"../PropTypes":14,"./RouteHandler":29,"react":"react","react/lib/invariant":191}],29:[function(require,module,exports){
"use strict";
+var _createClass = (function () { function defineProperties(target, props) { for (var key in props) { var prop = props[key]; prop.configurable = true; if (prop.value) prop.writable = true; } Object.defineProperties(target, props); } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+var _inherits = function (subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; };
+
+var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } };
+
var React = require("react");
-var RouteHandlerMixin = require("../RouteHandlerMixin");
+var ContextWrapper = require("./ContextWrapper");
+var assign = require("react/lib/Object.assign");
+var PropTypes = require("../PropTypes");
+
+var REF_NAME = "__routeHandler__";
/**
* A <RouteHandler> component renders the active child route handler
* when routes are nested.
*/
-var RouteHandler = React.createClass({
- displayName: "RouteHandler",
+var RouteHandler = (function (_React$Component) {
+ function RouteHandler() {
+ _classCallCheck(this, RouteHandler);
- mixins: [RouteHandlerMixin],
-
- render: function render() {
- return this.createChildRouteHandler();
+ if (_React$Component != null) {
+ _React$Component.apply(this, arguments);
+ }
}
-});
+ _inherits(RouteHandler, _React$Component);
+
+ _createClass(RouteHandler, {
+ getChildContext: {
+ value: function getChildContext() {
+ return {
+ routeDepth: this.context.routeDepth + 1
+ };
+ }
+ },
+ componentDidMount: {
+ value: function componentDidMount() {
+ this._updateRouteComponent(this.refs[REF_NAME]);
+ }
+ },
+ componentDidUpdate: {
+ value: function componentDidUpdate() {
+ this._updateRouteComponent(this.refs[REF_NAME]);
+ }
+ },
+ componentWillUnmount: {
+ value: function componentWillUnmount() {
+ this._updateRouteComponent(null);
+ }
+ },
+ _updateRouteComponent: {
+ value: function _updateRouteComponent(component) {
+ this.context.router.setRouteComponentAtDepth(this.getRouteDepth(), component);
+ }
+ },
+ getRouteDepth: {
+ value: function getRouteDepth() {
+ return this.context.routeDepth;
+ }
+ },
+ createChildRouteHandler: {
+ value: function createChildRouteHandler(props) {
+ var route = this.context.router.getRouteAtDepth(this.getRouteDepth());
+ return route ? React.createElement(route.handler, assign({}, props || this.props, { ref: REF_NAME })) : null;
+ }
+ },
+ render: {
+ value: function render() {
+ var handler = this.createChildRouteHandler();
+ // <script/> for things like <CSSTransitionGroup/> that don't like null
+ return handler ? React.createElement(
+ ContextWrapper,
+ null,
+ handler
+ ) : React.createElement("script", null);
+ }
+ }
+ });
+
+ return RouteHandler;
+})(React.Component);
+
+// TODO: Include these in the above class definition
+// once we can use ES7 property initializers.
+// https://github.com/babel/babel/issues/619
+
+RouteHandler.contextTypes = {
+ routeDepth: PropTypes.number.isRequired,
+ router: PropTypes.router.isRequired
+};
+
+RouteHandler.childContextTypes = {
+ routeDepth: PropTypes.number.isRequired
+};
module.exports = RouteHandler;
-},{"../RouteHandlerMixin":14,"react":"react"}],28:[function(require,module,exports){
+},{"../PropTypes":14,"./ContextWrapper":23,"react":"react","react/lib/Object.assign":69}],30:[function(require,module,exports){
(function (process){
"use strict";
@@ -1822,9 +2252,7 @@ var HashLocation = require("./locations/HashLocation");
var HistoryLocation = require("./locations/HistoryLocation");
var RefreshLocation = require("./locations/RefreshLocation");
var StaticLocation = require("./locations/StaticLocation");
-var NavigationContext = require("./NavigationContext");
var ScrollHistory = require("./ScrollHistory");
-var StateContext = require("./StateContext");
var createRoutesFromReactChildren = require("./createRoutesFromReactChildren");
var isReactChildren = require("./isReactChildren");
var Transition = require("./Transition");
@@ -1887,6 +2315,24 @@ function addRoutesToNamedRoutes(routes, namedRoutes) {
}
}
+function routeIsActive(activeRoutes, routeName) {
+ return activeRoutes.some(function (route) {
+ return route.name === routeName;
+ });
+}
+
+function paramsAreActive(activeParams, params) {
+ for (var property in params) if (String(activeParams[property]) !== String(params[property])) {
+ return false;
+ }return true;
+}
+
+function queryIsActive(activeQuery, query) {
+ for (var property in query) if (String(activeQuery[property]) !== String(query[property])) {
+ return false;
+ }return true;
+}
+
/**
* Creates and returns a new router using the given options. A router
* is a ReactComponent class that knows how to react to changes in the
@@ -1946,9 +2392,9 @@ function createRouter(options) {
},
clearAllRoutes: function clearAllRoutes() {
- this.cancelPendingTransition();
- this.namedRoutes = {};
- this.routes = [];
+ Router.cancelPendingTransition();
+ Router.namedRoutes = {};
+ Router.routes = [];
},
/**
@@ -1957,18 +2403,18 @@ function createRouter(options) {
addRoutes: function addRoutes(routes) {
if (isReactChildren(routes)) routes = createRoutesFromReactChildren(routes);
- addRoutesToNamedRoutes(routes, this.namedRoutes);
+ addRoutesToNamedRoutes(routes, Router.namedRoutes);
- this.routes.push.apply(this.routes, routes);
+ Router.routes.push.apply(Router.routes, routes);
},
/**
* Replaces routes of this router from the given children object (see ReactChildren).
*/
replaceRoutes: function replaceRoutes(routes) {
- this.clearAllRoutes();
- this.addRoutes(routes);
- this.refresh();
+ Router.clearAllRoutes();
+ Router.addRoutes(routes);
+ Router.refresh();
},
/**
@@ -1977,7 +2423,7 @@ function createRouter(options) {
* match can be made.
*/
match: function match(path) {
- return Match.findMatch(this.routes, path);
+ return Match.findMatch(Router.routes, path);
},
/**
@@ -1989,7 +2435,7 @@ function createRouter(options) {
if (PathUtils.isAbsolute(to)) {
path = to;
} else {
- var route = to instanceof Route ? to : this.namedRoutes[to];
+ var route = to instanceof Route ? to : Router.namedRoutes[to];
invariant(route instanceof Route, "Cannot find a route named \"%s\"", to);
@@ -2004,7 +2450,7 @@ function createRouter(options) {
* to the route with the given name, URL parameters, and query.
*/
makeHref: function makeHref(to, params, query) {
- var path = this.makePath(to, params, query);
+ var path = Router.makePath(to, params, query);
return location === HashLocation ? "#" + path : path;
},
@@ -2013,7 +2459,7 @@ function createRouter(options) {
* a new URL onto the history stack.
*/
transitionTo: function transitionTo(to, params, query) {
- var path = this.makePath(to, params, query);
+ var path = Router.makePath(to, params, query);
if (pendingTransition) {
// Replace so pending location does not stay in history.
@@ -2028,7 +2474,7 @@ function createRouter(options) {
* the current URL in the history stack.
*/
replaceWith: function replaceWith(to, params, query) {
- location.replace(this.makePath(to, params, query));
+ location.replace(Router.makePath(to, params, query));
},
/**
@@ -2059,7 +2505,7 @@ function createRouter(options) {
if (abortReason instanceof Cancellation) {
return;
} else if (abortReason instanceof Redirect) {
- location.replace(this.makePath(abortReason.to, abortReason.params, abortReason.query));
+ location.replace(Router.makePath(abortReason.to, abortReason.params, abortReason.query));
} else {
location.pop();
}
@@ -2071,7 +2517,7 @@ function createRouter(options) {
},
handleLocationChange: function handleLocationChange(change) {
- this.dispatch(change.path, change.type);
+ Router.dispatch(change.path, change.type);
},
/**
@@ -2091,7 +2537,7 @@ function createRouter(options) {
* hooks wait, the transition is fully synchronous.
*/
dispatch: function dispatch(path, action) {
- this.cancelPendingTransition();
+ Router.cancelPendingTransition();
var prevPath = state.path;
var isRefreshing = action == null;
@@ -2102,9 +2548,9 @@ function createRouter(options) {
// Record the scroll position as early as possible to
// get it before browsers try update it automatically.
- if (prevPath && action === LocationActions.PUSH) this.recordScrollPosition(prevPath);
+ if (prevPath && action === LocationActions.PUSH) Router.recordScrollPosition(prevPath);
- var match = this.match(path);
+ var match = Router.match(path);
warning(match != null, "No route matches path \"%s\". Make sure you have <Route path=\"%s\"> somewhere in your routes", path, path);
@@ -2132,7 +2578,7 @@ function createRouter(options) {
toRoutes = nextRoutes;
}
- var transition = new Transition(path, this.replaceWith.bind(this, path));
+ var transition = new Transition(path, Router.replaceWith.bind(Router, path));
pendingTransition = transition;
var fromComponents = mountedComponents.slice(prevRoutes.length - fromRoutes.length);
@@ -2161,7 +2607,7 @@ function createRouter(options) {
* Router.*Location objects (e.g. Router.HashLocation or Router.HistoryLocation).
*/
run: function run(callback) {
- invariant(!this.isRunning, "Router is already running");
+ invariant(!Router.isRunning, "Router is already running");
dispatchHandler = function (error, transition, newState) {
if (error) Router.handleError(error);
@@ -2173,18 +2619,18 @@ function createRouter(options) {
if (transition.abortReason) {
Router.handleAbort(transition.abortReason);
} else {
- callback.call(this, this, nextState = newState);
+ callback.call(Router, Router, nextState = newState);
}
};
if (!(location instanceof StaticLocation)) {
if (location.addChangeListener) location.addChangeListener(Router.handleLocationChange);
- this.isRunning = true;
+ Router.isRunning = true;
}
// Bootstrap using the current path.
- this.refresh();
+ Router.refresh();
},
refresh: function refresh() {
@@ -2192,36 +2638,91 @@ function createRouter(options) {
},
stop: function stop() {
- this.cancelPendingTransition();
+ Router.cancelPendingTransition();
if (location.removeChangeListener) location.removeChangeListener(Router.handleLocationChange);
- this.isRunning = false;
+ Router.isRunning = false;
+ },
+
+ getLocation: function getLocation() {
+ return location;
},
getScrollBehavior: function getScrollBehavior() {
return scrollBehavior;
+ },
+
+ getRouteAtDepth: function getRouteAtDepth(routeDepth) {
+ var routes = state.routes;
+ return routes && routes[routeDepth];
+ },
+
+ setRouteComponentAtDepth: function setRouteComponentAtDepth(routeDepth, component) {
+ mountedComponents[routeDepth] = component;
+ },
+
+ /**
+ * Returns the current URL path + query string.
+ */
+ getCurrentPath: function getCurrentPath() {
+ return state.path;
+ },
+
+ /**
+ * Returns the current URL path without the query string.
+ */
+ getCurrentPathname: function getCurrentPathname() {
+ return state.pathname;
+ },
+
+ /**
+ * Returns an object of the currently active URL parameters.
+ */
+ getCurrentParams: function getCurrentParams() {
+ return state.params;
+ },
+
+ /**
+ * Returns an object of the currently active query parameters.
+ */
+ getCurrentQuery: function getCurrentQuery() {
+ return state.query;
+ },
+
+ /**
+ * Returns an array of the currently active routes.
+ */
+ getCurrentRoutes: function getCurrentRoutes() {
+ return state.routes;
+ },
+
+ /**
+ * Returns true if the given route, params, and query are active.
+ */
+ isActive: function isActive(to, params, query) {
+ if (PathUtils.isAbsolute(to)) {
+ return to === state.path;
+ }return routeIsActive(state.routes, to) && paramsAreActive(state.params, params) && (query == null || queryIsActive(state.query, query));
}
},
- mixins: [NavigationContext, StateContext, ScrollHistory],
+ mixins: [ScrollHistory],
propTypes: {
children: PropTypes.falsy
},
childContextTypes: {
- getRouteAtDepth: React.PropTypes.func.isRequired,
- setRouteComponentAtDepth: React.PropTypes.func.isRequired,
- routeHandlers: React.PropTypes.array.isRequired
+ routeDepth: PropTypes.number.isRequired,
+ router: PropTypes.router.isRequired
},
getChildContext: function getChildContext() {
return {
- getRouteAtDepth: this.getRouteAtDepth,
- setRouteComponentAtDepth: this.setRouteComponentAtDepth,
- routeHandlers: [this]
+ routeDepth: 1,
+ router: Router
};
},
@@ -2237,21 +2738,8 @@ function createRouter(options) {
Router.stop();
},
- getLocation: function getLocation() {
- return location;
- },
-
- getRouteAtDepth: function getRouteAtDepth(depth) {
- var routes = this.state.routes;
- return routes && routes[depth];
- },
-
- setRouteComponentAtDepth: function setRouteComponentAtDepth(depth, component) {
- mountedComponents[depth] = component;
- },
-
render: function render() {
- var route = this.getRouteAtDepth(0);
+ var route = Router.getRouteAtDepth(0);
return route ? React.createElement(route.handler, this.props) : null;
}
@@ -2266,17 +2754,16 @@ function createRouter(options) {
module.exports = createRouter;
}).call(this,require('_process'))
-},{"./Cancellation":4,"./History":6,"./Match":7,"./NavigationContext":9,"./PathUtils":10,"./PropTypes":11,"./Redirect":12,"./Route":13,"./ScrollHistory":15,"./StateContext":17,"./Transition":18,"./actions/LocationActions":19,"./behaviors/ImitateBrowserBehavior":20,"./createRoutesFromReactChildren":29,"./isReactChildren":31,"./locations/HashLocation":32,"./locations/HistoryLocation":33,"./locations/RefreshLocation":34,"./locations/StaticLocation":35,"./supportsHistory":37,"_process":1,"react":"react","react/lib/ExecutionEnvironment":64,"react/lib/invariant":182,"react/lib/warning":202}],29:[function(require,module,exports){
+},{"./Cancellation":9,"./History":10,"./Match":11,"./PathUtils":13,"./PropTypes":14,"./Redirect":15,"./Route":16,"./ScrollHistory":17,"./Transition":19,"./actions/LocationActions":20,"./behaviors/ImitateBrowserBehavior":21,"./createRoutesFromReactChildren":31,"./isReactChildren":33,"./locations/HashLocation":34,"./locations/HistoryLocation":35,"./locations/RefreshLocation":36,"./locations/StaticLocation":37,"./supportsHistory":40,"_process":1,"react":"react","react/lib/ExecutionEnvironment":62,"react/lib/invariant":191,"react/lib/warning":212}],31:[function(require,module,exports){
"use strict";
/* jshint -W084 */
-
var React = require("react");
var assign = require("react/lib/Object.assign");
var warning = require("react/lib/warning");
-var DefaultRouteType = require("./components/DefaultRoute").type;
-var NotFoundRouteType = require("./components/NotFoundRoute").type;
-var RedirectType = require("./components/Redirect").type;
+var DefaultRoute = require("./components/DefaultRoute");
+var NotFoundRoute = require("./components/NotFoundRoute");
+var Redirect = require("./components/Redirect");
var Route = require("./Route");
function checkPropTypes(componentName, propTypes, props) {
@@ -2307,15 +2794,15 @@ function createRouteFromReactElement(element) {
if (!React.isValidElement(element)) {
return;
}var type = element.type;
- var props = element.props;
+ var props = assign({}, type.defaultProps, element.props);
if (type.propTypes) checkPropTypes(type.displayName, type.propTypes, props);
- if (type === DefaultRouteType) {
+ if (type === DefaultRoute) {
return Route.createDefaultRoute(createRouteOptions(props));
- }if (type === NotFoundRouteType) {
+ }if (type === NotFoundRoute) {
return Route.createNotFoundRoute(createRouteOptions(props));
- }if (type === RedirectType) {
+ }if (type === Redirect) {
return Route.createRedirect(createRouteOptions(props));
}return Route.createRoute(createRouteOptions(props), function () {
if (props.children) createRoutesFromReactChildren(props.children);
@@ -2349,7 +2836,7 @@ function createRoutesFromReactChildren(children) {
}
module.exports = createRoutesFromReactChildren;
-},{"./Route":13,"./components/DefaultRoute":22,"./components/NotFoundRoute":24,"./components/Redirect":25,"react":"react","react/lib/Object.assign":70,"react/lib/warning":202}],30:[function(require,module,exports){
+},{"./Route":16,"./components/DefaultRoute":24,"./components/NotFoundRoute":26,"./components/Redirect":27,"react":"react","react/lib/Object.assign":69,"react/lib/warning":212}],32:[function(require,module,exports){
"use strict";
var invariant = require("react/lib/invariant");
@@ -2368,7 +2855,7 @@ function getWindowScrollPosition() {
}
module.exports = getWindowScrollPosition;
-},{"react/lib/ExecutionEnvironment":64,"react/lib/invariant":182}],31:[function(require,module,exports){
+},{"react/lib/ExecutionEnvironment":62,"react/lib/invariant":191}],33:[function(require,module,exports){
"use strict";
var React = require("react");
@@ -2382,51 +2869,38 @@ function isReactChildren(object) {
}
module.exports = isReactChildren;
-},{"react":"react"}],32:[function(require,module,exports){
+},{"react":"react"}],34:[function(require,module,exports){
"use strict";
var LocationActions = require("../actions/LocationActions");
var History = require("../History");
-/**
- * Returns the current URL path from the `hash` portion of the URL, including
- * query string.
- */
-function getHashPath() {
- return decodeURI(
- // We can't use window.location.hash here because it's not
- // consistent across browsers - Firefox will pre-decode it!
- window.location.href.split("#")[1] || "");
-}
-
+var _listeners = [];
+var _isListening = false;
var _actionType;
-function ensureSlash() {
- var path = getHashPath();
-
- if (path.charAt(0) === "/") {
- return true;
- }HashLocation.replace("/" + path);
-
- return false;
-}
-
-var _changeListeners = [];
-
function notifyChange(type) {
if (type === LocationActions.PUSH) History.length += 1;
var change = {
- path: getHashPath(),
+ path: HashLocation.getCurrentPath(),
type: type
};
- _changeListeners.forEach(function (listener) {
- listener(change);
+ _listeners.forEach(function (listener) {
+ listener.call(HashLocation, change);
});
}
-var _isListening = false;
+function ensureSlash() {
+ var path = HashLocation.getCurrentPath();
+
+ if (path.charAt(0) === "/") {
+ return true;
+ }HashLocation.replace("/" + path);
+
+ return false;
+}
function onHashChange() {
if (ensureSlash()) {
@@ -2434,8 +2908,9 @@ function onHashChange() {
// changed. It was probably caused by the user clicking the Back
// button, but may have also been the Forward button or manual
// manipulation. So just guess 'pop'.
- notifyChange(_actionType || LocationActions.POP);
+ var curActionType = _actionType;
_actionType = null;
+ notifyChange(curActionType || LocationActions.POP);
}
}
@@ -2445,7 +2920,7 @@ function onHashChange() {
var HashLocation = {
addChangeListener: function addChangeListener(listener) {
- _changeListeners.push(listener);
+ _listeners.push(listener);
// Do this BEFORE listening for hashchange.
ensureSlash();
@@ -2462,11 +2937,11 @@ var HashLocation = {
},
removeChangeListener: function removeChangeListener(listener) {
- _changeListeners = _changeListeners.filter(function (l) {
+ _listeners = _listeners.filter(function (l) {
return l !== listener;
});
- if (_changeListeners.length === 0) {
+ if (_listeners.length === 0) {
if (window.removeEventListener) {
window.removeEventListener("hashchange", onHashChange, false);
} else {
@@ -2492,7 +2967,12 @@ var HashLocation = {
History.back();
},
- getCurrentPath: getHashPath,
+ getCurrentPath: function getCurrentPath() {
+ return decodeURI(
+ // We can't use window.location.hash here because it's not
+ // consistent across browsers - Firefox will pre-decode it!
+ window.location.href.split("#")[1] || "");
+ },
toString: function toString() {
return "<HashLocation>";
@@ -2501,34 +2981,26 @@ var HashLocation = {
};
module.exports = HashLocation;
-},{"../History":6,"../actions/LocationActions":19}],33:[function(require,module,exports){
+},{"../History":10,"../actions/LocationActions":20}],35:[function(require,module,exports){
"use strict";
var LocationActions = require("../actions/LocationActions");
var History = require("../History");
-/**
- * Returns the current URL path from `window.location`, including query string.
- */
-function getWindowPath() {
- return decodeURI(window.location.pathname + window.location.search);
-}
-
-var _changeListeners = [];
+var _listeners = [];
+var _isListening = false;
function notifyChange(type) {
var change = {
- path: getWindowPath(),
+ path: HistoryLocation.getCurrentPath(),
type: type
};
- _changeListeners.forEach(function (listener) {
- listener(change);
+ _listeners.forEach(function (listener) {
+ listener.call(HistoryLocation, change);
});
}
-var _isListening = false;
-
function onPopState(event) {
if (event.state === undefined) {
return;
@@ -2543,7 +3015,7 @@ function onPopState(event) {
var HistoryLocation = {
addChangeListener: function addChangeListener(listener) {
- _changeListeners.push(listener);
+ _listeners.push(listener);
if (!_isListening) {
if (window.addEventListener) {
@@ -2557,11 +3029,11 @@ var HistoryLocation = {
},
removeChangeListener: function removeChangeListener(listener) {
- _changeListeners = _changeListeners.filter(function (l) {
+ _listeners = _listeners.filter(function (l) {
return l !== listener;
});
- if (_changeListeners.length === 0) {
+ if (_listeners.length === 0) {
if (window.addEventListener) {
window.removeEventListener("popstate", onPopState, false);
} else {
@@ -2585,7 +3057,9 @@ var HistoryLocation = {
pop: History.back,
- getCurrentPath: getWindowPath,
+ getCurrentPath: function getCurrentPath() {
+ return decodeURI(window.location.pathname + window.location.search);
+ },
toString: function toString() {
return "<HistoryLocation>";
@@ -2594,7 +3068,7 @@ var HistoryLocation = {
};
module.exports = HistoryLocation;
-},{"../History":6,"../actions/LocationActions":19}],34:[function(require,module,exports){
+},{"../History":10,"../actions/LocationActions":20}],36:[function(require,module,exports){
"use strict";
var HistoryLocation = require("./HistoryLocation");
@@ -2626,10 +3100,10 @@ var RefreshLocation = {
};
module.exports = RefreshLocation;
-},{"../History":6,"./HistoryLocation":33}],35:[function(require,module,exports){
+},{"../History":10,"./HistoryLocation":35}],37:[function(require,module,exports){
"use strict";
-var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); };
+var _createClass = (function () { function defineProperties(target, props) { for (var key in props) { var prop = props[key]; prop.configurable = true; if (prop.value) prop.writable = true; } Object.defineProperties(target, props); } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } };
@@ -2652,20 +3126,16 @@ var StaticLocation = (function () {
this.path = path;
}
- _prototypeProperties(StaticLocation, null, {
+ _createClass(StaticLocation, {
getCurrentPath: {
value: function getCurrentPath() {
return this.path;
- },
- writable: true,
- configurable: true
+ }
},
toString: {
value: function toString() {
return "<StaticLocation path=\"" + this.path + "\">";
- },
- writable: true,
- configurable: true
+ }
}
});
@@ -2674,12 +3144,110 @@ var StaticLocation = (function () {
// TODO: Include these in the above class definition
// once we can use ES7 property initializers.
+// https://github.com/babel/babel/issues/619
+
StaticLocation.prototype.push = throwCannotModify;
StaticLocation.prototype.replace = throwCannotModify;
StaticLocation.prototype.pop = throwCannotModify;
module.exports = StaticLocation;
-},{"react/lib/invariant":182}],36:[function(require,module,exports){
+},{"react/lib/invariant":191}],38:[function(require,module,exports){
+"use strict";
+
+var _createClass = (function () { function defineProperties(target, props) { for (var key in props) { var prop = props[key]; prop.configurable = true; if (prop.value) prop.writable = true; } Object.defineProperties(target, props); } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } };
+
+var invariant = require("react/lib/invariant");
+var LocationActions = require("../actions/LocationActions");
+var History = require("../History");
+
+/**
+ * A location that is convenient for testing and does not require a DOM.
+ */
+
+var TestLocation = (function () {
+ function TestLocation(history) {
+ _classCallCheck(this, TestLocation);
+
+ this.history = history || [];
+ this.listeners = [];
+ this._updateHistoryLength();
+ }
+
+ _createClass(TestLocation, {
+ needsDOM: {
+ get: function () {
+ return false;
+ }
+ },
+ _updateHistoryLength: {
+ value: function _updateHistoryLength() {
+ History.length = this.history.length;
+ }
+ },
+ _notifyChange: {
+ value: function _notifyChange(type) {
+ var change = {
+ path: this.getCurrentPath(),
+ type: type
+ };
+
+ for (var i = 0, len = this.listeners.length; i < len; ++i) this.listeners[i].call(this, change);
+ }
+ },
+ addChangeListener: {
+ value: function addChangeListener(listener) {
+ this.listeners.push(listener);
+ }
+ },
+ removeChangeListener: {
+ value: function removeChangeListener(listener) {
+ this.listeners = this.listeners.filter(function (l) {
+ return l !== listener;
+ });
+ }
+ },
+ push: {
+ value: function push(path) {
+ this.history.push(path);
+ this._updateHistoryLength();
+ this._notifyChange(LocationActions.PUSH);
+ }
+ },
+ replace: {
+ value: function replace(path) {
+ invariant(this.history.length, "You cannot replace the current path with no history");
+
+ this.history[this.history.length - 1] = path;
+
+ this._notifyChange(LocationActions.REPLACE);
+ }
+ },
+ pop: {
+ value: function pop() {
+ this.history.pop();
+ this._updateHistoryLength();
+ this._notifyChange(LocationActions.POP);
+ }
+ },
+ getCurrentPath: {
+ value: function getCurrentPath() {
+ return this.history[this.history.length - 1];
+ }
+ },
+ toString: {
+ value: function toString() {
+ return "<TestLocation>";
+ }
+ }
+ });
+
+ return TestLocation;
+})();
+
+module.exports = TestLocation;
+},{"../History":10,"../actions/LocationActions":20,"react/lib/invariant":191}],39:[function(require,module,exports){
"use strict";
var createRouter = require("./createRouter");
@@ -2730,7 +3298,7 @@ function runRouter(routes, location, callback) {
}
module.exports = runRouter;
-},{"./createRouter":28}],37:[function(require,module,exports){
+},{"./createRouter":30}],40:[function(require,module,exports){
"use strict";
function supportsHistory() {
@@ -2747,401 +3315,37 @@ function supportsHistory() {
}
module.exports = supportsHistory;
-},{}],38:[function(require,module,exports){
-module.exports = require('./lib/');
-
-},{"./lib/":39}],39:[function(require,module,exports){
-// Load modules
-
-var Stringify = require('./stringify');
-var Parse = require('./parse');
-
-
-// Declare internals
-
-var internals = {};
-
-
-module.exports = {
- stringify: Stringify,
- parse: Parse
-};
-
-},{"./parse":40,"./stringify":41}],40:[function(require,module,exports){
-// Load modules
-
-var Utils = require('./utils');
-
-
-// Declare internals
-
-var internals = {
- delimiter: '&',
- depth: 5,
- arrayLimit: 20,
- parameterLimit: 1000
-};
-
-
-internals.parseValues = function (str, options) {
-
- var obj = {};
- var parts = str.split(options.delimiter, options.parameterLimit === Infinity ? undefined : options.parameterLimit);
-
- for (var i = 0, il = parts.length; i < il; ++i) {
- var part = parts[i];
- var pos = part.indexOf(']=') === -1 ? part.indexOf('=') : part.indexOf(']=') + 1;
-
- if (pos === -1) {
- obj[Utils.decode(part)] = '';
- }
- else {
- var key = Utils.decode(part.slice(0, pos));
- var val = Utils.decode(part.slice(pos + 1));
-
- if (!obj.hasOwnProperty(key)) {
- obj[key] = val;
- }
- else {
- obj[key] = [].concat(obj[key]).concat(val);
- }
- }
- }
-
- return obj;
-};
-
-
-internals.parseObject = function (chain, val, options) {
-
- if (!chain.length) {
- return val;
- }
-
- var root = chain.shift();
-
- var obj = {};
- if (root === '[]') {
- obj = [];
- obj = obj.concat(internals.parseObject(chain, val, options));
- }
- else {
- var cleanRoot = root[0] === '[' && root[root.length - 1] === ']' ? root.slice(1, root.length - 1) : root;
- var index = parseInt(cleanRoot, 10);
- var indexString = '' + index;
- if (!isNaN(index) &&
- root !== cleanRoot &&
- indexString === cleanRoot &&
- index >= 0 &&
- index <= options.arrayLimit) {
-
- obj = [];
- obj[index] = internals.parseObject(chain, val, options);
- }
- else {
- obj[cleanRoot] = internals.parseObject(chain, val, options);
- }
- }
-
- return obj;
-};
-
-
-internals.parseKeys = function (key, val, options) {
-
- if (!key) {
- return;
- }
-
- // The regex chunks
-
- var parent = /^([^\[\]]*)/;
- var child = /(\[[^\[\]]*\])/g;
-
- // Get the parent
-
- var segment = parent.exec(key);
-
- // Don't allow them to overwrite object prototype properties
-
- if (Object.prototype.hasOwnProperty(segment[1])) {
- return;
- }
-
- // Stash the parent if it exists
-
- var keys = [];
- if (segment[1]) {
- keys.push(segment[1]);
- }
-
- // Loop through children appending to the array until we hit depth
-
- var i = 0;
- while ((segment = child.exec(key)) !== null && i < options.depth) {
-
- ++i;
- if (!Object.prototype.hasOwnProperty(segment[1].replace(/\[|\]/g, ''))) {
- keys.push(segment[1]);
- }
- }
-
- // If there's a remainder, just add whatever is left
-
- if (segment) {
- keys.push('[' + key.slice(segment.index) + ']');
- }
-
- return internals.parseObject(keys, val, options);
-};
-
-
-module.exports = function (str, options) {
-
- if (str === '' ||
- str === null ||
- typeof str === 'undefined') {
-
- return {};
- }
-
- options = options || {};
- options.delimiter = typeof options.delimiter === 'string' || Utils.isRegExp(options.delimiter) ? options.delimiter : internals.delimiter;
- options.depth = typeof options.depth === 'number' ? options.depth : internals.depth;
- options.arrayLimit = typeof options.arrayLimit === 'number' ? options.arrayLimit : internals.arrayLimit;
- options.parameterLimit = typeof options.parameterLimit === 'number' ? options.parameterLimit : internals.parameterLimit;
-
- var tempObj = typeof str === 'string' ? internals.parseValues(str, options) : str;
- var obj = {};
-
- // Iterate over the keys and setup the new object
-
- var keys = Object.keys(tempObj);
- for (var i = 0, il = keys.length; i < il; ++i) {
- var key = keys[i];
- var newObj = internals.parseKeys(key, tempObj[key], options);
- obj = Utils.merge(obj, newObj);
- }
-
- return Utils.compact(obj);
-};
-
-},{"./utils":42}],41:[function(require,module,exports){
-// Load modules
-
-var Utils = require('./utils');
-
-
-// Declare internals
-
-var internals = {
- delimiter: '&',
- indices: true
-};
-
-
-internals.stringify = function (obj, prefix, options) {
-
- if (Utils.isBuffer(obj)) {
- obj = obj.toString();
- }
- else if (obj instanceof Date) {
- obj = obj.toISOString();
- }
- else if (obj === null) {
- obj = '';
- }
-
- if (typeof obj === 'string' ||
- typeof obj === 'number' ||
- typeof obj === 'boolean') {
-
- return [encodeURIComponent(prefix) + '=' + encodeURIComponent(obj)];
- }
-
- var values = [];
-
- if (typeof obj === 'undefined') {
- return values;
- }
-
- var objKeys = Object.keys(obj);
- for (var i = 0, il = objKeys.length; i < il; ++i) {
- var key = objKeys[i];
- if (!options.indices &&
- Array.isArray(obj)) {
-
- values = values.concat(internals.stringify(obj[key], prefix, options));
- }
- else {
- values = values.concat(internals.stringify(obj[key], prefix + '[' + key + ']', options));
- }
- }
-
- return values;
-};
-
-
-module.exports = function (obj, options) {
-
- options = options || {};
- var delimiter = typeof options.delimiter === 'undefined' ? internals.delimiter : options.delimiter;
- options.indices = typeof options.indices === 'boolean' ? options.indices : internals.indices;
-
- var keys = [];
-
- if (typeof obj !== 'object' ||
- obj === null) {
-
- return '';
- }
-
- var objKeys = Object.keys(obj);
- for (var i = 0, il = objKeys.length; i < il; ++i) {
- var key = objKeys[i];
- keys = keys.concat(internals.stringify(obj[key], key, options));
- }
-
- return keys.join(delimiter);
-};
-
-},{"./utils":42}],42:[function(require,module,exports){
-// Load modules
-
-
-// Declare internals
-
-var internals = {};
-
-
-exports.arrayToObject = function (source) {
-
- var obj = {};
- for (var i = 0, il = source.length; i < il; ++i) {
- if (typeof source[i] !== 'undefined') {
-
- obj[i] = source[i];
- }
- }
-
- return obj;
-};
-
-
-exports.merge = function (target, source) {
-
- if (!source) {
- return target;
- }
-
- if (typeof source !== 'object') {
- if (Array.isArray(target)) {
- target.push(source);
- }
- else {
- target[source] = true;
- }
-
- return target;
- }
-
- if (typeof target !== 'object') {
- target = [target].concat(source);
- return target;
- }
-
- if (Array.isArray(target) &&
- !Array.isArray(source)) {
-
- target = exports.arrayToObject(target);
- }
-
- var keys = Object.keys(source);
- for (var k = 0, kl = keys.length; k < kl; ++k) {
- var key = keys[k];
- var value = source[key];
-
- if (!target[key]) {
- target[key] = value;
- }
- else {
- target[key] = exports.merge(target[key], value);
- }
- }
-
- return target;
-};
-
-
-exports.decode = function (str) {
-
- try {
- return decodeURIComponent(str.replace(/\+/g, ' '));
- } catch (e) {
- return str;
- }
-};
-
-
-exports.compact = function (obj, refs) {
-
- if (typeof obj !== 'object' ||
- obj === null) {
-
- return obj;
- }
-
- refs = refs || [];
- var lookup = refs.indexOf(obj);
- if (lookup !== -1) {
- return refs[lookup];
- }
-
- refs.push(obj);
-
- if (Array.isArray(obj)) {
- var compacted = [];
-
- for (var i = 0, il = obj.length; i < il; ++i) {
- if (typeof obj[i] !== 'undefined') {
- compacted.push(obj[i]);
- }
- }
-
- return compacted;
- }
-
- var keys = Object.keys(obj);
- for (i = 0, il = keys.length; i < il; ++i) {
- var key = keys[i];
- obj[key] = exports.compact(obj[key], refs);
- }
-
- return obj;
-};
-
+},{}],41:[function(require,module,exports){
+'use strict';
-exports.isRegExp = function (obj) {
- return Object.prototype.toString.call(obj) === '[object RegExp]';
-};
+function ToObject(val) {
+ if (val == null) {
+ throw new TypeError('Object.assign cannot be called with null or undefined');
+ }
+ return Object(val);
+}
-exports.isBuffer = function (obj) {
+module.exports = Object.assign || function (target, source) {
+ var from;
+ var keys;
+ var to = ToObject(target);
- if (obj === null ||
- typeof obj === 'undefined') {
+ for (var s = 1; s < arguments.length; s++) {
+ from = arguments[s];
+ keys = Object.keys(Object(from));
- return false;
- }
+ for (var i = 0; i < keys.length; i++) {
+ to[keys[i]] = from[keys[i]];
+ }
+ }
- return !!(obj.constructor &&
- obj.constructor.isBuffer &&
- obj.constructor.isBuffer(obj));
+ return to;
};
-},{}],43:[function(require,module,exports){
+},{}],42:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -3152,7 +3356,7 @@ exports.isBuffer = function (obj) {
* @typechecks static-only
*/
-"use strict";
+'use strict';
var focusNode = require("./focusNode");
@@ -3166,9 +3370,9 @@ var AutoFocusMixin = {
module.exports = AutoFocusMixin;
-},{"./focusNode":167}],44:[function(require,module,exports){
+},{"./focusNode":175}],43:[function(require,module,exports){
/**
- * Copyright 2013 Facebook, Inc.
+ * Copyright 2013-2015 Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -3179,19 +3383,48 @@ module.exports = AutoFocusMixin;
* @typechecks static-only
*/
-"use strict";
+'use strict';
var EventConstants = require("./EventConstants");
var EventPropagators = require("./EventPropagators");
var ExecutionEnvironment = require("./ExecutionEnvironment");
+var FallbackCompositionState = require("./FallbackCompositionState");
+var SyntheticCompositionEvent = require("./SyntheticCompositionEvent");
var SyntheticInputEvent = require("./SyntheticInputEvent");
var keyOf = require("./keyOf");
+var END_KEYCODES = [9, 13, 27, 32]; // Tab, Return, Esc, Space
+var START_KEYCODE = 229;
+
+var canUseCompositionEvent = (
+ ExecutionEnvironment.canUseDOM &&
+ 'CompositionEvent' in window
+);
+
+var documentMode = null;
+if (ExecutionEnvironment.canUseDOM && 'documentMode' in document) {
+ documentMode = document.documentMode;
+}
+
+// Webkit offers a very useful `textInput` event that can be used to
+// directly represent `beforeInput`. The IE `textinput` event is not as
+// useful, so we don't use it.
var canUseTextInputEvent = (
ExecutionEnvironment.canUseDOM &&
'TextEvent' in window &&
- !('documentMode' in document || isPresto())
+ !documentMode &&
+ !isPresto()
+);
+
+// In IE9+, we have access to composition events, but the data supplied
+// by the native compositionend event may be incorrect. Japanese ideographic
+// spaces, for instance (\u3000) are not recorded correctly.
+var useFallbackCompositionData = (
+ ExecutionEnvironment.canUseDOM &&
+ (
+ (!canUseCompositionEvent || documentMode && documentMode > 8 && documentMode <= 11)
+ )
);
/**
@@ -3225,12 +3458,51 @@ var eventTypes = {
topLevelTypes.topTextInput,
topLevelTypes.topPaste
]
+ },
+ compositionEnd: {
+ phasedRegistrationNames: {
+ bubbled: keyOf({onCompositionEnd: null}),
+ captured: keyOf({onCompositionEndCapture: null})
+ },
+ dependencies: [
+ topLevelTypes.topBlur,
+ topLevelTypes.topCompositionEnd,
+ topLevelTypes.topKeyDown,
+ topLevelTypes.topKeyPress,
+ topLevelTypes.topKeyUp,
+ topLevelTypes.topMouseDown
+ ]
+ },
+ compositionStart: {
+ phasedRegistrationNames: {
+ bubbled: keyOf({onCompositionStart: null}),
+ captured: keyOf({onCompositionStartCapture: null})
+ },
+ dependencies: [
+ topLevelTypes.topBlur,
+ topLevelTypes.topCompositionStart,
+ topLevelTypes.topKeyDown,
+ topLevelTypes.topKeyPress,
+ topLevelTypes.topKeyUp,
+ topLevelTypes.topMouseDown
+ ]
+ },
+ compositionUpdate: {
+ phasedRegistrationNames: {
+ bubbled: keyOf({onCompositionUpdate: null}),
+ captured: keyOf({onCompositionUpdateCapture: null})
+ },
+ dependencies: [
+ topLevelTypes.topBlur,
+ topLevelTypes.topCompositionUpdate,
+ topLevelTypes.topKeyDown,
+ topLevelTypes.topKeyPress,
+ topLevelTypes.topKeyUp,
+ topLevelTypes.topMouseDown
+ ]
}
};
-// Track characters inserted via keypress and composition events.
-var fallbackChars = null;
-
// Track whether we've ever handled a keypress on the space key.
var hasSpaceKeypress = false;
@@ -3247,6 +3519,297 @@ function isKeypressCommand(nativeEvent) {
);
}
+
+/**
+ * Translate native top level events into event types.
+ *
+ * @param {string} topLevelType
+ * @return {object}
+ */
+function getCompositionEventType(topLevelType) {
+ switch (topLevelType) {
+ case topLevelTypes.topCompositionStart:
+ return eventTypes.compositionStart;
+ case topLevelTypes.topCompositionEnd:
+ return eventTypes.compositionEnd;
+ case topLevelTypes.topCompositionUpdate:
+ return eventTypes.compositionUpdate;
+ }
+}
+
+/**
+ * Does our fallback best-guess model think this event signifies that
+ * composition has begun?
+ *
+ * @param {string} topLevelType
+ * @param {object} nativeEvent
+ * @return {boolean}
+ */
+function isFallbackCompositionStart(topLevelType, nativeEvent) {
+ return (
+ topLevelType === topLevelTypes.topKeyDown &&
+ nativeEvent.keyCode === START_KEYCODE
+ );
+}
+
+/**
+ * Does our fallback mode think that this event is the end of composition?
+ *
+ * @param {string} topLevelType
+ * @param {object} nativeEvent
+ * @return {boolean}
+ */
+function isFallbackCompositionEnd(topLevelType, nativeEvent) {
+ switch (topLevelType) {
+ case topLevelTypes.topKeyUp:
+ // Command keys insert or clear IME input.
+ return (END_KEYCODES.indexOf(nativeEvent.keyCode) !== -1);
+ case topLevelTypes.topKeyDown:
+ // Expect IME keyCode on each keydown. If we get any other
+ // code we must have exited earlier.
+ return (nativeEvent.keyCode !== START_KEYCODE);
+ case topLevelTypes.topKeyPress:
+ case topLevelTypes.topMouseDown:
+ case topLevelTypes.topBlur:
+ // Events are not possible without cancelling IME.
+ return true;
+ default:
+ return false;
+ }
+}
+
+/**
+ * Google Input Tools provides composition data via a CustomEvent,
+ * with the `data` property populated in the `detail` object. If this
+ * is available on the event object, use it. If not, this is a plain
+ * composition event and we have nothing special to extract.
+ *
+ * @param {object} nativeEvent
+ * @return {?string}
+ */
+function getDataFromCustomEvent(nativeEvent) {
+ var detail = nativeEvent.detail;
+ if (typeof detail === 'object' && 'data' in detail) {
+ return detail.data;
+ }
+ return null;
+}
+
+// Track the current IME composition fallback object, if any.
+var currentComposition = null;
+
+/**
+ * @param {string} topLevelType Record from `EventConstants`.
+ * @param {DOMEventTarget} topLevelTarget The listening component root node.
+ * @param {string} topLevelTargetID ID of `topLevelTarget`.
+ * @param {object} nativeEvent Native browser event.
+ * @return {?object} A SyntheticCompositionEvent.
+ */
+function extractCompositionEvent(
+ topLevelType,
+ topLevelTarget,
+ topLevelTargetID,
+ nativeEvent
+) {
+ var eventType;
+ var fallbackData;
+
+ if (canUseCompositionEvent) {
+ eventType = getCompositionEventType(topLevelType);
+ } else if (!currentComposition) {
+ if (isFallbackCompositionStart(topLevelType, nativeEvent)) {
+ eventType = eventTypes.compositionStart;
+ }
+ } else if (isFallbackCompositionEnd(topLevelType, nativeEvent)) {
+ eventType = eventTypes.compositionEnd;
+ }
+
+ if (!eventType) {
+ return null;
+ }
+
+ if (useFallbackCompositionData) {
+ // The current composition is stored statically and must not be
+ // overwritten while composition continues.
+ if (!currentComposition && eventType === eventTypes.compositionStart) {
+ currentComposition = FallbackCompositionState.getPooled(topLevelTarget);
+ } else if (eventType === eventTypes.compositionEnd) {
+ if (currentComposition) {
+ fallbackData = currentComposition.getData();
+ }
+ }
+ }
+
+ var event = SyntheticCompositionEvent.getPooled(
+ eventType,
+ topLevelTargetID,
+ nativeEvent
+ );
+
+ if (fallbackData) {
+ // Inject data generated from fallback path into the synthetic event.
+ // This matches the property of native CompositionEventInterface.
+ event.data = fallbackData;
+ } else {
+ var customData = getDataFromCustomEvent(nativeEvent);
+ if (customData !== null) {
+ event.data = customData;
+ }
+ }
+
+ EventPropagators.accumulateTwoPhaseDispatches(event);
+ return event;
+}
+
+/**
+ * @param {string} topLevelType Record from `EventConstants`.
+ * @param {object} nativeEvent Native browser event.
+ * @return {?string} The string corresponding to this `beforeInput` event.
+ */
+function getNativeBeforeInputChars(topLevelType, nativeEvent) {
+ switch (topLevelType) {
+ case topLevelTypes.topCompositionEnd:
+ return getDataFromCustomEvent(nativeEvent);
+ case topLevelTypes.topKeyPress:
+ /**
+ * If native `textInput` events are available, our goal is to make
+ * use of them. However, there is a special case: the spacebar key.
+ * In Webkit, preventing default on a spacebar `textInput` event
+ * cancels character insertion, but it *also* causes the browser
+ * to fall back to its default spacebar behavior of scrolling the
+ * page.
+ *
+ * Tracking at:
+ * https://code.google.com/p/chromium/issues/detail?id=355103
+ *
+ * To avoid this issue, use the keypress event as if no `textInput`
+ * event is available.
+ */
+ var which = nativeEvent.which;
+ if (which !== SPACEBAR_CODE) {
+ return null;
+ }
+
+ hasSpaceKeypress = true;
+ return SPACEBAR_CHAR;
+
+ case topLevelTypes.topTextInput:
+ // Record the characters to be added to the DOM.
+ var chars = nativeEvent.data;
+
+ // If it's a spacebar character, assume that we have already handled
+ // it at the keypress level and bail immediately. Android Chrome
+ // doesn't give us keycodes, so we need to blacklist it.
+ if (chars === SPACEBAR_CHAR && hasSpaceKeypress) {
+ return null;
+ }
+
+ return chars;
+
+ default:
+ // For other native event types, do nothing.
+ return null;
+ }
+}
+
+/**
+ * For browsers that do not provide the `textInput` event, extract the
+ * appropriate string to use for SyntheticInputEvent.
+ *
+ * @param {string} topLevelType Record from `EventConstants`.
+ * @param {object} nativeEvent Native browser event.
+ * @return {?string} The fallback string for this `beforeInput` event.
+ */
+function getFallbackBeforeInputChars(topLevelType, nativeEvent) {
+ // If we are currently composing (IME) and using a fallback to do so,
+ // try to extract the composed characters from the fallback object.
+ if (currentComposition) {
+ if (
+ topLevelType === topLevelTypes.topCompositionEnd ||
+ isFallbackCompositionEnd(topLevelType, nativeEvent)
+ ) {
+ var chars = currentComposition.getData();
+ FallbackCompositionState.release(currentComposition);
+ currentComposition = null;
+ return chars;
+ }
+ return null;
+ }
+
+ switch (topLevelType) {
+ case topLevelTypes.topPaste:
+ // If a paste event occurs after a keypress, throw out the input
+ // chars. Paste events should not lead to BeforeInput events.
+ return null;
+ case topLevelTypes.topKeyPress:
+ /**
+ * As of v27, Firefox may fire keypress events even when no character
+ * will be inserted. A few possibilities:
+ *
+ * - `which` is `0`. Arrow keys, Esc key, etc.
+ *
+ * - `which` is the pressed key code, but no char is available.
+ * Ex: 'AltGr + d` in Polish. There is no modified character for
+ * this key combination and no character is inserted into the
+ * document, but FF fires the keypress for char code `100` anyway.
+ * No `input` event will occur.
+ *
+ * - `which` is the pressed key code, but a command combination is
+ * being used. Ex: `Cmd+C`. No character is inserted, and no
+ * `input` event will occur.
+ */
+ if (nativeEvent.which && !isKeypressCommand(nativeEvent)) {
+ return String.fromCharCode(nativeEvent.which);
+ }
+ return null;
+ case topLevelTypes.topCompositionEnd:
+ return useFallbackCompositionData ? null : nativeEvent.data;
+ default:
+ return null;
+ }
+}
+
+/**
+ * Extract a SyntheticInputEvent for `beforeInput`, based on either native
+ * `textInput` or fallback behavior.
+ *
+ * @param {string} topLevelType Record from `EventConstants`.
+ * @param {DOMEventTarget} topLevelTarget The listening component root node.
+ * @param {string} topLevelTargetID ID of `topLevelTarget`.
+ * @param {object} nativeEvent Native browser event.
+ * @return {?object} A SyntheticInputEvent.
+ */
+function extractBeforeInputEvent(
+ topLevelType,
+ topLevelTarget,
+ topLevelTargetID,
+ nativeEvent
+) {
+ var chars;
+
+ if (canUseTextInputEvent) {
+ chars = getNativeBeforeInputChars(topLevelType, nativeEvent);
+ } else {
+ chars = getFallbackBeforeInputChars(topLevelType, nativeEvent);
+ }
+
+ // If no characters are being inserted, no BeforeInput event should
+ // be fired.
+ if (!chars) {
+ return null;
+ }
+
+ var event = SyntheticInputEvent.getPooled(
+ eventTypes.beforeInput,
+ topLevelTargetID,
+ nativeEvent
+ );
+
+ event.data = chars;
+ EventPropagators.accumulateTwoPhaseDispatches(event);
+ return event;
+}
+
/**
* Create an `onBeforeInput` event to match
* http://www.w3.org/TR/2013/WD-DOM-Level-3-Events-20131105/#events-inputevents.
@@ -3260,6 +3823,10 @@ function isKeypressCommand(nativeEvent) {
* actually been added, contrary to the spec. Thus, `textInput` is the best
* available event to identify the characters that have actually been inserted
* into the target node.
+ *
+ * This plugin is also responsible for emitting `composition` events, thus
+ * allowing us to share composition fallback code for both `beforeInput` and
+ * `composition` event types.
*/
var BeforeInputEventPlugin = {
@@ -3274,124 +3841,34 @@ var BeforeInputEventPlugin = {
* @see {EventPluginHub.extractEvents}
*/
extractEvents: function(
- topLevelType,
- topLevelTarget,
- topLevelTargetID,
- nativeEvent) {
-
- var chars;
-
- if (canUseTextInputEvent) {
- switch (topLevelType) {
- case topLevelTypes.topKeyPress:
- /**
- * If native `textInput` events are available, our goal is to make
- * use of them. However, there is a special case: the spacebar key.
- * In Webkit, preventing default on a spacebar `textInput` event
- * cancels character insertion, but it *also* causes the browser
- * to fall back to its default spacebar behavior of scrolling the
- * page.
- *
- * Tracking at:
- * https://code.google.com/p/chromium/issues/detail?id=355103
- *
- * To avoid this issue, use the keypress event as if no `textInput`
- * event is available.
- */
- var which = nativeEvent.which;
- if (which !== SPACEBAR_CODE) {
- return;
- }
-
- hasSpaceKeypress = true;
- chars = SPACEBAR_CHAR;
- break;
-
- case topLevelTypes.topTextInput:
- // Record the characters to be added to the DOM.
- chars = nativeEvent.data;
-
- // If it's a spacebar character, assume that we have already handled
- // it at the keypress level and bail immediately. Android Chrome
- // doesn't give us keycodes, so we need to blacklist it.
- if (chars === SPACEBAR_CHAR && hasSpaceKeypress) {
- return;
- }
-
- // Otherwise, carry on.
- break;
-
- default:
- // For other native event types, do nothing.
- return;
- }
- } else {
- switch (topLevelType) {
- case topLevelTypes.topPaste:
- // If a paste event occurs after a keypress, throw out the input
- // chars. Paste events should not lead to BeforeInput events.
- fallbackChars = null;
- break;
- case topLevelTypes.topKeyPress:
- /**
- * As of v27, Firefox may fire keypress events even when no character
- * will be inserted. A few possibilities:
- *
- * - `which` is `0`. Arrow keys, Esc key, etc.
- *
- * - `which` is the pressed key code, but no char is available.
- * Ex: 'AltGr + d` in Polish. There is no modified character for
- * this key combination and no character is inserted into the
- * document, but FF fires the keypress for char code `100` anyway.
- * No `input` event will occur.
- *
- * - `which` is the pressed key code, but a command combination is
- * being used. Ex: `Cmd+C`. No character is inserted, and no
- * `input` event will occur.
- */
- if (nativeEvent.which && !isKeypressCommand(nativeEvent)) {
- fallbackChars = String.fromCharCode(nativeEvent.which);
- }
- break;
- case topLevelTypes.topCompositionEnd:
- fallbackChars = nativeEvent.data;
- break;
- }
-
- // If no changes have occurred to the fallback string, no relevant
- // event has fired and we're done.
- if (fallbackChars === null) {
- return;
- }
-
- chars = fallbackChars;
- }
-
- // If no characters are being inserted, no BeforeInput event should
- // be fired.
- if (!chars) {
- return;
- }
-
- var event = SyntheticInputEvent.getPooled(
- eventTypes.beforeInput,
- topLevelTargetID,
- nativeEvent
- );
-
- event.data = chars;
- fallbackChars = null;
- EventPropagators.accumulateTwoPhaseDispatches(event);
- return event;
+ topLevelType,
+ topLevelTarget,
+ topLevelTargetID,
+ nativeEvent
+ ) {
+ return [
+ extractCompositionEvent(
+ topLevelType,
+ topLevelTarget,
+ topLevelTargetID,
+ nativeEvent
+ ),
+ extractBeforeInputEvent(
+ topLevelType,
+ topLevelTarget,
+ topLevelTargetID,
+ nativeEvent
+ )
+ ];
}
};
module.exports = BeforeInputEventPlugin;
-},{"./EventConstants":58,"./EventPropagators":63,"./ExecutionEnvironment":64,"./SyntheticInputEvent":143,"./keyOf":189}],45:[function(require,module,exports){
+},{"./EventConstants":56,"./EventPropagators":61,"./ExecutionEnvironment":62,"./FallbackCompositionState":63,"./SyntheticCompositionEvent":147,"./SyntheticInputEvent":151,"./keyOf":198}],44:[function(require,module,exports){
(function (process){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -3500,9 +3977,9 @@ var CSSCore = {
module.exports = CSSCore;
}).call(this,require('_process'))
-},{"./invariant":182,"_process":1}],46:[function(require,module,exports){
+},{"./invariant":191,"_process":1}],45:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -3512,14 +3989,15 @@ module.exports = CSSCore;
* @providesModule CSSProperty
*/
-"use strict";
+'use strict';
/**
* CSS properties which accept numbers but are not in units of "px".
*/
var isUnitlessNumber = {
+ boxFlex: true,
+ boxFlexGroup: true,
columnCount: true,
- fillOpacity: true,
flex: true,
flexGrow: true,
flexShrink: true,
@@ -3531,7 +4009,11 @@ var isUnitlessNumber = {
orphans: true,
widows: true,
zIndex: true,
- zoom: true
+ zoom: true,
+
+ // SVG-related properties
+ fillOpacity: true,
+ strokeOpacity: true
};
/**
@@ -3616,10 +4098,10 @@ var CSSProperty = {
module.exports = CSSProperty;
-},{}],47:[function(require,module,exports){
+},{}],46:[function(require,module,exports){
(function (process){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -3630,7 +4112,7 @@ module.exports = CSSProperty;
* @typechecks static-only
*/
-"use strict";
+'use strict';
var CSSProperty = require("./CSSProperty");
var ExecutionEnvironment = require("./ExecutionEnvironment");
@@ -3654,7 +4136,14 @@ if (ExecutionEnvironment.canUseDOM) {
}
if ("production" !== process.env.NODE_ENV) {
+ // 'msTransform' is correct, but the other prefixes should be capitalized
+ var badVendoredStyleNamePattern = /^(?:webkit|moz|o)[A-Z]/;
+
+ // style values shouldn't contain a semicolon
+ var badStyleValueWithSemicolonPattern = /;\s*$/;
+
var warnedStyleNames = {};
+ var warnedStyleValues = {};
var warnHyphenatedStyleName = function(name) {
if (warnedStyleNames.hasOwnProperty(name) && warnedStyleNames[name]) {
@@ -3664,10 +4153,54 @@ if ("production" !== process.env.NODE_ENV) {
warnedStyleNames[name] = true;
("production" !== process.env.NODE_ENV ? warning(
false,
- 'Unsupported style property ' + name + '. Did you mean ' +
- camelizeStyleName(name) + '?'
+ 'Unsupported style property %s. Did you mean %s?',
+ name,
+ camelizeStyleName(name)
) : null);
};
+
+ var warnBadVendoredStyleName = function(name) {
+ if (warnedStyleNames.hasOwnProperty(name) && warnedStyleNames[name]) {
+ return;
+ }
+
+ warnedStyleNames[name] = true;
+ ("production" !== process.env.NODE_ENV ? warning(
+ false,
+ 'Unsupported vendor-prefixed style property %s. Did you mean %s?',
+ name,
+ name.charAt(0).toUpperCase() + name.slice(1)
+ ) : null);
+ };
+
+ var warnStyleValueWithSemicolon = function(name, value) {
+ if (warnedStyleValues.hasOwnProperty(value) && warnedStyleValues[value]) {
+ return;
+ }
+
+ warnedStyleValues[value] = true;
+ ("production" !== process.env.NODE_ENV ? warning(
+ false,
+ 'Style property values shouldn\'t contain a semicolon. ' +
+ 'Try "%s: %s" instead.',
+ name,
+ value.replace(badStyleValueWithSemicolonPattern, '')
+ ) : null);
+ };
+
+ /**
+ * @param {string} name
+ * @param {*} value
+ */
+ var warnValidStyle = function(name, value) {
+ if (name.indexOf('-') > -1) {
+ warnHyphenatedStyleName(name);
+ } else if (badVendoredStyleNamePattern.test(name)) {
+ warnBadVendoredStyleName(name);
+ } else if (badStyleValueWithSemicolonPattern.test(value)) {
+ warnStyleValueWithSemicolon(name, value);
+ }
+ };
}
/**
@@ -3693,12 +4226,10 @@ var CSSPropertyOperations = {
if (!styles.hasOwnProperty(styleName)) {
continue;
}
+ var styleValue = styles[styleName];
if ("production" !== process.env.NODE_ENV) {
- if (styleName.indexOf('-') > -1) {
- warnHyphenatedStyleName(styleName);
- }
+ warnValidStyle(styleName, styleValue);
}
- var styleValue = styles[styleName];
if (styleValue != null) {
serialized += processStyleName(styleName) + ':';
serialized += dangerousStyleValue(styleName, styleValue) + ';';
@@ -3721,9 +4252,7 @@ var CSSPropertyOperations = {
continue;
}
if ("production" !== process.env.NODE_ENV) {
- if (styleName.indexOf('-') > -1) {
- warnHyphenatedStyleName(styleName);
- }
+ warnValidStyle(styleName, styles[styleName]);
}
var styleValue = dangerousStyleValue(styleName, styles[styleName]);
if (styleName === 'float') {
@@ -3751,10 +4280,10 @@ var CSSPropertyOperations = {
module.exports = CSSPropertyOperations;
}).call(this,require('_process'))
-},{"./CSSProperty":46,"./ExecutionEnvironment":64,"./camelizeStyleName":154,"./dangerousStyleValue":161,"./hyphenateStyleName":180,"./memoizeStringOnly":191,"./warning":202,"_process":1}],48:[function(require,module,exports){
+},{"./CSSProperty":45,"./ExecutionEnvironment":62,"./camelizeStyleName":162,"./dangerousStyleValue":169,"./hyphenateStyleName":189,"./memoizeStringOnly":200,"./warning":212,"_process":1}],47:[function(require,module,exports){
(function (process){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -3764,7 +4293,7 @@ module.exports = CSSPropertyOperations;
* @providesModule CallbackQueue
*/
-"use strict";
+'use strict';
var PooledClass = require("./PooledClass");
@@ -3815,7 +4344,7 @@ assign(CallbackQueue.prototype, {
if (callbacks) {
("production" !== process.env.NODE_ENV ? invariant(
callbacks.length === contexts.length,
- "Mismatched list of contexts in callback queue"
+ 'Mismatched list of contexts in callback queue'
) : invariant(callbacks.length === contexts.length));
this._callbacks = null;
this._contexts = null;
@@ -3851,9 +4380,9 @@ PooledClass.addPoolingTo(CallbackQueue);
module.exports = CallbackQueue;
}).call(this,require('_process'))
-},{"./Object.assign":70,"./PooledClass":71,"./invariant":182,"_process":1}],49:[function(require,module,exports){
+},{"./Object.assign":69,"./PooledClass":70,"./invariant":191,"_process":1}],48:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -3863,7 +4392,7 @@ module.exports = CallbackQueue;
* @providesModule ChangeEventPlugin
*/
-"use strict";
+'use strict';
var EventConstants = require("./EventConstants");
var EventPluginHub = require("./EventPluginHub");
@@ -3919,7 +4448,7 @@ var doesChangeEventBubble = false;
if (ExecutionEnvironment.canUseDOM) {
// See `handleChange` comment below
doesChangeEventBubble = isEventSupported('change') && (
- !('documentMode' in document) || document.documentMode > 8
+ (!('documentMode' in document) || document.documentMode > 8)
);
}
@@ -3996,7 +4525,7 @@ if (ExecutionEnvironment.canUseDOM) {
// IE9 claims to support the input event but fails to trigger it when
// deleting text, so we ignore its input events
isInputEventSupported = isEventSupported('input') && (
- !('documentMode' in document) || document.documentMode > 9
+ (!('documentMode' in document) || document.documentMode > 9)
);
}
@@ -4233,9 +4762,9 @@ var ChangeEventPlugin = {
module.exports = ChangeEventPlugin;
-},{"./EventConstants":58,"./EventPluginHub":60,"./EventPropagators":63,"./ExecutionEnvironment":64,"./ReactUpdates":132,"./SyntheticEvent":141,"./isEventSupported":183,"./isTextInputElement":185,"./keyOf":189}],50:[function(require,module,exports){
+},{"./EventConstants":56,"./EventPluginHub":58,"./EventPropagators":61,"./ExecutionEnvironment":62,"./ReactUpdates":140,"./SyntheticEvent":149,"./isEventSupported":192,"./isTextInputElement":194,"./keyOf":198}],49:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -4246,7 +4775,7 @@ module.exports = ChangeEventPlugin;
* @typechecks
*/
-"use strict";
+'use strict';
var nextReactRootIndex = 0;
@@ -4258,269 +4787,10 @@ var ClientReactRootIndex = {
module.exports = ClientReactRootIndex;
-},{}],51:[function(require,module,exports){
-/**
- * Copyright 2013-2014, Facebook, Inc.
- * All rights reserved.
- *
- * This source code is licensed under the BSD-style license found in the
- * LICENSE file in the root directory of this source tree. An additional grant
- * of patent rights can be found in the PATENTS file in the same directory.
- *
- * @providesModule CompositionEventPlugin
- * @typechecks static-only
- */
-
-"use strict";
-
-var EventConstants = require("./EventConstants");
-var EventPropagators = require("./EventPropagators");
-var ExecutionEnvironment = require("./ExecutionEnvironment");
-var ReactInputSelection = require("./ReactInputSelection");
-var SyntheticCompositionEvent = require("./SyntheticCompositionEvent");
-
-var getTextContentAccessor = require("./getTextContentAccessor");
-var keyOf = require("./keyOf");
-
-var END_KEYCODES = [9, 13, 27, 32]; // Tab, Return, Esc, Space
-var START_KEYCODE = 229;
-
-var useCompositionEvent = (
- ExecutionEnvironment.canUseDOM &&
- 'CompositionEvent' in window
-);
-
-// In IE9+, we have access to composition events, but the data supplied
-// by the native compositionend event may be incorrect. In Korean, for example,
-// the compositionend event contains only one character regardless of
-// how many characters have been composed since compositionstart.
-// We therefore use the fallback data while still using the native
-// events as triggers.
-var useFallbackData = (
- !useCompositionEvent ||
- (
- 'documentMode' in document &&
- document.documentMode > 8 &&
- document.documentMode <= 11
- )
-);
-
-var topLevelTypes = EventConstants.topLevelTypes;
-var currentComposition = null;
-
-// Events and their corresponding property names.
-var eventTypes = {
- compositionEnd: {
- phasedRegistrationNames: {
- bubbled: keyOf({onCompositionEnd: null}),
- captured: keyOf({onCompositionEndCapture: null})
- },
- dependencies: [
- topLevelTypes.topBlur,
- topLevelTypes.topCompositionEnd,
- topLevelTypes.topKeyDown,
- topLevelTypes.topKeyPress,
- topLevelTypes.topKeyUp,
- topLevelTypes.topMouseDown
- ]
- },
- compositionStart: {
- phasedRegistrationNames: {
- bubbled: keyOf({onCompositionStart: null}),
- captured: keyOf({onCompositionStartCapture: null})
- },
- dependencies: [
- topLevelTypes.topBlur,
- topLevelTypes.topCompositionStart,
- topLevelTypes.topKeyDown,
- topLevelTypes.topKeyPress,
- topLevelTypes.topKeyUp,
- topLevelTypes.topMouseDown
- ]
- },
- compositionUpdate: {
- phasedRegistrationNames: {
- bubbled: keyOf({onCompositionUpdate: null}),
- captured: keyOf({onCompositionUpdateCapture: null})
- },
- dependencies: [
- topLevelTypes.topBlur,
- topLevelTypes.topCompositionUpdate,
- topLevelTypes.topKeyDown,
- topLevelTypes.topKeyPress,
- topLevelTypes.topKeyUp,
- topLevelTypes.topMouseDown
- ]
- }
-};
-
-/**
- * Translate native top level events into event types.
- *
- * @param {string} topLevelType
- * @return {object}
- */
-function getCompositionEventType(topLevelType) {
- switch (topLevelType) {
- case topLevelTypes.topCompositionStart:
- return eventTypes.compositionStart;
- case topLevelTypes.topCompositionEnd:
- return eventTypes.compositionEnd;
- case topLevelTypes.topCompositionUpdate:
- return eventTypes.compositionUpdate;
- }
-}
-
-/**
- * Does our fallback best-guess model think this event signifies that
- * composition has begun?
- *
- * @param {string} topLevelType
- * @param {object} nativeEvent
- * @return {boolean}
- */
-function isFallbackStart(topLevelType, nativeEvent) {
- return (
- topLevelType === topLevelTypes.topKeyDown &&
- nativeEvent.keyCode === START_KEYCODE
- );
-}
-
-/**
- * Does our fallback mode think that this event is the end of composition?
- *
- * @param {string} topLevelType
- * @param {object} nativeEvent
- * @return {boolean}
- */
-function isFallbackEnd(topLevelType, nativeEvent) {
- switch (topLevelType) {
- case topLevelTypes.topKeyUp:
- // Command keys insert or clear IME input.
- return (END_KEYCODES.indexOf(nativeEvent.keyCode) !== -1);
- case topLevelTypes.topKeyDown:
- // Expect IME keyCode on each keydown. If we get any other
- // code we must have exited earlier.
- return (nativeEvent.keyCode !== START_KEYCODE);
- case topLevelTypes.topKeyPress:
- case topLevelTypes.topMouseDown:
- case topLevelTypes.topBlur:
- // Events are not possible without cancelling IME.
- return true;
- default:
- return false;
- }
-}
-
-/**
- * Helper class stores information about selection and document state
- * so we can figure out what changed at a later date.
- *
- * @param {DOMEventTarget} root
- */
-function FallbackCompositionState(root) {
- this.root = root;
- this.startSelection = ReactInputSelection.getSelection(root);
- this.startValue = this.getText();
-}
-
-/**
- * Get current text of input.
- *
- * @return {string}
- */
-FallbackCompositionState.prototype.getText = function() {
- return this.root.value || this.root[getTextContentAccessor()];
-};
-
-/**
- * Text that has changed since the start of composition.
- *
- * @return {string}
- */
-FallbackCompositionState.prototype.getData = function() {
- var endValue = this.getText();
- var prefixLength = this.startSelection.start;
- var suffixLength = this.startValue.length - this.startSelection.end;
-
- return endValue.substr(
- prefixLength,
- endValue.length - suffixLength - prefixLength
- );
-};
-
-/**
- * This plugin creates `onCompositionStart`, `onCompositionUpdate` and
- * `onCompositionEnd` events on inputs, textareas and contentEditable
- * nodes.
- */
-var CompositionEventPlugin = {
-
- eventTypes: eventTypes,
-
- /**
- * @param {string} topLevelType Record from `EventConstants`.
- * @param {DOMEventTarget} topLevelTarget The listening component root node.
- * @param {string} topLevelTargetID ID of `topLevelTarget`.
- * @param {object} nativeEvent Native browser event.
- * @return {*} An accumulation of synthetic events.
- * @see {EventPluginHub.extractEvents}
- */
- extractEvents: function(
- topLevelType,
- topLevelTarget,
- topLevelTargetID,
- nativeEvent) {
-
- var eventType;
- var data;
-
- if (useCompositionEvent) {
- eventType = getCompositionEventType(topLevelType);
- } else if (!currentComposition) {
- if (isFallbackStart(topLevelType, nativeEvent)) {
- eventType = eventTypes.compositionStart;
- }
- } else if (isFallbackEnd(topLevelType, nativeEvent)) {
- eventType = eventTypes.compositionEnd;
- }
-
- if (useFallbackData) {
- // The current composition is stored statically and must not be
- // overwritten while composition continues.
- if (!currentComposition && eventType === eventTypes.compositionStart) {
- currentComposition = new FallbackCompositionState(topLevelTarget);
- } else if (eventType === eventTypes.compositionEnd) {
- if (currentComposition) {
- data = currentComposition.getData();
- currentComposition = null;
- }
- }
- }
-
- if (eventType) {
- var event = SyntheticCompositionEvent.getPooled(
- eventType,
- topLevelTargetID,
- nativeEvent
- );
- if (data) {
- // Inject data generated from fallback path into the synthetic event.
- // This matches the property of native CompositionEventInterface.
- event.data = data;
- }
- EventPropagators.accumulateTwoPhaseDispatches(event);
- return event;
- }
- }
-};
-
-module.exports = CompositionEventPlugin;
-
-},{"./EventConstants":58,"./EventPropagators":63,"./ExecutionEnvironment":64,"./ReactInputSelection":106,"./SyntheticCompositionEvent":139,"./getTextContentAccessor":177,"./keyOf":189}],52:[function(require,module,exports){
+},{}],50:[function(require,module,exports){
(function (process){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -4531,23 +4801,15 @@ module.exports = CompositionEventPlugin;
* @typechecks static-only
*/
-"use strict";
+'use strict';
var Danger = require("./Danger");
var ReactMultiChildUpdateTypes = require("./ReactMultiChildUpdateTypes");
-var getTextContentAccessor = require("./getTextContentAccessor");
+var setTextContent = require("./setTextContent");
var invariant = require("./invariant");
/**
- * The DOM property to use when setting text content.
- *
- * @type {string}
- * @private
- */
-var textContentAccessor = getTextContentAccessor();
-
-/**
* Inserts `childNode` as a child of `parentNode` at the `index`.
*
* @param {DOMElement} parentNode Parent node in which to insert.
@@ -4566,37 +4828,6 @@ function insertChildAt(parentNode, childNode, index) {
);
}
-var updateTextContent;
-if (textContentAccessor === 'textContent') {
- /**
- * Sets the text content of `node` to `text`.
- *
- * @param {DOMElement} node Node to change
- * @param {string} text New text content
- */
- updateTextContent = function(node, text) {
- node.textContent = text;
- };
-} else {
- /**
- * Sets the text content of `node` to `text`.
- *
- * @param {DOMElement} node Node to change
- * @param {string} text New text content
- */
- updateTextContent = function(node, text) {
- // In order to preserve newlines correctly, we can't use .innerText to set
- // the contents (see #1080), so we empty the element then append a text node
- while (node.firstChild) {
- node.removeChild(node.firstChild);
- }
- if (text) {
- var doc = node.ownerDocument || document;
- node.appendChild(doc.createTextNode(text));
- }
- };
-}
-
/**
* Operations for updating with DOM children.
*/
@@ -4604,7 +4835,7 @@ var DOMChildrenOperations = {
dangerouslyReplaceNodeWithMarkup: Danger.dangerouslyReplaceNodeWithMarkup,
- updateTextContent: updateTextContent,
+ updateTextContent: setTextContent,
/**
* Updates a component's children by processing a series of updates. The
@@ -4621,7 +4852,8 @@ var DOMChildrenOperations = {
// List of children that will be moved or removed.
var updatedChildren = null;
- for (var i = 0; update = updates[i]; i++) {
+ for (var i = 0; i < updates.length; i++) {
+ update = updates[i];
if (update.type === ReactMultiChildUpdateTypes.MOVE_EXISTING ||
update.type === ReactMultiChildUpdateTypes.REMOVE_NODE) {
var updatedIndex = update.fromIndex;
@@ -4633,7 +4865,7 @@ var DOMChildrenOperations = {
'processUpdates(): Unable to find child %s of element. This ' +
'probably means the DOM was unexpectedly mutated (e.g., by the ' +
'browser), usually due to forgetting a <tbody> when using tables, ' +
- 'nesting tags like <form>, <p>, or <a>, or using non-SVG elements '+
+ 'nesting tags like <form>, <p>, or <a>, or using non-SVG elements ' +
'in an <svg> parent. Try inspecting the child nodes of the element ' +
'with React ID `%s`.',
updatedIndex,
@@ -4658,7 +4890,8 @@ var DOMChildrenOperations = {
}
}
- for (var k = 0; update = updates[k]; k++) {
+ for (var k = 0; k < updates.length; k++) {
+ update = updates[k];
switch (update.type) {
case ReactMultiChildUpdateTypes.INSERT_MARKUP:
insertChildAt(
@@ -4675,7 +4908,7 @@ var DOMChildrenOperations = {
);
break;
case ReactMultiChildUpdateTypes.TEXT_CONTENT:
- updateTextContent(
+ setTextContent(
update.parentNode,
update.textContent
);
@@ -4692,10 +4925,10 @@ var DOMChildrenOperations = {
module.exports = DOMChildrenOperations;
}).call(this,require('_process'))
-},{"./Danger":55,"./ReactMultiChildUpdateTypes":113,"./getTextContentAccessor":177,"./invariant":182,"_process":1}],53:[function(require,module,exports){
+},{"./Danger":53,"./ReactMultiChildUpdateTypes":119,"./invariant":191,"./setTextContent":206,"_process":1}],51:[function(require,module,exports){
(function (process){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -4708,7 +4941,7 @@ module.exports = DOMChildrenOperations;
/*jslint bitwise: true */
-"use strict";
+'use strict';
var invariant = require("./invariant");
@@ -4991,10 +5224,10 @@ var DOMProperty = {
module.exports = DOMProperty;
}).call(this,require('_process'))
-},{"./invariant":182,"_process":1}],54:[function(require,module,exports){
+},{"./invariant":191,"_process":1}],52:[function(require,module,exports){
(function (process){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -5005,12 +5238,11 @@ module.exports = DOMProperty;
* @typechecks static-only
*/
-"use strict";
+'use strict';
var DOMProperty = require("./DOMProperty");
-var escapeTextForBrowser = require("./escapeTextForBrowser");
-var memoizeStringOnly = require("./memoizeStringOnly");
+var quoteAttributeValueForBrowser = require("./quoteAttributeValueForBrowser");
var warning = require("./warning");
function shouldIgnoreValue(name, value) {
@@ -5021,10 +5253,6 @@ function shouldIgnoreValue(name, value) {
(DOMProperty.hasOverloadedBooleanValue[name] && value === false);
}
-var processAttributeNameAndPrefix = memoizeStringOnly(function(name) {
- return escapeTextForBrowser(name) + '="';
-});
-
if ("production" !== process.env.NODE_ENV) {
var reactProps = {
children: true,
@@ -5056,7 +5284,9 @@ if ("production" !== process.env.NODE_ENV) {
// logging too much when using transferPropsTo.
("production" !== process.env.NODE_ENV ? warning(
standardName == null,
- 'Unknown DOM property ' + name + '. Did you mean ' + standardName + '?'
+ 'Unknown DOM property %s. Did you mean %s?',
+ name,
+ standardName
) : null);
};
@@ -5074,8 +5304,8 @@ var DOMPropertyOperations = {
* @return {string} Markup string.
*/
createMarkupForID: function(id) {
- return processAttributeNameAndPrefix(DOMProperty.ID_ATTRIBUTE_NAME) +
- escapeTextForBrowser(id) + '"';
+ return DOMProperty.ID_ATTRIBUTE_NAME + '=' +
+ quoteAttributeValueForBrowser(id);
},
/**
@@ -5094,16 +5324,14 @@ var DOMPropertyOperations = {
var attributeName = DOMProperty.getAttributeName[name];
if (DOMProperty.hasBooleanValue[name] ||
(DOMProperty.hasOverloadedBooleanValue[name] && value === true)) {
- return escapeTextForBrowser(attributeName);
+ return attributeName;
}
- return processAttributeNameAndPrefix(attributeName) +
- escapeTextForBrowser(value) + '"';
+ return attributeName + '=' + quoteAttributeValueForBrowser(value);
} else if (DOMProperty.isCustomAttribute(name)) {
if (value == null) {
return '';
}
- return processAttributeNameAndPrefix(name) +
- escapeTextForBrowser(value) + '"';
+ return name + '=' + quoteAttributeValueForBrowser(value);
} else if ("production" !== process.env.NODE_ENV) {
warnUnknownProperty(name);
}
@@ -5188,10 +5416,10 @@ var DOMPropertyOperations = {
module.exports = DOMPropertyOperations;
}).call(this,require('_process'))
-},{"./DOMProperty":53,"./escapeTextForBrowser":165,"./memoizeStringOnly":191,"./warning":202,"_process":1}],55:[function(require,module,exports){
+},{"./DOMProperty":51,"./quoteAttributeValueForBrowser":204,"./warning":212,"_process":1}],53:[function(require,module,exports){
(function (process){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -5204,7 +5432,7 @@ module.exports = DOMPropertyOperations;
/*jslint evil: true, sub: true */
-"use strict";
+'use strict';
var ExecutionEnvironment = require("./ExecutionEnvironment");
@@ -5274,7 +5502,8 @@ var Danger = {
// This for-in loop skips the holes of the sparse array. The order of
// iteration should follow the order of assignment, which happens to match
// numerical index order, but we don't rely on that.
- for (var resultIndex in markupListByNodeName) {
+ var resultIndex;
+ for (resultIndex in markupListByNodeName) {
if (markupListByNodeName.hasOwnProperty(resultIndex)) {
var markup = markupListByNodeName[resultIndex];
@@ -5295,8 +5524,8 @@ var Danger = {
emptyFunction // Do nothing special with <script> tags.
);
- for (i = 0; i < renderNodes.length; ++i) {
- var renderNode = renderNodes[i];
+ for (var j = 0; j < renderNodes.length; ++j) {
+ var renderNode = renderNodes[j];
if (renderNode.hasAttribute &&
renderNode.hasAttribute(RESULT_INDEX_ATTR)) {
@@ -5316,7 +5545,7 @@ var Danger = {
} else if ("production" !== process.env.NODE_ENV) {
console.error(
- "Danger: Discarding unexpected node:",
+ 'Danger: Discarding unexpected node:',
renderNode
);
}
@@ -5362,7 +5591,7 @@ var Danger = {
'dangerouslyReplaceNodeWithMarkup(...): Cannot replace markup of the ' +
'<html> node. This is because browser quirks make this unreliable ' +
'and/or slow. If you want to render to the root you must use ' +
- 'server rendering. See renderComponentToString().'
+ 'server rendering. See React.renderToString().'
) : invariant(oldChild.tagName.toLowerCase() !== 'html'));
var newChild = createNodesFromMarkup(markup, emptyFunction)[0];
@@ -5374,9 +5603,9 @@ var Danger = {
module.exports = Danger;
}).call(this,require('_process'))
-},{"./ExecutionEnvironment":64,"./createNodesFromMarkup":159,"./emptyFunction":163,"./getMarkupWrap":174,"./invariant":182,"_process":1}],56:[function(require,module,exports){
+},{"./ExecutionEnvironment":62,"./createNodesFromMarkup":167,"./emptyFunction":170,"./getMarkupWrap":183,"./invariant":191,"_process":1}],54:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -5386,9 +5615,9 @@ module.exports = Danger;
* @providesModule DefaultEventPluginOrder
*/
-"use strict";
+'use strict';
- var keyOf = require("./keyOf");
+var keyOf = require("./keyOf");
/**
* Module that is injectable into `EventPluginHub`, that specifies a
@@ -5406,7 +5635,6 @@ var DefaultEventPluginOrder = [
keyOf({EnterLeaveEventPlugin: null}),
keyOf({ChangeEventPlugin: null}),
keyOf({SelectEventPlugin: null}),
- keyOf({CompositionEventPlugin: null}),
keyOf({BeforeInputEventPlugin: null}),
keyOf({AnalyticsEventPlugin: null}),
keyOf({MobileSafariClickEventPlugin: null})
@@ -5414,9 +5642,9 @@ var DefaultEventPluginOrder = [
module.exports = DefaultEventPluginOrder;
-},{"./keyOf":189}],57:[function(require,module,exports){
+},{"./keyOf":198}],55:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -5427,7 +5655,7 @@ module.exports = DefaultEventPluginOrder;
* @typechecks static-only
*/
-"use strict";
+'use strict';
var EventConstants = require("./EventConstants");
var EventPropagators = require("./EventPropagators");
@@ -5554,9 +5782,9 @@ var EnterLeaveEventPlugin = {
module.exports = EnterLeaveEventPlugin;
-},{"./EventConstants":58,"./EventPropagators":63,"./ReactMount":111,"./SyntheticMouseEvent":145,"./keyOf":189}],58:[function(require,module,exports){
+},{"./EventConstants":56,"./EventPropagators":61,"./ReactMount":117,"./SyntheticMouseEvent":153,"./keyOf":198}],56:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -5566,7 +5794,7 @@ module.exports = EnterLeaveEventPlugin;
* @providesModule EventConstants
*/
-"use strict";
+'use strict';
var keyMirror = require("./keyMirror");
@@ -5626,10 +5854,10 @@ var EventConstants = {
module.exports = EventConstants;
-},{"./keyMirror":188}],59:[function(require,module,exports){
+},{"./keyMirror":197}],57:[function(require,module,exports){
(function (process){
/**
- * Copyright 2013-2014 Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -5716,10 +5944,10 @@ var EventListener = {
module.exports = EventListener;
}).call(this,require('_process'))
-},{"./emptyFunction":163,"_process":1}],60:[function(require,module,exports){
+},{"./emptyFunction":170,"_process":1}],58:[function(require,module,exports){
(function (process){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -5729,7 +5957,7 @@ module.exports = EventListener;
* @providesModule EventPluginHub
*/
-"use strict";
+'use strict';
var EventPluginRegistry = require("./EventPluginRegistry");
var EventPluginUtils = require("./EventPluginUtils");
@@ -5778,12 +6006,14 @@ var executeDispatchesAndRelease = function(event) {
var InstanceHandle = null;
function validateInstanceHandle() {
- var invalid = !InstanceHandle||
- !InstanceHandle.traverseTwoPhase ||
- !InstanceHandle.traverseEnterLeave;
- if (invalid) {
- throw new Error('InstanceHandle not injected before use!');
- }
+ var valid =
+ InstanceHandle &&
+ InstanceHandle.traverseTwoPhase &&
+ InstanceHandle.traverseEnterLeave;
+ ("production" !== process.env.NODE_ENV ? invariant(
+ valid,
+ 'InstanceHandle not injected before use!'
+ ) : invariant(valid));
}
/**
@@ -5992,10 +6222,10 @@ var EventPluginHub = {
module.exports = EventPluginHub;
}).call(this,require('_process'))
-},{"./EventPluginRegistry":61,"./EventPluginUtils":62,"./accumulateInto":151,"./forEachAccumulated":168,"./invariant":182,"_process":1}],61:[function(require,module,exports){
+},{"./EventPluginRegistry":59,"./EventPluginUtils":60,"./accumulateInto":159,"./forEachAccumulated":176,"./invariant":191,"_process":1}],59:[function(require,module,exports){
(function (process){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -6006,7 +6236,7 @@ module.exports = EventPluginHub;
* @typechecks static-only
*/
-"use strict";
+'use strict';
var invariant = require("./invariant");
@@ -6272,10 +6502,10 @@ var EventPluginRegistry = {
module.exports = EventPluginRegistry;
}).call(this,require('_process'))
-},{"./invariant":182,"_process":1}],62:[function(require,module,exports){
+},{"./invariant":191,"_process":1}],60:[function(require,module,exports){
(function (process){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -6285,7 +6515,7 @@ module.exports = EventPluginRegistry;
* @providesModule EventPluginUtils
*/
-"use strict";
+'use strict';
var EventConstants = require("./EventConstants");
@@ -6391,8 +6621,8 @@ function executeDispatch(event, listener, domID) {
/**
* Standard/simple iteration through an event's collected dispatches.
*/
-function executeDispatchesInOrder(event, executeDispatch) {
- forEachEventDispatch(event, executeDispatch);
+function executeDispatchesInOrder(event, cb) {
+ forEachEventDispatch(event, cb);
event._dispatchListeners = null;
event._dispatchIDs = null;
}
@@ -6493,10 +6723,10 @@ var EventPluginUtils = {
module.exports = EventPluginUtils;
}).call(this,require('_process'))
-},{"./EventConstants":58,"./invariant":182,"_process":1}],63:[function(require,module,exports){
+},{"./EventConstants":56,"./invariant":191,"_process":1}],61:[function(require,module,exports){
(function (process){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -6506,7 +6736,7 @@ module.exports = EventPluginUtils;
* @providesModule EventPropagators
*/
-"use strict";
+'use strict';
var EventConstants = require("./EventConstants");
var EventPluginHub = require("./EventPluginHub");
@@ -6635,9 +6865,9 @@ var EventPropagators = {
module.exports = EventPropagators;
}).call(this,require('_process'))
-},{"./EventConstants":58,"./EventPluginHub":60,"./accumulateInto":151,"./forEachAccumulated":168,"_process":1}],64:[function(require,module,exports){
+},{"./EventConstants":56,"./EventPluginHub":58,"./accumulateInto":159,"./forEachAccumulated":176,"_process":1}],62:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -6652,9 +6882,8 @@ module.exports = EventPropagators;
"use strict";
var canUseDOM = !!(
- typeof window !== 'undefined' &&
- window.document &&
- window.document.createElement
+ (typeof window !== 'undefined' &&
+ window.document && window.document.createElement)
);
/**
@@ -6680,9 +6909,100 @@ var ExecutionEnvironment = {
module.exports = ExecutionEnvironment;
-},{}],65:[function(require,module,exports){
+},{}],63:[function(require,module,exports){
+/**
+ * Copyright 2013-2015, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ * @providesModule FallbackCompositionState
+ * @typechecks static-only
+ */
+
+'use strict';
+
+var PooledClass = require("./PooledClass");
+
+var assign = require("./Object.assign");
+var getTextContentAccessor = require("./getTextContentAccessor");
+
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * This helper class stores information about text content of a target node,
+ * allowing comparison of content before and after a given event.
+ *
+ * Identify the node where selection currently begins, then observe
+ * both its text content and its current position in the DOM. Since the
+ * browser may natively replace the target node during composition, we can
+ * use its position to find its replacement.
+ *
+ * @param {DOMEventTarget} root
+ */
+function FallbackCompositionState(root) {
+ this._root = root;
+ this._startText = this.getText();
+ this._fallbackText = null;
+}
+
+assign(FallbackCompositionState.prototype, {
+ /**
+ * Get current text of input.
+ *
+ * @return {string}
+ */
+ getText: function() {
+ if ('value' in this._root) {
+ return this._root.value;
+ }
+ return this._root[getTextContentAccessor()];
+ },
+
+ /**
+ * Determine the differing substring between the initially stored
+ * text content and the current content.
+ *
+ * @return {string}
+ */
+ getData: function() {
+ if (this._fallbackText) {
+ return this._fallbackText;
+ }
+
+ var start;
+ var startValue = this._startText;
+ var startLength = startValue.length;
+ var end;
+ var endValue = this.getText();
+ var endLength = endValue.length;
+
+ for (start = 0; start < startLength; start++) {
+ if (startValue[start] !== endValue[start]) {
+ break;
+ }
+ }
+
+ var minEnd = startLength - start;
+ for (end = 1; end <= minEnd; end++) {
+ if (startValue[startLength - end] !== endValue[endLength - end]) {
+ break;
+ }
+ }
+
+ var sliceTail = end > 1 ? 1 - end : undefined;
+ this._fallbackText = endValue.slice(start, sliceTail);
+ return this._fallbackText;
+ }
+});
+
+PooledClass.addPoolingTo(FallbackCompositionState);
+
+module.exports = FallbackCompositionState;
+
+},{"./Object.assign":69,"./PooledClass":70,"./getTextContentAccessor":186}],64:[function(require,module,exports){
+/**
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -6694,7 +7014,7 @@ module.exports = ExecutionEnvironment;
/*jslint bitwise: true*/
-"use strict";
+'use strict';
var DOMProperty = require("./DOMProperty");
var ExecutionEnvironment = require("./ExecutionEnvironment");
@@ -6771,8 +7091,13 @@ var HTMLDOMPropertyConfig = {
draggable: null,
encType: null,
form: MUST_USE_ATTRIBUTE,
+ formAction: MUST_USE_ATTRIBUTE,
+ formEncType: MUST_USE_ATTRIBUTE,
+ formMethod: MUST_USE_ATTRIBUTE,
formNoValidate: HAS_BOOLEAN_VALUE,
+ formTarget: MUST_USE_ATTRIBUTE,
frameBorder: MUST_USE_ATTRIBUTE,
+ headers: null,
height: MUST_USE_ATTRIBUTE,
hidden: MUST_USE_ATTRIBUTE | HAS_BOOLEAN_VALUE,
href: null,
@@ -6786,6 +7111,8 @@ var HTMLDOMPropertyConfig = {
list: MUST_USE_ATTRIBUTE,
loop: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
manifest: MUST_USE_ATTRIBUTE,
+ marginHeight: null,
+ marginWidth: null,
max: null,
maxLength: MUST_USE_ATTRIBUTE,
media: MUST_USE_ATTRIBUTE,
@@ -6796,7 +7123,7 @@ var HTMLDOMPropertyConfig = {
muted: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
name: null,
noValidate: HAS_BOOLEAN_VALUE,
- open: null,
+ open: HAS_BOOLEAN_VALUE,
pattern: null,
placeholder: null,
poster: null,
@@ -6836,12 +7163,22 @@ var HTMLDOMPropertyConfig = {
/**
* Non-standard Properties
*/
- autoCapitalize: null, // Supported in Mobile Safari for keyboard hints
- autoCorrect: null, // Supported in Mobile Safari for keyboard hints
- itemProp: MUST_USE_ATTRIBUTE, // Microdata: http://schema.org/docs/gs.html
- itemScope: MUST_USE_ATTRIBUTE | HAS_BOOLEAN_VALUE, // Microdata: http://schema.org/docs/gs.html
- itemType: MUST_USE_ATTRIBUTE, // Microdata: http://schema.org/docs/gs.html
- property: null // Supports OG in meta tags
+ // autoCapitalize and autoCorrect are supported in Mobile Safari for
+ // keyboard hints.
+ autoCapitalize: null,
+ autoCorrect: null,
+ // itemProp, itemScope, itemType are for
+ // Microdata support. See http://schema.org/docs/gs.html
+ itemProp: MUST_USE_ATTRIBUTE,
+ itemScope: MUST_USE_ATTRIBUTE | HAS_BOOLEAN_VALUE,
+ itemType: MUST_USE_ATTRIBUTE,
+ // itemID and itemRef are for Microdata support as well but
+ // only specified in the the WHATWG spec document. See
+ // https://html.spec.whatwg.org/multipage/microdata.html#microdata-dom-api
+ itemID: MUST_USE_ATTRIBUTE,
+ itemRef: MUST_USE_ATTRIBUTE,
+ // property is supported for OpenGraph in meta tags.
+ property: null
},
DOMAttributeNames: {
acceptCharset: 'accept-charset',
@@ -6855,7 +7192,9 @@ var HTMLDOMPropertyConfig = {
autoCorrect: 'autocorrect',
autoFocus: 'autofocus',
autoPlay: 'autoplay',
- encType: 'enctype',
+ // `encoding` is equivalent to `enctype`, IE8 lacks an `enctype` setter.
+ // http://www.w3.org/TR/html5/forms.html#dom-fs-encoding
+ encType: 'encoding',
hrefLang: 'hreflang',
radioGroup: 'radiogroup',
spellCheck: 'spellcheck',
@@ -6866,9 +7205,9 @@ var HTMLDOMPropertyConfig = {
module.exports = HTMLDOMPropertyConfig;
-},{"./DOMProperty":53,"./ExecutionEnvironment":64}],66:[function(require,module,exports){
+},{"./DOMProperty":51,"./ExecutionEnvironment":62}],65:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -6879,7 +7218,7 @@ module.exports = HTMLDOMPropertyConfig;
* @typechecks static-only
*/
-"use strict";
+'use strict';
var ReactLink = require("./ReactLink");
var ReactStateSetters = require("./ReactStateSetters");
@@ -6907,10 +7246,10 @@ var LinkedStateMixin = {
module.exports = LinkedStateMixin;
-},{"./ReactLink":109,"./ReactStateSetters":126}],67:[function(require,module,exports){
+},{"./ReactLink":115,"./ReactStateSetters":134}],66:[function(require,module,exports){
(function (process){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -6921,7 +7260,7 @@ module.exports = LinkedStateMixin;
* @typechecks static-only
*/
-"use strict";
+'use strict';
var ReactPropTypes = require("./ReactPropTypes");
@@ -6992,7 +7331,7 @@ var LinkedValueUtils = {
props.onChange ||
props.readOnly ||
props.disabled) {
- return;
+ return null;
}
return new Error(
'You provided a `value` prop to a form field without an ' +
@@ -7006,7 +7345,7 @@ var LinkedValueUtils = {
props.onChange ||
props.readOnly ||
props.disabled) {
- return;
+ return null;
}
return new Error(
'You provided a `checked` prop to a form field without an ' +
@@ -7063,10 +7402,10 @@ var LinkedValueUtils = {
module.exports = LinkedValueUtils;
}).call(this,require('_process'))
-},{"./ReactPropTypes":120,"./invariant":182,"_process":1}],68:[function(require,module,exports){
+},{"./ReactPropTypes":126,"./invariant":191,"_process":1}],67:[function(require,module,exports){
(function (process){
/**
- * Copyright 2014, Facebook, Inc.
+ * Copyright 2014-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -7076,7 +7415,7 @@ module.exports = LinkedValueUtils;
* @providesModule LocalEventTrapMixin
*/
-"use strict";
+'use strict';
var ReactBrowserEventEmitter = require("./ReactBrowserEventEmitter");
@@ -7091,10 +7430,17 @@ function remove(event) {
var LocalEventTrapMixin = {
trapBubbledEvent:function(topLevelType, handlerBaseName) {
("production" !== process.env.NODE_ENV ? invariant(this.isMounted(), 'Must be mounted to trap events') : invariant(this.isMounted()));
+ // If a component renders to null or if another component fatals and causes
+ // the state of the tree to be corrupted, `node` here can be null.
+ var node = this.getDOMNode();
+ ("production" !== process.env.NODE_ENV ? invariant(
+ node,
+ 'LocalEventTrapMixin.trapBubbledEvent(...): Requires node to be rendered.'
+ ) : invariant(node));
var listener = ReactBrowserEventEmitter.trapBubbledEvent(
topLevelType,
handlerBaseName,
- this.getDOMNode()
+ node
);
this._localEventListeners =
accumulateInto(this._localEventListeners, listener);
@@ -7113,9 +7459,9 @@ var LocalEventTrapMixin = {
module.exports = LocalEventTrapMixin;
}).call(this,require('_process'))
-},{"./ReactBrowserEventEmitter":74,"./accumulateInto":151,"./forEachAccumulated":168,"./invariant":182,"_process":1}],69:[function(require,module,exports){
+},{"./ReactBrowserEventEmitter":73,"./accumulateInto":159,"./forEachAccumulated":176,"./invariant":191,"_process":1}],68:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -7126,7 +7472,7 @@ module.exports = LocalEventTrapMixin;
* @typechecks static-only
*/
-"use strict";
+'use strict';
var EventConstants = require("./EventConstants");
@@ -7171,9 +7517,9 @@ var MobileSafariClickEventPlugin = {
module.exports = MobileSafariClickEventPlugin;
-},{"./EventConstants":58,"./emptyFunction":163}],70:[function(require,module,exports){
+},{"./EventConstants":56,"./emptyFunction":170}],69:[function(require,module,exports){
/**
- * Copyright 2014, Facebook, Inc.
+ * Copyright 2014-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -7185,6 +7531,8 @@ module.exports = MobileSafariClickEventPlugin;
// https://people.mozilla.org/~jorendorff/es6-draft.html#sec-object.assign
+'use strict';
+
function assign(target, sources) {
if (target == null) {
throw new TypeError('Object.assign target cannot be null or undefined');
@@ -7214,14 +7562,14 @@ function assign(target, sources) {
}
return to;
-};
+}
module.exports = assign;
-},{}],71:[function(require,module,exports){
+},{}],70:[function(require,module,exports){
(function (process){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -7231,7 +7579,7 @@ module.exports = assign;
* @providesModule PooledClass
*/
-"use strict";
+'use strict';
var invariant = require("./invariant");
@@ -7334,10 +7682,10 @@ var PooledClass = {
module.exports = PooledClass;
}).call(this,require('_process'))
-},{"./invariant":182,"_process":1}],72:[function(require,module,exports){
+},{"./invariant":191,"_process":1}],71:[function(require,module,exports){
(function (process){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -7347,51 +7695,44 @@ module.exports = PooledClass;
* @providesModule React
*/
-"use strict";
+/* globals __REACT_DEVTOOLS_GLOBAL_HOOK__*/
+
+'use strict';
-var DOMPropertyOperations = require("./DOMPropertyOperations");
var EventPluginUtils = require("./EventPluginUtils");
var ReactChildren = require("./ReactChildren");
var ReactComponent = require("./ReactComponent");
-var ReactCompositeComponent = require("./ReactCompositeComponent");
+var ReactClass = require("./ReactClass");
var ReactContext = require("./ReactContext");
var ReactCurrentOwner = require("./ReactCurrentOwner");
var ReactElement = require("./ReactElement");
var ReactElementValidator = require("./ReactElementValidator");
var ReactDOM = require("./ReactDOM");
-var ReactDOMComponent = require("./ReactDOMComponent");
+var ReactDOMTextComponent = require("./ReactDOMTextComponent");
var ReactDefaultInjection = require("./ReactDefaultInjection");
var ReactInstanceHandles = require("./ReactInstanceHandles");
-var ReactLegacyElement = require("./ReactLegacyElement");
var ReactMount = require("./ReactMount");
-var ReactMultiChild = require("./ReactMultiChild");
var ReactPerf = require("./ReactPerf");
var ReactPropTypes = require("./ReactPropTypes");
+var ReactReconciler = require("./ReactReconciler");
var ReactServerRendering = require("./ReactServerRendering");
-var ReactTextComponent = require("./ReactTextComponent");
var assign = require("./Object.assign");
-var deprecated = require("./deprecated");
+var findDOMNode = require("./findDOMNode");
var onlyChild = require("./onlyChild");
ReactDefaultInjection.inject();
var createElement = ReactElement.createElement;
var createFactory = ReactElement.createFactory;
+var cloneElement = ReactElement.cloneElement;
if ("production" !== process.env.NODE_ENV) {
createElement = ReactElementValidator.createElement;
createFactory = ReactElementValidator.createFactory;
+ cloneElement = ReactElementValidator.cloneElement;
}
-// TODO: Drop legacy elements once classes no longer export these factories
-createElement = ReactLegacyElement.wrapCreateElement(
- createElement
-);
-createFactory = ReactLegacyElement.wrapCreateFactory(
- createFactory
-);
-
var render = ReactPerf.measure('React', 'render', ReactMount.render);
var React = {
@@ -7401,56 +7742,32 @@ var React = {
count: ReactChildren.count,
only: onlyChild
},
+ Component: ReactComponent,
DOM: ReactDOM,
PropTypes: ReactPropTypes,
initializeTouchEvents: function(shouldUseTouch) {
EventPluginUtils.useTouchEvents = shouldUseTouch;
},
- createClass: ReactCompositeComponent.createClass,
+ createClass: ReactClass.createClass,
createElement: createElement,
+ cloneElement: cloneElement,
createFactory: createFactory,
+ createMixin: function(mixin) {
+ // Currently a noop. Will be used to validate and trace mixins.
+ return mixin;
+ },
constructAndRenderComponent: ReactMount.constructAndRenderComponent,
constructAndRenderComponentByID: ReactMount.constructAndRenderComponentByID,
+ findDOMNode: findDOMNode,
render: render,
renderToString: ReactServerRendering.renderToString,
renderToStaticMarkup: ReactServerRendering.renderToStaticMarkup,
unmountComponentAtNode: ReactMount.unmountComponentAtNode,
- isValidClass: ReactLegacyElement.isValidClass,
isValidElement: ReactElement.isValidElement,
withContext: ReactContext.withContext,
// Hook for JSX spread, don't use this for anything else.
- __spread: assign,
-
- // Deprecations (remove for 0.13)
- renderComponent: deprecated(
- 'React',
- 'renderComponent',
- 'render',
- this,
- render
- ),
- renderComponentToString: deprecated(
- 'React',
- 'renderComponentToString',
- 'renderToString',
- this,
- ReactServerRendering.renderToString
- ),
- renderComponentToStaticMarkup: deprecated(
- 'React',
- 'renderComponentToStaticMarkup',
- 'renderToStaticMarkup',
- this,
- ReactServerRendering.renderToStaticMarkup
- ),
- isValidComponent: deprecated(
- 'React',
- 'isValidComponent',
- 'isValidElement',
- this,
- ReactElement.isValidElement
- )
+ __spread: assign
};
// Inject the runtime into a devtools global hook regardless of browser.
@@ -7459,14 +7776,11 @@ if (
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined' &&
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.inject === 'function') {
__REACT_DEVTOOLS_GLOBAL_HOOK__.inject({
- Component: ReactComponent,
CurrentOwner: ReactCurrentOwner,
- DOMComponent: ReactDOMComponent,
- DOMPropertyOperations: DOMPropertyOperations,
InstanceHandles: ReactInstanceHandles,
Mount: ReactMount,
- MultiChild: ReactMultiChild,
- TextComponent: ReactTextComponent
+ Reconciler: ReactReconciler,
+ TextComponent: ReactDOMTextComponent
});
}
@@ -7515,17 +7829,14 @@ if ("production" !== process.env.NODE_ENV) {
}
}
-// Version exists only in the open-source version of React, not in Facebook's
-// internal version.
-React.version = '0.12.0';
+React.version = '0.13.1';
module.exports = React;
}).call(this,require('_process'))
-},{"./DOMPropertyOperations":54,"./EventPluginUtils":62,"./ExecutionEnvironment":64,"./Object.assign":70,"./ReactChildren":77,"./ReactComponent":78,"./ReactCompositeComponent":81,"./ReactContext":82,"./ReactCurrentOwner":83,"./ReactDOM":84,"./ReactDOMComponent":86,"./ReactDefaultInjection":96,"./ReactElement":99,"./ReactElementValidator":100,"./ReactInstanceHandles":107,"./ReactLegacyElement":108,"./ReactMount":111,"./ReactMultiChild":112,"./ReactPerf":116,"./ReactPropTypes":120,"./ReactServerRendering":124,"./ReactTextComponent":128,"./deprecated":162,"./onlyChild":193,"_process":1}],73:[function(require,module,exports){
-(function (process){
+},{"./EventPluginUtils":60,"./ExecutionEnvironment":62,"./Object.assign":69,"./ReactChildren":77,"./ReactClass":78,"./ReactComponent":79,"./ReactContext":84,"./ReactCurrentOwner":85,"./ReactDOM":86,"./ReactDOMTextComponent":97,"./ReactDefaultInjection":100,"./ReactElement":103,"./ReactElementValidator":104,"./ReactInstanceHandles":112,"./ReactMount":117,"./ReactPerf":122,"./ReactPropTypes":126,"./ReactReconciler":129,"./ReactServerRendering":132,"./findDOMNode":173,"./onlyChild":201,"_process":1}],72:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -7535,12 +7846,9 @@ module.exports = React;
* @providesModule ReactBrowserComponentMixin
*/
-"use strict";
-
-var ReactEmptyComponent = require("./ReactEmptyComponent");
-var ReactMount = require("./ReactMount");
+'use strict';
-var invariant = require("./invariant");
+var findDOMNode = require("./findDOMNode");
var ReactBrowserComponentMixin = {
/**
@@ -7551,23 +7859,15 @@ var ReactBrowserComponentMixin = {
* @protected
*/
getDOMNode: function() {
- ("production" !== process.env.NODE_ENV ? invariant(
- this.isMounted(),
- 'getDOMNode(): A component must be mounted to have a DOM node.'
- ) : invariant(this.isMounted()));
- if (ReactEmptyComponent.isNullComponentID(this._rootNodeID)) {
- return null;
- }
- return ReactMount.getNode(this._rootNodeID);
+ return findDOMNode(this);
}
};
module.exports = ReactBrowserComponentMixin;
-}).call(this,require('_process'))
-},{"./ReactEmptyComponent":101,"./ReactMount":111,"./invariant":182,"_process":1}],74:[function(require,module,exports){
+},{"./findDOMNode":173}],73:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -7578,7 +7878,7 @@ module.exports = ReactBrowserComponentMixin;
* @typechecks static-only
*/
-"use strict";
+'use strict';
var EventConstants = require("./EventConstants");
var EventPluginHub = require("./EventPluginHub");
@@ -7694,7 +7994,7 @@ var topEventMapping = {
/**
* To ensure no conflicts with other potential React instances on the page
*/
-var topListenersIDKey = "_reactListenersID" + String(Math.random()).slice(2);
+var topListenersIDKey = '_reactListenersID' + String(Math.random()).slice(2);
function getListeningForDocument(mountAt) {
// In IE8, `mountAt` is a host object and doesn't have `hasOwnProperty`
@@ -7751,8 +8051,7 @@ var ReactBrowserEventEmitter = assign({}, ReactEventEmitterMixin, {
*/
isEnabled: function() {
return !!(
- ReactBrowserEventEmitter.ReactEventListener &&
- ReactBrowserEventEmitter.ReactEventListener.isEnabled()
+ (ReactBrowserEventEmitter.ReactEventListener && ReactBrowserEventEmitter.ReactEventListener.isEnabled())
);
},
@@ -7787,8 +8086,7 @@ var ReactBrowserEventEmitter = assign({}, ReactEventEmitterMixin, {
for (var i = 0, l = dependencies.length; i < l; i++) {
var dependency = dependencies[i];
if (!(
- isListening.hasOwnProperty(dependency) &&
- isListening[dependency]
+ (isListening.hasOwnProperty(dependency) && isListening[dependency])
)) {
if (dependency === topLevelTypes.topWheel) {
if (isEventSupported('wheel')) {
@@ -7896,7 +8194,7 @@ var ReactBrowserEventEmitter = assign({}, ReactEventEmitterMixin, {
*
* @see http://www.quirksmode.org/dom/events/scroll.html
*/
- ensureScrollValueMonitoring: function(){
+ ensureScrollValueMonitoring: function() {
if (!isMonitoringScrollValue) {
var refresh = ViewportMetrics.refreshScrollValues;
ReactBrowserEventEmitter.ReactEventListener.monitorScrollValue(refresh);
@@ -7920,9 +8218,9 @@ var ReactBrowserEventEmitter = assign({}, ReactEventEmitterMixin, {
module.exports = ReactBrowserEventEmitter;
-},{"./EventConstants":58,"./EventPluginHub":60,"./EventPluginRegistry":61,"./Object.assign":70,"./ReactEventEmitterMixin":103,"./ViewportMetrics":150,"./isEventSupported":183}],75:[function(require,module,exports){
+},{"./EventConstants":56,"./EventPluginHub":58,"./EventPluginRegistry":59,"./Object.assign":69,"./ReactEventEmitterMixin":107,"./ViewportMetrics":158,"./isEventSupported":192}],74:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -7933,7 +8231,7 @@ module.exports = ReactBrowserEventEmitter;
* @providesModule ReactCSSTransitionGroup
*/
-"use strict";
+'use strict';
var React = require("./React");
@@ -7951,12 +8249,14 @@ var ReactCSSTransitionGroup = React.createClass({
propTypes: {
transitionName: React.PropTypes.string.isRequired,
+ transitionAppear: React.PropTypes.bool,
transitionEnter: React.PropTypes.bool,
transitionLeave: React.PropTypes.bool
},
getDefaultProps: function() {
return {
+ transitionAppear: false,
transitionEnter: true,
transitionLeave: true
};
@@ -7969,6 +8269,7 @@ var ReactCSSTransitionGroup = React.createClass({
return ReactCSSTransitionGroupChild(
{
name: this.props.transitionName,
+ appear: this.props.transitionAppear,
enter: this.props.transitionEnter,
leave: this.props.transitionLeave
},
@@ -7987,10 +8288,10 @@ var ReactCSSTransitionGroup = React.createClass({
module.exports = ReactCSSTransitionGroup;
-},{"./Object.assign":70,"./React":72,"./ReactCSSTransitionGroupChild":76,"./ReactTransitionGroup":131}],76:[function(require,module,exports){
+},{"./Object.assign":69,"./React":71,"./ReactCSSTransitionGroupChild":75,"./ReactTransitionGroup":138}],75:[function(require,module,exports){
(function (process){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -8001,7 +8302,7 @@ module.exports = ReactCSSTransitionGroup;
* @providesModule ReactCSSTransitionGroupChild
*/
-"use strict";
+'use strict';
var React = require("./React");
@@ -8009,6 +8310,7 @@ var CSSCore = require("./CSSCore");
var ReactTransitionEvents = require("./ReactTransitionEvents");
var onlyChild = require("./onlyChild");
+var warning = require("./warning");
// We don't remove the element from the DOM until we receive an animationend or
// transitionend event. If the user screws up and forgets to add an animation
@@ -8022,12 +8324,14 @@ var noEventListener = null;
if ("production" !== process.env.NODE_ENV) {
noEventListener = function() {
- console.warn(
+ ("production" !== process.env.NODE_ENV ? warning(
+ false,
'transition(): tried to perform an animation without ' +
'an animationend or transitionend event after timeout (' +
- NO_EVENT_TIMEOUT + 'ms). You should either disable this ' +
- 'transition in JS or add a CSS animation/transition.'
- );
+ '%sms). You should either disable this ' +
+ 'transition in JS or add a CSS animation/transition.',
+ NO_EVENT_TIMEOUT
+ ) : null);
};
}
@@ -8055,7 +8359,9 @@ var ReactCSSTransitionGroupChild = React.createClass({
// Usually this optional callback is used for informing an owner of
// a leave animation and telling it to remove the child.
- finishCallback && finishCallback();
+ if (finishCallback) {
+ finishCallback();
+ }
};
ReactTransitionEvents.addEndEventListener(node, endListener);
@@ -8098,6 +8404,14 @@ var ReactCSSTransitionGroupChild = React.createClass({
}
},
+ componentWillAppear: function(done) {
+ if (this.props.appear) {
+ this.transition('appear', done);
+ } else {
+ done();
+ }
+ },
+
componentWillEnter: function(done) {
if (this.props.enter) {
this.transition('enter', done);
@@ -8122,10 +8436,137 @@ var ReactCSSTransitionGroupChild = React.createClass({
module.exports = ReactCSSTransitionGroupChild;
}).call(this,require('_process'))
-},{"./CSSCore":45,"./React":72,"./ReactTransitionEvents":130,"./onlyChild":193,"_process":1}],77:[function(require,module,exports){
+},{"./CSSCore":44,"./React":71,"./ReactTransitionEvents":137,"./onlyChild":201,"./warning":212,"_process":1}],76:[function(require,module,exports){
+/**
+ * Copyright 2014-2015, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ * @providesModule ReactChildReconciler
+ * @typechecks static-only
+ */
+
+'use strict';
+
+var ReactReconciler = require("./ReactReconciler");
+
+var flattenChildren = require("./flattenChildren");
+var instantiateReactComponent = require("./instantiateReactComponent");
+var shouldUpdateReactComponent = require("./shouldUpdateReactComponent");
+
+/**
+ * ReactChildReconciler provides helpers for initializing or updating a set of
+ * children. Its output is suitable for passing it onto ReactMultiChild which
+ * does diffed reordering and insertion.
+ */
+var ReactChildReconciler = {
+
+ /**
+ * Generates a "mount image" for each of the supplied children. In the case
+ * of `ReactDOMComponent`, a mount image is a string of markup.
+ *
+ * @param {?object} nestedChildNodes Nested child maps.
+ * @return {?object} A set of child instances.
+ * @internal
+ */
+ instantiateChildren: function(nestedChildNodes, transaction, context) {
+ var children = flattenChildren(nestedChildNodes);
+ for (var name in children) {
+ if (children.hasOwnProperty(name)) {
+ var child = children[name];
+ // The rendered children must be turned into instances as they're
+ // mounted.
+ var childInstance = instantiateReactComponent(child, null);
+ children[name] = childInstance;
+ }
+ }
+ return children;
+ },
+
+ /**
+ * Updates the rendered children and returns a new set of children.
+ *
+ * @param {?object} prevChildren Previously initialized set of children.
+ * @param {?object} nextNestedChildNodes Nested child maps.
+ * @param {ReactReconcileTransaction} transaction
+ * @param {object} context
+ * @return {?object} A new set of child instances.
+ * @internal
+ */
+ updateChildren: function(
+ prevChildren,
+ nextNestedChildNodes,
+ transaction,
+ context) {
+ // We currently don't have a way to track moves here but if we use iterators
+ // instead of for..in we can zip the iterators and check if an item has
+ // moved.
+ // TODO: If nothing has changed, return the prevChildren object so that we
+ // can quickly bailout if nothing has changed.
+ var nextChildren = flattenChildren(nextNestedChildNodes);
+ if (!nextChildren && !prevChildren) {
+ return null;
+ }
+ var name;
+ for (name in nextChildren) {
+ if (!nextChildren.hasOwnProperty(name)) {
+ continue;
+ }
+ var prevChild = prevChildren && prevChildren[name];
+ var prevElement = prevChild && prevChild._currentElement;
+ var nextElement = nextChildren[name];
+ if (shouldUpdateReactComponent(prevElement, nextElement)) {
+ ReactReconciler.receiveComponent(
+ prevChild, nextElement, transaction, context
+ );
+ nextChildren[name] = prevChild;
+ } else {
+ if (prevChild) {
+ ReactReconciler.unmountComponent(prevChild, name);
+ }
+ // The child must be instantiated before it's mounted.
+ var nextChildInstance = instantiateReactComponent(
+ nextElement,
+ null
+ );
+ nextChildren[name] = nextChildInstance;
+ }
+ }
+ // Unmount children that are no longer present.
+ for (name in prevChildren) {
+ if (prevChildren.hasOwnProperty(name) &&
+ !(nextChildren && nextChildren.hasOwnProperty(name))) {
+ ReactReconciler.unmountComponent(prevChildren[name]);
+ }
+ }
+ return nextChildren;
+ },
+
+ /**
+ * Unmounts all rendered children. This should be used to clean up children
+ * when this component is unmounted.
+ *
+ * @param {?object} renderedChildren Previously initialized set of children.
+ * @internal
+ */
+ unmountChildren: function(renderedChildren) {
+ for (var name in renderedChildren) {
+ var renderedChild = renderedChildren[name];
+ ReactReconciler.unmountComponent(renderedChild);
+ }
+ }
+
+};
+
+module.exports = ReactChildReconciler;
+
+},{"./ReactReconciler":129,"./flattenChildren":174,"./instantiateReactComponent":190,"./shouldUpdateReactComponent":208}],77:[function(require,module,exports){
(function (process){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -8135,9 +8576,10 @@ module.exports = ReactCSSTransitionGroupChild;
* @providesModule ReactChildren
*/
-"use strict";
+'use strict';
var PooledClass = require("./PooledClass");
+var ReactFragment = require("./ReactFragment");
var traverseAllChildren = require("./traverseAllChildren");
var warning = require("./warning");
@@ -8207,13 +8649,15 @@ function mapSingleChildIntoContext(traverseContext, child, name, i) {
var mapResult = mapBookKeeping.mapResult;
var keyUnique = !mapResult.hasOwnProperty(name);
- ("production" !== process.env.NODE_ENV ? warning(
- keyUnique,
- 'ReactChildren.map(...): Encountered two children with the same key, ' +
- '`%s`. Child keys must be unique; when two children share a key, only ' +
- 'the first child will be used.',
- name
- ) : null);
+ if ("production" !== process.env.NODE_ENV) {
+ ("production" !== process.env.NODE_ENV ? warning(
+ keyUnique,
+ 'ReactChildren.map(...): Encountered two children with the same key, ' +
+ '`%s`. Child keys must be unique; when two children share a key, only ' +
+ 'the first child will be used.',
+ name
+ ) : null);
+ }
if (keyUnique) {
var mappedChild =
@@ -8245,7 +8689,7 @@ function mapChildren(children, func, context) {
var traverseContext = MapBookKeeping.getPooled(mapResult, func, context);
traverseAllChildren(children, mapSingleChildIntoContext, traverseContext);
MapBookKeeping.release(traverseContext);
- return mapResult;
+ return ReactFragment.create(mapResult);
}
function forEachSingleChildDummy(traverseContext, child, name, i) {
@@ -8272,664 +8716,41 @@ var ReactChildren = {
module.exports = ReactChildren;
}).call(this,require('_process'))
-},{"./PooledClass":71,"./traverseAllChildren":200,"./warning":202,"_process":1}],78:[function(require,module,exports){
+},{"./PooledClass":70,"./ReactFragment":109,"./traverseAllChildren":210,"./warning":212,"_process":1}],78:[function(require,module,exports){
(function (process){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
- * @providesModule ReactComponent
- */
-
-"use strict";
-
-var ReactElement = require("./ReactElement");
-var ReactOwner = require("./ReactOwner");
-var ReactUpdates = require("./ReactUpdates");
-
-var assign = require("./Object.assign");
-var invariant = require("./invariant");
-var keyMirror = require("./keyMirror");
-
-/**
- * Every React component is in one of these life cycles.
- */
-var ComponentLifeCycle = keyMirror({
- /**
- * Mounted components have a DOM node representation and are capable of
- * receiving new props.
- */
- MOUNTED: null,
- /**
- * Unmounted components are inactive and cannot receive new props.
- */
- UNMOUNTED: null
-});
-
-var injected = false;
-
-/**
- * Optionally injectable environment dependent cleanup hook. (server vs.
- * browser etc). Example: A browser system caches DOM nodes based on component
- * ID and must remove that cache entry when this instance is unmounted.
- *
- * @private
+ * @providesModule ReactClass
*/
-var unmountIDFromEnvironment = null;
-
-/**
- * The "image" of a component tree, is the platform specific (typically
- * serialized) data that represents a tree of lower level UI building blocks.
- * On the web, this "image" is HTML markup which describes a construction of
- * low level `div` and `span` nodes. Other platforms may have different
- * encoding of this "image". This must be injected.
- *
- * @private
- */
-var mountImageIntoNode = null;
-
-/**
- * Components are the basic units of composition in React.
- *
- * Every component accepts a set of keyed input parameters known as "props" that
- * are initialized by the constructor. Once a component is mounted, the props
- * can be mutated using `setProps` or `replaceProps`.
- *
- * Every component is capable of the following operations:
- *
- * `mountComponent`
- * Initializes the component, renders markup, and registers event listeners.
- *
- * `receiveComponent`
- * Updates the rendered DOM nodes to match the given component.
- *
- * `unmountComponent`
- * Releases any resources allocated by this component.
- *
- * Components can also be "owned" by other components. Being owned by another
- * component means being constructed by that component. This is different from
- * being the child of a component, which means having a DOM representation that
- * is a child of the DOM representation of that component.
- *
- * @class ReactComponent
- */
-var ReactComponent = {
-
- injection: {
- injectEnvironment: function(ReactComponentEnvironment) {
- ("production" !== process.env.NODE_ENV ? invariant(
- !injected,
- 'ReactComponent: injectEnvironment() can only be called once.'
- ) : invariant(!injected));
- mountImageIntoNode = ReactComponentEnvironment.mountImageIntoNode;
- unmountIDFromEnvironment =
- ReactComponentEnvironment.unmountIDFromEnvironment;
- ReactComponent.BackendIDOperations =
- ReactComponentEnvironment.BackendIDOperations;
- injected = true;
- }
- },
-
- /**
- * @internal
- */
- LifeCycle: ComponentLifeCycle,
-
- /**
- * Injected module that provides ability to mutate individual properties.
- * Injected into the base class because many different subclasses need access
- * to this.
- *
- * @internal
- */
- BackendIDOperations: null,
-
- /**
- * Base functionality for every ReactComponent constructor. Mixed into the
- * `ReactComponent` prototype, but exposed statically for easy access.
- *
- * @lends {ReactComponent.prototype}
- */
- Mixin: {
-
- /**
- * Checks whether or not this component is mounted.
- *
- * @return {boolean} True if mounted, false otherwise.
- * @final
- * @protected
- */
- isMounted: function() {
- return this._lifeCycleState === ComponentLifeCycle.MOUNTED;
- },
-
- /**
- * Sets a subset of the props.
- *
- * @param {object} partialProps Subset of the next props.
- * @param {?function} callback Called after props are updated.
- * @final
- * @public
- */
- setProps: function(partialProps, callback) {
- // Merge with the pending element if it exists, otherwise with existing
- // element props.
- var element = this._pendingElement || this._currentElement;
- this.replaceProps(
- assign({}, element.props, partialProps),
- callback
- );
- },
-
- /**
- * Replaces all of the props.
- *
- * @param {object} props New props.
- * @param {?function} callback Called after props are updated.
- * @final
- * @public
- */
- replaceProps: function(props, callback) {
- ("production" !== process.env.NODE_ENV ? invariant(
- this.isMounted(),
- 'replaceProps(...): Can only update a mounted component.'
- ) : invariant(this.isMounted()));
- ("production" !== process.env.NODE_ENV ? invariant(
- this._mountDepth === 0,
- 'replaceProps(...): You called `setProps` or `replaceProps` on a ' +
- 'component with a parent. This is an anti-pattern since props will ' +
- 'get reactively updated when rendered. Instead, change the owner\'s ' +
- '`render` method to pass the correct value as props to the component ' +
- 'where it is created.'
- ) : invariant(this._mountDepth === 0));
- // This is a deoptimized path. We optimize for always having a element.
- // This creates an extra internal element.
- this._pendingElement = ReactElement.cloneAndReplaceProps(
- this._pendingElement || this._currentElement,
- props
- );
- ReactUpdates.enqueueUpdate(this, callback);
- },
-
- /**
- * Schedule a partial update to the props. Only used for internal testing.
- *
- * @param {object} partialProps Subset of the next props.
- * @param {?function} callback Called after props are updated.
- * @final
- * @internal
- */
- _setPropsInternal: function(partialProps, callback) {
- // This is a deoptimized path. We optimize for always having a element.
- // This creates an extra internal element.
- var element = this._pendingElement || this._currentElement;
- this._pendingElement = ReactElement.cloneAndReplaceProps(
- element,
- assign({}, element.props, partialProps)
- );
- ReactUpdates.enqueueUpdate(this, callback);
- },
- /**
- * Base constructor for all React components.
- *
- * Subclasses that override this method should make sure to invoke
- * `ReactComponent.Mixin.construct.call(this, ...)`.
- *
- * @param {ReactElement} element
- * @internal
- */
- construct: function(element) {
- // This is the public exposed props object after it has been processed
- // with default props. The element's props represents the true internal
- // state of the props.
- this.props = element.props;
- // Record the component responsible for creating this component.
- // This is accessible through the element but we maintain an extra
- // field for compatibility with devtools and as a way to make an
- // incremental update. TODO: Consider deprecating this field.
- this._owner = element._owner;
-
- // All components start unmounted.
- this._lifeCycleState = ComponentLifeCycle.UNMOUNTED;
-
- // See ReactUpdates.
- this._pendingCallbacks = null;
-
- // We keep the old element and a reference to the pending element
- // to track updates.
- this._currentElement = element;
- this._pendingElement = null;
- },
-
- /**
- * Initializes the component, renders markup, and registers event listeners.
- *
- * NOTE: This does not insert any nodes into the DOM.
- *
- * Subclasses that override this method should make sure to invoke
- * `ReactComponent.Mixin.mountComponent.call(this, ...)`.
- *
- * @param {string} rootID DOM ID of the root node.
- * @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction
- * @param {number} mountDepth number of components in the owner hierarchy.
- * @return {?string} Rendered markup to be inserted into the DOM.
- * @internal
- */
- mountComponent: function(rootID, transaction, mountDepth) {
- ("production" !== process.env.NODE_ENV ? invariant(
- !this.isMounted(),
- 'mountComponent(%s, ...): Can only mount an unmounted component. ' +
- 'Make sure to avoid storing components between renders or reusing a ' +
- 'single component instance in multiple places.',
- rootID
- ) : invariant(!this.isMounted()));
- var ref = this._currentElement.ref;
- if (ref != null) {
- var owner = this._currentElement._owner;
- ReactOwner.addComponentAsRefTo(this, ref, owner);
- }
- this._rootNodeID = rootID;
- this._lifeCycleState = ComponentLifeCycle.MOUNTED;
- this._mountDepth = mountDepth;
- // Effectively: return '';
- },
-
- /**
- * Releases any resources allocated by `mountComponent`.
- *
- * NOTE: This does not remove any nodes from the DOM.
- *
- * Subclasses that override this method should make sure to invoke
- * `ReactComponent.Mixin.unmountComponent.call(this)`.
- *
- * @internal
- */
- unmountComponent: function() {
- ("production" !== process.env.NODE_ENV ? invariant(
- this.isMounted(),
- 'unmountComponent(): Can only unmount a mounted component.'
- ) : invariant(this.isMounted()));
- var ref = this._currentElement.ref;
- if (ref != null) {
- ReactOwner.removeComponentAsRefFrom(this, ref, this._owner);
- }
- unmountIDFromEnvironment(this._rootNodeID);
- this._rootNodeID = null;
- this._lifeCycleState = ComponentLifeCycle.UNMOUNTED;
- },
-
- /**
- * Given a new instance of this component, updates the rendered DOM nodes
- * as if that instance was rendered instead.
- *
- * Subclasses that override this method should make sure to invoke
- * `ReactComponent.Mixin.receiveComponent.call(this, ...)`.
- *
- * @param {object} nextComponent Next set of properties.
- * @param {ReactReconcileTransaction} transaction
- * @internal
- */
- receiveComponent: function(nextElement, transaction) {
- ("production" !== process.env.NODE_ENV ? invariant(
- this.isMounted(),
- 'receiveComponent(...): Can only update a mounted component.'
- ) : invariant(this.isMounted()));
- this._pendingElement = nextElement;
- this.performUpdateIfNecessary(transaction);
- },
-
- /**
- * If `_pendingElement` is set, update the component.
- *
- * @param {ReactReconcileTransaction} transaction
- * @internal
- */
- performUpdateIfNecessary: function(transaction) {
- if (this._pendingElement == null) {
- return;
- }
- var prevElement = this._currentElement;
- var nextElement = this._pendingElement;
- this._currentElement = nextElement;
- this.props = nextElement.props;
- this._owner = nextElement._owner;
- this._pendingElement = null;
- this.updateComponent(transaction, prevElement);
- },
-
- /**
- * Updates the component's currently mounted representation.
- *
- * @param {ReactReconcileTransaction} transaction
- * @param {object} prevElement
- * @internal
- */
- updateComponent: function(transaction, prevElement) {
- var nextElement = this._currentElement;
-
- // If either the owner or a `ref` has changed, make sure the newest owner
- // has stored a reference to `this`, and the previous owner (if different)
- // has forgotten the reference to `this`. We use the element instead
- // of the public this.props because the post processing cannot determine
- // a ref. The ref conceptually lives on the element.
-
- // TODO: Should this even be possible? The owner cannot change because
- // it's forbidden by shouldUpdateReactComponent. The ref can change
- // if you swap the keys of but not the refs. Reconsider where this check
- // is made. It probably belongs where the key checking and
- // instantiateReactComponent is done.
-
- if (nextElement._owner !== prevElement._owner ||
- nextElement.ref !== prevElement.ref) {
- if (prevElement.ref != null) {
- ReactOwner.removeComponentAsRefFrom(
- this, prevElement.ref, prevElement._owner
- );
- }
- // Correct, even if the owner is the same, and only the ref has changed.
- if (nextElement.ref != null) {
- ReactOwner.addComponentAsRefTo(
- this,
- nextElement.ref,
- nextElement._owner
- );
- }
- }
- },
-
- /**
- * Mounts this component and inserts it into the DOM.
- *
- * @param {string} rootID DOM ID of the root node.
- * @param {DOMElement} container DOM element to mount into.
- * @param {boolean} shouldReuseMarkup If true, do not insert markup
- * @final
- * @internal
- * @see {ReactMount.render}
- */
- mountComponentIntoNode: function(rootID, container, shouldReuseMarkup) {
- var transaction = ReactUpdates.ReactReconcileTransaction.getPooled();
- transaction.perform(
- this._mountComponentIntoNode,
- this,
- rootID,
- container,
- transaction,
- shouldReuseMarkup
- );
- ReactUpdates.ReactReconcileTransaction.release(transaction);
- },
-
- /**
- * @param {string} rootID DOM ID of the root node.
- * @param {DOMElement} container DOM element to mount into.
- * @param {ReactReconcileTransaction} transaction
- * @param {boolean} shouldReuseMarkup If true, do not insert markup
- * @final
- * @private
- */
- _mountComponentIntoNode: function(
- rootID,
- container,
- transaction,
- shouldReuseMarkup) {
- var markup = this.mountComponent(rootID, transaction, 0);
- mountImageIntoNode(markup, container, shouldReuseMarkup);
- },
-
- /**
- * Checks if this component is owned by the supplied `owner` component.
- *
- * @param {ReactComponent} owner Component to check.
- * @return {boolean} True if `owners` owns this component.
- * @final
- * @internal
- */
- isOwnedBy: function(owner) {
- return this._owner === owner;
- },
-
- /**
- * Gets another component, that shares the same owner as this one, by ref.
- *
- * @param {string} ref of a sibling Component.
- * @return {?ReactComponent} the actual sibling Component.
- * @final
- * @internal
- */
- getSiblingByRef: function(ref) {
- var owner = this._owner;
- if (!owner || !owner.refs) {
- return null;
- }
- return owner.refs[ref];
- }
- }
-};
-
-module.exports = ReactComponent;
-
-}).call(this,require('_process'))
-},{"./Object.assign":70,"./ReactElement":99,"./ReactOwner":115,"./ReactUpdates":132,"./invariant":182,"./keyMirror":188,"_process":1}],79:[function(require,module,exports){
-(function (process){
-/**
- * Copyright 2013-2014, Facebook, Inc.
- * All rights reserved.
- *
- * This source code is licensed under the BSD-style license found in the
- * LICENSE file in the root directory of this source tree. An additional grant
- * of patent rights can be found in the PATENTS file in the same directory.
- *
- * @providesModule ReactComponentBrowserEnvironment
- */
-
-/*jslint evil: true */
-
-"use strict";
-
-var ReactDOMIDOperations = require("./ReactDOMIDOperations");
-var ReactMarkupChecksum = require("./ReactMarkupChecksum");
-var ReactMount = require("./ReactMount");
-var ReactPerf = require("./ReactPerf");
-var ReactReconcileTransaction = require("./ReactReconcileTransaction");
-
-var getReactRootElementInContainer = require("./getReactRootElementInContainer");
-var invariant = require("./invariant");
-var setInnerHTML = require("./setInnerHTML");
-
-
-var ELEMENT_NODE_TYPE = 1;
-var DOC_NODE_TYPE = 9;
-
-
-/**
- * Abstracts away all functionality of `ReactComponent` requires knowledge of
- * the browser context.
- */
-var ReactComponentBrowserEnvironment = {
- ReactReconcileTransaction: ReactReconcileTransaction,
-
- BackendIDOperations: ReactDOMIDOperations,
-
- /**
- * If a particular environment requires that some resources be cleaned up,
- * specify this in the injected Mixin. In the DOM, we would likely want to
- * purge any cached node ID lookups.
- *
- * @private
- */
- unmountIDFromEnvironment: function(rootNodeID) {
- ReactMount.purgeID(rootNodeID);
- },
-
- /**
- * @param {string} markup Markup string to place into the DOM Element.
- * @param {DOMElement} container DOM Element to insert markup into.
- * @param {boolean} shouldReuseMarkup Should reuse the existing markup in the
- * container if possible.
- */
- mountImageIntoNode: ReactPerf.measure(
- 'ReactComponentBrowserEnvironment',
- 'mountImageIntoNode',
- function(markup, container, shouldReuseMarkup) {
- ("production" !== process.env.NODE_ENV ? invariant(
- container && (
- container.nodeType === ELEMENT_NODE_TYPE ||
- container.nodeType === DOC_NODE_TYPE
- ),
- 'mountComponentIntoNode(...): Target container is not valid.'
- ) : invariant(container && (
- container.nodeType === ELEMENT_NODE_TYPE ||
- container.nodeType === DOC_NODE_TYPE
- )));
-
- if (shouldReuseMarkup) {
- if (ReactMarkupChecksum.canReuseMarkup(
- markup,
- getReactRootElementInContainer(container))) {
- return;
- } else {
- ("production" !== process.env.NODE_ENV ? invariant(
- container.nodeType !== DOC_NODE_TYPE,
- 'You\'re trying to render a component to the document using ' +
- 'server rendering but the checksum was invalid. This usually ' +
- 'means you rendered a different component type or props on ' +
- 'the client from the one on the server, or your render() ' +
- 'methods are impure. React cannot handle this case due to ' +
- 'cross-browser quirks by rendering at the document root. You ' +
- 'should look for environment dependent code in your components ' +
- 'and ensure the props are the same client and server side.'
- ) : invariant(container.nodeType !== DOC_NODE_TYPE));
-
- if ("production" !== process.env.NODE_ENV) {
- console.warn(
- 'React attempted to use reuse markup in a container but the ' +
- 'checksum was invalid. This generally means that you are ' +
- 'using server rendering and the markup generated on the ' +
- 'server was not what the client was expecting. React injected ' +
- 'new markup to compensate which works but you have lost many ' +
- 'of the benefits of server rendering. Instead, figure out ' +
- 'why the markup being generated is different on the client ' +
- 'or server.'
- );
- }
- }
- }
-
- ("production" !== process.env.NODE_ENV ? invariant(
- container.nodeType !== DOC_NODE_TYPE,
- 'You\'re trying to render a component to the document but ' +
- 'you didn\'t use server rendering. We can\'t do this ' +
- 'without using server rendering due to cross-browser quirks. ' +
- 'See renderComponentToString() for server rendering.'
- ) : invariant(container.nodeType !== DOC_NODE_TYPE));
-
- setInnerHTML(container, markup);
- }
- )
-};
-
-module.exports = ReactComponentBrowserEnvironment;
-
-}).call(this,require('_process'))
-},{"./ReactDOMIDOperations":88,"./ReactMarkupChecksum":110,"./ReactMount":111,"./ReactPerf":116,"./ReactReconcileTransaction":122,"./getReactRootElementInContainer":176,"./invariant":182,"./setInnerHTML":196,"_process":1}],80:[function(require,module,exports){
-/**
- * Copyright 2013-2014, Facebook, Inc.
- * All rights reserved.
- *
- * This source code is licensed under the BSD-style license found in the
- * LICENSE file in the root directory of this source tree. An additional grant
- * of patent rights can be found in the PATENTS file in the same directory.
- *
-* @providesModule ReactComponentWithPureRenderMixin
-*/
-
-"use strict";
-
-var shallowEqual = require("./shallowEqual");
-
-/**
- * If your React component's render function is "pure", e.g. it will render the
- * same result given the same props and state, provide this Mixin for a
- * considerable performance boost.
- *
- * Most React components have pure render functions.
- *
- * Example:
- *
- * var ReactComponentWithPureRenderMixin =
- * require('ReactComponentWithPureRenderMixin');
- * React.createClass({
- * mixins: [ReactComponentWithPureRenderMixin],
- *
- * render: function() {
- * return <div className={this.props.className}>foo</div>;
- * }
- * });
- *
- * Note: This only checks shallow equality for props and state. If these contain
- * complex data structures this mixin may have false-negatives for deeper
- * differences. Only mixin to components which have simple props and state, or
- * use `forceUpdate()` when you know deep data structures have changed.
- */
-var ReactComponentWithPureRenderMixin = {
- shouldComponentUpdate: function(nextProps, nextState) {
- return !shallowEqual(this.props, nextProps) ||
- !shallowEqual(this.state, nextState);
- }
-};
-
-module.exports = ReactComponentWithPureRenderMixin;
-
-},{"./shallowEqual":197}],81:[function(require,module,exports){
-(function (process){
-/**
- * Copyright 2013-2014, Facebook, Inc.
- * All rights reserved.
- *
- * This source code is licensed under the BSD-style license found in the
- * LICENSE file in the root directory of this source tree. An additional grant
- * of patent rights can be found in the PATENTS file in the same directory.
- *
- * @providesModule ReactCompositeComponent
- */
-
-"use strict";
+'use strict';
var ReactComponent = require("./ReactComponent");
-var ReactContext = require("./ReactContext");
var ReactCurrentOwner = require("./ReactCurrentOwner");
var ReactElement = require("./ReactElement");
-var ReactElementValidator = require("./ReactElementValidator");
-var ReactEmptyComponent = require("./ReactEmptyComponent");
var ReactErrorUtils = require("./ReactErrorUtils");
-var ReactLegacyElement = require("./ReactLegacyElement");
-var ReactOwner = require("./ReactOwner");
-var ReactPerf = require("./ReactPerf");
-var ReactPropTransferer = require("./ReactPropTransferer");
+var ReactInstanceMap = require("./ReactInstanceMap");
+var ReactLifeCycle = require("./ReactLifeCycle");
var ReactPropTypeLocations = require("./ReactPropTypeLocations");
var ReactPropTypeLocationNames = require("./ReactPropTypeLocationNames");
-var ReactUpdates = require("./ReactUpdates");
+var ReactUpdateQueue = require("./ReactUpdateQueue");
var assign = require("./Object.assign");
-var instantiateReactComponent = require("./instantiateReactComponent");
var invariant = require("./invariant");
var keyMirror = require("./keyMirror");
var keyOf = require("./keyOf");
-var monitorCodeUse = require("./monitorCodeUse");
-var mapObject = require("./mapObject");
-var shouldUpdateReactComponent = require("./shouldUpdateReactComponent");
var warning = require("./warning");
var MIXINS_KEY = keyOf({mixins: null});
/**
- * Policies that describe methods in `ReactCompositeComponentInterface`.
+ * Policies that describe methods in `ReactClassInterface`.
*/
var SpecPolicy = keyMirror({
/**
@@ -8942,7 +8763,7 @@ var SpecPolicy = keyMirror({
*/
DEFINE_MANY: null,
/**
- * These methods are overriding the base ReactCompositeComponent class.
+ * These methods are overriding the base class.
*/
OVERRIDE_BASE: null,
/**
@@ -8960,7 +8781,7 @@ var injectedMixins = [];
* Composite components are higher-level components that compose other composite
* or native components.
*
- * To create a new type of `ReactCompositeComponent`, pass a specification of
+ * To create a new type of `ReactClass`, pass a specification of
* your new class to `React.createClass`. The only requirement of your class
* specification is that you implement a `render` method.
*
@@ -8971,14 +8792,14 @@ var injectedMixins = [];
* });
*
* The class specification supports a specific protocol of methods that have
- * special meaning (e.g. `render`). See `ReactCompositeComponentInterface` for
+ * special meaning (e.g. `render`). See `ReactClassInterface` for
* more the comprehensive protocol. Any other properties and methods in the
* class specification will available on the prototype.
*
- * @interface ReactCompositeComponentInterface
+ * @interface ReactClassInterface
* @internal
*/
-var ReactCompositeComponentInterface = {
+var ReactClassInterface = {
/**
* An array of Mixin objects to include when defining your component.
@@ -9226,11 +9047,13 @@ var RESERVED_SPEC_KEYS = {
}
},
childContextTypes: function(Constructor, childContextTypes) {
- validateTypeDef(
- Constructor,
- childContextTypes,
- ReactPropTypeLocations.childContext
- );
+ if ("production" !== process.env.NODE_ENV) {
+ validateTypeDef(
+ Constructor,
+ childContextTypes,
+ ReactPropTypeLocations.childContext
+ );
+ }
Constructor.childContextTypes = assign(
{},
Constructor.childContextTypes,
@@ -9238,11 +9061,13 @@ var RESERVED_SPEC_KEYS = {
);
},
contextTypes: function(Constructor, contextTypes) {
- validateTypeDef(
- Constructor,
- contextTypes,
- ReactPropTypeLocations.context
- );
+ if ("production" !== process.env.NODE_ENV) {
+ validateTypeDef(
+ Constructor,
+ contextTypes,
+ ReactPropTypeLocations.context
+ );
+ }
Constructor.contextTypes = assign(
{},
Constructor.contextTypes,
@@ -9264,11 +9089,13 @@ var RESERVED_SPEC_KEYS = {
}
},
propTypes: function(Constructor, propTypes) {
- validateTypeDef(
- Constructor,
- propTypes,
- ReactPropTypeLocations.prop
- );
+ if ("production" !== process.env.NODE_ENV) {
+ validateTypeDef(
+ Constructor,
+ propTypes,
+ ReactPropTypeLocations.prop
+ );
+ }
Constructor.propTypes = assign(
{},
Constructor.propTypes,
@@ -9280,40 +9107,33 @@ var RESERVED_SPEC_KEYS = {
}
};
-function getDeclarationErrorAddendum(component) {
- var owner = component._owner || null;
- if (owner && owner.constructor && owner.constructor.displayName) {
- return ' Check the render method of `' + owner.constructor.displayName +
- '`.';
- }
- return '';
-}
-
function validateTypeDef(Constructor, typeDef, location) {
for (var propName in typeDef) {
if (typeDef.hasOwnProperty(propName)) {
- ("production" !== process.env.NODE_ENV ? invariant(
- typeof typeDef[propName] == 'function',
+ // use a warning instead of an invariant so components
+ // don't show up in prod but not in __DEV__
+ ("production" !== process.env.NODE_ENV ? warning(
+ typeof typeDef[propName] === 'function',
'%s: %s type `%s` is invalid; it must be a function, usually from ' +
'React.PropTypes.',
- Constructor.displayName || 'ReactCompositeComponent',
+ Constructor.displayName || 'ReactClass',
ReactPropTypeLocationNames[location],
propName
- ) : invariant(typeof typeDef[propName] == 'function'));
+ ) : null);
}
}
}
function validateMethodOverride(proto, name) {
- var specPolicy = ReactCompositeComponentInterface.hasOwnProperty(name) ?
- ReactCompositeComponentInterface[name] :
+ var specPolicy = ReactClassInterface.hasOwnProperty(name) ?
+ ReactClassInterface[name] :
null;
// Disallow overriding of base class methods unless explicitly allowed.
- if (ReactCompositeComponentMixin.hasOwnProperty(name)) {
+ if (ReactClassMixin.hasOwnProperty(name)) {
("production" !== process.env.NODE_ENV ? invariant(
specPolicy === SpecPolicy.OVERRIDE_BASE,
- 'ReactCompositeComponentInterface: You are attempting to override ' +
+ 'ReactClassInterface: You are attempting to override ' +
'`%s` from your class specification. Ensure that your method names ' +
'do not overlap with React methods.',
name
@@ -9325,7 +9145,7 @@ function validateMethodOverride(proto, name) {
("production" !== process.env.NODE_ENV ? invariant(
specPolicy === SpecPolicy.DEFINE_MANY ||
specPolicy === SpecPolicy.DEFINE_MANY_MERGED,
- 'ReactCompositeComponentInterface: You are attempting to define ' +
+ 'ReactClassInterface: You are attempting to define ' +
'`%s` on your component more than once. This conflict may be due ' +
'to a mixin.',
name
@@ -9334,29 +9154,9 @@ function validateMethodOverride(proto, name) {
}
}
-function validateLifeCycleOnReplaceState(instance) {
- var compositeLifeCycleState = instance._compositeLifeCycleState;
- ("production" !== process.env.NODE_ENV ? invariant(
- instance.isMounted() ||
- compositeLifeCycleState === CompositeLifeCycle.MOUNTING,
- 'replaceState(...): Can only update a mounted or mounting component.'
- ) : invariant(instance.isMounted() ||
- compositeLifeCycleState === CompositeLifeCycle.MOUNTING));
- ("production" !== process.env.NODE_ENV ? invariant(
- ReactCurrentOwner.current == null,
- 'replaceState(...): Cannot update during an existing state transition ' +
- '(such as within `render`). Render methods should be a pure function ' +
- 'of props and state.'
- ) : invariant(ReactCurrentOwner.current == null));
- ("production" !== process.env.NODE_ENV ? invariant(compositeLifeCycleState !== CompositeLifeCycle.UNMOUNTING,
- 'replaceState(...): Cannot update while unmounting component. This ' +
- 'usually means you called setState() on an unmounted component.'
- ) : invariant(compositeLifeCycleState !== CompositeLifeCycle.UNMOUNTING));
-}
-
/**
* Mixin helper which handles policy validation and reserved
- * specification keys when building `ReactCompositeComponent` classses.
+ * specification keys when building React classses.
*/
function mixSpecIntoComponent(Constructor, spec) {
if (!spec) {
@@ -9364,13 +9164,13 @@ function mixSpecIntoComponent(Constructor, spec) {
}
("production" !== process.env.NODE_ENV ? invariant(
- !ReactLegacyElement.isValidFactory(spec),
- 'ReactCompositeComponent: You\'re attempting to ' +
+ typeof spec !== 'function',
+ 'ReactClass: You\'re attempting to ' +
'use a component class as a mixin. Instead, just use a regular object.'
- ) : invariant(!ReactLegacyElement.isValidFactory(spec)));
+ ) : invariant(typeof spec !== 'function'));
("production" !== process.env.NODE_ENV ? invariant(
!ReactElement.isValidElement(spec),
- 'ReactCompositeComponent: You\'re attempting to ' +
+ 'ReactClass: You\'re attempting to ' +
'use a component as a mixin. Instead, just use a regular object.'
) : invariant(!ReactElement.isValidElement(spec)));
@@ -9401,16 +9201,16 @@ function mixSpecIntoComponent(Constructor, spec) {
} else {
// Setup methods on prototype:
// The following member methods should not be automatically bound:
- // 1. Expected ReactCompositeComponent methods (in the "interface").
+ // 1. Expected ReactClass methods (in the "interface").
// 2. Overridden methods (that were mixed in).
- var isCompositeComponentMethod =
- ReactCompositeComponentInterface.hasOwnProperty(name);
+ var isReactClassMethod =
+ ReactClassInterface.hasOwnProperty(name);
var isAlreadyDefined = proto.hasOwnProperty(name);
var markedDontBind = property && property.__reactDontBind;
var isFunction = typeof property === 'function';
var shouldAutoBind =
isFunction &&
- !isCompositeComponentMethod &&
+ !isReactClassMethod &&
!isAlreadyDefined &&
!markedDontBind;
@@ -9422,21 +9222,19 @@ function mixSpecIntoComponent(Constructor, spec) {
proto[name] = property;
} else {
if (isAlreadyDefined) {
- var specPolicy = ReactCompositeComponentInterface[name];
+ var specPolicy = ReactClassInterface[name];
// These cases should already be caught by validateMethodOverride
("production" !== process.env.NODE_ENV ? invariant(
- isCompositeComponentMethod && (
- specPolicy === SpecPolicy.DEFINE_MANY_MERGED ||
- specPolicy === SpecPolicy.DEFINE_MANY
+ isReactClassMethod && (
+ (specPolicy === SpecPolicy.DEFINE_MANY_MERGED || specPolicy === SpecPolicy.DEFINE_MANY)
),
- 'ReactCompositeComponent: Unexpected spec policy %s for key %s ' +
+ 'ReactClass: Unexpected spec policy %s for key %s ' +
'when mixing in component specs.',
specPolicy,
name
- ) : invariant(isCompositeComponentMethod && (
- specPolicy === SpecPolicy.DEFINE_MANY_MERGED ||
- specPolicy === SpecPolicy.DEFINE_MANY
+ ) : invariant(isReactClassMethod && (
+ (specPolicy === SpecPolicy.DEFINE_MANY_MERGED || specPolicy === SpecPolicy.DEFINE_MANY)
)));
// For methods which are defined more than once, call the existing
@@ -9474,7 +9272,7 @@ function mixStaticSpecIntoComponent(Constructor, statics) {
var isReserved = name in RESERVED_SPEC_KEYS;
("production" !== process.env.NODE_ENV ? invariant(
!isReserved,
- 'ReactCompositeComponent: You are attempting to define a reserved ' +
+ 'ReactClass: You are attempting to define a reserved ' +
'property, `%s`, that shouldn\'t be on the "statics" key. Define it ' +
'as an instance property instead; it will still be accessible on the ' +
'constructor.',
@@ -9484,7 +9282,7 @@ function mixStaticSpecIntoComponent(Constructor, statics) {
var isInherited = name in Constructor;
("production" !== process.env.NODE_ENV ? invariant(
!isInherited,
- 'ReactCompositeComponent: You are attempting to define ' +
+ 'ReactClass: You are attempting to define ' +
'`%s` on your component more than once. This conflict may be ' +
'due to a mixin.',
name
@@ -9500,24 +9298,26 @@ function mixStaticSpecIntoComponent(Constructor, statics) {
* @param {object} two The second object
* @return {object} one after it has been mutated to contain everything in two.
*/
-function mergeObjectsWithNoDuplicateKeys(one, two) {
+function mergeIntoWithNoDuplicateKeys(one, two) {
("production" !== process.env.NODE_ENV ? invariant(
one && two && typeof one === 'object' && typeof two === 'object',
- 'mergeObjectsWithNoDuplicateKeys(): Cannot merge non-objects'
+ 'mergeIntoWithNoDuplicateKeys(): Cannot merge non-objects.'
) : invariant(one && two && typeof one === 'object' && typeof two === 'object'));
- mapObject(two, function(value, key) {
- ("production" !== process.env.NODE_ENV ? invariant(
- one[key] === undefined,
- 'mergeObjectsWithNoDuplicateKeys(): ' +
- 'Tried to merge two objects with the same key: `%s`. This conflict ' +
- 'may be due to a mixin; in particular, this may be caused by two ' +
- 'getInitialState() or getDefaultProps() methods returning objects ' +
- 'with clashing keys.',
- key
- ) : invariant(one[key] === undefined));
- one[key] = value;
- });
+ for (var key in two) {
+ if (two.hasOwnProperty(key)) {
+ ("production" !== process.env.NODE_ENV ? invariant(
+ one[key] === undefined,
+ 'mergeIntoWithNoDuplicateKeys(): ' +
+ 'Tried to merge two objects with the same key: `%s`. This conflict ' +
+ 'may be due to a mixin; in particular, this may be caused by two ' +
+ 'getInitialState() or getDefaultProps() methods returning objects ' +
+ 'with clashing keys.',
+ key
+ ) : invariant(one[key] === undefined));
+ one[key] = two[key];
+ }
+ }
return one;
}
@@ -9538,7 +9338,10 @@ function createMergedResultFunction(one, two) {
} else if (b == null) {
return a;
}
- return mergeObjectsWithNoDuplicateKeys(a, b);
+ var c = {};
+ mergeIntoWithNoDuplicateKeys(c, a);
+ mergeIntoWithNoDuplicateKeys(c, b);
+ return c;
};
}
@@ -9558,48 +9361,680 @@ function createChainedFunction(one, two) {
}
/**
- * `ReactCompositeComponent` maintains an auxiliary life cycle state in
- * `this._compositeLifeCycleState` (which can be null).
+ * Binds a method to the component.
+ *
+ * @param {object} component Component whose method is going to be bound.
+ * @param {function} method Method to be bound.
+ * @return {function} The bound method.
+ */
+function bindAutoBindMethod(component, method) {
+ var boundMethod = method.bind(component);
+ if ("production" !== process.env.NODE_ENV) {
+ boundMethod.__reactBoundContext = component;
+ boundMethod.__reactBoundMethod = method;
+ boundMethod.__reactBoundArguments = null;
+ var componentName = component.constructor.displayName;
+ var _bind = boundMethod.bind;
+ /* eslint-disable block-scoped-var, no-undef */
+ boundMethod.bind = function(newThis ) {for (var args=[],$__0=1,$__1=arguments.length;$__0<$__1;$__0++) args.push(arguments[$__0]);
+ // User is trying to bind() an autobound method; we effectively will
+ // ignore the value of "this" that the user is trying to use, so
+ // let's warn.
+ if (newThis !== component && newThis !== null) {
+ ("production" !== process.env.NODE_ENV ? warning(
+ false,
+ 'bind(): React component methods may only be bound to the ' +
+ 'component instance. See %s',
+ componentName
+ ) : null);
+ } else if (!args.length) {
+ ("production" !== process.env.NODE_ENV ? warning(
+ false,
+ 'bind(): You are binding a component method to the component. ' +
+ 'React does this for you automatically in a high-performance ' +
+ 'way, so you can safely remove this call. See %s',
+ componentName
+ ) : null);
+ return boundMethod;
+ }
+ var reboundMethod = _bind.apply(boundMethod, arguments);
+ reboundMethod.__reactBoundContext = component;
+ reboundMethod.__reactBoundMethod = method;
+ reboundMethod.__reactBoundArguments = args;
+ return reboundMethod;
+ /* eslint-enable */
+ };
+ }
+ return boundMethod;
+}
+
+/**
+ * Binds all auto-bound methods in a component.
+ *
+ * @param {object} component Component whose method is going to be bound.
+ */
+function bindAutoBindMethods(component) {
+ for (var autoBindKey in component.__reactAutoBindMap) {
+ if (component.__reactAutoBindMap.hasOwnProperty(autoBindKey)) {
+ var method = component.__reactAutoBindMap[autoBindKey];
+ component[autoBindKey] = bindAutoBindMethod(
+ component,
+ ReactErrorUtils.guard(
+ method,
+ component.constructor.displayName + '.' + autoBindKey
+ )
+ );
+ }
+ }
+}
+
+var typeDeprecationDescriptor = {
+ enumerable: false,
+ get: function() {
+ var displayName = this.displayName || this.name || 'Component';
+ ("production" !== process.env.NODE_ENV ? warning(
+ false,
+ '%s.type is deprecated. Use %s directly to access the class.',
+ displayName,
+ displayName
+ ) : null);
+ Object.defineProperty(this, 'type', {
+ value: this
+ });
+ return this;
+ }
+};
+
+/**
+ * Add more to the ReactClass base class. These are all legacy features and
+ * therefore not already part of the modern ReactComponent.
+ */
+var ReactClassMixin = {
+
+ /**
+ * TODO: This will be deprecated because state should always keep a consistent
+ * type signature and the only use case for this, is to avoid that.
+ */
+ replaceState: function(newState, callback) {
+ ReactUpdateQueue.enqueueReplaceState(this, newState);
+ if (callback) {
+ ReactUpdateQueue.enqueueCallback(this, callback);
+ }
+ },
+
+ /**
+ * Checks whether or not this composite component is mounted.
+ * @return {boolean} True if mounted, false otherwise.
+ * @protected
+ * @final
+ */
+ isMounted: function() {
+ if ("production" !== process.env.NODE_ENV) {
+ var owner = ReactCurrentOwner.current;
+ if (owner !== null) {
+ ("production" !== process.env.NODE_ENV ? warning(
+ owner._warnedAboutRefsInRender,
+ '%s is accessing isMounted inside its render() function. ' +
+ 'render() should be a pure function of props and state. It should ' +
+ 'never access something that requires stale data from the previous ' +
+ 'render, such as refs. Move this logic to componentDidMount and ' +
+ 'componentDidUpdate instead.',
+ owner.getName() || 'A component'
+ ) : null);
+ owner._warnedAboutRefsInRender = true;
+ }
+ }
+ var internalInstance = ReactInstanceMap.get(this);
+ return (
+ internalInstance &&
+ internalInstance !== ReactLifeCycle.currentlyMountingInstance
+ );
+ },
+
+ /**
+ * Sets a subset of the props.
+ *
+ * @param {object} partialProps Subset of the next props.
+ * @param {?function} callback Called after props are updated.
+ * @final
+ * @public
+ * @deprecated
+ */
+ setProps: function(partialProps, callback) {
+ ReactUpdateQueue.enqueueSetProps(this, partialProps);
+ if (callback) {
+ ReactUpdateQueue.enqueueCallback(this, callback);
+ }
+ },
+
+ /**
+ * Replace all the props.
+ *
+ * @param {object} newProps Subset of the next props.
+ * @param {?function} callback Called after props are updated.
+ * @final
+ * @public
+ * @deprecated
+ */
+ replaceProps: function(newProps, callback) {
+ ReactUpdateQueue.enqueueReplaceProps(this, newProps);
+ if (callback) {
+ ReactUpdateQueue.enqueueCallback(this, callback);
+ }
+ }
+};
+
+var ReactClassComponent = function() {};
+assign(
+ ReactClassComponent.prototype,
+ ReactComponent.prototype,
+ ReactClassMixin
+);
+
+/**
+ * Module for creating composite components.
+ *
+ * @class ReactClass
+ */
+var ReactClass = {
+
+ /**
+ * Creates a composite component class given a class specification.
+ *
+ * @param {object} spec Class specification (which must define `render`).
+ * @return {function} Component constructor function.
+ * @public
+ */
+ createClass: function(spec) {
+ var Constructor = function(props, context) {
+ // This constructor is overridden by mocks. The argument is used
+ // by mocks to assert on what gets mounted.
+
+ if ("production" !== process.env.NODE_ENV) {
+ ("production" !== process.env.NODE_ENV ? warning(
+ this instanceof Constructor,
+ 'Something is calling a React component directly. Use a factory or ' +
+ 'JSX instead. See: http://fb.me/react-legacyfactory'
+ ) : null);
+ }
+
+ // Wire up auto-binding
+ if (this.__reactAutoBindMap) {
+ bindAutoBindMethods(this);
+ }
+
+ this.props = props;
+ this.context = context;
+ this.state = null;
+
+ // ReactClasses doesn't have constructors. Instead, they use the
+ // getInitialState and componentWillMount methods for initialization.
+
+ var initialState = this.getInitialState ? this.getInitialState() : null;
+ if ("production" !== process.env.NODE_ENV) {
+ // We allow auto-mocks to proceed as if they're returning null.
+ if (typeof initialState === 'undefined' &&
+ this.getInitialState._isMockFunction) {
+ // This is probably bad practice. Consider warning here and
+ // deprecating this convenience.
+ initialState = null;
+ }
+ }
+ ("production" !== process.env.NODE_ENV ? invariant(
+ typeof initialState === 'object' && !Array.isArray(initialState),
+ '%s.getInitialState(): must return an object or null',
+ Constructor.displayName || 'ReactCompositeComponent'
+ ) : invariant(typeof initialState === 'object' && !Array.isArray(initialState)));
+
+ this.state = initialState;
+ };
+ Constructor.prototype = new ReactClassComponent();
+ Constructor.prototype.constructor = Constructor;
+
+ injectedMixins.forEach(
+ mixSpecIntoComponent.bind(null, Constructor)
+ );
+
+ mixSpecIntoComponent(Constructor, spec);
+
+ // Initialize the defaultProps property after all mixins have been merged
+ if (Constructor.getDefaultProps) {
+ Constructor.defaultProps = Constructor.getDefaultProps();
+ }
+
+ if ("production" !== process.env.NODE_ENV) {
+ // This is a tag to indicate that the use of these method names is ok,
+ // since it's used with createClass. If it's not, then it's likely a
+ // mistake so we'll warn you to use the static property, property
+ // initializer or constructor respectively.
+ if (Constructor.getDefaultProps) {
+ Constructor.getDefaultProps.isReactClassApproved = {};
+ }
+ if (Constructor.prototype.getInitialState) {
+ Constructor.prototype.getInitialState.isReactClassApproved = {};
+ }
+ }
+
+ ("production" !== process.env.NODE_ENV ? invariant(
+ Constructor.prototype.render,
+ 'createClass(...): Class specification must implement a `render` method.'
+ ) : invariant(Constructor.prototype.render));
+
+ if ("production" !== process.env.NODE_ENV) {
+ ("production" !== process.env.NODE_ENV ? warning(
+ !Constructor.prototype.componentShouldUpdate,
+ '%s has a method called ' +
+ 'componentShouldUpdate(). Did you mean shouldComponentUpdate()? ' +
+ 'The name is phrased as a question because the function is ' +
+ 'expected to return a value.',
+ spec.displayName || 'A component'
+ ) : null);
+ }
+
+ // Reduce time spent doing lookups by setting these on the prototype.
+ for (var methodName in ReactClassInterface) {
+ if (!Constructor.prototype[methodName]) {
+ Constructor.prototype[methodName] = null;
+ }
+ }
+
+ // Legacy hook
+ Constructor.type = Constructor;
+ if ("production" !== process.env.NODE_ENV) {
+ try {
+ Object.defineProperty(Constructor, 'type', typeDeprecationDescriptor);
+ } catch (x) {
+ // IE will fail on defineProperty (es5-shim/sham too)
+ }
+ }
+
+ return Constructor;
+ },
+
+ injection: {
+ injectMixin: function(mixin) {
+ injectedMixins.push(mixin);
+ }
+ }
+
+};
+
+module.exports = ReactClass;
+
+}).call(this,require('_process'))
+},{"./Object.assign":69,"./ReactComponent":79,"./ReactCurrentOwner":85,"./ReactElement":103,"./ReactErrorUtils":106,"./ReactInstanceMap":113,"./ReactLifeCycle":114,"./ReactPropTypeLocationNames":124,"./ReactPropTypeLocations":125,"./ReactUpdateQueue":139,"./invariant":191,"./keyMirror":197,"./keyOf":198,"./warning":212,"_process":1}],79:[function(require,module,exports){
+(function (process){
+/**
+ * Copyright 2013-2015, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ * @providesModule ReactComponent
+ */
+
+'use strict';
+
+var ReactUpdateQueue = require("./ReactUpdateQueue");
+
+var invariant = require("./invariant");
+var warning = require("./warning");
+
+/**
+ * Base class helpers for the updating state of a component.
+ */
+function ReactComponent(props, context) {
+ this.props = props;
+ this.context = context;
+}
+
+/**
+ * Sets a subset of the state. Always use this to mutate
+ * state. You should treat `this.state` as immutable.
+ *
+ * There is no guarantee that `this.state` will be immediately updated, so
+ * accessing `this.state` after calling this method may return the old value.
+ *
+ * There is no guarantee that calls to `setState` will run synchronously,
+ * as they may eventually be batched together. You can provide an optional
+ * callback that will be executed when the call to setState is actually
+ * completed.
+ *
+ * When a function is provided to setState, it will be called at some point in
+ * the future (not synchronously). It will be called with the up to date
+ * component arguments (state, props, context). These values can be different
+ * from this.* because your function may be called after receiveProps but before
+ * shouldComponentUpdate, and this new state, props, and context will not yet be
+ * assigned to this.
+ *
+ * @param {object|function} partialState Next partial state or function to
+ * produce next partial state to be merged with current state.
+ * @param {?function} callback Called after state is updated.
+ * @final
+ * @protected
+ */
+ReactComponent.prototype.setState = function(partialState, callback) {
+ ("production" !== process.env.NODE_ENV ? invariant(
+ typeof partialState === 'object' ||
+ typeof partialState === 'function' ||
+ partialState == null,
+ 'setState(...): takes an object of state variables to update or a ' +
+ 'function which returns an object of state variables.'
+ ) : invariant(typeof partialState === 'object' ||
+ typeof partialState === 'function' ||
+ partialState == null));
+ if ("production" !== process.env.NODE_ENV) {
+ ("production" !== process.env.NODE_ENV ? warning(
+ partialState != null,
+ 'setState(...): You passed an undefined or null state object; ' +
+ 'instead, use forceUpdate().'
+ ) : null);
+ }
+ ReactUpdateQueue.enqueueSetState(this, partialState);
+ if (callback) {
+ ReactUpdateQueue.enqueueCallback(this, callback);
+ }
+};
+
+/**
+ * Forces an update. This should only be invoked when it is known with
+ * certainty that we are **not** in a DOM transaction.
+ *
+ * You may want to call this when you know that some deeper aspect of the
+ * component's state has changed but `setState` was not called.
+ *
+ * This will not invoke `shouldComponentUpdate`, but it will invoke
+ * `componentWillUpdate` and `componentDidUpdate`.
+ *
+ * @param {?function} callback Called after update is complete.
+ * @final
+ * @protected
+ */
+ReactComponent.prototype.forceUpdate = function(callback) {
+ ReactUpdateQueue.enqueueForceUpdate(this);
+ if (callback) {
+ ReactUpdateQueue.enqueueCallback(this, callback);
+ }
+};
+
+/**
+ * Deprecated APIs. These APIs used to exist on classic React classes but since
+ * we would like to deprecate them, we're not going to move them over to this
+ * modern base class. Instead, we define a getter that warns if it's accessed.
+ */
+if ("production" !== process.env.NODE_ENV) {
+ var deprecatedAPIs = {
+ getDOMNode: 'getDOMNode',
+ isMounted: 'isMounted',
+ replaceProps: 'replaceProps',
+ replaceState: 'replaceState',
+ setProps: 'setProps'
+ };
+ var defineDeprecationWarning = function(methodName, displayName) {
+ try {
+ Object.defineProperty(ReactComponent.prototype, methodName, {
+ get: function() {
+ ("production" !== process.env.NODE_ENV ? warning(
+ false,
+ '%s(...) is deprecated in plain JavaScript React classes.',
+ displayName
+ ) : null);
+ return undefined;
+ }
+ });
+ } catch (x) {
+ // IE will fail on defineProperty (es5-shim/sham too)
+ }
+ };
+ for (var fnName in deprecatedAPIs) {
+ if (deprecatedAPIs.hasOwnProperty(fnName)) {
+ defineDeprecationWarning(fnName, deprecatedAPIs[fnName]);
+ }
+ }
+}
+
+module.exports = ReactComponent;
+
+}).call(this,require('_process'))
+},{"./ReactUpdateQueue":139,"./invariant":191,"./warning":212,"_process":1}],80:[function(require,module,exports){
+/**
+ * Copyright 2013-2015, Facebook, Inc.
+ * All rights reserved.
*
- * This is different from the life cycle state maintained by `ReactComponent` in
- * `this._lifeCycleState`. The following diagram shows how the states overlap in
- * time. There are times when the CompositeLifeCycle is null - at those times it
- * is only meaningful to look at ComponentLifeCycle alone.
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
*
- * Top Row: ReactComponent.ComponentLifeCycle
- * Low Row: ReactComponent.CompositeLifeCycle
+ * @providesModule ReactComponentBrowserEnvironment
+ */
+
+/*jslint evil: true */
+
+'use strict';
+
+var ReactDOMIDOperations = require("./ReactDOMIDOperations");
+var ReactMount = require("./ReactMount");
+
+/**
+ * Abstracts away all functionality of the reconciler that requires knowledge of
+ * the browser context. TODO: These callers should be refactored to avoid the
+ * need for this injection.
+ */
+var ReactComponentBrowserEnvironment = {
+
+ processChildrenUpdates:
+ ReactDOMIDOperations.dangerouslyProcessChildrenUpdates,
+
+ replaceNodeWithMarkupByID:
+ ReactDOMIDOperations.dangerouslyReplaceNodeWithMarkupByID,
+
+ /**
+ * If a particular environment requires that some resources be cleaned up,
+ * specify this in the injected Mixin. In the DOM, we would likely want to
+ * purge any cached node ID lookups.
+ *
+ * @private
+ */
+ unmountIDFromEnvironment: function(rootNodeID) {
+ ReactMount.purgeID(rootNodeID);
+ }
+
+};
+
+module.exports = ReactComponentBrowserEnvironment;
+
+},{"./ReactDOMIDOperations":90,"./ReactMount":117}],81:[function(require,module,exports){
+(function (process){
+/**
+ * Copyright 2014-2015, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
*
- * +-------+---------------------------------+--------+
- * | UN | MOUNTED | UN |
- * |MOUNTED| | MOUNTED|
- * +-------+---------------------------------+--------+
- * | ^--------+ +-------+ +--------^ |
- * | | | | | | | |
- * | 0--|MOUNTING|-0-|RECEIVE|-0-| UN |--->0 |
- * | | | |PROPS | |MOUNTING| |
- * | | | | | | | |
- * | | | | | | | |
- * | +--------+ +-------+ +--------+ |
- * | | | |
- * +-------+---------------------------------+--------+
+ * @providesModule ReactComponentEnvironment
*/
-var CompositeLifeCycle = keyMirror({
+
+'use strict';
+
+var invariant = require("./invariant");
+
+var injected = false;
+
+var ReactComponentEnvironment = {
+
/**
- * Components in the process of being mounted respond to state changes
- * differently.
+ * Optionally injectable environment dependent cleanup hook. (server vs.
+ * browser etc). Example: A browser system caches DOM nodes based on component
+ * ID and must remove that cache entry when this instance is unmounted.
*/
- MOUNTING: null,
+ unmountIDFromEnvironment: null,
+
/**
- * Components in the process of being unmounted are guarded against state
- * changes.
+ * Optionally injectable hook for swapping out mount images in the middle of
+ * the tree.
*/
- UNMOUNTING: null,
+ replaceNodeWithMarkupByID: null,
+
/**
- * Components that are mounted and receiving new props respond to state
- * changes differently.
+ * Optionally injectable hook for processing a queue of child updates. Will
+ * later move into MultiChildComponents.
*/
- RECEIVING_PROPS: null
-});
+ processChildrenUpdates: null,
+
+ injection: {
+ injectEnvironment: function(environment) {
+ ("production" !== process.env.NODE_ENV ? invariant(
+ !injected,
+ 'ReactCompositeComponent: injectEnvironment() can only be called once.'
+ ) : invariant(!injected));
+ ReactComponentEnvironment.unmountIDFromEnvironment =
+ environment.unmountIDFromEnvironment;
+ ReactComponentEnvironment.replaceNodeWithMarkupByID =
+ environment.replaceNodeWithMarkupByID;
+ ReactComponentEnvironment.processChildrenUpdates =
+ environment.processChildrenUpdates;
+ injected = true;
+ }
+ }
+
+};
+
+module.exports = ReactComponentEnvironment;
+
+}).call(this,require('_process'))
+},{"./invariant":191,"_process":1}],82:[function(require,module,exports){
+/**
+ * Copyright 2013-2015, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+* @providesModule ReactComponentWithPureRenderMixin
+*/
+
+'use strict';
+
+var shallowEqual = require("./shallowEqual");
+
+/**
+ * If your React component's render function is "pure", e.g. it will render the
+ * same result given the same props and state, provide this Mixin for a
+ * considerable performance boost.
+ *
+ * Most React components have pure render functions.
+ *
+ * Example:
+ *
+ * var ReactComponentWithPureRenderMixin =
+ * require('ReactComponentWithPureRenderMixin');
+ * React.createClass({
+ * mixins: [ReactComponentWithPureRenderMixin],
+ *
+ * render: function() {
+ * return <div className={this.props.className}>foo</div>;
+ * }
+ * });
+ *
+ * Note: This only checks shallow equality for props and state. If these contain
+ * complex data structures this mixin may have false-negatives for deeper
+ * differences. Only mixin to components which have simple props and state, or
+ * use `forceUpdate()` when you know deep data structures have changed.
+ */
+var ReactComponentWithPureRenderMixin = {
+ shouldComponentUpdate: function(nextProps, nextState) {
+ return !shallowEqual(this.props, nextProps) ||
+ !shallowEqual(this.state, nextState);
+ }
+};
+
+module.exports = ReactComponentWithPureRenderMixin;
+
+},{"./shallowEqual":207}],83:[function(require,module,exports){
+(function (process){
+/**
+ * Copyright 2013-2015, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ * @providesModule ReactCompositeComponent
+ */
+
+'use strict';
+
+var ReactComponentEnvironment = require("./ReactComponentEnvironment");
+var ReactContext = require("./ReactContext");
+var ReactCurrentOwner = require("./ReactCurrentOwner");
+var ReactElement = require("./ReactElement");
+var ReactElementValidator = require("./ReactElementValidator");
+var ReactInstanceMap = require("./ReactInstanceMap");
+var ReactLifeCycle = require("./ReactLifeCycle");
+var ReactNativeComponent = require("./ReactNativeComponent");
+var ReactPerf = require("./ReactPerf");
+var ReactPropTypeLocations = require("./ReactPropTypeLocations");
+var ReactPropTypeLocationNames = require("./ReactPropTypeLocationNames");
+var ReactReconciler = require("./ReactReconciler");
+var ReactUpdates = require("./ReactUpdates");
+
+var assign = require("./Object.assign");
+var emptyObject = require("./emptyObject");
+var invariant = require("./invariant");
+var shouldUpdateReactComponent = require("./shouldUpdateReactComponent");
+var warning = require("./warning");
+
+function getDeclarationErrorAddendum(component) {
+ var owner = component._currentElement._owner || null;
+ if (owner) {
+ var name = owner.getName();
+ if (name) {
+ return ' Check the render method of `' + name + '`.';
+ }
+ }
+ return '';
+}
+
+/**
+ * ------------------ The Life-Cycle of a Composite Component ------------------
+ *
+ * - constructor: Initialization of state. The instance is now retained.
+ * - componentWillMount
+ * - render
+ * - [children's constructors]
+ * - [children's componentWillMount and render]
+ * - [children's componentDidMount]
+ * - componentDidMount
+ *
+ * Update Phases:
+ * - componentWillReceiveProps (only called if parent updated)
+ * - shouldComponentUpdate
+ * - componentWillUpdate
+ * - render
+ * - [children's constructors or receive props phases]
+ * - componentDidUpdate
+ *
+ * - componentWillUnmount
+ * - [children's componentWillUnmount]
+ * - [children destroyed]
+ * - (destroyed): The instance is now blank, released by React and ready for GC.
+ *
+ * -----------------------------------------------------------------------------
+ */
+
+/**
+ * An incrementing ID assigned to each component when it is mounted. This is
+ * used to enforce the order in which `ReactUpdates` updates dirty components.
+ *
+ * @private
+ */
+var nextMountID = 1;
/**
* @lends {ReactCompositeComponent.prototype}
@@ -9614,29 +10049,24 @@ var ReactCompositeComponentMixin = {
* @internal
*/
construct: function(element) {
- // Children can be either an array or more than one argument
- ReactComponent.Mixin.construct.apply(this, arguments);
- ReactOwner.Mixin.construct.apply(this, arguments);
+ this._currentElement = element;
+ this._rootNodeID = null;
+ this._instance = null;
- this.state = null;
- this._pendingState = null;
+ // See ReactUpdateQueue
+ this._pendingElement = null;
+ this._pendingStateQueue = null;
+ this._pendingReplaceState = false;
+ this._pendingForceUpdate = false;
- // This is the public post-processed context. The real context and pending
- // context lives on the element.
- this.context = null;
+ this._renderedComponent = null;
- this._compositeLifeCycleState = null;
- },
+ this._context = null;
+ this._mountOrder = 0;
+ this._isTopLevel = false;
- /**
- * Checks whether or not this composite component is mounted.
- * @return {boolean} True if mounted, false otherwise.
- * @protected
- * @final
- */
- isMounted: function() {
- return ReactComponent.Mixin.isMounted.call(this) &&
- this._compositeLifeCycleState !== CompositeLifeCycle.MOUNTING;
+ // See ReactUpdates and ReactUpdateQueue.
+ this._pendingCallbacks = null;
},
/**
@@ -9644,68 +10074,137 @@ var ReactCompositeComponentMixin = {
*
* @param {string} rootID DOM ID of the root node.
* @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction
- * @param {number} mountDepth number of components in the owner hierarchy
* @return {?string} Rendered markup to be inserted into the DOM.
* @final
* @internal
*/
- mountComponent: ReactPerf.measure(
- 'ReactCompositeComponent',
- 'mountComponent',
- function(rootID, transaction, mountDepth) {
- ReactComponent.Mixin.mountComponent.call(
- this,
- rootID,
- transaction,
- mountDepth
- );
- this._compositeLifeCycleState = CompositeLifeCycle.MOUNTING;
+ mountComponent: function(rootID, transaction, context) {
+ this._context = context;
+ this._mountOrder = nextMountID++;
+ this._rootNodeID = rootID;
- if (this.__reactAutoBindMap) {
- this._bindAutoBindMethods();
- }
+ var publicProps = this._processProps(this._currentElement.props);
+ var publicContext = this._processContext(this._currentElement._context);
+
+ var Component = ReactNativeComponent.getComponentClassForElement(
+ this._currentElement
+ );
- this.context = this._processContext(this._currentElement._context);
- this.props = this._processProps(this.props);
+ // Initialize the public class
+ var inst = new Component(publicProps, publicContext);
- this.state = this.getInitialState ? this.getInitialState() : null;
- ("production" !== process.env.NODE_ENV ? invariant(
- typeof this.state === 'object' && !Array.isArray(this.state),
- '%s.getInitialState(): must return an object or null',
- this.constructor.displayName || 'ReactCompositeComponent'
- ) : invariant(typeof this.state === 'object' && !Array.isArray(this.state)));
+ if ("production" !== process.env.NODE_ENV) {
+ // This will throw later in _renderValidatedComponent, but add an early
+ // warning now to help debugging
+ ("production" !== process.env.NODE_ENV ? warning(
+ inst.render != null,
+ '%s(...): No `render` method found on the returned component ' +
+ 'instance: you may have forgotten to define `render` in your ' +
+ 'component or you may have accidentally tried to render an element ' +
+ 'whose type is a function that isn\'t a React component.',
+ Component.displayName || Component.name || 'Component'
+ ) : null);
+ }
- this._pendingState = null;
- this._pendingForceUpdate = false;
+ // These should be set up in the constructor, but as a convenience for
+ // simpler class abstractions, we set them up after the fact.
+ inst.props = publicProps;
+ inst.context = publicContext;
+ inst.refs = emptyObject;
+
+ this._instance = inst;
- if (this.componentWillMount) {
- this.componentWillMount();
+ // Store a reference from the instance back to the internal representation
+ ReactInstanceMap.set(inst, this);
+
+ if ("production" !== process.env.NODE_ENV) {
+ this._warnIfContextsDiffer(this._currentElement._context, context);
+ }
+
+ if ("production" !== process.env.NODE_ENV) {
+ // Since plain JS classes are defined without any special initialization
+ // logic, we can not catch common errors early. Therefore, we have to
+ // catch them here, at initialization time, instead.
+ ("production" !== process.env.NODE_ENV ? warning(
+ !inst.getInitialState ||
+ inst.getInitialState.isReactClassApproved,
+ 'getInitialState was defined on %s, a plain JavaScript class. ' +
+ 'This is only supported for classes created using React.createClass. ' +
+ 'Did you mean to define a state property instead?',
+ this.getName() || 'a component'
+ ) : null);
+ ("production" !== process.env.NODE_ENV ? warning(
+ !inst.propTypes,
+ 'propTypes was defined as an instance property on %s. Use a static ' +
+ 'property to define propTypes instead.',
+ this.getName() || 'a component'
+ ) : null);
+ ("production" !== process.env.NODE_ENV ? warning(
+ !inst.contextTypes,
+ 'contextTypes was defined as an instance property on %s. Use a ' +
+ 'static property to define contextTypes instead.',
+ this.getName() || 'a component'
+ ) : null);
+ ("production" !== process.env.NODE_ENV ? warning(
+ typeof inst.componentShouldUpdate !== 'function',
+ '%s has a method called ' +
+ 'componentShouldUpdate(). Did you mean shouldComponentUpdate()? ' +
+ 'The name is phrased as a question because the function is ' +
+ 'expected to return a value.',
+ (this.getName() || 'A component')
+ ) : null);
+ }
+
+ var initialState = inst.state;
+ if (initialState === undefined) {
+ inst.state = initialState = null;
+ }
+ ("production" !== process.env.NODE_ENV ? invariant(
+ typeof initialState === 'object' && !Array.isArray(initialState),
+ '%s.state: must be set to an object or null',
+ this.getName() || 'ReactCompositeComponent'
+ ) : invariant(typeof initialState === 'object' && !Array.isArray(initialState)));
+
+ this._pendingStateQueue = null;
+ this._pendingReplaceState = false;
+ this._pendingForceUpdate = false;
+
+ var renderedElement;
+
+ var previouslyMounting = ReactLifeCycle.currentlyMountingInstance;
+ ReactLifeCycle.currentlyMountingInstance = this;
+ try {
+ if (inst.componentWillMount) {
+ inst.componentWillMount();
// When mounting, calls to `setState` by `componentWillMount` will set
- // `this._pendingState` without triggering a re-render.
- if (this._pendingState) {
- this.state = this._pendingState;
- this._pendingState = null;
+ // `this._pendingStateQueue` without triggering a re-render.
+ if (this._pendingStateQueue) {
+ inst.state = this._processPendingState(inst.props, inst.context);
}
}
- this._renderedComponent = instantiateReactComponent(
- this._renderValidatedComponent(),
- this._currentElement.type // The wrapping type
- );
+ renderedElement = this._renderValidatedComponent();
+ } finally {
+ ReactLifeCycle.currentlyMountingInstance = previouslyMounting;
+ }
- // Done with mounting, `setState` will now trigger UI changes.
- this._compositeLifeCycleState = null;
- var markup = this._renderedComponent.mountComponent(
- rootID,
- transaction,
- mountDepth + 1
- );
- if (this.componentDidMount) {
- transaction.getReactMountReady().enqueue(this.componentDidMount, this);
- }
- return markup;
+ this._renderedComponent = this._instantiateReactComponent(
+ renderedElement,
+ this._currentElement.type // The wrapping type
+ );
+
+ var markup = ReactReconciler.mountComponent(
+ this._renderedComponent,
+ rootID,
+ transaction,
+ this._processChildContext(context)
+ );
+ if (inst.componentDidMount) {
+ transaction.getReactMountReady().enqueue(inst.componentDidMount, inst);
}
- ),
+
+ return markup;
+ },
/**
* Releases any resources allocated by `mountComponent`.
@@ -9714,83 +10213,88 @@ var ReactCompositeComponentMixin = {
* @internal
*/
unmountComponent: function() {
- this._compositeLifeCycleState = CompositeLifeCycle.UNMOUNTING;
- if (this.componentWillUnmount) {
- this.componentWillUnmount();
+ var inst = this._instance;
+
+ if (inst.componentWillUnmount) {
+ var previouslyUnmounting = ReactLifeCycle.currentlyUnmountingInstance;
+ ReactLifeCycle.currentlyUnmountingInstance = this;
+ try {
+ inst.componentWillUnmount();
+ } finally {
+ ReactLifeCycle.currentlyUnmountingInstance = previouslyUnmounting;
+ }
}
- this._compositeLifeCycleState = null;
- this._renderedComponent.unmountComponent();
+ ReactReconciler.unmountComponent(this._renderedComponent);
this._renderedComponent = null;
- ReactComponent.Mixin.unmountComponent.call(this);
+ // Reset pending fields
+ this._pendingStateQueue = null;
+ this._pendingReplaceState = false;
+ this._pendingForceUpdate = false;
+ this._pendingCallbacks = null;
+ this._pendingElement = null;
+
+ // These fields do not really need to be reset since this object is no
+ // longer accessible.
+ this._context = null;
+ this._rootNodeID = null;
+
+ // Delete the reference from the instance to this internal representation
+ // which allow the internals to be properly cleaned up even if the user
+ // leaks a reference to the public instance.
+ ReactInstanceMap.remove(inst);
- // Some existing components rely on this.props even after they've been
+ // Some existing components rely on inst.props even after they've been
// destroyed (in event handlers).
- // TODO: this.props = null;
- // TODO: this.state = null;
+ // TODO: inst.props = null;
+ // TODO: inst.state = null;
+ // TODO: inst.context = null;
},
/**
- * Sets a subset of the state. Always use this or `replaceState` to mutate
- * state. You should treat `this.state` as immutable.
+ * Schedule a partial update to the props. Only used for internal testing.
*
- * There is no guarantee that `this.state` will be immediately updated, so
- * accessing `this.state` after calling this method may return the old value.
- *
- * There is no guarantee that calls to `setState` will run synchronously,
- * as they may eventually be batched together. You can provide an optional
- * callback that will be executed when the call to setState is actually
- * completed.
- *
- * @param {object} partialState Next partial state to be merged with state.
- * @param {?function} callback Called after state is updated.
+ * @param {object} partialProps Subset of the next props.
+ * @param {?function} callback Called after props are updated.
* @final
- * @protected
+ * @internal
*/
- setState: function(partialState, callback) {
- ("production" !== process.env.NODE_ENV ? invariant(
- typeof partialState === 'object' || partialState == null,
- 'setState(...): takes an object of state variables to update.'
- ) : invariant(typeof partialState === 'object' || partialState == null));
- if ("production" !== process.env.NODE_ENV){
- ("production" !== process.env.NODE_ENV ? warning(
- partialState != null,
- 'setState(...): You passed an undefined or null state object; ' +
- 'instead, use forceUpdate().'
- ) : null);
- }
- // Merge with `_pendingState` if it exists, otherwise with existing state.
- this.replaceState(
- assign({}, this._pendingState || this.state, partialState),
- callback
+ _setPropsInternal: function(partialProps, callback) {
+ // This is a deoptimized path. We optimize for always having an element.
+ // This creates an extra internal element.
+ var element = this._pendingElement || this._currentElement;
+ this._pendingElement = ReactElement.cloneAndReplaceProps(
+ element,
+ assign({}, element.props, partialProps)
);
+ ReactUpdates.enqueueUpdate(this, callback);
},
/**
- * Replaces all of the state. Always use this or `setState` to mutate state.
- * You should treat `this.state` as immutable.
- *
- * There is no guarantee that `this.state` will be immediately updated, so
- * accessing `this.state` after calling this method may return the old value.
+ * Filters the context object to only contain keys specified in
+ * `contextTypes`
*
- * @param {object} completeState Next state.
- * @param {?function} callback Called after state is updated.
- * @final
- * @protected
+ * @param {object} context
+ * @return {?object}
+ * @private
*/
- replaceState: function(completeState, callback) {
- validateLifeCycleOnReplaceState(this);
- this._pendingState = completeState;
- if (this._compositeLifeCycleState !== CompositeLifeCycle.MOUNTING) {
- // If we're in a componentWillMount handler, don't enqueue a rerender
- // because ReactUpdates assumes we're in a browser context (which is wrong
- // for server rendering) and we're about to do a render anyway.
- // TODO: The callback here is ignored when setState is called from
- // componentWillMount. Either fix it or disallow doing so completely in
- // favor of getInitialState.
- ReactUpdates.enqueueUpdate(this, callback);
+ _maskContext: function(context) {
+ var maskedContext = null;
+ // This really should be getting the component class for the element,
+ // but we know that we're not going to need it for built-ins.
+ if (typeof this._currentElement.type === 'string') {
+ return emptyObject;
+ }
+ var contextTypes = this._currentElement.type.contextTypes;
+ if (!contextTypes) {
+ return emptyObject;
}
+ maskedContext = {};
+ for (var contextName in contextTypes) {
+ maskedContext[contextName] = context[contextName];
+ }
+ return maskedContext;
},
/**
@@ -9802,16 +10306,14 @@ var ReactCompositeComponentMixin = {
* @private
*/
_processContext: function(context) {
- var maskedContext = null;
- var contextTypes = this.constructor.contextTypes;
- if (contextTypes) {
- maskedContext = {};
- for (var contextName in contextTypes) {
- maskedContext[contextName] = context[contextName];
- }
- if ("production" !== process.env.NODE_ENV) {
+ var maskedContext = this._maskContext(context);
+ if ("production" !== process.env.NODE_ENV) {
+ var Component = ReactNativeComponent.getComponentClassForElement(
+ this._currentElement
+ );
+ if (Component.contextTypes) {
this._checkPropTypes(
- contextTypes,
+ Component.contextTypes,
maskedContext,
ReactPropTypeLocations.context
);
@@ -9826,29 +10328,29 @@ var ReactCompositeComponentMixin = {
* @private
*/
_processChildContext: function(currentContext) {
- var childContext = this.getChildContext && this.getChildContext();
- var displayName = this.constructor.displayName || 'ReactCompositeComponent';
+ var inst = this._instance;
+ var childContext = inst.getChildContext && inst.getChildContext();
if (childContext) {
("production" !== process.env.NODE_ENV ? invariant(
- typeof this.constructor.childContextTypes === 'object',
+ typeof inst.constructor.childContextTypes === 'object',
'%s.getChildContext(): childContextTypes must be defined in order to ' +
'use getChildContext().',
- displayName
- ) : invariant(typeof this.constructor.childContextTypes === 'object'));
+ this.getName() || 'ReactCompositeComponent'
+ ) : invariant(typeof inst.constructor.childContextTypes === 'object'));
if ("production" !== process.env.NODE_ENV) {
this._checkPropTypes(
- this.constructor.childContextTypes,
+ inst.constructor.childContextTypes,
childContext,
ReactPropTypeLocations.childContext
);
}
for (var name in childContext) {
("production" !== process.env.NODE_ENV ? invariant(
- name in this.constructor.childContextTypes,
+ name in inst.constructor.childContextTypes,
'%s.getChildContext(): key "%s" is not defined in childContextTypes.',
- displayName,
+ this.getName() || 'ReactCompositeComponent',
name
- ) : invariant(name in this.constructor.childContextTypes));
+ ) : invariant(name in inst.constructor.childContextTypes));
}
return assign({}, currentContext, childContext);
}
@@ -9866,9 +10368,15 @@ var ReactCompositeComponentMixin = {
*/
_processProps: function(newProps) {
if ("production" !== process.env.NODE_ENV) {
- var propTypes = this.constructor.propTypes;
- if (propTypes) {
- this._checkPropTypes(propTypes, newProps, ReactPropTypeLocations.prop);
+ var Component = ReactNativeComponent.getComponentClassForElement(
+ this._currentElement
+ );
+ if (Component.propTypes) {
+ this._checkPropTypes(
+ Component.propTypes,
+ newProps,
+ ReactPropTypeLocations.prop
+ );
}
}
return newProps;
@@ -9885,101 +10393,236 @@ var ReactCompositeComponentMixin = {
_checkPropTypes: function(propTypes, props, location) {
// TODO: Stop validating prop types here and only use the element
// validation.
- var componentName = this.constructor.displayName;
+ var componentName = this.getName();
for (var propName in propTypes) {
if (propTypes.hasOwnProperty(propName)) {
- var error =
- propTypes[propName](props, propName, componentName, location);
+ var error;
+ try {
+ // This is intentionally an invariant that gets caught. It's the same
+ // behavior as without this statement except with a better message.
+ ("production" !== process.env.NODE_ENV ? invariant(
+ typeof propTypes[propName] === 'function',
+ '%s: %s type `%s` is invalid; it must be a function, usually ' +
+ 'from React.PropTypes.',
+ componentName || 'React class',
+ ReactPropTypeLocationNames[location],
+ propName
+ ) : invariant(typeof propTypes[propName] === 'function'));
+ error = propTypes[propName](props, propName, componentName, location);
+ } catch (ex) {
+ error = ex;
+ }
if (error instanceof Error) {
// We may want to extend this logic for similar errors in
- // renderComponent calls, so I'm abstracting it away into
+ // React.render calls, so I'm abstracting it away into
// a function to minimize refactoring in the future
var addendum = getDeclarationErrorAddendum(this);
- ("production" !== process.env.NODE_ENV ? warning(false, error.message + addendum) : null);
+
+ if (location === ReactPropTypeLocations.prop) {
+ // Preface gives us something to blacklist in warning module
+ ("production" !== process.env.NODE_ENV ? warning(
+ false,
+ 'Failed Composite propType: %s%s',
+ error.message,
+ addendum
+ ) : null);
+ } else {
+ ("production" !== process.env.NODE_ENV ? warning(
+ false,
+ 'Failed Context Types: %s%s',
+ error.message,
+ addendum
+ ) : null);
+ }
}
}
}
},
+ receiveComponent: function(nextElement, transaction, nextContext) {
+ var prevElement = this._currentElement;
+ var prevContext = this._context;
+
+ this._pendingElement = null;
+
+ this.updateComponent(
+ transaction,
+ prevElement,
+ nextElement,
+ prevContext,
+ nextContext
+ );
+ },
+
/**
- * If any of `_pendingElement`, `_pendingState`, or `_pendingForceUpdate`
+ * If any of `_pendingElement`, `_pendingStateQueue`, or `_pendingForceUpdate`
* is set, update the component.
*
* @param {ReactReconcileTransaction} transaction
* @internal
*/
performUpdateIfNecessary: function(transaction) {
- var compositeLifeCycleState = this._compositeLifeCycleState;
- // Do not trigger a state transition if we are in the middle of mounting or
- // receiving props because both of those will already be doing this.
- if (compositeLifeCycleState === CompositeLifeCycle.MOUNTING ||
- compositeLifeCycleState === CompositeLifeCycle.RECEIVING_PROPS) {
- return;
+ if (this._pendingElement != null) {
+ ReactReconciler.receiveComponent(
+ this,
+ this._pendingElement || this._currentElement,
+ transaction,
+ this._context
+ );
}
- if (this._pendingElement == null &&
- this._pendingState == null &&
- !this._pendingForceUpdate) {
- return;
+ if (this._pendingStateQueue !== null || this._pendingForceUpdate) {
+ if ("production" !== process.env.NODE_ENV) {
+ ReactElementValidator.checkAndWarnForMutatedProps(
+ this._currentElement
+ );
+ }
+
+ this.updateComponent(
+ transaction,
+ this._currentElement,
+ this._currentElement,
+ this._context,
+ this._context
+ );
}
+ },
- var nextContext = this.context;
- var nextProps = this.props;
- var nextElement = this._currentElement;
- if (this._pendingElement != null) {
- nextElement = this._pendingElement;
- nextContext = this._processContext(nextElement._context);
- nextProps = this._processProps(nextElement.props);
- this._pendingElement = null;
+ /**
+ * Compare two contexts, warning if they are different
+ * TODO: Remove this check when owner-context is removed
+ */
+ _warnIfContextsDiffer: function(ownerBasedContext, parentBasedContext) {
+ ownerBasedContext = this._maskContext(ownerBasedContext);
+ parentBasedContext = this._maskContext(parentBasedContext);
+ var parentKeys = Object.keys(parentBasedContext).sort();
+ var displayName = this.getName() || 'ReactCompositeComponent';
+ for (var i = 0; i < parentKeys.length; i++) {
+ var key = parentKeys[i];
+ ("production" !== process.env.NODE_ENV ? warning(
+ ownerBasedContext[key] === parentBasedContext[key],
+ 'owner-based and parent-based contexts differ ' +
+ '(values: `%s` vs `%s`) for key (%s) while mounting %s ' +
+ '(see: http://fb.me/react-context-by-parent)',
+ ownerBasedContext[key],
+ parentBasedContext[key],
+ key,
+ displayName
+ ) : null);
+ }
+ },
- this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_PROPS;
- if (this.componentWillReceiveProps) {
- this.componentWillReceiveProps(nextProps, nextContext);
+ /**
+ * Perform an update to a mounted component. The componentWillReceiveProps and
+ * shouldComponentUpdate methods are called, then (assuming the update isn't
+ * skipped) the remaining update lifecycle methods are called and the DOM
+ * representation is updated.
+ *
+ * By default, this implements React's rendering and reconciliation algorithm.
+ * Sophisticated clients may wish to override this.
+ *
+ * @param {ReactReconcileTransaction} transaction
+ * @param {ReactElement} prevParentElement
+ * @param {ReactElement} nextParentElement
+ * @internal
+ * @overridable
+ */
+ updateComponent: function(
+ transaction,
+ prevParentElement,
+ nextParentElement,
+ prevUnmaskedContext,
+ nextUnmaskedContext
+ ) {
+ var inst = this._instance;
+
+ var nextContext = inst.context;
+ var nextProps = inst.props;
+
+ // Distinguish between a props update versus a simple state update
+ if (prevParentElement !== nextParentElement) {
+ nextContext = this._processContext(nextParentElement._context);
+ nextProps = this._processProps(nextParentElement.props);
+
+ if ("production" !== process.env.NODE_ENV) {
+ if (nextUnmaskedContext != null) {
+ this._warnIfContextsDiffer(
+ nextParentElement._context,
+ nextUnmaskedContext
+ );
+ }
}
- }
- this._compositeLifeCycleState = null;
+ // An update here will schedule an update but immediately set
+ // _pendingStateQueue which will ensure that any state updates gets
+ // immediately reconciled instead of waiting for the next batch.
+
+ if (inst.componentWillReceiveProps) {
+ inst.componentWillReceiveProps(nextProps, nextContext);
+ }
+ }
- var nextState = this._pendingState || this.state;
- this._pendingState = null;
+ var nextState = this._processPendingState(nextProps, nextContext);
var shouldUpdate =
this._pendingForceUpdate ||
- !this.shouldComponentUpdate ||
- this.shouldComponentUpdate(nextProps, nextState, nextContext);
+ !inst.shouldComponentUpdate ||
+ inst.shouldComponentUpdate(nextProps, nextState, nextContext);
if ("production" !== process.env.NODE_ENV) {
- if (typeof shouldUpdate === "undefined") {
- console.warn(
- (this.constructor.displayName || 'ReactCompositeComponent') +
- '.shouldComponentUpdate(): Returned undefined instead of a ' +
- 'boolean value. Make sure to return true or false.'
- );
- }
+ ("production" !== process.env.NODE_ENV ? warning(
+ typeof shouldUpdate !== 'undefined',
+ '%s.shouldComponentUpdate(): Returned undefined instead of a ' +
+ 'boolean value. Make sure to return true or false.',
+ this.getName() || 'ReactCompositeComponent'
+ ) : null);
}
if (shouldUpdate) {
this._pendingForceUpdate = false;
// Will set `this.props`, `this.state` and `this.context`.
this._performComponentUpdate(
- nextElement,
+ nextParentElement,
nextProps,
nextState,
nextContext,
- transaction
+ transaction,
+ nextUnmaskedContext
);
} else {
// If it's determined that a component should not update, we still want
- // to set props and state.
- this._currentElement = nextElement;
- this.props = nextProps;
- this.state = nextState;
- this.context = nextContext;
+ // to set props and state but we shortcut the rest of the update.
+ this._currentElement = nextParentElement;
+ this._context = nextUnmaskedContext;
+ inst.props = nextProps;
+ inst.state = nextState;
+ inst.context = nextContext;
+ }
+ },
+
+ _processPendingState: function(props, context) {
+ var inst = this._instance;
+ var queue = this._pendingStateQueue;
+ var replace = this._pendingReplaceState;
+ this._pendingReplaceState = false;
+ this._pendingStateQueue = null;
- // Owner cannot change because shouldUpdateReactComponent doesn't allow
- // it. TODO: Remove this._owner completely.
- this._owner = nextElement._owner;
+ if (!queue) {
+ return inst.state;
}
+
+ var nextState = assign({}, replace ? queue[0] : inst.state);
+ for (var i = replace ? 1 : 0; i < queue.length; i++) {
+ var partial = queue[i];
+ assign(
+ nextState,
+ typeof partial === 'function' ?
+ partial.call(inst, nextState, props, context) :
+ partial
+ );
+ }
+
+ return nextState;
},
/**
@@ -9991,6 +10634,7 @@ var ReactCompositeComponentMixin = {
* @param {?object} nextState Next object to set as state.
* @param {?object} nextContext Next public object to set as context.
* @param {ReactReconcileTransaction} transaction
+ * @param {?object} unmaskedContext
* @private
*/
_performComponentUpdate: function(
@@ -9998,337 +10642,213 @@ var ReactCompositeComponentMixin = {
nextProps,
nextState,
nextContext,
- transaction
+ transaction,
+ unmaskedContext
) {
- var prevElement = this._currentElement;
- var prevProps = this.props;
- var prevState = this.state;
- var prevContext = this.context;
+ var inst = this._instance;
+
+ var prevProps = inst.props;
+ var prevState = inst.state;
+ var prevContext = inst.context;
- if (this.componentWillUpdate) {
- this.componentWillUpdate(nextProps, nextState, nextContext);
+ if (inst.componentWillUpdate) {
+ inst.componentWillUpdate(nextProps, nextState, nextContext);
}
this._currentElement = nextElement;
- this.props = nextProps;
- this.state = nextState;
- this.context = nextContext;
+ this._context = unmaskedContext;
+ inst.props = nextProps;
+ inst.state = nextState;
+ inst.context = nextContext;
- // Owner cannot change because shouldUpdateReactComponent doesn't allow
- // it. TODO: Remove this._owner completely.
- this._owner = nextElement._owner;
+ this._updateRenderedComponent(transaction, unmaskedContext);
- this.updateComponent(
- transaction,
- prevElement
- );
-
- if (this.componentDidUpdate) {
+ if (inst.componentDidUpdate) {
transaction.getReactMountReady().enqueue(
- this.componentDidUpdate.bind(this, prevProps, prevState, prevContext),
- this
+ inst.componentDidUpdate.bind(inst, prevProps, prevState, prevContext),
+ inst
);
}
},
- receiveComponent: function(nextElement, transaction) {
- if (nextElement === this._currentElement &&
- nextElement._owner != null) {
- // Since elements are immutable after the owner is rendered,
- // we can do a cheap identity compare here to determine if this is a
- // superfluous reconcile. It's possible for state to be mutable but such
- // change should trigger an update of the owner which would recreate
- // the element. We explicitly check for the existence of an owner since
- // it's possible for a element created outside a composite to be
- // deeply mutated and reused.
- return;
- }
-
- ReactComponent.Mixin.receiveComponent.call(
- this,
- nextElement,
- transaction
- );
- },
-
/**
- * Updates the component's currently mounted DOM representation.
- *
- * By default, this implements React's rendering and reconciliation algorithm.
- * Sophisticated clients may wish to override this.
+ * Call the component's `render` method and update the DOM accordingly.
*
* @param {ReactReconcileTransaction} transaction
- * @param {ReactElement} prevElement
* @internal
- * @overridable
*/
- updateComponent: ReactPerf.measure(
- 'ReactCompositeComponent',
- 'updateComponent',
- function(transaction, prevParentElement) {
- ReactComponent.Mixin.updateComponent.call(
- this,
+ _updateRenderedComponent: function(transaction, context) {
+ var prevComponentInstance = this._renderedComponent;
+ var prevRenderedElement = prevComponentInstance._currentElement;
+ var nextRenderedElement = this._renderValidatedComponent();
+ if (shouldUpdateReactComponent(prevRenderedElement, nextRenderedElement)) {
+ ReactReconciler.receiveComponent(
+ prevComponentInstance,
+ nextRenderedElement,
transaction,
- prevParentElement
+ this._processChildContext(context)
);
-
- var prevComponentInstance = this._renderedComponent;
- var prevElement = prevComponentInstance._currentElement;
- var nextElement = this._renderValidatedComponent();
- if (shouldUpdateReactComponent(prevElement, nextElement)) {
- prevComponentInstance.receiveComponent(nextElement, transaction);
- } else {
- // These two IDs are actually the same! But nothing should rely on that.
- var thisID = this._rootNodeID;
- var prevComponentID = prevComponentInstance._rootNodeID;
- prevComponentInstance.unmountComponent();
- this._renderedComponent = instantiateReactComponent(
- nextElement,
- this._currentElement.type
- );
- var nextMarkup = this._renderedComponent.mountComponent(
- thisID,
- transaction,
- this._mountDepth + 1
- );
- ReactComponent.BackendIDOperations.dangerouslyReplaceNodeWithMarkupByID(
- prevComponentID,
- nextMarkup
- );
- }
+ } else {
+ // These two IDs are actually the same! But nothing should rely on that.
+ var thisID = this._rootNodeID;
+ var prevComponentID = prevComponentInstance._rootNodeID;
+ ReactReconciler.unmountComponent(prevComponentInstance);
+
+ this._renderedComponent = this._instantiateReactComponent(
+ nextRenderedElement,
+ this._currentElement.type
+ );
+ var nextMarkup = ReactReconciler.mountComponent(
+ this._renderedComponent,
+ thisID,
+ transaction,
+ context
+ );
+ this._replaceNodeWithMarkupByID(prevComponentID, nextMarkup);
}
- ),
+ },
/**
- * Forces an update. This should only be invoked when it is known with
- * certainty that we are **not** in a DOM transaction.
- *
- * You may want to call this when you know that some deeper aspect of the
- * component's state has changed but `setState` was not called.
- *
- * This will not invoke `shouldUpdateComponent`, but it will invoke
- * `componentWillUpdate` and `componentDidUpdate`.
- *
- * @param {?function} callback Called after update is complete.
- * @final
* @protected
*/
- forceUpdate: function(callback) {
- var compositeLifeCycleState = this._compositeLifeCycleState;
- ("production" !== process.env.NODE_ENV ? invariant(
- this.isMounted() ||
- compositeLifeCycleState === CompositeLifeCycle.MOUNTING,
- 'forceUpdate(...): Can only force an update on mounted or mounting ' +
- 'components.'
- ) : invariant(this.isMounted() ||
- compositeLifeCycleState === CompositeLifeCycle.MOUNTING));
- ("production" !== process.env.NODE_ENV ? invariant(
- compositeLifeCycleState !== CompositeLifeCycle.UNMOUNTING &&
- ReactCurrentOwner.current == null,
- 'forceUpdate(...): Cannot force an update while unmounting component ' +
- 'or within a `render` function.'
- ) : invariant(compositeLifeCycleState !== CompositeLifeCycle.UNMOUNTING &&
- ReactCurrentOwner.current == null));
- this._pendingForceUpdate = true;
- ReactUpdates.enqueueUpdate(this, callback);
+ _replaceNodeWithMarkupByID: function(prevComponentID, nextMarkup) {
+ ReactComponentEnvironment.replaceNodeWithMarkupByID(
+ prevComponentID,
+ nextMarkup
+ );
},
/**
- * @private
+ * @protected
*/
- _renderValidatedComponent: ReactPerf.measure(
- 'ReactCompositeComponent',
- '_renderValidatedComponent',
- function() {
- var renderedComponent;
- var previousContext = ReactContext.current;
- ReactContext.current = this._processChildContext(
- this._currentElement._context
- );
- ReactCurrentOwner.current = this;
- try {
- renderedComponent = this.render();
- if (renderedComponent === null || renderedComponent === false) {
- renderedComponent = ReactEmptyComponent.getEmptyComponent();
- ReactEmptyComponent.registerNullComponentID(this._rootNodeID);
- } else {
- ReactEmptyComponent.deregisterNullComponentID(this._rootNodeID);
- }
- } finally {
- ReactContext.current = previousContext;
- ReactCurrentOwner.current = null;
+ _renderValidatedComponentWithoutOwnerOrContext: function() {
+ var inst = this._instance;
+ var renderedComponent = inst.render();
+ if ("production" !== process.env.NODE_ENV) {
+ // We allow auto-mocks to proceed as if they're returning null.
+ if (typeof renderedComponent === 'undefined' &&
+ inst.render._isMockFunction) {
+ // This is probably bad practice. Consider warning here and
+ // deprecating this convenience.
+ renderedComponent = null;
}
- ("production" !== process.env.NODE_ENV ? invariant(
- ReactElement.isValidElement(renderedComponent),
- '%s.render(): A valid ReactComponent must be returned. You may have ' +
- 'returned undefined, an array or some other invalid object.',
- this.constructor.displayName || 'ReactCompositeComponent'
- ) : invariant(ReactElement.isValidElement(renderedComponent)));
- return renderedComponent;
}
- ),
+
+ return renderedComponent;
+ },
/**
* @private
*/
- _bindAutoBindMethods: function() {
- for (var autoBindKey in this.__reactAutoBindMap) {
- if (!this.__reactAutoBindMap.hasOwnProperty(autoBindKey)) {
- continue;
- }
- var method = this.__reactAutoBindMap[autoBindKey];
- this[autoBindKey] = this._bindAutoBindMethod(ReactErrorUtils.guard(
- method,
- this.constructor.displayName + '.' + autoBindKey
- ));
+ _renderValidatedComponent: function() {
+ var renderedComponent;
+ var previousContext = ReactContext.current;
+ ReactContext.current = this._processChildContext(
+ this._currentElement._context
+ );
+ ReactCurrentOwner.current = this;
+ try {
+ renderedComponent =
+ this._renderValidatedComponentWithoutOwnerOrContext();
+ } finally {
+ ReactContext.current = previousContext;
+ ReactCurrentOwner.current = null;
}
+ ("production" !== process.env.NODE_ENV ? invariant(
+ // TODO: An `isValidNode` function would probably be more appropriate
+ renderedComponent === null || renderedComponent === false ||
+ ReactElement.isValidElement(renderedComponent),
+ '%s.render(): A valid ReactComponent must be returned. You may have ' +
+ 'returned undefined, an array or some other invalid object.',
+ this.getName() || 'ReactCompositeComponent'
+ ) : invariant(// TODO: An `isValidNode` function would probably be more appropriate
+ renderedComponent === null || renderedComponent === false ||
+ ReactElement.isValidElement(renderedComponent)));
+ return renderedComponent;
},
/**
- * Binds a method to the component.
+ * Lazily allocates the refs object and stores `component` as `ref`.
*
- * @param {function} method Method to be bound.
+ * @param {string} ref Reference name.
+ * @param {component} component Component to store as `ref`.
+ * @final
* @private
*/
- _bindAutoBindMethod: function(method) {
- var component = this;
- var boundMethod = method.bind(component);
- if ("production" !== process.env.NODE_ENV) {
- boundMethod.__reactBoundContext = component;
- boundMethod.__reactBoundMethod = method;
- boundMethod.__reactBoundArguments = null;
- var componentName = component.constructor.displayName;
- var _bind = boundMethod.bind;
- boundMethod.bind = function(newThis ) {var args=Array.prototype.slice.call(arguments,1);
- // User is trying to bind() an autobound method; we effectively will
- // ignore the value of "this" that the user is trying to use, so
- // let's warn.
- if (newThis !== component && newThis !== null) {
- monitorCodeUse('react_bind_warning', { component: componentName });
- console.warn(
- 'bind(): React component methods may only be bound to the ' +
- 'component instance. See ' + componentName
- );
- } else if (!args.length) {
- monitorCodeUse('react_bind_warning', { component: componentName });
- console.warn(
- 'bind(): You are binding a component method to the component. ' +
- 'React does this for you automatically in a high-performance ' +
- 'way, so you can safely remove this call. See ' + componentName
- );
- return boundMethod;
- }
- var reboundMethod = _bind.apply(boundMethod, arguments);
- reboundMethod.__reactBoundContext = component;
- reboundMethod.__reactBoundMethod = method;
- reboundMethod.__reactBoundArguments = args;
- return reboundMethod;
- };
- }
- return boundMethod;
- }
-};
-
-var ReactCompositeComponentBase = function() {};
-assign(
- ReactCompositeComponentBase.prototype,
- ReactComponent.Mixin,
- ReactOwner.Mixin,
- ReactPropTransferer.Mixin,
- ReactCompositeComponentMixin
-);
-
-/**
- * Module for creating composite components.
- *
- * @class ReactCompositeComponent
- * @extends ReactComponent
- * @extends ReactOwner
- * @extends ReactPropTransferer
- */
-var ReactCompositeComponent = {
-
- LifeCycle: CompositeLifeCycle,
-
- Base: ReactCompositeComponentBase,
+ attachRef: function(ref, component) {
+ var inst = this.getPublicInstance();
+ var refs = inst.refs === emptyObject ? (inst.refs = {}) : inst.refs;
+ refs[ref] = component.getPublicInstance();
+ },
/**
- * Creates a composite component class given a class specification.
+ * Detaches a reference name.
*
- * @param {object} spec Class specification (which must define `render`).
- * @return {function} Component constructor function.
- * @public
+ * @param {string} ref Name to dereference.
+ * @final
+ * @private
*/
- createClass: function(spec) {
- var Constructor = function(props) {
- // This constructor is overridden by mocks. The argument is used
- // by mocks to assert on what gets mounted. This will later be used
- // by the stand-alone class implementation.
- };
- Constructor.prototype = new ReactCompositeComponentBase();
- Constructor.prototype.constructor = Constructor;
+ detachRef: function(ref) {
+ var refs = this.getPublicInstance().refs;
+ delete refs[ref];
+ },
- injectedMixins.forEach(
- mixSpecIntoComponent.bind(null, Constructor)
+ /**
+ * Get a text description of the component that can be used to identify it
+ * in error messages.
+ * @return {string} The name or null.
+ * @internal
+ */
+ getName: function() {
+ var type = this._currentElement.type;
+ var constructor = this._instance && this._instance.constructor;
+ return (
+ type.displayName || (constructor && constructor.displayName) ||
+ type.name || (constructor && constructor.name) ||
+ null
);
+ },
- mixSpecIntoComponent(Constructor, spec);
+ /**
+ * Get the publicly accessible representation of this component - i.e. what
+ * is exposed by refs and returned by React.render. Can be null for stateless
+ * components.
+ *
+ * @return {ReactComponent} the public component instance.
+ * @internal
+ */
+ getPublicInstance: function() {
+ return this._instance;
+ },
- // Initialize the defaultProps property after all mixins have been merged
- if (Constructor.getDefaultProps) {
- Constructor.defaultProps = Constructor.getDefaultProps();
- }
+ // Stub
+ _instantiateReactComponent: null
- ("production" !== process.env.NODE_ENV ? invariant(
- Constructor.prototype.render,
- 'createClass(...): Class specification must implement a `render` method.'
- ) : invariant(Constructor.prototype.render));
+};
- if ("production" !== process.env.NODE_ENV) {
- if (Constructor.prototype.componentShouldUpdate) {
- monitorCodeUse(
- 'react_component_should_update_warning',
- { component: spec.displayName }
- );
- console.warn(
- (spec.displayName || 'A component') + ' has a method called ' +
- 'componentShouldUpdate(). Did you mean shouldComponentUpdate()? ' +
- 'The name is phrased as a question because the function is ' +
- 'expected to return a value.'
- );
- }
- }
+ReactPerf.measureMethods(
+ ReactCompositeComponentMixin,
+ 'ReactCompositeComponent',
+ {
+ mountComponent: 'mountComponent',
+ updateComponent: 'updateComponent',
+ _renderValidatedComponent: '_renderValidatedComponent'
+ }
+);
- // Reduce time spent doing lookups by setting these on the prototype.
- for (var methodName in ReactCompositeComponentInterface) {
- if (!Constructor.prototype[methodName]) {
- Constructor.prototype[methodName] = null;
- }
- }
+var ReactCompositeComponent = {
- if ("production" !== process.env.NODE_ENV) {
- return ReactLegacyElement.wrapFactory(
- ReactElementValidator.createFactory(Constructor)
- );
- }
- return ReactLegacyElement.wrapFactory(
- ReactElement.createFactory(Constructor)
- );
- },
+ Mixin: ReactCompositeComponentMixin
- injection: {
- injectMixin: function(mixin) {
- injectedMixins.push(mixin);
- }
- }
};
module.exports = ReactCompositeComponent;
}).call(this,require('_process'))
-},{"./Object.assign":70,"./ReactComponent":78,"./ReactContext":82,"./ReactCurrentOwner":83,"./ReactElement":99,"./ReactElementValidator":100,"./ReactEmptyComponent":101,"./ReactErrorUtils":102,"./ReactLegacyElement":108,"./ReactOwner":115,"./ReactPerf":116,"./ReactPropTransferer":117,"./ReactPropTypeLocationNames":118,"./ReactPropTypeLocations":119,"./ReactUpdates":132,"./instantiateReactComponent":181,"./invariant":182,"./keyMirror":188,"./keyOf":189,"./mapObject":190,"./monitorCodeUse":192,"./shouldUpdateReactComponent":198,"./warning":202,"_process":1}],82:[function(require,module,exports){
+},{"./Object.assign":69,"./ReactComponentEnvironment":81,"./ReactContext":84,"./ReactCurrentOwner":85,"./ReactElement":103,"./ReactElementValidator":104,"./ReactInstanceMap":113,"./ReactLifeCycle":114,"./ReactNativeComponent":120,"./ReactPerf":122,"./ReactPropTypeLocationNames":124,"./ReactPropTypeLocations":125,"./ReactReconciler":129,"./ReactUpdates":140,"./emptyObject":171,"./invariant":191,"./shouldUpdateReactComponent":208,"./warning":212,"_process":1}],84:[function(require,module,exports){
+(function (process){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -10338,9 +10858,13 @@ module.exports = ReactCompositeComponent;
* @providesModule ReactContext
*/
-"use strict";
+'use strict';
var assign = require("./Object.assign");
+var emptyObject = require("./emptyObject");
+var warning = require("./warning");
+
+var didWarn = false;
/**
* Keeps track of the current context.
@@ -10354,7 +10878,7 @@ var ReactContext = {
* @internal
* @type {object}
*/
- current: {},
+ current: emptyObject,
/**
* Temporarily extends the current context while executing scopedCallback.
@@ -10373,6 +10897,16 @@ var ReactContext = {
* @return {ReactComponent|array<ReactComponent>}
*/
withContext: function(newContext, scopedCallback) {
+ if ("production" !== process.env.NODE_ENV) {
+ ("production" !== process.env.NODE_ENV ? warning(
+ didWarn,
+ 'withContext is deprecated and will be removed in a future version. ' +
+ 'Use a wrapper component with getChildContext instead.'
+ ) : null);
+
+ didWarn = true;
+ }
+
var result;
var previousContext = ReactContext.current;
ReactContext.current = assign({}, previousContext, newContext);
@@ -10388,9 +10922,10 @@ var ReactContext = {
module.exports = ReactContext;
-},{"./Object.assign":70}],83:[function(require,module,exports){
+}).call(this,require('_process'))
+},{"./Object.assign":69,"./emptyObject":171,"./warning":212,"_process":1}],85:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -10400,7 +10935,7 @@ module.exports = ReactContext;
* @providesModule ReactCurrentOwner
*/
-"use strict";
+'use strict';
/**
* Keeps track of the current owner.
@@ -10422,10 +10957,10 @@ var ReactCurrentOwner = {
module.exports = ReactCurrentOwner;
-},{}],84:[function(require,module,exports){
+},{}],86:[function(require,module,exports){
(function (process){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -10436,11 +10971,10 @@ module.exports = ReactCurrentOwner;
* @typechecks static-only
*/
-"use strict";
+'use strict';
var ReactElement = require("./ReactElement");
var ReactElementValidator = require("./ReactElementValidator");
-var ReactLegacyElement = require("./ReactLegacyElement");
var mapObject = require("./mapObject");
@@ -10452,13 +10986,9 @@ var mapObject = require("./mapObject");
*/
function createDOMFactory(tag) {
if ("production" !== process.env.NODE_ENV) {
- return ReactLegacyElement.markNonLegacyFactory(
- ReactElementValidator.createFactory(tag)
- );
+ return ReactElementValidator.createFactory(tag);
}
- return ReactLegacyElement.markNonLegacyFactory(
- ReactElement.createFactory(tag)
- );
+ return ReactElement.createFactory(tag);
}
/**
@@ -10605,9 +11135,9 @@ var ReactDOM = mapObject({
module.exports = ReactDOM;
}).call(this,require('_process'))
-},{"./ReactElement":99,"./ReactElementValidator":100,"./ReactLegacyElement":108,"./mapObject":190,"_process":1}],85:[function(require,module,exports){
+},{"./ReactElement":103,"./ReactElementValidator":104,"./mapObject":199,"_process":1}],87:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -10617,18 +11147,16 @@ module.exports = ReactDOM;
* @providesModule ReactDOMButton
*/
-"use strict";
+'use strict';
var AutoFocusMixin = require("./AutoFocusMixin");
var ReactBrowserComponentMixin = require("./ReactBrowserComponentMixin");
-var ReactCompositeComponent = require("./ReactCompositeComponent");
+var ReactClass = require("./ReactClass");
var ReactElement = require("./ReactElement");
-var ReactDOM = require("./ReactDOM");
var keyMirror = require("./keyMirror");
-// Store a reference to the <button> `ReactDOMComponent`. TODO: use string
-var button = ReactElement.createFactory(ReactDOM.button.type);
+var button = ReactElement.createFactory('button');
var mouseListenerNames = keyMirror({
onClick: true,
@@ -10647,8 +11175,9 @@ var mouseListenerNames = keyMirror({
* Implements a <button> native component that does not receive mouse events
* when `disabled` is set.
*/
-var ReactDOMButton = ReactCompositeComponent.createClass({
+var ReactDOMButton = ReactClass.createClass({
displayName: 'ReactDOMButton',
+ tagName: 'BUTTON',
mixins: [AutoFocusMixin, ReactBrowserComponentMixin],
@@ -10670,10 +11199,10 @@ var ReactDOMButton = ReactCompositeComponent.createClass({
module.exports = ReactDOMButton;
-},{"./AutoFocusMixin":43,"./ReactBrowserComponentMixin":73,"./ReactCompositeComponent":81,"./ReactDOM":84,"./ReactElement":99,"./keyMirror":188}],86:[function(require,module,exports){
+},{"./AutoFocusMixin":42,"./ReactBrowserComponentMixin":72,"./ReactClass":78,"./ReactElement":103,"./keyMirror":197}],88:[function(require,module,exports){
(function (process){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -10684,24 +11213,26 @@ module.exports = ReactDOMButton;
* @typechecks static-only
*/
-"use strict";
+/* global hasOwnProperty:true */
+
+'use strict';
var CSSPropertyOperations = require("./CSSPropertyOperations");
var DOMProperty = require("./DOMProperty");
var DOMPropertyOperations = require("./DOMPropertyOperations");
-var ReactBrowserComponentMixin = require("./ReactBrowserComponentMixin");
-var ReactComponent = require("./ReactComponent");
var ReactBrowserEventEmitter = require("./ReactBrowserEventEmitter");
+var ReactComponentBrowserEnvironment =
+ require("./ReactComponentBrowserEnvironment");
var ReactMount = require("./ReactMount");
var ReactMultiChild = require("./ReactMultiChild");
var ReactPerf = require("./ReactPerf");
var assign = require("./Object.assign");
-var escapeTextForBrowser = require("./escapeTextForBrowser");
+var escapeTextContentForBrowser = require("./escapeTextContentForBrowser");
var invariant = require("./invariant");
var isEventSupported = require("./isEventSupported");
var keyOf = require("./keyOf");
-var monitorCodeUse = require("./monitorCodeUse");
+var warning = require("./warning");
var deleteListener = ReactBrowserEventEmitter.deleteListener;
var listenTo = ReactBrowserEventEmitter.listenTo;
@@ -10715,6 +11246,11 @@ var STYLE = keyOf({style: null});
var ELEMENT_NODE_TYPE = 1;
/**
+ * Optionally injectable operations for mutating the DOM
+ */
+var BackendIDOperations = null;
+
+/**
* @param {?object} props
*/
function assertValidProps(props) {
@@ -10722,24 +11258,37 @@ function assertValidProps(props) {
return;
}
// Note the use of `==` which checks for null or undefined.
- ("production" !== process.env.NODE_ENV ? invariant(
- props.children == null || props.dangerouslySetInnerHTML == null,
- 'Can only set one of `children` or `props.dangerouslySetInnerHTML`.'
- ) : invariant(props.children == null || props.dangerouslySetInnerHTML == null));
+ if (props.dangerouslySetInnerHTML != null) {
+ ("production" !== process.env.NODE_ENV ? invariant(
+ props.children == null,
+ 'Can only set one of `children` or `props.dangerouslySetInnerHTML`.'
+ ) : invariant(props.children == null));
+ ("production" !== process.env.NODE_ENV ? invariant(
+ props.dangerouslySetInnerHTML.__html != null,
+ '`props.dangerouslySetInnerHTML` must be in the form `{__html: ...}`. ' +
+ 'Please visit http://fb.me/react-invariant-dangerously-set-inner-html ' +
+ 'for more information.'
+ ) : invariant(props.dangerouslySetInnerHTML.__html != null));
+ }
if ("production" !== process.env.NODE_ENV) {
- if (props.contentEditable && props.children != null) {
- console.warn(
- 'A component is `contentEditable` and contains `children` managed by ' +
- 'React. It is now your responsibility to guarantee that none of those '+
- 'nodes are unexpectedly modified or duplicated. This is probably not ' +
- 'intentional.'
- );
- }
+ ("production" !== process.env.NODE_ENV ? warning(
+ props.innerHTML == null,
+ 'Directly setting property `innerHTML` is not permitted. ' +
+ 'For more information, lookup documentation on `dangerouslySetInnerHTML`.'
+ ) : null);
+ ("production" !== process.env.NODE_ENV ? warning(
+ !props.contentEditable || props.children == null,
+ 'A component is `contentEditable` and contains `children` managed by ' +
+ 'React. It is now your responsibility to guarantee that none of ' +
+ 'those nodes are unexpectedly modified or duplicated. This is ' +
+ 'probably not intentional.'
+ ) : null);
}
("production" !== process.env.NODE_ENV ? invariant(
props.style == null || typeof props.style === 'object',
'The `style` prop expects a mapping from style properties to values, ' +
- 'not a string.'
+ 'not a string. For example, style={{marginRight: spacing + \'em\'}} when ' +
+ 'using JSX.'
) : invariant(props.style == null || typeof props.style === 'object'));
}
@@ -10747,11 +11296,10 @@ function putListener(id, registrationName, listener, transaction) {
if ("production" !== process.env.NODE_ENV) {
// IE8 has no API for event capturing and the `onScroll` event doesn't
// bubble.
- if (registrationName === 'onScroll' &&
- !isEventSupported('scroll', true)) {
- monitorCodeUse('react_no_scroll_event');
- console.warn('This browser doesn\'t support the `onScroll` event');
- }
+ ("production" !== process.env.NODE_ENV ? warning(
+ registrationName !== 'onScroll' || isEventSupported('scroll', true),
+ 'This browser doesn\'t support the `onScroll` event'
+ ) : null);
}
var container = ReactMount.findReactContainerForID(id);
if (container) {
@@ -10816,19 +11364,24 @@ function validateDangerousTag(tag) {
* object mapping of style properties to values.
*
* @constructor ReactDOMComponent
- * @extends ReactComponent
* @extends ReactMultiChild
*/
function ReactDOMComponent(tag) {
validateDangerousTag(tag);
this._tag = tag;
- this.tagName = tag.toUpperCase();
+ this._renderedChildren = null;
+ this._previousStyleCopy = null;
+ this._rootNodeID = null;
}
ReactDOMComponent.displayName = 'ReactDOMComponent';
ReactDOMComponent.Mixin = {
+ construct: function(element) {
+ this._currentElement = element;
+ },
+
/**
* Generates root tag markup then recurses. This method has side effects and
* is not idempotent.
@@ -10836,28 +11389,18 @@ ReactDOMComponent.Mixin = {
* @internal
* @param {string} rootID The root DOM ID for this node.
* @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction
- * @param {number} mountDepth number of components in the owner hierarchy
* @return {string} The computed markup.
*/
- mountComponent: ReactPerf.measure(
- 'ReactDOMComponent',
- 'mountComponent',
- function(rootID, transaction, mountDepth) {
- ReactComponent.Mixin.mountComponent.call(
- this,
- rootID,
- transaction,
- mountDepth
- );
- assertValidProps(this.props);
- var closeTag = omittedCloseTags[this._tag] ? '' : '</' + this._tag + '>';
- return (
- this._createOpenTagMarkupAndPutListeners(transaction) +
- this._createContentMarkup(transaction) +
- closeTag
- );
- }
- ),
+ mountComponent: function(rootID, transaction, context) {
+ this._rootNodeID = rootID;
+ assertValidProps(this._currentElement.props);
+ var closeTag = omittedCloseTags[this._tag] ? '' : '</' + this._tag + '>';
+ return (
+ this._createOpenTagMarkupAndPutListeners(transaction) +
+ this._createContentMarkup(transaction, context) +
+ closeTag
+ );
+ },
/**
* Creates markup for the open tag and all attributes.
@@ -10872,7 +11415,7 @@ ReactDOMComponent.Mixin = {
* @return {string} Markup of opening tag.
*/
_createOpenTagMarkupAndPutListeners: function(transaction) {
- var props = this.props;
+ var props = this._currentElement.props;
var ret = '<' + this._tag;
for (var propKey in props) {
@@ -10888,7 +11431,7 @@ ReactDOMComponent.Mixin = {
} else {
if (propKey === STYLE) {
if (propValue) {
- propValue = props.style = assign({}, props.style);
+ propValue = this._previousStyleCopy = assign({}, props.style);
}
propValue = CSSPropertyOperations.createMarkupForStyles(propValue);
}
@@ -10915,50 +11458,50 @@ ReactDOMComponent.Mixin = {
*
* @private
* @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction
+ * @param {object} context
* @return {string} Content markup.
*/
- _createContentMarkup: function(transaction) {
+ _createContentMarkup: function(transaction, context) {
+ var prefix = '';
+ if (this._tag === 'listing' ||
+ this._tag === 'pre' ||
+ this._tag === 'textarea') {
+ // Add an initial newline because browsers ignore the first newline in
+ // a <listing>, <pre>, or <textarea> as an "authoring convenience" -- see
+ // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inbody.
+ prefix = '\n';
+ }
+
+ var props = this._currentElement.props;
+
// Intentional use of != to avoid catching zero/false.
- var innerHTML = this.props.dangerouslySetInnerHTML;
+ var innerHTML = props.dangerouslySetInnerHTML;
if (innerHTML != null) {
if (innerHTML.__html != null) {
- return innerHTML.__html;
+ return prefix + innerHTML.__html;
}
} else {
var contentToUse =
- CONTENT_TYPES[typeof this.props.children] ? this.props.children : null;
- var childrenToUse = contentToUse != null ? null : this.props.children;
+ CONTENT_TYPES[typeof props.children] ? props.children : null;
+ var childrenToUse = contentToUse != null ? null : props.children;
if (contentToUse != null) {
- return escapeTextForBrowser(contentToUse);
+ return prefix + escapeTextContentForBrowser(contentToUse);
} else if (childrenToUse != null) {
var mountImages = this.mountChildren(
childrenToUse,
- transaction
+ transaction,
+ context
);
- return mountImages.join('');
+ return prefix + mountImages.join('');
}
}
- return '';
+ return prefix;
},
- receiveComponent: function(nextElement, transaction) {
- if (nextElement === this._currentElement &&
- nextElement._owner != null) {
- // Since elements are immutable after the owner is rendered,
- // we can do a cheap identity compare here to determine if this is a
- // superfluous reconcile. It's possible for state to be mutable but such
- // change should trigger an update of the owner which would recreate
- // the element. We explicitly check for the existence of an owner since
- // it's possible for a element created outside a composite to be
- // deeply mutated and reused.
- return;
- }
-
- ReactComponent.Mixin.receiveComponent.call(
- this,
- nextElement,
- transaction
- );
+ receiveComponent: function(nextElement, transaction, context) {
+ var prevElement = this._currentElement;
+ this._currentElement = nextElement;
+ this.updateComponent(transaction, prevElement, nextElement, context);
},
/**
@@ -10967,23 +11510,15 @@ ReactDOMComponent.Mixin = {
*
* @param {ReactReconcileTransaction} transaction
* @param {ReactElement} prevElement
+ * @param {ReactElement} nextElement
* @internal
* @overridable
*/
- updateComponent: ReactPerf.measure(
- 'ReactDOMComponent',
- 'updateComponent',
- function(transaction, prevElement) {
- assertValidProps(this._currentElement.props);
- ReactComponent.Mixin.updateComponent.call(
- this,
- transaction,
- prevElement
- );
- this._updateDOMProperties(prevElement.props, transaction);
- this._updateDOMChildren(prevElement.props, transaction);
- }
- ),
+ updateComponent: function(transaction, prevElement, nextElement, context) {
+ assertValidProps(this._currentElement.props);
+ this._updateDOMProperties(prevElement.props, transaction);
+ this._updateDOMChildren(prevElement.props, transaction, context);
+ },
/**
* Reconciles the properties by detecting differences in property values and
@@ -11001,7 +11536,7 @@ ReactDOMComponent.Mixin = {
* @param {ReactReconcileTransaction} transaction
*/
_updateDOMProperties: function(lastProps, transaction) {
- var nextProps = this.props;
+ var nextProps = this._currentElement.props;
var propKey;
var styleName;
var styleUpdates;
@@ -11011,19 +11546,20 @@ ReactDOMComponent.Mixin = {
continue;
}
if (propKey === STYLE) {
- var lastStyle = lastProps[propKey];
+ var lastStyle = this._previousStyleCopy;
for (styleName in lastStyle) {
if (lastStyle.hasOwnProperty(styleName)) {
styleUpdates = styleUpdates || {};
styleUpdates[styleName] = '';
}
}
+ this._previousStyleCopy = null;
} else if (registrationNameModules.hasOwnProperty(propKey)) {
deleteListener(this._rootNodeID, propKey);
} else if (
DOMProperty.isStandardName[propKey] ||
DOMProperty.isCustomAttribute(propKey)) {
- ReactComponent.BackendIDOperations.deletePropertyByID(
+ BackendIDOperations.deletePropertyByID(
this._rootNodeID,
propKey
);
@@ -11031,13 +11567,15 @@ ReactDOMComponent.Mixin = {
}
for (propKey in nextProps) {
var nextProp = nextProps[propKey];
- var lastProp = lastProps[propKey];
+ var lastProp = propKey === STYLE ?
+ this._previousStyleCopy :
+ lastProps[propKey];
if (!nextProps.hasOwnProperty(propKey) || nextProp === lastProp) {
continue;
}
if (propKey === STYLE) {
if (nextProp) {
- nextProp = nextProps.style = assign({}, nextProp);
+ nextProp = this._previousStyleCopy = assign({}, nextProp);
}
if (lastProp) {
// Unset styles on `lastProp` but not on `nextProp`.
@@ -11065,7 +11603,7 @@ ReactDOMComponent.Mixin = {
} else if (
DOMProperty.isStandardName[propKey] ||
DOMProperty.isCustomAttribute(propKey)) {
- ReactComponent.BackendIDOperations.updatePropertyByID(
+ BackendIDOperations.updatePropertyByID(
this._rootNodeID,
propKey,
nextProp
@@ -11073,7 +11611,7 @@ ReactDOMComponent.Mixin = {
}
}
if (styleUpdates) {
- ReactComponent.BackendIDOperations.updateStylesByID(
+ BackendIDOperations.updateStylesByID(
this._rootNodeID,
styleUpdates
);
@@ -11087,8 +11625,8 @@ ReactDOMComponent.Mixin = {
* @param {object} lastProps
* @param {ReactReconcileTransaction} transaction
*/
- _updateDOMChildren: function(lastProps, transaction) {
- var nextProps = this.props;
+ _updateDOMChildren: function(lastProps, transaction, context) {
+ var nextProps = this._currentElement.props;
var lastContent =
CONTENT_TYPES[typeof lastProps.children] ? lastProps.children : null;
@@ -11111,7 +11649,7 @@ ReactDOMComponent.Mixin = {
var lastHasContentOrHtml = lastContent != null || lastHtml != null;
var nextHasContentOrHtml = nextContent != null || nextHtml != null;
if (lastChildren != null && nextChildren == null) {
- this.updateChildren(null, transaction);
+ this.updateChildren(null, transaction, context);
} else if (lastHasContentOrHtml && !nextHasContentOrHtml) {
this.updateTextContent('');
}
@@ -11122,13 +11660,13 @@ ReactDOMComponent.Mixin = {
}
} else if (nextHtml != null) {
if (lastHtml !== nextHtml) {
- ReactComponent.BackendIDOperations.updateInnerHTMLByID(
+ BackendIDOperations.updateInnerHTMLByID(
this._rootNodeID,
nextHtml
);
}
} else if (nextChildren != null) {
- this.updateChildren(nextChildren, transaction);
+ this.updateChildren(nextChildren, transaction, context);
}
},
@@ -11141,25 +11679,35 @@ ReactDOMComponent.Mixin = {
unmountComponent: function() {
this.unmountChildren();
ReactBrowserEventEmitter.deleteAllListeners(this._rootNodeID);
- ReactComponent.Mixin.unmountComponent.call(this);
+ ReactComponentBrowserEnvironment.unmountIDFromEnvironment(this._rootNodeID);
+ this._rootNodeID = null;
}
};
+ReactPerf.measureMethods(ReactDOMComponent, 'ReactDOMComponent', {
+ mountComponent: 'mountComponent',
+ updateComponent: 'updateComponent'
+});
+
assign(
ReactDOMComponent.prototype,
- ReactComponent.Mixin,
ReactDOMComponent.Mixin,
- ReactMultiChild.Mixin,
- ReactBrowserComponentMixin
+ ReactMultiChild.Mixin
);
+ReactDOMComponent.injection = {
+ injectIDOperations: function(IDOperations) {
+ ReactDOMComponent.BackendIDOperations = BackendIDOperations = IDOperations;
+ }
+};
+
module.exports = ReactDOMComponent;
}).call(this,require('_process'))
-},{"./CSSPropertyOperations":47,"./DOMProperty":53,"./DOMPropertyOperations":54,"./Object.assign":70,"./ReactBrowserComponentMixin":73,"./ReactBrowserEventEmitter":74,"./ReactComponent":78,"./ReactMount":111,"./ReactMultiChild":112,"./ReactPerf":116,"./escapeTextForBrowser":165,"./invariant":182,"./isEventSupported":183,"./keyOf":189,"./monitorCodeUse":192,"_process":1}],87:[function(require,module,exports){
+},{"./CSSPropertyOperations":46,"./DOMProperty":51,"./DOMPropertyOperations":52,"./Object.assign":69,"./ReactBrowserEventEmitter":73,"./ReactComponentBrowserEnvironment":80,"./ReactMount":117,"./ReactMultiChild":118,"./ReactPerf":122,"./escapeTextContentForBrowser":172,"./invariant":191,"./isEventSupported":192,"./keyOf":198,"./warning":212,"_process":1}],89:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -11169,17 +11717,15 @@ module.exports = ReactDOMComponent;
* @providesModule ReactDOMForm
*/
-"use strict";
+'use strict';
var EventConstants = require("./EventConstants");
var LocalEventTrapMixin = require("./LocalEventTrapMixin");
var ReactBrowserComponentMixin = require("./ReactBrowserComponentMixin");
-var ReactCompositeComponent = require("./ReactCompositeComponent");
+var ReactClass = require("./ReactClass");
var ReactElement = require("./ReactElement");
-var ReactDOM = require("./ReactDOM");
-// Store a reference to the <form> `ReactDOMComponent`. TODO: use string
-var form = ReactElement.createFactory(ReactDOM.form.type);
+var form = ReactElement.createFactory('form');
/**
* Since onSubmit doesn't bubble OR capture on the top level in IE8, we need
@@ -11187,8 +11733,9 @@ var form = ReactElement.createFactory(ReactDOM.form.type);
* do to accomplish this, but the most reliable is to make <form> a
* composite component and use `componentDidMount` to attach the event handlers.
*/
-var ReactDOMForm = ReactCompositeComponent.createClass({
+var ReactDOMForm = ReactClass.createClass({
displayName: 'ReactDOMForm',
+ tagName: 'FORM',
mixins: [ReactBrowserComponentMixin, LocalEventTrapMixin],
@@ -11207,10 +11754,10 @@ var ReactDOMForm = ReactCompositeComponent.createClass({
module.exports = ReactDOMForm;
-},{"./EventConstants":58,"./LocalEventTrapMixin":68,"./ReactBrowserComponentMixin":73,"./ReactCompositeComponent":81,"./ReactDOM":84,"./ReactElement":99}],88:[function(require,module,exports){
+},{"./EventConstants":56,"./LocalEventTrapMixin":67,"./ReactBrowserComponentMixin":72,"./ReactClass":78,"./ReactElement":103}],90:[function(require,module,exports){
(function (process){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -11223,7 +11770,7 @@ module.exports = ReactDOMForm;
/*jslint evil: true */
-"use strict";
+'use strict';
var CSSPropertyOperations = require("./CSSPropertyOperations");
var DOMChildrenOperations = require("./DOMChildrenOperations");
@@ -11248,7 +11795,7 @@ var INVALID_PROPERTY_ERRORS = {
/**
* Operations used to process updates to DOM nodes. This is made injectable via
- * `ReactComponent.BackendIDOperations`.
+ * `ReactDOMComponent.BackendIDOperations`.
*/
var ReactDOMIDOperations = {
@@ -11261,27 +11808,23 @@ var ReactDOMIDOperations = {
* @param {*} value New value of the property.
* @internal
*/
- updatePropertyByID: ReactPerf.measure(
- 'ReactDOMIDOperations',
- 'updatePropertyByID',
- function(id, name, value) {
- var node = ReactMount.getNode(id);
- ("production" !== process.env.NODE_ENV ? invariant(
- !INVALID_PROPERTY_ERRORS.hasOwnProperty(name),
- 'updatePropertyByID(...): %s',
- INVALID_PROPERTY_ERRORS[name]
- ) : invariant(!INVALID_PROPERTY_ERRORS.hasOwnProperty(name)));
-
- // If we're updating to null or undefined, we should remove the property
- // from the DOM node instead of inadvertantly setting to a string. This
- // brings us in line with the same behavior we have on initial render.
- if (value != null) {
- DOMPropertyOperations.setValueForProperty(node, name, value);
- } else {
- DOMPropertyOperations.deleteValueForProperty(node, name);
- }
+ updatePropertyByID: function(id, name, value) {
+ var node = ReactMount.getNode(id);
+ ("production" !== process.env.NODE_ENV ? invariant(
+ !INVALID_PROPERTY_ERRORS.hasOwnProperty(name),
+ 'updatePropertyByID(...): %s',
+ INVALID_PROPERTY_ERRORS[name]
+ ) : invariant(!INVALID_PROPERTY_ERRORS.hasOwnProperty(name)));
+
+ // If we're updating to null or undefined, we should remove the property
+ // from the DOM node instead of inadvertantly setting to a string. This
+ // brings us in line with the same behavior we have on initial render.
+ if (value != null) {
+ DOMPropertyOperations.setValueForProperty(node, name, value);
+ } else {
+ DOMPropertyOperations.deleteValueForProperty(node, name);
}
- ),
+ },
/**
* Updates a DOM node to remove a property. This should only be used to remove
@@ -11291,19 +11834,15 @@ var ReactDOMIDOperations = {
* @param {string} name A property name to remove, see `DOMProperty`.
* @internal
*/
- deletePropertyByID: ReactPerf.measure(
- 'ReactDOMIDOperations',
- 'deletePropertyByID',
- function(id, name, value) {
- var node = ReactMount.getNode(id);
- ("production" !== process.env.NODE_ENV ? invariant(
- !INVALID_PROPERTY_ERRORS.hasOwnProperty(name),
- 'updatePropertyByID(...): %s',
- INVALID_PROPERTY_ERRORS[name]
- ) : invariant(!INVALID_PROPERTY_ERRORS.hasOwnProperty(name)));
- DOMPropertyOperations.deleteValueForProperty(node, name, value);
- }
- ),
+ deletePropertyByID: function(id, name, value) {
+ var node = ReactMount.getNode(id);
+ ("production" !== process.env.NODE_ENV ? invariant(
+ !INVALID_PROPERTY_ERRORS.hasOwnProperty(name),
+ 'updatePropertyByID(...): %s',
+ INVALID_PROPERTY_ERRORS[name]
+ ) : invariant(!INVALID_PROPERTY_ERRORS.hasOwnProperty(name)));
+ DOMPropertyOperations.deleteValueForProperty(node, name, value);
+ },
/**
* Updates a DOM node with new style values. If a value is specified as '',
@@ -11313,14 +11852,10 @@ var ReactDOMIDOperations = {
* @param {object} styles Mapping from styles to values.
* @internal
*/
- updateStylesByID: ReactPerf.measure(
- 'ReactDOMIDOperations',
- 'updateStylesByID',
- function(id, styles) {
- var node = ReactMount.getNode(id);
- CSSPropertyOperations.setValueForStyles(node, styles);
- }
- ),
+ updateStylesByID: function(id, styles) {
+ var node = ReactMount.getNode(id);
+ CSSPropertyOperations.setValueForStyles(node, styles);
+ },
/**
* Updates a DOM node's innerHTML.
@@ -11329,14 +11864,10 @@ var ReactDOMIDOperations = {
* @param {string} html An HTML string.
* @internal
*/
- updateInnerHTMLByID: ReactPerf.measure(
- 'ReactDOMIDOperations',
- 'updateInnerHTMLByID',
- function(id, html) {
- var node = ReactMount.getNode(id);
- setInnerHTML(node, html);
- }
- ),
+ updateInnerHTMLByID: function(id, html) {
+ var node = ReactMount.getNode(id);
+ setInnerHTML(node, html);
+ },
/**
* Updates a DOM node's text content set by `props.content`.
@@ -11345,14 +11876,10 @@ var ReactDOMIDOperations = {
* @param {string} content Text content.
* @internal
*/
- updateTextContentByID: ReactPerf.measure(
- 'ReactDOMIDOperations',
- 'updateTextContentByID',
- function(id, content) {
- var node = ReactMount.getNode(id);
- DOMChildrenOperations.updateTextContent(node, content);
- }
- ),
+ updateTextContentByID: function(id, content) {
+ var node = ReactMount.getNode(id);
+ DOMChildrenOperations.updateTextContent(node, content);
+ },
/**
* Replaces a DOM node that exists in the document with markup.
@@ -11362,14 +11889,10 @@ var ReactDOMIDOperations = {
* @internal
* @see {Danger.dangerouslyReplaceNodeWithMarkup}
*/
- dangerouslyReplaceNodeWithMarkupByID: ReactPerf.measure(
- 'ReactDOMIDOperations',
- 'dangerouslyReplaceNodeWithMarkupByID',
- function(id, markup) {
- var node = ReactMount.getNode(id);
- DOMChildrenOperations.dangerouslyReplaceNodeWithMarkup(node, markup);
- }
- ),
+ dangerouslyReplaceNodeWithMarkupByID: function(id, markup) {
+ var node = ReactMount.getNode(id);
+ DOMChildrenOperations.dangerouslyReplaceNodeWithMarkup(node, markup);
+ },
/**
* Updates a component's children by processing a series of updates.
@@ -11378,24 +11901,75 @@ var ReactDOMIDOperations = {
* @param {array<string>} markup List of markup strings.
* @internal
*/
- dangerouslyProcessChildrenUpdates: ReactPerf.measure(
- 'ReactDOMIDOperations',
- 'dangerouslyProcessChildrenUpdates',
- function(updates, markup) {
- for (var i = 0; i < updates.length; i++) {
- updates[i].parentNode = ReactMount.getNode(updates[i].parentID);
- }
- DOMChildrenOperations.processUpdates(updates, markup);
+ dangerouslyProcessChildrenUpdates: function(updates, markup) {
+ for (var i = 0; i < updates.length; i++) {
+ updates[i].parentNode = ReactMount.getNode(updates[i].parentID);
}
- )
+ DOMChildrenOperations.processUpdates(updates, markup);
+ }
};
+ReactPerf.measureMethods(ReactDOMIDOperations, 'ReactDOMIDOperations', {
+ updatePropertyByID: 'updatePropertyByID',
+ deletePropertyByID: 'deletePropertyByID',
+ updateStylesByID: 'updateStylesByID',
+ updateInnerHTMLByID: 'updateInnerHTMLByID',
+ updateTextContentByID: 'updateTextContentByID',
+ dangerouslyReplaceNodeWithMarkupByID: 'dangerouslyReplaceNodeWithMarkupByID',
+ dangerouslyProcessChildrenUpdates: 'dangerouslyProcessChildrenUpdates'
+});
+
module.exports = ReactDOMIDOperations;
}).call(this,require('_process'))
-},{"./CSSPropertyOperations":47,"./DOMChildrenOperations":52,"./DOMPropertyOperations":54,"./ReactMount":111,"./ReactPerf":116,"./invariant":182,"./setInnerHTML":196,"_process":1}],89:[function(require,module,exports){
+},{"./CSSPropertyOperations":46,"./DOMChildrenOperations":50,"./DOMPropertyOperations":52,"./ReactMount":117,"./ReactPerf":122,"./invariant":191,"./setInnerHTML":205,"_process":1}],91:[function(require,module,exports){
+/**
+ * Copyright 2013-2015, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ * @providesModule ReactDOMIframe
+ */
+
+'use strict';
+
+var EventConstants = require("./EventConstants");
+var LocalEventTrapMixin = require("./LocalEventTrapMixin");
+var ReactBrowserComponentMixin = require("./ReactBrowserComponentMixin");
+var ReactClass = require("./ReactClass");
+var ReactElement = require("./ReactElement");
+
+var iframe = ReactElement.createFactory('iframe');
+
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Since onLoad doesn't bubble OR capture on the top level in IE8, we need to
+ * capture it on the <iframe> element itself. There are lots of hacks we could
+ * do to accomplish this, but the most reliable is to make <iframe> a composite
+ * component and use `componentDidMount` to attach the event handlers.
+ */
+var ReactDOMIframe = ReactClass.createClass({
+ displayName: 'ReactDOMIframe',
+ tagName: 'IFRAME',
+
+ mixins: [ReactBrowserComponentMixin, LocalEventTrapMixin],
+
+ render: function() {
+ return iframe(this.props);
+ },
+
+ componentDidMount: function() {
+ this.trapBubbledEvent(EventConstants.topLevelTypes.topLoad, 'load');
+ }
+});
+
+module.exports = ReactDOMIframe;
+
+},{"./EventConstants":56,"./LocalEventTrapMixin":67,"./ReactBrowserComponentMixin":72,"./ReactClass":78,"./ReactElement":103}],92:[function(require,module,exports){
+/**
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -11405,17 +11979,15 @@ module.exports = ReactDOMIDOperations;
* @providesModule ReactDOMImg
*/
-"use strict";
+'use strict';
var EventConstants = require("./EventConstants");
var LocalEventTrapMixin = require("./LocalEventTrapMixin");
var ReactBrowserComponentMixin = require("./ReactBrowserComponentMixin");
-var ReactCompositeComponent = require("./ReactCompositeComponent");
+var ReactClass = require("./ReactClass");
var ReactElement = require("./ReactElement");
-var ReactDOM = require("./ReactDOM");
-// Store a reference to the <img> `ReactDOMComponent`. TODO: use string
-var img = ReactElement.createFactory(ReactDOM.img.type);
+var img = ReactElement.createFactory('img');
/**
* Since onLoad doesn't bubble OR capture on the top level in IE8, we need to
@@ -11423,7 +11995,7 @@ var img = ReactElement.createFactory(ReactDOM.img.type);
* to accomplish this, but the most reliable is to make <img> a composite
* component and use `componentDidMount` to attach the event handlers.
*/
-var ReactDOMImg = ReactCompositeComponent.createClass({
+var ReactDOMImg = ReactClass.createClass({
displayName: 'ReactDOMImg',
tagName: 'IMG',
@@ -11441,10 +12013,10 @@ var ReactDOMImg = ReactCompositeComponent.createClass({
module.exports = ReactDOMImg;
-},{"./EventConstants":58,"./LocalEventTrapMixin":68,"./ReactBrowserComponentMixin":73,"./ReactCompositeComponent":81,"./ReactDOM":84,"./ReactElement":99}],90:[function(require,module,exports){
+},{"./EventConstants":56,"./LocalEventTrapMixin":67,"./ReactBrowserComponentMixin":72,"./ReactClass":78,"./ReactElement":103}],93:[function(require,module,exports){
(function (process){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -11454,23 +12026,21 @@ module.exports = ReactDOMImg;
* @providesModule ReactDOMInput
*/
-"use strict";
+'use strict';
var AutoFocusMixin = require("./AutoFocusMixin");
var DOMPropertyOperations = require("./DOMPropertyOperations");
var LinkedValueUtils = require("./LinkedValueUtils");
var ReactBrowserComponentMixin = require("./ReactBrowserComponentMixin");
-var ReactCompositeComponent = require("./ReactCompositeComponent");
+var ReactClass = require("./ReactClass");
var ReactElement = require("./ReactElement");
-var ReactDOM = require("./ReactDOM");
var ReactMount = require("./ReactMount");
var ReactUpdates = require("./ReactUpdates");
var assign = require("./Object.assign");
var invariant = require("./invariant");
-// Store a reference to the <input> `ReactDOMComponent`. TODO: use string
-var input = ReactElement.createFactory(ReactDOM.input.type);
+var input = ReactElement.createFactory('input');
var instancesByReactID = {};
@@ -11497,8 +12067,9 @@ function forceUpdateIfMounted() {
*
* @see http://www.w3.org/TR/2012/WD-html5-20121025/the-input-element.html
*/
-var ReactDOMInput = ReactCompositeComponent.createClass({
+var ReactDOMInput = ReactClass.createClass({
displayName: 'ReactDOMInput',
+ tagName: 'INPUT',
mixins: [AutoFocusMixin, LinkedValueUtils.Mixin, ReactBrowserComponentMixin],
@@ -11619,10 +12190,10 @@ var ReactDOMInput = ReactCompositeComponent.createClass({
module.exports = ReactDOMInput;
}).call(this,require('_process'))
-},{"./AutoFocusMixin":43,"./DOMPropertyOperations":54,"./LinkedValueUtils":67,"./Object.assign":70,"./ReactBrowserComponentMixin":73,"./ReactCompositeComponent":81,"./ReactDOM":84,"./ReactElement":99,"./ReactMount":111,"./ReactUpdates":132,"./invariant":182,"_process":1}],91:[function(require,module,exports){
+},{"./AutoFocusMixin":42,"./DOMPropertyOperations":52,"./LinkedValueUtils":66,"./Object.assign":69,"./ReactBrowserComponentMixin":72,"./ReactClass":78,"./ReactElement":103,"./ReactMount":117,"./ReactUpdates":140,"./invariant":191,"_process":1}],94:[function(require,module,exports){
(function (process){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -11632,23 +12203,22 @@ module.exports = ReactDOMInput;
* @providesModule ReactDOMOption
*/
-"use strict";
+'use strict';
var ReactBrowserComponentMixin = require("./ReactBrowserComponentMixin");
-var ReactCompositeComponent = require("./ReactCompositeComponent");
+var ReactClass = require("./ReactClass");
var ReactElement = require("./ReactElement");
-var ReactDOM = require("./ReactDOM");
var warning = require("./warning");
-// Store a reference to the <option> `ReactDOMComponent`. TODO: use string
-var option = ReactElement.createFactory(ReactDOM.option.type);
+var option = ReactElement.createFactory('option');
/**
* Implements an <option> native component that warns when `selected` is set.
*/
-var ReactDOMOption = ReactCompositeComponent.createClass({
+var ReactDOMOption = ReactClass.createClass({
displayName: 'ReactDOMOption',
+ tagName: 'OPTION',
mixins: [ReactBrowserComponentMixin],
@@ -11672,9 +12242,9 @@ var ReactDOMOption = ReactCompositeComponent.createClass({
module.exports = ReactDOMOption;
}).call(this,require('_process'))
-},{"./ReactBrowserComponentMixin":73,"./ReactCompositeComponent":81,"./ReactDOM":84,"./ReactElement":99,"./warning":202,"_process":1}],92:[function(require,module,exports){
+},{"./ReactBrowserComponentMixin":72,"./ReactClass":78,"./ReactElement":103,"./warning":212,"_process":1}],95:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -11684,26 +12254,27 @@ module.exports = ReactDOMOption;
* @providesModule ReactDOMSelect
*/
-"use strict";
+'use strict';
var AutoFocusMixin = require("./AutoFocusMixin");
var LinkedValueUtils = require("./LinkedValueUtils");
var ReactBrowserComponentMixin = require("./ReactBrowserComponentMixin");
-var ReactCompositeComponent = require("./ReactCompositeComponent");
+var ReactClass = require("./ReactClass");
var ReactElement = require("./ReactElement");
-var ReactDOM = require("./ReactDOM");
var ReactUpdates = require("./ReactUpdates");
var assign = require("./Object.assign");
-// Store a reference to the <select> `ReactDOMComponent`. TODO: use string
-var select = ReactElement.createFactory(ReactDOM.select.type);
+var select = ReactElement.createFactory('select');
-function updateWithPendingValueIfMounted() {
+function updateOptionsIfPendingUpdateAndMounted() {
/*jshint validthis:true */
- if (this.isMounted()) {
- this.setState({value: this._pendingValue});
- this._pendingValue = 0;
+ if (this._pendingUpdate) {
+ this._pendingUpdate = false;
+ var value = LinkedValueUtils.getValue(this);
+ if (value != null && this.isMounted()) {
+ updateOptions(this, value);
+ }
}
}
@@ -11713,7 +12284,7 @@ function updateWithPendingValueIfMounted() {
*/
function selectValueType(props, propName, componentName) {
if (props[propName] == null) {
- return;
+ return null;
}
if (props.multiple) {
if (!Array.isArray(props[propName])) {
@@ -11733,32 +12304,37 @@ function selectValueType(props, propName, componentName) {
}
/**
- * If `value` is supplied, updates <option> elements on mount and update.
* @param {ReactComponent} component Instance of ReactDOMSelect
- * @param {?*} propValue For uncontrolled components, null/undefined. For
- * controlled components, a string (or with `multiple`, a list of strings).
+ * @param {*} propValue A stringable (with `multiple`, a list of stringables).
* @private
*/
function updateOptions(component, propValue) {
- var multiple = component.props.multiple;
- var value = propValue != null ? propValue : component.state.value;
- var options = component.getDOMNode().options;
var selectedValue, i, l;
- if (multiple) {
+ var options = component.getDOMNode().options;
+
+ if (component.props.multiple) {
selectedValue = {};
- for (i = 0, l = value.length; i < l; ++i) {
- selectedValue['' + value[i]] = true;
+ for (i = 0, l = propValue.length; i < l; i++) {
+ selectedValue['' + propValue[i]] = true;
+ }
+ for (i = 0, l = options.length; i < l; i++) {
+ var selected = selectedValue.hasOwnProperty(options[i].value);
+ if (options[i].selected !== selected) {
+ options[i].selected = selected;
+ }
}
} else {
- selectedValue = '' + value;
- }
- for (i = 0, l = options.length; i < l; i++) {
- var selected = multiple ?
- selectedValue.hasOwnProperty(options[i].value) :
- options[i].value === selectedValue;
-
- if (selected !== options[i].selected) {
- options[i].selected = selected;
+ // Do not set `select.value` as exact behavior isn't consistent across all
+ // browsers for all cases.
+ selectedValue = '' + propValue;
+ for (i = 0, l = options.length; i < l; i++) {
+ if (options[i].value === selectedValue) {
+ options[i].selected = true;
+ return;
+ }
+ }
+ if (options.length) {
+ options[0].selected = true;
}
}
}
@@ -11766,7 +12342,7 @@ function updateOptions(component, propValue) {
/**
* Implements a <select> native component that allows optionally setting the
* props `value` and `defaultValue`. If `multiple` is false, the prop must be a
- * string. If `multiple` is true, the prop must be an array of strings.
+ * stringable. If `multiple` is true, the prop must be an array of stringables.
*
* If `value` is not supplied (or null/undefined), user actions that change the
* selected option will trigger updates to the rendered options.
@@ -11778,8 +12354,9 @@ function updateOptions(component, propValue) {
* If `defaultValue` is provided, any options with the supplied values will be
* selected.
*/
-var ReactDOMSelect = ReactCompositeComponent.createClass({
+var ReactDOMSelect = ReactClass.createClass({
displayName: 'ReactDOMSelect',
+ tagName: 'SELECT',
mixins: [AutoFocusMixin, LinkedValueUtils.Mixin, ReactBrowserComponentMixin],
@@ -11788,22 +12365,6 @@ var ReactDOMSelect = ReactCompositeComponent.createClass({
value: selectValueType
},
- getInitialState: function() {
- return {value: this.props.defaultValue || (this.props.multiple ? [] : '')};
- },
-
- componentWillMount: function() {
- this._pendingValue = null;
- },
-
- componentWillReceiveProps: function(nextProps) {
- if (!this.props.multiple && nextProps.multiple) {
- this.setState({value: [this.state.value]});
- } else if (this.props.multiple && !nextProps.multiple) {
- this.setState({value: this.state.value[0]});
- }
- },
-
render: function() {
// Clone `this.props` so we don't mutate the input.
var props = assign({}, this.props);
@@ -11814,16 +12375,32 @@ var ReactDOMSelect = ReactCompositeComponent.createClass({
return select(props, this.props.children);
},
+ componentWillMount: function() {
+ this._pendingUpdate = false;
+ },
+
componentDidMount: function() {
- updateOptions(this, LinkedValueUtils.getValue(this));
+ var value = LinkedValueUtils.getValue(this);
+ if (value != null) {
+ updateOptions(this, value);
+ } else if (this.props.defaultValue != null) {
+ updateOptions(this, this.props.defaultValue);
+ }
},
componentDidUpdate: function(prevProps) {
var value = LinkedValueUtils.getValue(this);
- var prevMultiple = !!prevProps.multiple;
- var multiple = !!this.props.multiple;
- if (value != null || prevMultiple !== multiple) {
+ if (value != null) {
+ this._pendingUpdate = false;
updateOptions(this, value);
+ } else if (!prevProps.multiple !== !this.props.multiple) {
+ // For simplicity, reapply `defaultValue` if `multiple` is toggled.
+ if (this.props.defaultValue != null) {
+ updateOptions(this, this.props.defaultValue);
+ } else {
+ // Revert the select back to its default unselected state.
+ updateOptions(this, this.props.multiple ? [] : '');
+ }
}
},
@@ -11834,21 +12411,8 @@ var ReactDOMSelect = ReactCompositeComponent.createClass({
returnValue = onChange.call(this, event);
}
- var selectedValue;
- if (this.props.multiple) {
- selectedValue = [];
- var options = event.target.options;
- for (var i = 0, l = options.length; i < l; i++) {
- if (options[i].selected) {
- selectedValue.push(options[i].value);
- }
- }
- } else {
- selectedValue = event.target.value;
- }
-
- this._pendingValue = selectedValue;
- ReactUpdates.asap(updateWithPendingValueIfMounted, this);
+ this._pendingUpdate = true;
+ ReactUpdates.asap(updateOptionsIfPendingUpdateAndMounted, this);
return returnValue;
}
@@ -11856,9 +12420,9 @@ var ReactDOMSelect = ReactCompositeComponent.createClass({
module.exports = ReactDOMSelect;
-},{"./AutoFocusMixin":43,"./LinkedValueUtils":67,"./Object.assign":70,"./ReactBrowserComponentMixin":73,"./ReactCompositeComponent":81,"./ReactDOM":84,"./ReactElement":99,"./ReactUpdates":132}],93:[function(require,module,exports){
+},{"./AutoFocusMixin":42,"./LinkedValueUtils":66,"./Object.assign":69,"./ReactBrowserComponentMixin":72,"./ReactClass":78,"./ReactElement":103,"./ReactUpdates":140}],96:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -11868,7 +12432,7 @@ module.exports = ReactDOMSelect;
* @providesModule ReactDOMSelection
*/
-"use strict";
+'use strict';
var ExecutionEnvironment = require("./ExecutionEnvironment");
@@ -12048,7 +12612,11 @@ function setModernOffsets(node, offsets) {
}
}
-var useIEOffsets = ExecutionEnvironment.canUseDOM && document.selection;
+var useIEOffsets = (
+ ExecutionEnvironment.canUseDOM &&
+ 'selection' in document &&
+ !('getSelection' in window)
+);
var ReactDOMSelection = {
/**
@@ -12065,10 +12633,127 @@ var ReactDOMSelection = {
module.exports = ReactDOMSelection;
-},{"./ExecutionEnvironment":64,"./getNodeForCharacterOffset":175,"./getTextContentAccessor":177}],94:[function(require,module,exports){
+},{"./ExecutionEnvironment":62,"./getNodeForCharacterOffset":184,"./getTextContentAccessor":186}],97:[function(require,module,exports){
+/**
+ * Copyright 2013-2015, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ * @providesModule ReactDOMTextComponent
+ * @typechecks static-only
+ */
+
+'use strict';
+
+var DOMPropertyOperations = require("./DOMPropertyOperations");
+var ReactComponentBrowserEnvironment =
+ require("./ReactComponentBrowserEnvironment");
+var ReactDOMComponent = require("./ReactDOMComponent");
+
+var assign = require("./Object.assign");
+var escapeTextContentForBrowser = require("./escapeTextContentForBrowser");
+
+/**
+ * Text nodes violate a couple assumptions that React makes about components:
+ *
+ * - When mounting text into the DOM, adjacent text nodes are merged.
+ * - Text nodes cannot be assigned a React root ID.
+ *
+ * This component is used to wrap strings in elements so that they can undergo
+ * the same reconciliation that is applied to elements.
+ *
+ * TODO: Investigate representing React components in the DOM with text nodes.
+ *
+ * @class ReactDOMTextComponent
+ * @extends ReactComponent
+ * @internal
+ */
+var ReactDOMTextComponent = function(props) {
+ // This constructor and its argument is currently used by mocks.
+};
+
+assign(ReactDOMTextComponent.prototype, {
+
+ /**
+ * @param {ReactText} text
+ * @internal
+ */
+ construct: function(text) {
+ // TODO: This is really a ReactText (ReactNode), not a ReactElement
+ this._currentElement = text;
+ this._stringText = '' + text;
+
+ // Properties
+ this._rootNodeID = null;
+ this._mountIndex = 0;
+ },
+
+ /**
+ * Creates the markup for this text node. This node is not intended to have
+ * any features besides containing text content.
+ *
+ * @param {string} rootID DOM ID of the root node.
+ * @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction
+ * @return {string} Markup for this text node.
+ * @internal
+ */
+ mountComponent: function(rootID, transaction, context) {
+ this._rootNodeID = rootID;
+ var escapedText = escapeTextContentForBrowser(this._stringText);
+
+ if (transaction.renderToStaticMarkup) {
+ // Normally we'd wrap this in a `span` for the reasons stated above, but
+ // since this is a situation where React won't take over (static pages),
+ // we can simply return the text as it is.
+ return escapedText;
+ }
+
+ return (
+ '<span ' + DOMPropertyOperations.createMarkupForID(rootID) + '>' +
+ escapedText +
+ '</span>'
+ );
+ },
+
+ /**
+ * Updates this component by updating the text content.
+ *
+ * @param {ReactText} nextText The next text content
+ * @param {ReactReconcileTransaction} transaction
+ * @internal
+ */
+ receiveComponent: function(nextText, transaction) {
+ if (nextText !== this._currentElement) {
+ this._currentElement = nextText;
+ var nextStringText = '' + nextText;
+ if (nextStringText !== this._stringText) {
+ // TODO: Save this as pending props and use performUpdateIfNecessary
+ // and/or updateComponent to do the actual update for consistency with
+ // other component types?
+ this._stringText = nextStringText;
+ ReactDOMComponent.BackendIDOperations.updateTextContentByID(
+ this._rootNodeID,
+ nextStringText
+ );
+ }
+ }
+ },
+
+ unmountComponent: function() {
+ ReactComponentBrowserEnvironment.unmountIDFromEnvironment(this._rootNodeID);
+ }
+
+});
+
+module.exports = ReactDOMTextComponent;
+
+},{"./DOMPropertyOperations":52,"./Object.assign":69,"./ReactComponentBrowserEnvironment":80,"./ReactDOMComponent":88,"./escapeTextContentForBrowser":172}],98:[function(require,module,exports){
(function (process){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -12078,15 +12763,14 @@ module.exports = ReactDOMSelection;
* @providesModule ReactDOMTextarea
*/
-"use strict";
+'use strict';
var AutoFocusMixin = require("./AutoFocusMixin");
var DOMPropertyOperations = require("./DOMPropertyOperations");
var LinkedValueUtils = require("./LinkedValueUtils");
var ReactBrowserComponentMixin = require("./ReactBrowserComponentMixin");
-var ReactCompositeComponent = require("./ReactCompositeComponent");
+var ReactClass = require("./ReactClass");
var ReactElement = require("./ReactElement");
-var ReactDOM = require("./ReactDOM");
var ReactUpdates = require("./ReactUpdates");
var assign = require("./Object.assign");
@@ -12094,8 +12778,7 @@ var invariant = require("./invariant");
var warning = require("./warning");
-// Store a reference to the <textarea> `ReactDOMComponent`. TODO: use string
-var textarea = ReactElement.createFactory(ReactDOM.textarea.type);
+var textarea = ReactElement.createFactory('textarea');
function forceUpdateIfMounted() {
/*jshint validthis:true */
@@ -12119,8 +12802,9 @@ function forceUpdateIfMounted() {
* The rendered element will be initialized with an empty value, the prop
* `defaultValue` if specified, or the children content (deprecated).
*/
-var ReactDOMTextarea = ReactCompositeComponent.createClass({
+var ReactDOMTextarea = ReactClass.createClass({
displayName: 'ReactDOMTextarea',
+ tagName: 'TEXTAREA',
mixins: [AutoFocusMixin, LinkedValueUtils.Mixin, ReactBrowserComponentMixin],
@@ -12206,9 +12890,9 @@ var ReactDOMTextarea = ReactCompositeComponent.createClass({
module.exports = ReactDOMTextarea;
}).call(this,require('_process'))
-},{"./AutoFocusMixin":43,"./DOMPropertyOperations":54,"./LinkedValueUtils":67,"./Object.assign":70,"./ReactBrowserComponentMixin":73,"./ReactCompositeComponent":81,"./ReactDOM":84,"./ReactElement":99,"./ReactUpdates":132,"./invariant":182,"./warning":202,"_process":1}],95:[function(require,module,exports){
+},{"./AutoFocusMixin":42,"./DOMPropertyOperations":52,"./LinkedValueUtils":66,"./Object.assign":69,"./ReactBrowserComponentMixin":72,"./ReactClass":78,"./ReactElement":103,"./ReactUpdates":140,"./invariant":191,"./warning":212,"_process":1}],99:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -12218,7 +12902,7 @@ module.exports = ReactDOMTextarea;
* @providesModule ReactDefaultBatchingStrategy
*/
-"use strict";
+'use strict';
var ReactUpdates = require("./ReactUpdates");
var Transaction = require("./Transaction");
@@ -12263,26 +12947,26 @@ var ReactDefaultBatchingStrategy = {
* Call the provided function in a context within which calls to `setState`
* and friends are batched such that components aren't updated unnecessarily.
*/
- batchedUpdates: function(callback, a, b) {
+ batchedUpdates: function(callback, a, b, c, d) {
var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
ReactDefaultBatchingStrategy.isBatchingUpdates = true;
// The code is written this way to avoid extra allocations
if (alreadyBatchingUpdates) {
- callback(a, b);
+ callback(a, b, c, d);
} else {
- transaction.perform(callback, null, a, b);
+ transaction.perform(callback, null, a, b, c, d);
}
}
};
module.exports = ReactDefaultBatchingStrategy;
-},{"./Object.assign":70,"./ReactUpdates":132,"./Transaction":149,"./emptyFunction":163}],96:[function(require,module,exports){
+},{"./Object.assign":69,"./ReactUpdates":140,"./Transaction":157,"./emptyFunction":170}],100:[function(require,module,exports){
(function (process){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -12292,18 +12976,18 @@ module.exports = ReactDefaultBatchingStrategy;
* @providesModule ReactDefaultInjection
*/
-"use strict";
+'use strict';
var BeforeInputEventPlugin = require("./BeforeInputEventPlugin");
var ChangeEventPlugin = require("./ChangeEventPlugin");
var ClientReactRootIndex = require("./ClientReactRootIndex");
-var CompositionEventPlugin = require("./CompositionEventPlugin");
var DefaultEventPluginOrder = require("./DefaultEventPluginOrder");
var EnterLeaveEventPlugin = require("./EnterLeaveEventPlugin");
var ExecutionEnvironment = require("./ExecutionEnvironment");
var HTMLDOMPropertyConfig = require("./HTMLDOMPropertyConfig");
var MobileSafariClickEventPlugin = require("./MobileSafariClickEventPlugin");
var ReactBrowserComponentMixin = require("./ReactBrowserComponentMixin");
+var ReactClass = require("./ReactClass");
var ReactComponentBrowserEnvironment =
require("./ReactComponentBrowserEnvironment");
var ReactDefaultBatchingStrategy = require("./ReactDefaultBatchingStrategy");
@@ -12311,14 +12995,19 @@ var ReactDOMComponent = require("./ReactDOMComponent");
var ReactDOMButton = require("./ReactDOMButton");
var ReactDOMForm = require("./ReactDOMForm");
var ReactDOMImg = require("./ReactDOMImg");
+var ReactDOMIDOperations = require("./ReactDOMIDOperations");
+var ReactDOMIframe = require("./ReactDOMIframe");
var ReactDOMInput = require("./ReactDOMInput");
var ReactDOMOption = require("./ReactDOMOption");
var ReactDOMSelect = require("./ReactDOMSelect");
var ReactDOMTextarea = require("./ReactDOMTextarea");
+var ReactDOMTextComponent = require("./ReactDOMTextComponent");
+var ReactElement = require("./ReactElement");
var ReactEventListener = require("./ReactEventListener");
var ReactInjection = require("./ReactInjection");
var ReactInstanceHandles = require("./ReactInstanceHandles");
var ReactMount = require("./ReactMount");
+var ReactReconcileTransaction = require("./ReactReconcileTransaction");
var SelectEventPlugin = require("./SelectEventPlugin");
var ServerReactRootIndex = require("./ServerReactRootIndex");
var SimpleEventPlugin = require("./SimpleEventPlugin");
@@ -12326,6 +13015,22 @@ var SVGDOMPropertyConfig = require("./SVGDOMPropertyConfig");
var createFullPageComponent = require("./createFullPageComponent");
+function autoGenerateWrapperClass(type) {
+ return ReactClass.createClass({
+ tagName: type.toUpperCase(),
+ render: function() {
+ return new ReactElement(
+ type,
+ null,
+ null,
+ null,
+ null,
+ this.props
+ );
+ }
+ });
+}
+
function inject() {
ReactInjection.EventEmitter.injectReactEventListener(
ReactEventListener
@@ -12346,7 +13051,6 @@ function inject() {
SimpleEventPlugin: SimpleEventPlugin,
EnterLeaveEventPlugin: EnterLeaveEventPlugin,
ChangeEventPlugin: ChangeEventPlugin,
- CompositionEventPlugin: CompositionEventPlugin,
MobileSafariClickEventPlugin: MobileSafariClickEventPlugin,
SelectEventPlugin: SelectEventPlugin,
BeforeInputEventPlugin: BeforeInputEventPlugin
@@ -12356,9 +13060,22 @@ function inject() {
ReactDOMComponent
);
+ ReactInjection.NativeComponent.injectTextComponentClass(
+ ReactDOMTextComponent
+ );
+
+ ReactInjection.NativeComponent.injectAutoWrapper(
+ autoGenerateWrapperClass
+ );
+
+ // This needs to happen before createFullPageComponent() otherwise the mixin
+ // won't be included.
+ ReactInjection.Class.injectMixin(ReactBrowserComponentMixin);
+
ReactInjection.NativeComponent.injectComponentClasses({
'button': ReactDOMButton,
'form': ReactDOMForm,
+ 'iframe': ReactDOMIframe,
'img': ReactDOMImg,
'input': ReactDOMInput,
'option': ReactDOMOption,
@@ -12370,17 +13087,13 @@ function inject() {
'body': createFullPageComponent('body')
});
- // This needs to happen after createFullPageComponent() otherwise the mixin
- // gets double injected.
- ReactInjection.CompositeComponent.injectMixin(ReactBrowserComponentMixin);
-
ReactInjection.DOMProperty.injectDOMPropertyConfig(HTMLDOMPropertyConfig);
ReactInjection.DOMProperty.injectDOMPropertyConfig(SVGDOMPropertyConfig);
ReactInjection.EmptyComponent.injectEmptyComponent('noscript');
ReactInjection.Updates.injectReconcileTransaction(
- ReactComponentBrowserEnvironment.ReactReconcileTransaction
+ ReactReconcileTransaction
);
ReactInjection.Updates.injectBatchingStrategy(
ReactDefaultBatchingStrategy
@@ -12393,6 +13106,7 @@ function inject() {
);
ReactInjection.Component.injectEnvironment(ReactComponentBrowserEnvironment);
+ ReactInjection.DOMComponent.injectIDOperations(ReactDOMIDOperations);
if ("production" !== process.env.NODE_ENV) {
var url = (ExecutionEnvironment.canUseDOM && window.location.href) || '';
@@ -12408,9 +13122,9 @@ module.exports = {
};
}).call(this,require('_process'))
-},{"./BeforeInputEventPlugin":44,"./ChangeEventPlugin":49,"./ClientReactRootIndex":50,"./CompositionEventPlugin":51,"./DefaultEventPluginOrder":56,"./EnterLeaveEventPlugin":57,"./ExecutionEnvironment":64,"./HTMLDOMPropertyConfig":65,"./MobileSafariClickEventPlugin":69,"./ReactBrowserComponentMixin":73,"./ReactComponentBrowserEnvironment":79,"./ReactDOMButton":85,"./ReactDOMComponent":86,"./ReactDOMForm":87,"./ReactDOMImg":89,"./ReactDOMInput":90,"./ReactDOMOption":91,"./ReactDOMSelect":92,"./ReactDOMTextarea":94,"./ReactDefaultBatchingStrategy":95,"./ReactDefaultPerf":97,"./ReactEventListener":104,"./ReactInjection":105,"./ReactInstanceHandles":107,"./ReactMount":111,"./SVGDOMPropertyConfig":134,"./SelectEventPlugin":135,"./ServerReactRootIndex":136,"./SimpleEventPlugin":137,"./createFullPageComponent":158,"_process":1}],97:[function(require,module,exports){
+},{"./BeforeInputEventPlugin":43,"./ChangeEventPlugin":48,"./ClientReactRootIndex":49,"./DefaultEventPluginOrder":54,"./EnterLeaveEventPlugin":55,"./ExecutionEnvironment":62,"./HTMLDOMPropertyConfig":64,"./MobileSafariClickEventPlugin":68,"./ReactBrowserComponentMixin":72,"./ReactClass":78,"./ReactComponentBrowserEnvironment":80,"./ReactDOMButton":87,"./ReactDOMComponent":88,"./ReactDOMForm":89,"./ReactDOMIDOperations":90,"./ReactDOMIframe":91,"./ReactDOMImg":92,"./ReactDOMInput":93,"./ReactDOMOption":94,"./ReactDOMSelect":95,"./ReactDOMTextComponent":97,"./ReactDOMTextarea":98,"./ReactDefaultBatchingStrategy":99,"./ReactDefaultPerf":101,"./ReactElement":103,"./ReactEventListener":108,"./ReactInjection":110,"./ReactInstanceHandles":112,"./ReactMount":117,"./ReactReconcileTransaction":128,"./SVGDOMPropertyConfig":142,"./SelectEventPlugin":143,"./ServerReactRootIndex":144,"./SimpleEventPlugin":145,"./createFullPageComponent":166,"_process":1}],101:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -12421,7 +13135,7 @@ module.exports = {
* @typechecks static-only
*/
-"use strict";
+'use strict';
var DOMProperty = require("./DOMProperty");
var ReactDefaultPerfAnalysis = require("./ReactDefaultPerfAnalysis");
@@ -12548,7 +13262,7 @@ var ReactDefaultPerf = {
},
measure: function(moduleName, fnName, func) {
- return function() {var args=Array.prototype.slice.call(arguments,0);
+ return function() {for (var args=[],$__0=0,$__1=arguments.length;$__0<$__1;$__0++) args.push(arguments[$__0]);
var totalTime;
var rv;
var start;
@@ -12574,13 +13288,13 @@ var ReactDefaultPerf = {
ReactDefaultPerf._allMeasurements.length - 1
].totalTime = performanceNow() - start;
return rv;
- } else if (moduleName === 'ReactDOMIDOperations' ||
- moduleName === 'ReactComponentBrowserEnvironment') {
+ } else if (fnName === '_mountImageIntoNode' ||
+ moduleName === 'ReactDOMIDOperations') {
start = performanceNow();
rv = func.apply(this, args);
totalTime = performanceNow() - start;
- if (fnName === 'mountImageIntoNode') {
+ if (fnName === '_mountImageIntoNode') {
var mountID = ReactMount.getID(args[1]);
ReactDefaultPerf._recordWrite(mountID, fnName, totalTime, args[0]);
} else if (fnName === 'dangerouslyProcessChildrenUpdates') {
@@ -12617,9 +13331,13 @@ var ReactDefaultPerf = {
}
return rv;
} else if (moduleName === 'ReactCompositeComponent' && (
- fnName === 'mountComponent' ||
- fnName === 'updateComponent' || // TODO: receiveComponent()?
- fnName === '_renderValidatedComponent')) {
+ (// TODO: receiveComponent()?
+ (fnName === 'mountComponent' ||
+ fnName === 'updateComponent' || fnName === '_renderValidatedComponent')))) {
+
+ if (typeof this._currentElement.type === 'string') {
+ return func.apply(this, args);
+ }
var rootNodeID = fnName === 'mountComponent' ?
args[0] :
@@ -12654,8 +13372,10 @@ var ReactDefaultPerf = {
}
entry.displayNames[rootNodeID] = {
- current: this.constructor.displayName,
- owner: this._owner ? this._owner.constructor.displayName : '<root>'
+ current: this.getName(),
+ owner: this._currentElement._owner ?
+ this._currentElement._owner.getName() :
+ '<root>'
};
return rv;
@@ -12668,9 +13388,9 @@ var ReactDefaultPerf = {
module.exports = ReactDefaultPerf;
-},{"./DOMProperty":53,"./ReactDefaultPerfAnalysis":98,"./ReactMount":111,"./ReactPerf":116,"./performanceNow":195}],98:[function(require,module,exports){
+},{"./DOMProperty":51,"./ReactDefaultPerfAnalysis":102,"./ReactMount":117,"./ReactPerf":122,"./performanceNow":203}],102:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -12685,7 +13405,7 @@ var assign = require("./Object.assign");
// Don't try to save users less than 1.2ms (a number I made up)
var DONT_CARE_THRESHOLD = 1.2;
var DOM_OPERATION_TYPES = {
- 'mountImageIntoNode': 'set innerHTML',
+ '_mountImageIntoNode': 'set innerHTML',
INSERT_MARKUP: 'set innerHTML',
MOVE_EXISTING: 'move',
REMOVE_NODE: 'remove',
@@ -12874,10 +13594,10 @@ var ReactDefaultPerfAnalysis = {
module.exports = ReactDefaultPerfAnalysis;
-},{"./Object.assign":70}],99:[function(require,module,exports){
+},{"./Object.assign":69}],103:[function(require,module,exports){
(function (process){
/**
- * Copyright 2014, Facebook, Inc.
+ * Copyright 2014-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -12887,11 +13607,12 @@ module.exports = ReactDefaultPerfAnalysis;
* @providesModule ReactElement
*/
-"use strict";
+'use strict';
var ReactContext = require("./ReactContext");
var ReactCurrentOwner = require("./ReactCurrentOwner");
+var assign = require("./Object.assign");
var warning = require("./warning");
var RESERVED_PROPS = {
@@ -12922,8 +13643,9 @@ function defineWarningProperty(object, key) {
set: function(value) {
("production" !== process.env.NODE_ENV ? warning(
false,
- 'Don\'t set the ' + key + ' property of the component. ' +
- 'Mutate the existing props object instead.'
+ 'Don\'t set the %s property of the React element. Instead, ' +
+ 'specify the correct value when initially creating the element.',
+ key
) : null);
this._store[key] = value;
}
@@ -12984,7 +13706,21 @@ var ReactElement = function(type, key, ref, owner, context, props) {
// an external backing store so that we can freeze the whole object.
// This can be replaced with a WeakMap once they are implemented in
// commonly used development environments.
- this._store = { validated: false, props: props };
+ this._store = {props: props, originalProps: assign({}, props)};
+
+ // To make comparing ReactElements easier for testing purposes, we make
+ // the validation flag non-enumerable (where possible, which should
+ // include every environment we run tests in), so the test framework
+ // ignores it.
+ try {
+ Object.defineProperty(this._store, 'validated', {
+ configurable: false,
+ enumerable: false,
+ writable: true
+ });
+ } catch (x) {
+ }
+ this._store.validated = false;
// We're not allowed to set props directly on the object so we early
// return and rely on the prototype membrane to forward to the backing
@@ -13019,16 +13755,7 @@ ReactElement.createElement = function(type, config, children) {
if (config != null) {
ref = config.ref === undefined ? null : config.ref;
- if ("production" !== process.env.NODE_ENV) {
- ("production" !== process.env.NODE_ENV ? warning(
- config.key !== null,
- 'createElement(...): Encountered component with a `key` of null. In ' +
- 'a future version, this will be treated as equivalent to the string ' +
- '\'null\'; instead, provide an explicit key or use undefined.'
- ) : null);
- }
- // TODO: Change this back to `config.key === undefined`
- key = config.key == null ? null : '' + config.key;
+ key = config.key === undefined ? null : '' + config.key;
// Remaining properties are added to a new props object
for (propName in config) {
if (config.hasOwnProperty(propName) &&
@@ -13052,7 +13779,7 @@ ReactElement.createElement = function(type, config, children) {
}
// Resolve default props
- if (type.defaultProps) {
+ if (type && type.defaultProps) {
var defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (typeof props[propName] === 'undefined') {
@@ -13077,6 +13804,7 @@ ReactElement.createFactory = function(type) {
// easily accessed on elements. E.g. <Foo />.type === Foo.type.
// This should not be named `constructor` since this may not be the function
// that created the element, and it may not even be a constructor.
+ // Legacy hook TODO: Warn if this is accessed
factory.type = type;
return factory;
};
@@ -13098,6 +13826,60 @@ ReactElement.cloneAndReplaceProps = function(oldElement, newProps) {
return newElement;
};
+ReactElement.cloneElement = function(element, config, children) {
+ var propName;
+
+ // Original props are copied
+ var props = assign({}, element.props);
+
+ // Reserved names are extracted
+ var key = element.key;
+ var ref = element.ref;
+
+ // Owner will be preserved, unless ref is overridden
+ var owner = element._owner;
+
+ if (config != null) {
+ if (config.ref !== undefined) {
+ // Silently steal the ref from the parent.
+ ref = config.ref;
+ owner = ReactCurrentOwner.current;
+ }
+ if (config.key !== undefined) {
+ key = '' + config.key;
+ }
+ // Remaining properties override existing props
+ for (propName in config) {
+ if (config.hasOwnProperty(propName) &&
+ !RESERVED_PROPS.hasOwnProperty(propName)) {
+ props[propName] = config[propName];
+ }
+ }
+ }
+
+ // Children can be more than one argument, and those are transferred onto
+ // the newly allocated props object.
+ var childrenLength = arguments.length - 2;
+ if (childrenLength === 1) {
+ props.children = children;
+ } else if (childrenLength > 1) {
+ var childArray = Array(childrenLength);
+ for (var i = 0; i < childrenLength; i++) {
+ childArray[i] = arguments[i + 2];
+ }
+ props.children = childArray;
+ }
+
+ return new ReactElement(
+ element.type,
+ key,
+ ref,
+ owner,
+ element._context,
+ props
+ );
+};
+
/**
* @param {?object} object
* @return {boolean} True if `object` is a valid component.
@@ -13120,9 +13902,10 @@ ReactElement.isValidElement = function(object) {
module.exports = ReactElement;
}).call(this,require('_process'))
-},{"./ReactContext":82,"./ReactCurrentOwner":83,"./warning":202,"_process":1}],100:[function(require,module,exports){
+},{"./Object.assign":69,"./ReactContext":84,"./ReactCurrentOwner":85,"./warning":212,"_process":1}],104:[function(require,module,exports){
+(function (process){
/**
- * Copyright 2014, Facebook, Inc.
+ * Copyright 2014-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -13139,30 +13922,59 @@ module.exports = ReactElement;
* that support it.
*/
-"use strict";
+'use strict';
var ReactElement = require("./ReactElement");
+var ReactFragment = require("./ReactFragment");
var ReactPropTypeLocations = require("./ReactPropTypeLocations");
+var ReactPropTypeLocationNames = require("./ReactPropTypeLocationNames");
var ReactCurrentOwner = require("./ReactCurrentOwner");
+var ReactNativeComponent = require("./ReactNativeComponent");
-var monitorCodeUse = require("./monitorCodeUse");
+var getIteratorFn = require("./getIteratorFn");
+var invariant = require("./invariant");
+var warning = require("./warning");
+
+function getDeclarationErrorAddendum() {
+ if (ReactCurrentOwner.current) {
+ var name = ReactCurrentOwner.current.getName();
+ if (name) {
+ return ' Check the render method of `' + name + '`.';
+ }
+ }
+ return '';
+}
/**
* Warn if there's no key explicitly set on dynamic arrays of children or
* object keys are not valid. This allows us to keep track of children between
* updates.
*/
-var ownerHasKeyUseWarning = {
- 'react_key_warning': {},
- 'react_numeric_key_warning': {}
-};
-var ownerHasMonitoredObjectMap = {};
+var ownerHasKeyUseWarning = {};
var loggedTypeFailures = {};
var NUMERIC_PROPERTY_REGEX = /^\d+$/;
/**
+ * Gets the instance's name for use in warnings.
+ *
+ * @internal
+ * @return {?string} Display name or undefined
+ */
+function getName(instance) {
+ var publicInstance = instance && instance.getPublicInstance();
+ if (!publicInstance) {
+ return undefined;
+ }
+ var constructor = publicInstance.constructor;
+ if (!constructor) {
+ return undefined;
+ }
+ return constructor.displayName || constructor.name || undefined;
+}
+
+/**
* Gets the current owner's displayName for use in warnings.
*
* @internal
@@ -13170,29 +13982,30 @@ var NUMERIC_PROPERTY_REGEX = /^\d+$/;
*/
function getCurrentOwnerDisplayName() {
var current = ReactCurrentOwner.current;
- return current && current.constructor.displayName || undefined;
+ return (
+ current && getName(current) || undefined
+ );
}
/**
- * Warn if the component doesn't have an explicit key assigned to it.
- * This component is in an array. The array could grow and shrink or be
+ * Warn if the element doesn't have an explicit key assigned to it.
+ * This element is in an array. The array could grow and shrink or be
* reordered. All children that haven't already been validated are required to
* have a "key" property assigned to it.
*
* @internal
- * @param {ReactComponent} component Component that requires a key.
- * @param {*} parentType component's parent's type.
+ * @param {ReactElement} element Element that requires a key.
+ * @param {*} parentType element's parent's type.
*/
-function validateExplicitKey(component, parentType) {
- if (component._store.validated || component.key != null) {
+function validateExplicitKey(element, parentType) {
+ if (element._store.validated || element.key != null) {
return;
}
- component._store.validated = true;
+ element._store.validated = true;
warnAndMonitorForKeyUse(
- 'react_key_warning',
- 'Each child in an array should have a unique "key" prop.',
- component,
+ 'Each child in an array or iterator should have a unique "key" prop.',
+ element,
parentType
);
}
@@ -13203,17 +14016,16 @@ function validateExplicitKey(component, parentType) {
*
* @internal
* @param {string} name Property name of the key.
- * @param {ReactComponent} component Component that requires a key.
- * @param {*} parentType component's parent's type.
+ * @param {ReactElement} element Component that requires a key.
+ * @param {*} parentType element's parent's type.
*/
-function validatePropertyKey(name, component, parentType) {
+function validatePropertyKey(name, element, parentType) {
if (!NUMERIC_PROPERTY_REGEX.test(name)) {
return;
}
warnAndMonitorForKeyUse(
- 'react_numeric_key_warning',
'Child objects should have non-numeric keys so ordering is preserved.',
- component,
+ element,
parentType
);
}
@@ -13222,85 +14034,90 @@ function validatePropertyKey(name, component, parentType) {
* Shared warning and monitoring code for the key warnings.
*
* @internal
- * @param {string} warningID The id used when logging.
* @param {string} message The base warning that gets output.
- * @param {ReactComponent} component Component that requires a key.
- * @param {*} parentType component's parent's type.
+ * @param {ReactElement} element Component that requires a key.
+ * @param {*} parentType element's parent's type.
*/
-function warnAndMonitorForKeyUse(warningID, message, component, parentType) {
+function warnAndMonitorForKeyUse(message, element, parentType) {
var ownerName = getCurrentOwnerDisplayName();
- var parentName = parentType.displayName;
+ var parentName = typeof parentType === 'string' ?
+ parentType : parentType.displayName || parentType.name;
var useName = ownerName || parentName;
- var memoizer = ownerHasKeyUseWarning[warningID];
+ var memoizer = ownerHasKeyUseWarning[message] || (
+ (ownerHasKeyUseWarning[message] = {})
+ );
if (memoizer.hasOwnProperty(useName)) {
return;
}
memoizer[useName] = true;
- message += ownerName ?
- (" Check the render method of " + ownerName + ".") :
- (" Check the renderComponent call using <" + parentName + ">.");
+ var parentOrOwnerAddendum =
+ ownerName ? (" Check the render method of " + ownerName + ".") :
+ parentName ? (" Check the React.render call using <" + parentName + ">.") :
+ '';
// Usually the current owner is the offender, but if it accepts children as a
// property, it may be the creator of the child that's responsible for
// assigning it a key.
- var childOwnerName = null;
- if (component._owner && component._owner !== ReactCurrentOwner.current) {
+ var childOwnerAddendum = '';
+ if (element &&
+ element._owner &&
+ element._owner !== ReactCurrentOwner.current) {
// Name of the component that originally created this child.
- childOwnerName = component._owner.constructor.displayName;
+ var childOwnerName = getName(element._owner);
- message += (" It was passed a child from " + childOwnerName + ".");
+ childOwnerAddendum = (" It was passed a child from " + childOwnerName + ".");
}
- message += ' See http://fb.me/react-warning-keys for more information.';
- monitorCodeUse(warningID, {
- component: useName,
- componentOwner: childOwnerName
- });
- console.warn(message);
-}
-
-/**
- * Log that we're using an object map. We're considering deprecating this
- * feature and replace it with proper Map and ImmutableMap data structures.
- *
- * @internal
- */
-function monitorUseOfObjectMap() {
- var currentName = getCurrentOwnerDisplayName() || '';
- if (ownerHasMonitoredObjectMap.hasOwnProperty(currentName)) {
- return;
- }
- ownerHasMonitoredObjectMap[currentName] = true;
- monitorCodeUse('react_object_map_children');
+ ("production" !== process.env.NODE_ENV ? warning(
+ false,
+ message + '%s%s See http://fb.me/react-warning-keys for more information.',
+ parentOrOwnerAddendum,
+ childOwnerAddendum
+ ) : null);
}
/**
- * Ensure that every component either is passed in a static location, in an
+ * Ensure that every element either is passed in a static location, in an
* array with an explicit keys property defined, or in an object literal
* with valid key property.
*
* @internal
- * @param {*} component Statically passed child of any type.
- * @param {*} parentType component's parent's type.
- * @return {boolean}
+ * @param {ReactNode} node Statically passed child of any type.
+ * @param {*} parentType node's parent's type.
*/
-function validateChildKeys(component, parentType) {
- if (Array.isArray(component)) {
- for (var i = 0; i < component.length; i++) {
- var child = component[i];
+function validateChildKeys(node, parentType) {
+ if (Array.isArray(node)) {
+ for (var i = 0; i < node.length; i++) {
+ var child = node[i];
if (ReactElement.isValidElement(child)) {
validateExplicitKey(child, parentType);
}
}
- } else if (ReactElement.isValidElement(component)) {
- // This component was passed in a valid location.
- component._store.validated = true;
- } else if (component && typeof component === 'object') {
- monitorUseOfObjectMap();
- for (var name in component) {
- validatePropertyKey(name, component[name], parentType);
+ } else if (ReactElement.isValidElement(node)) {
+ // This element was passed in a valid location.
+ node._store.validated = true;
+ } else if (node) {
+ var iteratorFn = getIteratorFn(node);
+ // Entry iterators provide implicit keys.
+ if (iteratorFn) {
+ if (iteratorFn !== node.entries) {
+ var iterator = iteratorFn.call(node);
+ var step;
+ while (!(step = iterator.next()).done) {
+ if (ReactElement.isValidElement(step.value)) {
+ validateExplicitKey(step.value, parentType);
+ }
+ }
+ }
+ } else if (typeof node === 'object') {
+ var fragment = ReactFragment.extractIfFragment(node);
+ for (var key in fragment) {
+ if (fragment.hasOwnProperty(key)) {
+ validatePropertyKey(key, fragment[key], parentType);
+ }
+ }
}
}
}
@@ -13322,6 +14139,16 @@ function checkPropTypes(componentName, propTypes, props, location) {
// fail the render phase where it didn't fail before. So we log it.
// After these have been cleaned up, we'll let them throw.
try {
+ // This is intentionally an invariant that gets caught. It's the same
+ // behavior as without this statement except with a better message.
+ ("production" !== process.env.NODE_ENV ? invariant(
+ typeof propTypes[propName] === 'function',
+ '%s: %s type `%s` is invalid; it must be a function, usually from ' +
+ 'React.PropTypes.',
+ componentName || 'React class',
+ ReactPropTypeLocationNames[location],
+ propName
+ ) : invariant(typeof propTypes[propName] === 'function'));
error = propTypes[propName](props, propName, componentName, location);
} catch (ex) {
error = ex;
@@ -13330,19 +14157,148 @@ function checkPropTypes(componentName, propTypes, props, location) {
// Only monitor this failure once because there tends to be a lot of the
// same error.
loggedTypeFailures[error.message] = true;
- // This will soon use the warning module
- monitorCodeUse(
- 'react_failed_descriptor_type_check',
- { message: error.message }
- );
+
+ var addendum = getDeclarationErrorAddendum(this);
+ ("production" !== process.env.NODE_ENV ? warning(false, 'Failed propType: %s%s', error.message, addendum) : null);
}
}
}
}
+var warnedPropsMutations = {};
+
+/**
+ * Warn about mutating props when setting `propName` on `element`.
+ *
+ * @param {string} propName The string key within props that was set
+ * @param {ReactElement} element
+ */
+function warnForPropsMutation(propName, element) {
+ var type = element.type;
+ var elementName = typeof type === 'string' ? type : type.displayName;
+ var ownerName = element._owner ?
+ element._owner.getPublicInstance().constructor.displayName : null;
+
+ var warningKey = propName + '|' + elementName + '|' + ownerName;
+ if (warnedPropsMutations.hasOwnProperty(warningKey)) {
+ return;
+ }
+ warnedPropsMutations[warningKey] = true;
+
+ var elementInfo = '';
+ if (elementName) {
+ elementInfo = ' <' + elementName + ' />';
+ }
+ var ownerInfo = '';
+ if (ownerName) {
+ ownerInfo = ' The element was created by ' + ownerName + '.';
+ }
+
+ ("production" !== process.env.NODE_ENV ? warning(
+ false,
+ 'Don\'t set .props.%s of the React component%s. ' +
+ 'Instead, specify the correct value when ' +
+ 'initially creating the element.%s',
+ propName,
+ elementInfo,
+ ownerInfo
+ ) : null);
+}
+
+// Inline Object.is polyfill
+function is(a, b) {
+ if (a !== a) {
+ // NaN
+ return b !== b;
+ }
+ if (a === 0 && b === 0) {
+ // +-0
+ return 1 / a === 1 / b;
+ }
+ return a === b;
+}
+
+/**
+ * Given an element, check if its props have been mutated since element
+ * creation (or the last call to this function). In particular, check if any
+ * new props have been added, which we can't directly catch by defining warning
+ * properties on the props object.
+ *
+ * @param {ReactElement} element
+ */
+function checkAndWarnForMutatedProps(element) {
+ if (!element._store) {
+ // Element was created using `new ReactElement` directly or with
+ // `ReactElement.createElement`; skip mutation checking
+ return;
+ }
+
+ var originalProps = element._store.originalProps;
+ var props = element.props;
+
+ for (var propName in props) {
+ if (props.hasOwnProperty(propName)) {
+ if (!originalProps.hasOwnProperty(propName) ||
+ !is(originalProps[propName], props[propName])) {
+ warnForPropsMutation(propName, element);
+
+ // Copy over the new value so that the two props objects match again
+ originalProps[propName] = props[propName];
+ }
+ }
+ }
+}
+
+/**
+ * Given an element, validate that its props follow the propTypes definition,
+ * provided by the type.
+ *
+ * @param {ReactElement} element
+ */
+function validatePropTypes(element) {
+ if (element.type == null) {
+ // This has already warned. Don't throw.
+ return;
+ }
+ // Extract the component class from the element. Converts string types
+ // to a composite class which may have propTypes.
+ // TODO: Validating a string's propTypes is not decoupled from the
+ // rendering target which is problematic.
+ var componentClass = ReactNativeComponent.getComponentClassForElement(
+ element
+ );
+ var name = componentClass.displayName || componentClass.name;
+ if (componentClass.propTypes) {
+ checkPropTypes(
+ name,
+ componentClass.propTypes,
+ element.props,
+ ReactPropTypeLocations.prop
+ );
+ }
+ if (typeof componentClass.getDefaultProps === 'function') {
+ ("production" !== process.env.NODE_ENV ? warning(
+ componentClass.getDefaultProps.isReactClassApproved,
+ 'getDefaultProps is only used on classic React.createClass ' +
+ 'definitions. Use a static property named `defaultProps` instead.'
+ ) : null);
+ }
+}
+
var ReactElementValidator = {
+ checkAndWarnForMutatedProps: checkAndWarnForMutatedProps,
+
createElement: function(type, props, children) {
+ // We warn in this case but don't throw. We expect the element creation to
+ // succeed and there will likely be errors in render.
+ ("production" !== process.env.NODE_ENV ? warning(
+ type != null,
+ 'React.createElement: type should not be null or undefined. It should ' +
+ 'be a string (for DOM elements) or a ReactClass (for composite ' +
+ 'components).'
+ ) : null);
+
var element = ReactElement.createElement.apply(this, arguments);
// The result can be nullish if a mock or a custom function is used.
@@ -13355,23 +14311,8 @@ var ReactElementValidator = {
validateChildKeys(arguments[i], type);
}
- var name = type.displayName;
- if (type.propTypes) {
- checkPropTypes(
- name,
- type.propTypes,
- element.props,
- ReactPropTypeLocations.prop
- );
- }
- if (type.contextTypes) {
- checkPropTypes(
- name,
- type.contextTypes,
- element._context,
- ReactPropTypeLocations.context
- );
- }
+ validatePropTypes(element);
+
return element;
},
@@ -13380,18 +14321,56 @@ var ReactElementValidator = {
null,
type
);
+ // Legacy hook TODO: Warn if this is accessed
validatedFactory.type = type;
+
+ if ("production" !== process.env.NODE_ENV) {
+ try {
+ Object.defineProperty(
+ validatedFactory,
+ 'type',
+ {
+ enumerable: false,
+ get: function() {
+ ("production" !== process.env.NODE_ENV ? warning(
+ false,
+ 'Factory.type is deprecated. Access the class directly ' +
+ 'before passing it to createFactory.'
+ ) : null);
+ Object.defineProperty(this, 'type', {
+ value: type
+ });
+ return type;
+ }
+ }
+ );
+ } catch (x) {
+ // IE will fail on defineProperty (es5-shim/sham too)
+ }
+ }
+
+
return validatedFactory;
+ },
+
+ cloneElement: function(element, props, children) {
+ var newElement = ReactElement.cloneElement.apply(this, arguments);
+ for (var i = 2; i < arguments.length; i++) {
+ validateChildKeys(arguments[i], newElement.type);
+ }
+ validatePropTypes(newElement);
+ return newElement;
}
};
module.exports = ReactElementValidator;
-},{"./ReactCurrentOwner":83,"./ReactElement":99,"./ReactPropTypeLocations":119,"./monitorCodeUse":192}],101:[function(require,module,exports){
+}).call(this,require('_process'))
+},{"./ReactCurrentOwner":85,"./ReactElement":103,"./ReactFragment":109,"./ReactNativeComponent":120,"./ReactPropTypeLocationNames":124,"./ReactPropTypeLocations":125,"./getIteratorFn":182,"./invariant":191,"./warning":212,"_process":1}],105:[function(require,module,exports){
(function (process){
/**
- * Copyright 2014, Facebook, Inc.
+ * Copyright 2014-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -13401,16 +14380,17 @@ module.exports = ReactElementValidator;
* @providesModule ReactEmptyComponent
*/
-"use strict";
+'use strict';
var ReactElement = require("./ReactElement");
+var ReactInstanceMap = require("./ReactInstanceMap");
var invariant = require("./invariant");
var component;
// This registry keeps track of the React IDs of the components that rendered to
// `null` (in reality a placeholder such as `noscript`)
-var nullComponentIdsRegistry = {};
+var nullComponentIDsRegistry = {};
var ReactEmptyComponentInjection = {
injectEmptyComponent: function(emptyComponent) {
@@ -13418,24 +14398,43 @@ var ReactEmptyComponentInjection = {
}
};
-/**
- * @return {ReactComponent} component The injected empty component.
- */
-function getEmptyComponent() {
+var ReactEmptyComponentType = function() {};
+ReactEmptyComponentType.prototype.componentDidMount = function() {
+ var internalInstance = ReactInstanceMap.get(this);
+ // TODO: Make sure we run these methods in the correct order, we shouldn't
+ // need this check. We're going to assume if we're here it means we ran
+ // componentWillUnmount already so there is no internal instance (it gets
+ // removed as part of the unmounting process).
+ if (!internalInstance) {
+ return;
+ }
+ registerNullComponentID(internalInstance._rootNodeID);
+};
+ReactEmptyComponentType.prototype.componentWillUnmount = function() {
+ var internalInstance = ReactInstanceMap.get(this);
+ // TODO: Get rid of this check. See TODO in componentDidMount.
+ if (!internalInstance) {
+ return;
+ }
+ deregisterNullComponentID(internalInstance._rootNodeID);
+};
+ReactEmptyComponentType.prototype.render = function() {
("production" !== process.env.NODE_ENV ? invariant(
component,
'Trying to return null from a render, but no null placeholder component ' +
'was injected.'
) : invariant(component));
return component();
-}
+};
+
+var emptyElement = ReactElement.createElement(ReactEmptyComponentType);
/**
* Mark the component as having rendered to null.
* @param {string} id Component's `_rootNodeID`.
*/
function registerNullComponentID(id) {
- nullComponentIdsRegistry[id] = true;
+ nullComponentIDsRegistry[id] = true;
}
/**
@@ -13443,7 +14442,7 @@ function registerNullComponentID(id) {
* @param {string} id Component's `_rootNodeID`.
*/
function deregisterNullComponentID(id) {
- delete nullComponentIdsRegistry[id];
+ delete nullComponentIDsRegistry[id];
}
/**
@@ -13451,23 +14450,21 @@ function deregisterNullComponentID(id) {
* @return {boolean} True if the component is rendered to null.
*/
function isNullComponentID(id) {
- return nullComponentIdsRegistry[id];
+ return !!nullComponentIDsRegistry[id];
}
var ReactEmptyComponent = {
- deregisterNullComponentID: deregisterNullComponentID,
- getEmptyComponent: getEmptyComponent,
+ emptyElement: emptyElement,
injection: ReactEmptyComponentInjection,
- isNullComponentID: isNullComponentID,
- registerNullComponentID: registerNullComponentID
+ isNullComponentID: isNullComponentID
};
module.exports = ReactEmptyComponent;
}).call(this,require('_process'))
-},{"./ReactElement":99,"./invariant":182,"_process":1}],102:[function(require,module,exports){
+},{"./ReactElement":103,"./ReactInstanceMap":113,"./invariant":191,"_process":1}],106:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -13497,9 +14494,9 @@ var ReactErrorUtils = {
module.exports = ReactErrorUtils;
-},{}],103:[function(require,module,exports){
+},{}],107:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -13509,7 +14506,7 @@ module.exports = ReactErrorUtils;
* @providesModule ReactEventEmitterMixin
*/
-"use strict";
+'use strict';
var EventPluginHub = require("./EventPluginHub");
@@ -13547,9 +14544,9 @@ var ReactEventEmitterMixin = {
module.exports = ReactEventEmitterMixin;
-},{"./EventPluginHub":60}],104:[function(require,module,exports){
+},{"./EventPluginHub":58}],108:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -13560,7 +14557,7 @@ module.exports = ReactEventEmitterMixin;
* @typechecks static-only
*/
-"use strict";
+'use strict';
var EventListener = require("./EventListener");
var ExecutionEnvironment = require("./ExecutionEnvironment");
@@ -13673,7 +14670,7 @@ var ReactEventListener = {
trapBubbledEvent: function(topLevelType, handlerBaseName, handle) {
var element = handle;
if (!element) {
- return;
+ return null;
}
return EventListener.listen(
element,
@@ -13695,7 +14692,7 @@ var ReactEventListener = {
trapCapturedEvent: function(topLevelType, handlerBaseName, handle) {
var element = handle;
if (!element) {
- return;
+ return null;
}
return EventListener.capture(
element,
@@ -13707,7 +14704,6 @@ var ReactEventListener = {
monitorScrollValue: function(refresh) {
var callback = scrollValueMonitor.bind(null, refresh);
EventListener.listen(window, 'scroll', callback);
- EventListener.listen(window, 'resize', callback);
},
dispatchEvent: function(topLevelType, nativeEvent) {
@@ -13731,9 +14727,194 @@ var ReactEventListener = {
module.exports = ReactEventListener;
-},{"./EventListener":59,"./ExecutionEnvironment":64,"./Object.assign":70,"./PooledClass":71,"./ReactInstanceHandles":107,"./ReactMount":111,"./ReactUpdates":132,"./getEventTarget":173,"./getUnboundedScrollPosition":178}],105:[function(require,module,exports){
+},{"./EventListener":57,"./ExecutionEnvironment":62,"./Object.assign":69,"./PooledClass":70,"./ReactInstanceHandles":112,"./ReactMount":117,"./ReactUpdates":140,"./getEventTarget":181,"./getUnboundedScrollPosition":187}],109:[function(require,module,exports){
+(function (process){
+/**
+ * Copyright 2015, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+* @providesModule ReactFragment
+*/
+
+'use strict';
+
+var ReactElement = require("./ReactElement");
+
+var warning = require("./warning");
+
+/**
+ * We used to allow keyed objects to serve as a collection of ReactElements,
+ * or nested sets. This allowed us a way to explicitly key a set a fragment of
+ * components. This is now being replaced with an opaque data structure.
+ * The upgrade path is to call React.addons.createFragment({ key: value }) to
+ * create a keyed fragment. The resulting data structure is opaque, for now.
+ */
+
+if ("production" !== process.env.NODE_ENV) {
+ var fragmentKey = '_reactFragment';
+ var didWarnKey = '_reactDidWarn';
+ var canWarnForReactFragment = false;
+
+ try {
+ // Feature test. Don't even try to issue this warning if we can't use
+ // enumerable: false.
+
+ var dummy = function() {
+ return 1;
+ };
+
+ Object.defineProperty(
+ {},
+ fragmentKey,
+ {enumerable: false, value: true}
+ );
+
+ Object.defineProperty(
+ {},
+ 'key',
+ {enumerable: true, get: dummy}
+ );
+
+ canWarnForReactFragment = true;
+ } catch (x) { }
+
+ var proxyPropertyAccessWithWarning = function(obj, key) {
+ Object.defineProperty(obj, key, {
+ enumerable: true,
+ get: function() {
+ ("production" !== process.env.NODE_ENV ? warning(
+ this[didWarnKey],
+ 'A ReactFragment is an opaque type. Accessing any of its ' +
+ 'properties is deprecated. Pass it to one of the React.Children ' +
+ 'helpers.'
+ ) : null);
+ this[didWarnKey] = true;
+ return this[fragmentKey][key];
+ },
+ set: function(value) {
+ ("production" !== process.env.NODE_ENV ? warning(
+ this[didWarnKey],
+ 'A ReactFragment is an immutable opaque type. Mutating its ' +
+ 'properties is deprecated.'
+ ) : null);
+ this[didWarnKey] = true;
+ this[fragmentKey][key] = value;
+ }
+ });
+ };
+
+ var issuedWarnings = {};
+
+ var didWarnForFragment = function(fragment) {
+ // We use the keys and the type of the value as a heuristic to dedupe the
+ // warning to avoid spamming too much.
+ var fragmentCacheKey = '';
+ for (var key in fragment) {
+ fragmentCacheKey += key + ':' + (typeof fragment[key]) + ',';
+ }
+ var alreadyWarnedOnce = !!issuedWarnings[fragmentCacheKey];
+ issuedWarnings[fragmentCacheKey] = true;
+ return alreadyWarnedOnce;
+ };
+}
+
+var ReactFragment = {
+ // Wrap a keyed object in an opaque proxy that warns you if you access any
+ // of its properties.
+ create: function(object) {
+ if ("production" !== process.env.NODE_ENV) {
+ if (typeof object !== 'object' || !object || Array.isArray(object)) {
+ ("production" !== process.env.NODE_ENV ? warning(
+ false,
+ 'React.addons.createFragment only accepts a single object.',
+ object
+ ) : null);
+ return object;
+ }
+ if (ReactElement.isValidElement(object)) {
+ ("production" !== process.env.NODE_ENV ? warning(
+ false,
+ 'React.addons.createFragment does not accept a ReactElement ' +
+ 'without a wrapper object.'
+ ) : null);
+ return object;
+ }
+ if (canWarnForReactFragment) {
+ var proxy = {};
+ Object.defineProperty(proxy, fragmentKey, {
+ enumerable: false,
+ value: object
+ });
+ Object.defineProperty(proxy, didWarnKey, {
+ writable: true,
+ enumerable: false,
+ value: false
+ });
+ for (var key in object) {
+ proxyPropertyAccessWithWarning(proxy, key);
+ }
+ Object.preventExtensions(proxy);
+ return proxy;
+ }
+ }
+ return object;
+ },
+ // Extract the original keyed object from the fragment opaque type. Warn if
+ // a plain object is passed here.
+ extract: function(fragment) {
+ if ("production" !== process.env.NODE_ENV) {
+ if (canWarnForReactFragment) {
+ if (!fragment[fragmentKey]) {
+ ("production" !== process.env.NODE_ENV ? warning(
+ didWarnForFragment(fragment),
+ 'Any use of a keyed object should be wrapped in ' +
+ 'React.addons.createFragment(object) before being passed as a ' +
+ 'child.'
+ ) : null);
+ return fragment;
+ }
+ return fragment[fragmentKey];
+ }
+ }
+ return fragment;
+ },
+ // Check if this is a fragment and if so, extract the keyed object. If it
+ // is a fragment-like object, warn that it should be wrapped. Ignore if we
+ // can't determine what kind of object this is.
+ extractIfFragment: function(fragment) {
+ if ("production" !== process.env.NODE_ENV) {
+ if (canWarnForReactFragment) {
+ // If it is the opaque type, return the keyed object.
+ if (fragment[fragmentKey]) {
+ return fragment[fragmentKey];
+ }
+ // Otherwise, check each property if it has an element, if it does
+ // it is probably meant as a fragment, so we can warn early. Defer,
+ // the warning to extract.
+ for (var key in fragment) {
+ if (fragment.hasOwnProperty(key) &&
+ ReactElement.isValidElement(fragment[key])) {
+ // This looks like a fragment object, we should provide an
+ // early warning.
+ return ReactFragment.extract(fragment);
+ }
+ }
+ }
+ }
+ return fragment;
+ }
+};
+
+module.exports = ReactFragment;
+
+}).call(this,require('_process'))
+},{"./ReactElement":103,"./warning":212,"_process":1}],110:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -13743,22 +14924,24 @@ module.exports = ReactEventListener;
* @providesModule ReactInjection
*/
-"use strict";
+'use strict';
var DOMProperty = require("./DOMProperty");
var EventPluginHub = require("./EventPluginHub");
-var ReactComponent = require("./ReactComponent");
-var ReactCompositeComponent = require("./ReactCompositeComponent");
+var ReactComponentEnvironment = require("./ReactComponentEnvironment");
+var ReactClass = require("./ReactClass");
var ReactEmptyComponent = require("./ReactEmptyComponent");
var ReactBrowserEventEmitter = require("./ReactBrowserEventEmitter");
var ReactNativeComponent = require("./ReactNativeComponent");
+var ReactDOMComponent = require("./ReactDOMComponent");
var ReactPerf = require("./ReactPerf");
var ReactRootIndex = require("./ReactRootIndex");
var ReactUpdates = require("./ReactUpdates");
var ReactInjection = {
- Component: ReactComponent.injection,
- CompositeComponent: ReactCompositeComponent.injection,
+ Component: ReactComponentEnvironment.injection,
+ Class: ReactClass.injection,
+ DOMComponent: ReactDOMComponent.injection,
DOMProperty: DOMProperty.injection,
EmptyComponent: ReactEmptyComponent.injection,
EventPluginHub: EventPluginHub.injection,
@@ -13771,9 +14954,9 @@ var ReactInjection = {
module.exports = ReactInjection;
-},{"./DOMProperty":53,"./EventPluginHub":60,"./ReactBrowserEventEmitter":74,"./ReactComponent":78,"./ReactCompositeComponent":81,"./ReactEmptyComponent":101,"./ReactNativeComponent":114,"./ReactPerf":116,"./ReactRootIndex":123,"./ReactUpdates":132}],106:[function(require,module,exports){
+},{"./DOMProperty":51,"./EventPluginHub":58,"./ReactBrowserEventEmitter":73,"./ReactClass":78,"./ReactComponentEnvironment":81,"./ReactDOMComponent":88,"./ReactEmptyComponent":105,"./ReactNativeComponent":120,"./ReactPerf":122,"./ReactRootIndex":131,"./ReactUpdates":140}],111:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -13783,7 +14966,7 @@ module.exports = ReactInjection;
* @providesModule ReactInputSelection
*/
-"use strict";
+'use strict';
var ReactDOMSelection = require("./ReactDOMSelection");
@@ -13805,9 +14988,8 @@ var ReactInputSelection = {
hasSelectionCapabilities: function(elem) {
return elem && (
- (elem.nodeName === 'INPUT' && elem.type === 'text') ||
- elem.nodeName === 'TEXTAREA' ||
- elem.contentEditable === 'true'
+ ((elem.nodeName === 'INPUT' && elem.type === 'text') ||
+ elem.nodeName === 'TEXTAREA' || elem.contentEditable === 'true')
);
},
@@ -13907,10 +15089,10 @@ var ReactInputSelection = {
module.exports = ReactInputSelection;
-},{"./ReactDOMSelection":93,"./containsNode":156,"./focusNode":167,"./getActiveElement":169}],107:[function(require,module,exports){
+},{"./ReactDOMSelection":96,"./containsNode":164,"./focusNode":175,"./getActiveElement":177}],112:[function(require,module,exports){
(function (process){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -13921,7 +15103,7 @@ module.exports = ReactInputSelection;
* @typechecks static-only
*/
-"use strict";
+'use strict';
var ReactRootIndex = require("./ReactRootIndex");
@@ -14026,7 +15208,8 @@ function getNextDescendantID(ancestorID, destinationID) {
// Skip over the ancestor and the immediate separator. Traverse until we hit
// another separator or we reach the end of `destinationID`.
var start = ancestorID.length + SEPARATOR_LENGTH;
- for (var i = start; i < destinationID.length; i++) {
+ var i;
+ for (i = start; i < destinationID.length; i++) {
if (isBoundary(destinationID, i)) {
break;
}
@@ -14242,256 +15425,95 @@ var ReactInstanceHandles = {
module.exports = ReactInstanceHandles;
}).call(this,require('_process'))
-},{"./ReactRootIndex":123,"./invariant":182,"_process":1}],108:[function(require,module,exports){
-(function (process){
+},{"./ReactRootIndex":131,"./invariant":191,"_process":1}],113:[function(require,module,exports){
/**
- * Copyright 2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
- * @providesModule ReactLegacyElement
+ * @providesModule ReactInstanceMap
*/
-"use strict";
-
-var ReactCurrentOwner = require("./ReactCurrentOwner");
-
-var invariant = require("./invariant");
-var monitorCodeUse = require("./monitorCodeUse");
-var warning = require("./warning");
-
-var legacyFactoryLogs = {};
-function warnForLegacyFactoryCall() {
- if (!ReactLegacyElementFactory._isLegacyCallWarningEnabled) {
- return;
- }
- var owner = ReactCurrentOwner.current;
- var name = owner && owner.constructor ? owner.constructor.displayName : '';
- if (!name) {
- name = 'Something';
- }
- if (legacyFactoryLogs.hasOwnProperty(name)) {
- return;
- }
- legacyFactoryLogs[name] = true;
- ("production" !== process.env.NODE_ENV ? warning(
- false,
- name + ' is calling a React component directly. ' +
- 'Use a factory or JSX instead. See: http://fb.me/react-legacyfactory'
- ) : null);
- monitorCodeUse('react_legacy_factory_call', { version: 3, name: name });
-}
-
-function warnForPlainFunctionType(type) {
- var isReactClass =
- type.prototype &&
- typeof type.prototype.mountComponent === 'function' &&
- typeof type.prototype.receiveComponent === 'function';
- if (isReactClass) {
- ("production" !== process.env.NODE_ENV ? warning(
- false,
- 'Did not expect to get a React class here. Use `Component` instead ' +
- 'of `Component.type` or `this.constructor`.'
- ) : null);
- } else {
- if (!type._reactWarnedForThisType) {
- try {
- type._reactWarnedForThisType = true;
- } catch (x) {
- // just incase this is a frozen object or some special object
- }
- monitorCodeUse(
- 'react_non_component_in_jsx',
- { version: 3, name: type.name }
- );
- }
- ("production" !== process.env.NODE_ENV ? warning(
- false,
- 'This JSX uses a plain function. Only React components are ' +
- 'valid in React\'s JSX transform.'
- ) : null);
- }
-}
-
-function warnForNonLegacyFactory(type) {
- ("production" !== process.env.NODE_ENV ? warning(
- false,
- 'Do not pass React.DOM.' + type.type + ' to JSX or createFactory. ' +
- 'Use the string "' + type.type + '" instead.'
- ) : null);
-}
+'use strict';
/**
- * Transfer static properties from the source to the target. Functions are
- * rebound to have this reflect the original source.
+ * `ReactInstanceMap` maintains a mapping from a public facing stateful
+ * instance (key) and the internal representation (value). This allows public
+ * methods to accept the user facing instance as an argument and map them back
+ * to internal methods.
*/
-function proxyStaticMethods(target, source) {
- if (typeof source !== 'function') {
- return;
- }
- for (var key in source) {
- if (source.hasOwnProperty(key)) {
- var value = source[key];
- if (typeof value === 'function') {
- var bound = value.bind(source);
- // Copy any properties defined on the function, such as `isRequired` on
- // a PropTypes validator.
- for (var k in value) {
- if (value.hasOwnProperty(k)) {
- bound[k] = value[k];
- }
- }
- target[key] = bound;
- } else {
- target[key] = value;
- }
- }
- }
-}
-// We use an object instead of a boolean because booleans are ignored by our
-// mocking libraries when these factories gets mocked.
-var LEGACY_MARKER = {};
-var NON_LEGACY_MARKER = {};
+// TODO: Replace this with ES6: var ReactInstanceMap = new Map();
+var ReactInstanceMap = {
-var ReactLegacyElementFactory = {};
-
-ReactLegacyElementFactory.wrapCreateFactory = function(createFactory) {
- var legacyCreateFactory = function(type) {
- if (typeof type !== 'function') {
- // Non-function types cannot be legacy factories
- return createFactory(type);
- }
+ /**
+ * This API should be called `delete` but we'd have to make sure to always
+ * transform these to strings for IE support. When this transform is fully
+ * supported we can rename it.
+ */
+ remove: function(key) {
+ key._reactInternalInstance = undefined;
+ },
- if (type.isReactNonLegacyFactory) {
- // This is probably a factory created by ReactDOM we unwrap it to get to
- // the underlying string type. It shouldn't have been passed here so we
- // warn.
- if ("production" !== process.env.NODE_ENV) {
- warnForNonLegacyFactory(type);
- }
- return createFactory(type.type);
- }
+ get: function(key) {
+ return key._reactInternalInstance;
+ },
- if (type.isReactLegacyFactory) {
- // This is probably a legacy factory created by ReactCompositeComponent.
- // We unwrap it to get to the underlying class.
- return createFactory(type.type);
- }
+ has: function(key) {
+ return key._reactInternalInstance !== undefined;
+ },
- if ("production" !== process.env.NODE_ENV) {
- warnForPlainFunctionType(type);
- }
+ set: function(key, value) {
+ key._reactInternalInstance = value;
+ }
- // Unless it's a legacy factory, then this is probably a plain function,
- // that is expecting to be invoked by JSX. We can just return it as is.
- return type;
- };
- return legacyCreateFactory;
};
-ReactLegacyElementFactory.wrapCreateElement = function(createElement) {
- var legacyCreateElement = function(type, props, children) {
- if (typeof type !== 'function') {
- // Non-function types cannot be legacy factories
- return createElement.apply(this, arguments);
- }
-
- var args;
+module.exports = ReactInstanceMap;
- if (type.isReactNonLegacyFactory) {
- // This is probably a factory created by ReactDOM we unwrap it to get to
- // the underlying string type. It shouldn't have been passed here so we
- // warn.
- if ("production" !== process.env.NODE_ENV) {
- warnForNonLegacyFactory(type);
- }
- args = Array.prototype.slice.call(arguments, 0);
- args[0] = type.type;
- return createElement.apply(this, args);
- }
-
- if (type.isReactLegacyFactory) {
- // This is probably a legacy factory created by ReactCompositeComponent.
- // We unwrap it to get to the underlying class.
- if (type._isMockFunction) {
- // If this is a mock function, people will expect it to be called. We
- // will actually call the original mock factory function instead. This
- // future proofs unit testing that assume that these are classes.
- type.type._mockedReactClassConstructor = type;
- }
- args = Array.prototype.slice.call(arguments, 0);
- args[0] = type.type;
- return createElement.apply(this, args);
- }
-
- if ("production" !== process.env.NODE_ENV) {
- warnForPlainFunctionType(type);
- }
-
- // This is being called with a plain function we should invoke it
- // immediately as if this was used with legacy JSX.
- return type.apply(null, Array.prototype.slice.call(arguments, 1));
- };
- return legacyCreateElement;
-};
-
-ReactLegacyElementFactory.wrapFactory = function(factory) {
- ("production" !== process.env.NODE_ENV ? invariant(
- typeof factory === 'function',
- 'This is suppose to accept a element factory'
- ) : invariant(typeof factory === 'function'));
- var legacyElementFactory = function(config, children) {
- // This factory should not be called when JSX is used. Use JSX instead.
- if ("production" !== process.env.NODE_ENV) {
- warnForLegacyFactoryCall();
- }
- return factory.apply(this, arguments);
- };
- proxyStaticMethods(legacyElementFactory, factory.type);
- legacyElementFactory.isReactLegacyFactory = LEGACY_MARKER;
- legacyElementFactory.type = factory.type;
- return legacyElementFactory;
-};
+},{}],114:[function(require,module,exports){
+/**
+ * Copyright 2015, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ * @providesModule ReactLifeCycle
+ */
-// This is used to mark a factory that will remain. E.g. we're allowed to call
-// it as a function. However, you're not suppose to pass it to createElement
-// or createFactory, so it will warn you if you do.
-ReactLegacyElementFactory.markNonLegacyFactory = function(factory) {
- factory.isReactNonLegacyFactory = NON_LEGACY_MARKER;
- return factory;
-};
+'use strict';
-// Checks if a factory function is actually a legacy factory pretending to
-// be a class.
-ReactLegacyElementFactory.isValidFactory = function(factory) {
- // TODO: This will be removed and moved into a class validator or something.
- return typeof factory === 'function' &&
- factory.isReactLegacyFactory === LEGACY_MARKER;
-};
+/**
+ * This module manages the bookkeeping when a component is in the process
+ * of being mounted or being unmounted. This is used as a way to enforce
+ * invariants (or warnings) when it is not recommended to call
+ * setState/forceUpdate.
+ *
+ * currentlyMountingInstance: During the construction phase, it is not possible
+ * to trigger an update since the instance is not fully mounted yet. However, we
+ * currently allow this as a convenience for mutating the initial state.
+ *
+ * currentlyUnmountingInstance: During the unmounting phase, the instance is
+ * still mounted and can therefore schedule an update. However, this is not
+ * recommended and probably an error since it's about to be unmounted.
+ * Therefore we still want to trigger in an error for that case.
+ */
-ReactLegacyElementFactory.isValidClass = function(factory) {
- if ("production" !== process.env.NODE_ENV) {
- ("production" !== process.env.NODE_ENV ? warning(
- false,
- 'isValidClass is deprecated and will be removed in a future release. ' +
- 'Use a more specific validator instead.'
- ) : null);
- }
- return ReactLegacyElementFactory.isValidFactory(factory);
+var ReactLifeCycle = {
+ currentlyMountingInstance: null,
+ currentlyUnmountingInstance: null
};
-ReactLegacyElementFactory._isLegacyCallWarningEnabled = true;
+module.exports = ReactLifeCycle;
-module.exports = ReactLegacyElementFactory;
-
-}).call(this,require('_process'))
-},{"./ReactCurrentOwner":83,"./invariant":182,"./monitorCodeUse":192,"./warning":202,"_process":1}],109:[function(require,module,exports){
+},{}],115:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -14502,7 +15524,7 @@ module.exports = ReactLegacyElementFactory;
* @typechecks static-only
*/
-"use strict";
+'use strict';
/**
* ReactLink encapsulates a common pattern in which a component wants to modify
@@ -14562,9 +15584,9 @@ ReactLink.PropTypes = {
module.exports = ReactLink;
-},{"./React":72}],110:[function(require,module,exports){
+},{"./React":71}],116:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -14574,7 +15596,7 @@ module.exports = ReactLink;
* @providesModule ReactMarkupChecksum
*/
-"use strict";
+'use strict';
var adler32 = require("./adler32");
@@ -14610,10 +15632,10 @@ var ReactMarkupChecksum = {
module.exports = ReactMarkupChecksum;
-},{"./adler32":152}],111:[function(require,module,exports){
+},{"./adler32":160}],117:[function(require,module,exports){
(function (process){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -14623,28 +15645,31 @@ module.exports = ReactMarkupChecksum;
* @providesModule ReactMount
*/
-"use strict";
+'use strict';
var DOMProperty = require("./DOMProperty");
var ReactBrowserEventEmitter = require("./ReactBrowserEventEmitter");
var ReactCurrentOwner = require("./ReactCurrentOwner");
var ReactElement = require("./ReactElement");
-var ReactLegacyElement = require("./ReactLegacyElement");
+var ReactElementValidator = require("./ReactElementValidator");
+var ReactEmptyComponent = require("./ReactEmptyComponent");
var ReactInstanceHandles = require("./ReactInstanceHandles");
+var ReactInstanceMap = require("./ReactInstanceMap");
+var ReactMarkupChecksum = require("./ReactMarkupChecksum");
var ReactPerf = require("./ReactPerf");
+var ReactReconciler = require("./ReactReconciler");
+var ReactUpdateQueue = require("./ReactUpdateQueue");
+var ReactUpdates = require("./ReactUpdates");
+var emptyObject = require("./emptyObject");
var containsNode = require("./containsNode");
-var deprecated = require("./deprecated");
var getReactRootElementInContainer = require("./getReactRootElementInContainer");
var instantiateReactComponent = require("./instantiateReactComponent");
var invariant = require("./invariant");
+var setInnerHTML = require("./setInnerHTML");
var shouldUpdateReactComponent = require("./shouldUpdateReactComponent");
var warning = require("./warning");
-var createElement = ReactLegacyElement.wrapCreateElement(
- ReactElement.createElement
-);
-
var SEPARATOR = ReactInstanceHandles.SEPARATOR;
var ATTR_NAME = DOMProperty.ID_ATTRIBUTE_NAME;
@@ -14668,6 +15693,22 @@ if ("production" !== process.env.NODE_ENV) {
var findComponentRootReusableArray = [];
/**
+ * Finds the index of the first character
+ * that's not common between the two given strings.
+ *
+ * @return {number} the index of the character where the strings diverge
+ */
+function firstDifferenceIndex(string1, string2) {
+ var minLen = Math.min(string1.length, string2.length);
+ for (var i = 0; i < minLen; i++) {
+ if (string1.charAt(i) !== string2.charAt(i)) {
+ return i;
+ }
+ }
+ return string1.length === string2.length ? -1 : minLen;
+}
+
+/**
* @param {DOMElement} container DOM element that may contain a React component.
* @return {?string} A "reactRoot" ID, if a React component is rendered.
*/
@@ -14745,6 +15786,24 @@ function getNode(id) {
}
/**
+ * Finds the node with the supplied public React instance.
+ *
+ * @param {*} instance A public React instance.
+ * @return {?DOMElement} DOM node with the suppled `id`.
+ * @internal
+ */
+function getNodeFromInstance(instance) {
+ var id = ReactInstanceMap.get(instance)._rootNodeID;
+ if (ReactEmptyComponent.isNullComponentID(id)) {
+ return null;
+ }
+ if (!nodeCache.hasOwnProperty(id) || !isValid(nodeCache[id], id)) {
+ nodeCache[id] = ReactMount.findReactNodeByID(id);
+ }
+ return nodeCache[id];
+}
+
+/**
* A node is "valid" if it is contained by a currently mounted container.
*
* This means that the node does not have to be contained by a document in
@@ -14808,7 +15867,55 @@ function findDeepestCachedAncestor(targetID) {
}
/**
- * Mounting is the process of initializing a React component by creatings its
+ * Mounts this component and inserts it into the DOM.
+ *
+ * @param {ReactComponent} componentInstance The instance to mount.
+ * @param {string} rootID DOM ID of the root node.
+ * @param {DOMElement} container DOM element to mount into.
+ * @param {ReactReconcileTransaction} transaction
+ * @param {boolean} shouldReuseMarkup If true, do not insert markup
+ */
+function mountComponentIntoNode(
+ componentInstance,
+ rootID,
+ container,
+ transaction,
+ shouldReuseMarkup) {
+ var markup = ReactReconciler.mountComponent(
+ componentInstance, rootID, transaction, emptyObject
+ );
+ componentInstance._isTopLevel = true;
+ ReactMount._mountImageIntoNode(markup, container, shouldReuseMarkup);
+}
+
+/**
+ * Batched mount.
+ *
+ * @param {ReactComponent} componentInstance The instance to mount.
+ * @param {string} rootID DOM ID of the root node.
+ * @param {DOMElement} container DOM element to mount into.
+ * @param {boolean} shouldReuseMarkup If true, do not insert markup
+ */
+function batchedMountComponentIntoNode(
+ componentInstance,
+ rootID,
+ container,
+ shouldReuseMarkup) {
+ var transaction = ReactUpdates.ReactReconcileTransaction.getPooled();
+ transaction.perform(
+ mountComponentIntoNode,
+ null,
+ componentInstance,
+ rootID,
+ container,
+ transaction,
+ shouldReuseMarkup
+ );
+ ReactUpdates.ReactReconcileTransaction.release(transaction);
+}
+
+/**
+ * Mounting is the process of initializing a React component by creating its
* representative DOM elements and inserting them into a supplied `container`.
* Any prior content inside `container` is destroyed in the process.
*
@@ -14844,18 +15951,24 @@ var ReactMount = {
/**
* Take a component that's already mounted into the DOM and replace its props
* @param {ReactComponent} prevComponent component instance already in the DOM
- * @param {ReactComponent} nextComponent component instance to render
+ * @param {ReactElement} nextElement component instance to render
* @param {DOMElement} container container to render into
* @param {?function} callback function triggered on completion
*/
_updateRootComponent: function(
prevComponent,
- nextComponent,
+ nextElement,
container,
callback) {
- var nextProps = nextComponent.props;
+ if ("production" !== process.env.NODE_ENV) {
+ ReactElementValidator.checkAndWarnForMutatedProps(nextElement);
+ }
+
ReactMount.scrollMonitor(container, function() {
- prevComponent.replaceProps(nextProps, callback);
+ ReactUpdateQueue.enqueueElementInternal(prevComponent, nextElement);
+ if (callback) {
+ ReactUpdateQueue.enqueueCallbackInternal(prevComponent, callback);
+ }
});
if ("production" !== process.env.NODE_ENV) {
@@ -14877,13 +15990,11 @@ var ReactMount = {
_registerComponent: function(nextComponent, container) {
("production" !== process.env.NODE_ENV ? invariant(
container && (
- container.nodeType === ELEMENT_NODE_TYPE ||
- container.nodeType === DOC_NODE_TYPE
+ (container.nodeType === ELEMENT_NODE_TYPE || container.nodeType === DOC_NODE_TYPE)
),
'_registerComponent(...): Target container is not a DOM element.'
) : invariant(container && (
- container.nodeType === ELEMENT_NODE_TYPE ||
- container.nodeType === DOC_NODE_TYPE
+ (container.nodeType === ELEMENT_NODE_TYPE || container.nodeType === DOC_NODE_TYPE)
)));
ReactBrowserEventEmitter.ensureScrollValueMonitoring();
@@ -14895,49 +16006,53 @@ var ReactMount = {
/**
* Render a new component into the DOM.
- * @param {ReactComponent} nextComponent component instance to render
+ * @param {ReactElement} nextElement element to render
* @param {DOMElement} container container to render into
* @param {boolean} shouldReuseMarkup if we should skip the markup insertion
* @return {ReactComponent} nextComponent
*/
- _renderNewRootComponent: ReactPerf.measure(
- 'ReactMount',
- '_renderNewRootComponent',
- function(
- nextComponent,
- container,
- shouldReuseMarkup) {
- // Various parts of our code (such as ReactCompositeComponent's
- // _renderValidatedComponent) assume that calls to render aren't nested;
- // verify that that's the case.
- ("production" !== process.env.NODE_ENV ? warning(
- ReactCurrentOwner.current == null,
- '_renderNewRootComponent(): Render methods should be a pure function ' +
- 'of props and state; triggering nested component updates from ' +
- 'render is not allowed. If necessary, trigger nested updates in ' +
- 'componentDidUpdate.'
- ) : null);
+ _renderNewRootComponent: function(
+ nextElement,
+ container,
+ shouldReuseMarkup
+ ) {
+ // Various parts of our code (such as ReactCompositeComponent's
+ // _renderValidatedComponent) assume that calls to render aren't nested;
+ // verify that that's the case.
+ ("production" !== process.env.NODE_ENV ? warning(
+ ReactCurrentOwner.current == null,
+ '_renderNewRootComponent(): Render methods should be a pure function ' +
+ 'of props and state; triggering nested component updates from ' +
+ 'render is not allowed. If necessary, trigger nested updates in ' +
+ 'componentDidUpdate.'
+ ) : null);
- var componentInstance = instantiateReactComponent(nextComponent, null);
- var reactRootID = ReactMount._registerComponent(
- componentInstance,
- container
- );
- componentInstance.mountComponentIntoNode(
- reactRootID,
- container,
- shouldReuseMarkup
- );
+ var componentInstance = instantiateReactComponent(nextElement, null);
+ var reactRootID = ReactMount._registerComponent(
+ componentInstance,
+ container
+ );
- if ("production" !== process.env.NODE_ENV) {
- // Record the root element in case it later gets transplanted.
- rootElementsByReactRootID[reactRootID] =
- getReactRootElementInContainer(container);
- }
+ // The initial render is synchronous but any updates that happen during
+ // rendering, in componentWillMount or componentDidMount, will be batched
+ // according to the current batching strategy.
+
+ ReactUpdates.batchedUpdates(
+ batchedMountComponentIntoNode,
+ componentInstance,
+ reactRootID,
+ container,
+ shouldReuseMarkup
+ );
- return componentInstance;
+ if ("production" !== process.env.NODE_ENV) {
+ // Record the root element in case it later gets transplanted.
+ rootElementsByReactRootID[reactRootID] =
+ getReactRootElementInContainer(container);
}
- ),
+
+ return componentInstance;
+ },
/**
* Renders a React component into the DOM in the supplied `container`.
@@ -14954,16 +16069,16 @@ var ReactMount = {
render: function(nextElement, container, callback) {
("production" !== process.env.NODE_ENV ? invariant(
ReactElement.isValidElement(nextElement),
- 'renderComponent(): Invalid component element.%s',
+ 'React.render(): Invalid component element.%s',
(
typeof nextElement === 'string' ?
' Instead of passing an element string, make sure to instantiate ' +
'it by passing it to React.createElement.' :
- ReactLegacyElement.isValidFactory(nextElement) ?
+ typeof nextElement === 'function' ?
' Instead of passing a component class, make sure to instantiate ' +
'it by passing it to React.createElement.' :
- // Check if it quacks like a element
- typeof nextElement.props !== "undefined" ?
+ // Check if it quacks like an element
+ nextElement != null && nextElement.props !== undefined ?
' This may be caused by unintentionally loading two independent ' +
'copies of React.' :
''
@@ -14980,7 +16095,7 @@ var ReactMount = {
nextElement,
container,
callback
- );
+ ).getPublicInstance();
} else {
ReactMount.unmountComponentAtNode(container);
}
@@ -14990,14 +16105,35 @@ var ReactMount = {
var containerHasReactMarkup =
reactRootElement && ReactMount.isRenderedByReact(reactRootElement);
+ if ("production" !== process.env.NODE_ENV) {
+ if (!containerHasReactMarkup || reactRootElement.nextSibling) {
+ var rootElementSibling = reactRootElement;
+ while (rootElementSibling) {
+ if (ReactMount.isRenderedByReact(rootElementSibling)) {
+ ("production" !== process.env.NODE_ENV ? warning(
+ false,
+ 'render(): Target node has markup rendered by React, but there ' +
+ 'are unrelated nodes as well. This is most commonly caused by ' +
+ 'white-space inserted around server-rendered markup.'
+ ) : null);
+ break;
+ }
+
+ rootElementSibling = rootElementSibling.nextSibling;
+ }
+ }
+ }
+
var shouldReuseMarkup = containerHasReactMarkup && !prevComponent;
var component = ReactMount._renderNewRootComponent(
nextElement,
container,
shouldReuseMarkup
- );
- callback && callback.call(component);
+ ).getPublicInstance();
+ if (callback) {
+ callback.call(component);
+ }
return component;
},
@@ -15011,7 +16147,7 @@ var ReactMount = {
* @return {ReactComponent} Component instance rendered in `container`.
*/
constructAndRenderComponent: function(constructor, props, container) {
- var element = createElement(constructor, props);
+ var element = ReactElement.createElement(constructor, props);
return ReactMount.render(element, container);
},
@@ -15076,6 +16212,15 @@ var ReactMount = {
'componentDidUpdate.'
) : null);
+ ("production" !== process.env.NODE_ENV ? invariant(
+ container && (
+ (container.nodeType === ELEMENT_NODE_TYPE || container.nodeType === DOC_NODE_TYPE)
+ ),
+ 'unmountComponentAtNode(...): Target container is not a DOM element.'
+ ) : invariant(container && (
+ (container.nodeType === ELEMENT_NODE_TYPE || container.nodeType === DOC_NODE_TYPE)
+ )));
+
var reactRootID = getReactRootID(container);
var component = instancesByReactRootID[reactRootID];
if (!component) {
@@ -15100,7 +16245,7 @@ var ReactMount = {
* @see {ReactMount.unmountComponentAtNode}
*/
unmountComponentFromNode: function(instance, container) {
- instance.unmountComponent();
+ ReactReconciler.unmountComponent(instance);
if (container.nodeType === DOC_NODE_TYPE) {
container = container.documentElement;
@@ -15144,10 +16289,11 @@ var ReactMount = {
// warning is when the container is empty.
rootElementsByReactRootID[reactRootID] = containerChild;
} else {
- console.warn(
+ ("production" !== process.env.NODE_ENV ? warning(
+ false,
'ReactMount: Root element has been removed from its original ' +
'container. New container:', rootElement.parentNode
- );
+ ) : null);
}
}
}
@@ -15280,6 +16426,77 @@ var ReactMount = {
) : invariant(false));
},
+ _mountImageIntoNode: function(markup, container, shouldReuseMarkup) {
+ ("production" !== process.env.NODE_ENV ? invariant(
+ container && (
+ (container.nodeType === ELEMENT_NODE_TYPE || container.nodeType === DOC_NODE_TYPE)
+ ),
+ 'mountComponentIntoNode(...): Target container is not valid.'
+ ) : invariant(container && (
+ (container.nodeType === ELEMENT_NODE_TYPE || container.nodeType === DOC_NODE_TYPE)
+ )));
+
+ if (shouldReuseMarkup) {
+ var rootElement = getReactRootElementInContainer(container);
+ if (ReactMarkupChecksum.canReuseMarkup(markup, rootElement)) {
+ return;
+ } else {
+ var checksum = rootElement.getAttribute(
+ ReactMarkupChecksum.CHECKSUM_ATTR_NAME
+ );
+ rootElement.removeAttribute(ReactMarkupChecksum.CHECKSUM_ATTR_NAME);
+
+ var rootMarkup = rootElement.outerHTML;
+ rootElement.setAttribute(
+ ReactMarkupChecksum.CHECKSUM_ATTR_NAME,
+ checksum
+ );
+
+ var diffIndex = firstDifferenceIndex(markup, rootMarkup);
+ var difference = ' (client) ' +
+ markup.substring(diffIndex - 20, diffIndex + 20) +
+ '\n (server) ' + rootMarkup.substring(diffIndex - 20, diffIndex + 20);
+
+ ("production" !== process.env.NODE_ENV ? invariant(
+ container.nodeType !== DOC_NODE_TYPE,
+ 'You\'re trying to render a component to the document using ' +
+ 'server rendering but the checksum was invalid. This usually ' +
+ 'means you rendered a different component type or props on ' +
+ 'the client from the one on the server, or your render() ' +
+ 'methods are impure. React cannot handle this case due to ' +
+ 'cross-browser quirks by rendering at the document root. You ' +
+ 'should look for environment dependent code in your components ' +
+ 'and ensure the props are the same client and server side:\n%s',
+ difference
+ ) : invariant(container.nodeType !== DOC_NODE_TYPE));
+
+ if ("production" !== process.env.NODE_ENV) {
+ ("production" !== process.env.NODE_ENV ? warning(
+ false,
+ 'React attempted to reuse markup in a container but the ' +
+ 'checksum was invalid. This generally means that you are ' +
+ 'using server rendering and the markup generated on the ' +
+ 'server was not what the client was expecting. React injected ' +
+ 'new markup to compensate which works but you have lost many ' +
+ 'of the benefits of server rendering. Instead, figure out ' +
+ 'why the markup being generated is different on the client ' +
+ 'or server:\n%s',
+ difference
+ ) : null);
+ }
+ }
+ }
+
+ ("production" !== process.env.NODE_ENV ? invariant(
+ container.nodeType !== DOC_NODE_TYPE,
+ 'You\'re trying to render a component to the document but ' +
+ 'you didn\'t use server rendering. We can\'t do this ' +
+ 'without using server rendering due to cross-browser quirks. ' +
+ 'See React.renderToString() for server rendering.'
+ ) : invariant(container.nodeType !== DOC_NODE_TYPE));
+
+ setInnerHTML(container, markup);
+ },
/**
* React ID utilities.
@@ -15293,24 +16510,22 @@ var ReactMount = {
getNode: getNode,
+ getNodeFromInstance: getNodeFromInstance,
+
purgeID: purgeID
};
-// Deprecations (remove for 0.13)
-ReactMount.renderComponent = deprecated(
- 'ReactMount',
- 'renderComponent',
- 'render',
- this,
- ReactMount.render
-);
+ReactPerf.measureMethods(ReactMount, 'ReactMount', {
+ _renderNewRootComponent: '_renderNewRootComponent',
+ _mountImageIntoNode: '_mountImageIntoNode'
+});
module.exports = ReactMount;
}).call(this,require('_process'))
-},{"./DOMProperty":53,"./ReactBrowserEventEmitter":74,"./ReactCurrentOwner":83,"./ReactElement":99,"./ReactInstanceHandles":107,"./ReactLegacyElement":108,"./ReactPerf":116,"./containsNode":156,"./deprecated":162,"./getReactRootElementInContainer":176,"./instantiateReactComponent":181,"./invariant":182,"./shouldUpdateReactComponent":198,"./warning":202,"_process":1}],112:[function(require,module,exports){
+},{"./DOMProperty":51,"./ReactBrowserEventEmitter":73,"./ReactCurrentOwner":85,"./ReactElement":103,"./ReactElementValidator":104,"./ReactEmptyComponent":105,"./ReactInstanceHandles":112,"./ReactInstanceMap":113,"./ReactMarkupChecksum":116,"./ReactPerf":122,"./ReactReconciler":129,"./ReactUpdateQueue":139,"./ReactUpdates":140,"./containsNode":164,"./emptyObject":171,"./getReactRootElementInContainer":185,"./instantiateReactComponent":190,"./invariant":191,"./setInnerHTML":205,"./shouldUpdateReactComponent":208,"./warning":212,"_process":1}],118:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -15321,14 +16536,13 @@ module.exports = ReactMount;
* @typechecks static-only
*/
-"use strict";
+'use strict';
-var ReactComponent = require("./ReactComponent");
+var ReactComponentEnvironment = require("./ReactComponentEnvironment");
var ReactMultiChildUpdateTypes = require("./ReactMultiChildUpdateTypes");
-var flattenChildren = require("./flattenChildren");
-var instantiateReactComponent = require("./instantiateReactComponent");
-var shouldUpdateReactComponent = require("./shouldUpdateReactComponent");
+var ReactReconciler = require("./ReactReconciler");
+var ReactChildReconciler = require("./ReactChildReconciler");
/**
* Updating children of a component may trigger recursive updates. The depth is
@@ -15446,7 +16660,7 @@ function enqueueTextContent(parentID, textContent) {
*/
function processQueue() {
if (updateQueue.length) {
- ReactComponent.BackendIDOperations.dangerouslyProcessChildrenUpdates(
+ ReactComponentEnvironment.processChildrenUpdates(
updateQueue,
markupQueue
);
@@ -15489,26 +16703,25 @@ var ReactMultiChild = {
* @return {array} An array of mounted representations.
* @internal
*/
- mountChildren: function(nestedChildren, transaction) {
- var children = flattenChildren(nestedChildren);
+ mountChildren: function(nestedChildren, transaction, context) {
+ var children = ReactChildReconciler.instantiateChildren(
+ nestedChildren, transaction, context
+ );
+ this._renderedChildren = children;
var mountImages = [];
var index = 0;
- this._renderedChildren = children;
for (var name in children) {
- var child = children[name];
if (children.hasOwnProperty(name)) {
- // The rendered children must be turned into instances as they're
- // mounted.
- var childInstance = instantiateReactComponent(child, null);
- children[name] = childInstance;
+ var child = children[name];
// Inlined for performance, see `ReactInstanceHandles.createReactID`.
var rootID = this._rootNodeID + name;
- var mountImage = childInstance.mountComponent(
+ var mountImage = ReactReconciler.mountComponent(
+ child,
rootID,
transaction,
- this._mountDepth + 1
+ context
);
- childInstance._mountIndex = index;
+ child._mountIndex = index;
mountImages.push(mountImage);
index++;
}
@@ -15528,6 +16741,8 @@ var ReactMultiChild = {
try {
var prevChildren = this._renderedChildren;
// Remove any rendered children.
+ ReactChildReconciler.unmountChildren(prevChildren);
+ // TODO: The setTextContent operation should be enough
for (var name in prevChildren) {
if (prevChildren.hasOwnProperty(name)) {
this._unmountChildByName(prevChildren[name], name);
@@ -15539,7 +16754,11 @@ var ReactMultiChild = {
} finally {
updateDepth--;
if (!updateDepth) {
- errorThrown ? clearQueue() : processQueue();
+ if (errorThrown) {
+ clearQueue();
+ } else {
+ processQueue();
+ }
}
}
},
@@ -15551,17 +16770,22 @@ var ReactMultiChild = {
* @param {ReactReconcileTransaction} transaction
* @internal
*/
- updateChildren: function(nextNestedChildren, transaction) {
+ updateChildren: function(nextNestedChildren, transaction, context) {
updateDepth++;
var errorThrown = true;
try {
- this._updateChildren(nextNestedChildren, transaction);
+ this._updateChildren(nextNestedChildren, transaction, context);
errorThrown = false;
} finally {
updateDepth--;
if (!updateDepth) {
- errorThrown ? clearQueue() : processQueue();
+ if (errorThrown) {
+ clearQueue();
+ } else {
+ processQueue();
+ }
}
+
}
},
@@ -15574,9 +16798,12 @@ var ReactMultiChild = {
* @final
* @protected
*/
- _updateChildren: function(nextNestedChildren, transaction) {
- var nextChildren = flattenChildren(nextNestedChildren);
+ _updateChildren: function(nextNestedChildren, transaction, context) {
var prevChildren = this._renderedChildren;
+ var nextChildren = ReactChildReconciler.updateChildren(
+ prevChildren, nextNestedChildren, transaction, context
+ );
+ this._renderedChildren = nextChildren;
if (!nextChildren && !prevChildren) {
return;
}
@@ -15590,12 +16817,10 @@ var ReactMultiChild = {
continue;
}
var prevChild = prevChildren && prevChildren[name];
- var prevElement = prevChild && prevChild._currentElement;
- var nextElement = nextChildren[name];
- if (shouldUpdateReactComponent(prevElement, nextElement)) {
+ var nextChild = nextChildren[name];
+ if (prevChild === nextChild) {
this.moveChild(prevChild, nextIndex, lastIndex);
lastIndex = Math.max(prevChild._mountIndex, lastIndex);
- prevChild.receiveComponent(nextElement, transaction);
prevChild._mountIndex = nextIndex;
} else {
if (prevChild) {
@@ -15604,12 +16829,8 @@ var ReactMultiChild = {
this._unmountChildByName(prevChild, name);
}
// The child must be instantiated before it's mounted.
- var nextChildInstance = instantiateReactComponent(
- nextElement,
- null
- );
this._mountChildByNameAtIndex(
- nextChildInstance, name, nextIndex, transaction
+ nextChild, name, nextIndex, transaction, context
);
}
nextIndex++;
@@ -15617,7 +16838,7 @@ var ReactMultiChild = {
// Remove children that are no longer present.
for (name in prevChildren) {
if (prevChildren.hasOwnProperty(name) &&
- !(nextChildren && nextChildren[name])) {
+ !(nextChildren && nextChildren.hasOwnProperty(name))) {
this._unmountChildByName(prevChildren[name], name);
}
}
@@ -15631,13 +16852,7 @@ var ReactMultiChild = {
*/
unmountChildren: function() {
var renderedChildren = this._renderedChildren;
- for (var name in renderedChildren) {
- var renderedChild = renderedChildren[name];
- // TODO: When is this not true?
- if (renderedChild.unmountComponent) {
- renderedChild.unmountComponent();
- }
- }
+ ReactChildReconciler.unmountChildren(renderedChildren);
this._renderedChildren = null;
},
@@ -15700,18 +16915,22 @@ var ReactMultiChild = {
* @param {ReactReconcileTransaction} transaction
* @private
*/
- _mountChildByNameAtIndex: function(child, name, index, transaction) {
+ _mountChildByNameAtIndex: function(
+ child,
+ name,
+ index,
+ transaction,
+ context) {
// Inlined for performance, see `ReactInstanceHandles.createReactID`.
var rootID = this._rootNodeID + name;
- var mountImage = child.mountComponent(
+ var mountImage = ReactReconciler.mountComponent(
+ child,
rootID,
transaction,
- this._mountDepth + 1
+ context
);
child._mountIndex = index;
this.createChild(child, mountImage);
- this._renderedChildren = this._renderedChildren || {};
- this._renderedChildren[name] = child;
},
/**
@@ -15726,8 +16945,6 @@ var ReactMultiChild = {
_unmountChildByName: function(child, name) {
this.removeChild(child);
child._mountIndex = null;
- child.unmountComponent();
- delete this._renderedChildren[name];
}
}
@@ -15736,9 +16953,9 @@ var ReactMultiChild = {
module.exports = ReactMultiChild;
-},{"./ReactComponent":78,"./ReactMultiChildUpdateTypes":113,"./flattenChildren":166,"./instantiateReactComponent":181,"./shouldUpdateReactComponent":198}],113:[function(require,module,exports){
+},{"./ReactChildReconciler":76,"./ReactComponentEnvironment":81,"./ReactMultiChildUpdateTypes":119,"./ReactReconciler":129}],119:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -15748,7 +16965,7 @@ module.exports = ReactMultiChild;
* @providesModule ReactMultiChildUpdateTypes
*/
-"use strict";
+'use strict';
var keyMirror = require("./keyMirror");
@@ -15769,10 +16986,10 @@ var ReactMultiChildUpdateTypes = keyMirror({
module.exports = ReactMultiChildUpdateTypes;
-},{"./keyMirror":188}],114:[function(require,module,exports){
+},{"./keyMirror":197}],120:[function(require,module,exports){
(function (process){
/**
- * Copyright 2014, Facebook, Inc.
+ * Copyright 2014-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -15782,14 +16999,16 @@ module.exports = ReactMultiChildUpdateTypes;
* @providesModule ReactNativeComponent
*/
-"use strict";
+'use strict';
var assign = require("./Object.assign");
var invariant = require("./invariant");
+var autoGenerateWrapperClass = null;
var genericComponentClass = null;
// This registry keeps track of wrapper classes around native tags
var tagToComponentClass = {};
+var textComponentClass = null;
var ReactNativeComponentInjection = {
// This accepts a class that receives the tag string. This is a catch all
@@ -15797,55 +17016,87 @@ var ReactNativeComponentInjection = {
injectGenericComponentClass: function(componentClass) {
genericComponentClass = componentClass;
},
+ // This accepts a text component class that takes the text string to be
+ // rendered as props.
+ injectTextComponentClass: function(componentClass) {
+ textComponentClass = componentClass;
+ },
// This accepts a keyed object with classes as values. Each key represents a
// tag. That particular tag will use this class instead of the generic one.
injectComponentClasses: function(componentClasses) {
assign(tagToComponentClass, componentClasses);
+ },
+ // Temporary hack since we expect DOM refs to behave like composites,
+ // for this release.
+ injectAutoWrapper: function(wrapperFactory) {
+ autoGenerateWrapperClass = wrapperFactory;
}
};
/**
- * Create an internal class for a specific tag.
+ * Get a composite component wrapper class for a specific tag.
*
- * @param {string} tag The tag for which to create an internal instance.
- * @param {any} props The props passed to the instance constructor.
- * @return {ReactComponent} component The injected empty component.
+ * @param {ReactElement} element The tag for which to get the class.
+ * @return {function} The React class constructor function.
*/
-function createInstanceForTag(tag, props, parentType) {
+function getComponentClassForElement(element) {
+ if (typeof element.type === 'function') {
+ return element.type;
+ }
+ var tag = element.type;
var componentClass = tagToComponentClass[tag];
if (componentClass == null) {
- ("production" !== process.env.NODE_ENV ? invariant(
- genericComponentClass,
- 'There is no registered component for the tag %s',
- tag
- ) : invariant(genericComponentClass));
- return new genericComponentClass(tag, props);
- }
- if (parentType === tag) {
- // Avoid recursion
- ("production" !== process.env.NODE_ENV ? invariant(
- genericComponentClass,
- 'There is no registered component for the tag %s',
- tag
- ) : invariant(genericComponentClass));
- return new genericComponentClass(tag, props);
+ tagToComponentClass[tag] = componentClass = autoGenerateWrapperClass(tag);
}
- // Unwrap legacy factories
- return new componentClass.type(props);
+ return componentClass;
+}
+
+/**
+ * Get a native internal component class for a specific tag.
+ *
+ * @param {ReactElement} element The element to create.
+ * @return {function} The internal class constructor function.
+ */
+function createInternalComponent(element) {
+ ("production" !== process.env.NODE_ENV ? invariant(
+ genericComponentClass,
+ 'There is no registered component for the tag %s',
+ element.type
+ ) : invariant(genericComponentClass));
+ return new genericComponentClass(element.type, element.props);
+}
+
+/**
+ * @param {ReactText} text
+ * @return {ReactComponent}
+ */
+function createInstanceForText(text) {
+ return new textComponentClass(text);
+}
+
+/**
+ * @param {ReactComponent} component
+ * @return {boolean}
+ */
+function isTextComponent(component) {
+ return component instanceof textComponentClass;
}
var ReactNativeComponent = {
- createInstanceForTag: createInstanceForTag,
- injection: ReactNativeComponentInjection,
+ getComponentClassForElement: getComponentClassForElement,
+ createInternalComponent: createInternalComponent,
+ createInstanceForText: createInstanceForText,
+ isTextComponent: isTextComponent,
+ injection: ReactNativeComponentInjection
};
module.exports = ReactNativeComponent;
}).call(this,require('_process'))
-},{"./Object.assign":70,"./invariant":182,"_process":1}],115:[function(require,module,exports){
+},{"./Object.assign":69,"./invariant":191,"_process":1}],121:[function(require,module,exports){
(function (process){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -15855,9 +17106,8 @@ module.exports = ReactNativeComponent;
* @providesModule ReactOwner
*/
-"use strict";
+'use strict';
-var emptyObject = require("./emptyObject");
var invariant = require("./invariant");
/**
@@ -15899,9 +17149,8 @@ var ReactOwner = {
*/
isValidOwner: function(object) {
return !!(
- object &&
- typeof object.attachRef === 'function' &&
- typeof object.detachRef === 'function'
+ (object &&
+ typeof object.attachRef === 'function' && typeof object.detachRef === 'function')
);
},
@@ -15946,51 +17195,9 @@ var ReactOwner = {
) : invariant(ReactOwner.isValidOwner(owner)));
// Check that `component` is still the current ref because we do not want to
// detach the ref if another component stole it.
- if (owner.refs[ref] === component) {
+ if (owner.getPublicInstance().refs[ref] === component.getPublicInstance()) {
owner.detachRef(ref);
}
- },
-
- /**
- * A ReactComponent must mix this in to have refs.
- *
- * @lends {ReactOwner.prototype}
- */
- Mixin: {
-
- construct: function() {
- this.refs = emptyObject;
- },
-
- /**
- * Lazily allocates the refs object and stores `component` as `ref`.
- *
- * @param {string} ref Reference name.
- * @param {component} component Component to store as `ref`.
- * @final
- * @private
- */
- attachRef: function(ref, component) {
- ("production" !== process.env.NODE_ENV ? invariant(
- component.isOwnedBy(this),
- 'attachRef(%s, ...): Only a component\'s owner can store a ref to it.',
- ref
- ) : invariant(component.isOwnedBy(this)));
- var refs = this.refs === emptyObject ? (this.refs = {}) : this.refs;
- refs[ref] = component;
- },
-
- /**
- * Detaches a reference name.
- *
- * @param {string} ref Name to dereference.
- * @final
- * @private
- */
- detachRef: function(ref) {
- delete this.refs[ref];
- }
-
}
};
@@ -15998,10 +17205,10 @@ var ReactOwner = {
module.exports = ReactOwner;
}).call(this,require('_process'))
-},{"./emptyObject":164,"./invariant":182,"_process":1}],116:[function(require,module,exports){
+},{"./invariant":191,"_process":1}],122:[function(require,module,exports){
(function (process){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -16012,7 +17219,7 @@ module.exports = ReactOwner;
* @typechecks static-only
*/
-"use strict";
+'use strict';
/**
* ReactPerf is a general AOP system designed to measure performance. This
@@ -16032,6 +17239,26 @@ var ReactPerf = {
storedMeasure: _noMeasure,
/**
+ * @param {object} object
+ * @param {string} objectName
+ * @param {object<string>} methodNames
+ */
+ measureMethods: function(object, objectName, methodNames) {
+ if ("production" !== process.env.NODE_ENV) {
+ for (var key in methodNames) {
+ if (!methodNames.hasOwnProperty(key)) {
+ continue;
+ }
+ object[key] = ReactPerf.measure(
+ objectName,
+ methodNames[key],
+ object[key]
+ );
+ }
+ }
+ },
+
+ /**
* Use this to wrap methods you want to measure. Zero overhead in production.
*
* @param {string} objName
@@ -16082,10 +17309,9 @@ function _noMeasure(objName, fnName, func) {
module.exports = ReactPerf;
}).call(this,require('_process'))
-},{"_process":1}],117:[function(require,module,exports){
-(function (process){
+},{"_process":1}],123:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -16095,15 +17321,11 @@ module.exports = ReactPerf;
* @providesModule ReactPropTransferer
*/
-"use strict";
+'use strict';
var assign = require("./Object.assign");
var emptyFunction = require("./emptyFunction");
-var invariant = require("./invariant");
var joinClasses = require("./joinClasses");
-var warning = require("./warning");
-
-var didWarn = false;
/**
* Creates a transfer strategy that will merge prop values using the supplied
@@ -16182,8 +17404,6 @@ function transferInto(props, newProps) {
*/
var ReactPropTransferer = {
- TransferStrategies: TransferStrategies,
-
/**
* Merge two props objects using TransferStrategies.
*
@@ -16193,66 +17413,16 @@ var ReactPropTransferer = {
*/
mergeProps: function(oldProps, newProps) {
return transferInto(assign({}, oldProps), newProps);
- },
-
- /**
- * @lends {ReactPropTransferer.prototype}
- */
- Mixin: {
-
- /**
- * Transfer props from this component to a target component.
- *
- * Props that do not have an explicit transfer strategy will be transferred
- * only if the target component does not already have the prop set.
- *
- * This is usually used to pass down props to a returned root component.
- *
- * @param {ReactElement} element Component receiving the properties.
- * @return {ReactElement} The supplied `component`.
- * @final
- * @protected
- */
- transferPropsTo: function(element) {
- ("production" !== process.env.NODE_ENV ? invariant(
- element._owner === this,
- '%s: You can\'t call transferPropsTo() on a component that you ' +
- 'don\'t own, %s. This usually means you are calling ' +
- 'transferPropsTo() on a component passed in as props or children.',
- this.constructor.displayName,
- typeof element.type === 'string' ?
- element.type :
- element.type.displayName
- ) : invariant(element._owner === this));
-
- if ("production" !== process.env.NODE_ENV) {
- if (!didWarn) {
- didWarn = true;
- ("production" !== process.env.NODE_ENV ? warning(
- false,
- 'transferPropsTo is deprecated. ' +
- 'See http://fb.me/react-transferpropsto for more information.'
- ) : null);
- }
- }
-
- // Because elements are immutable we have to merge into the existing
- // props object rather than clone it.
- transferInto(element.props, this.props);
-
- return element;
- }
-
}
+
};
module.exports = ReactPropTransferer;
-}).call(this,require('_process'))
-},{"./Object.assign":70,"./emptyFunction":163,"./invariant":182,"./joinClasses":187,"./warning":202,"_process":1}],118:[function(require,module,exports){
+},{"./Object.assign":69,"./emptyFunction":170,"./joinClasses":196}],124:[function(require,module,exports){
(function (process){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -16262,7 +17432,7 @@ module.exports = ReactPropTransferer;
* @providesModule ReactPropTypeLocationNames
*/
-"use strict";
+'use strict';
var ReactPropTypeLocationNames = {};
@@ -16277,9 +17447,9 @@ if ("production" !== process.env.NODE_ENV) {
module.exports = ReactPropTypeLocationNames;
}).call(this,require('_process'))
-},{"_process":1}],119:[function(require,module,exports){
+},{"_process":1}],125:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -16289,7 +17459,7 @@ module.exports = ReactPropTypeLocationNames;
* @providesModule ReactPropTypeLocations
*/
-"use strict";
+'use strict';
var keyMirror = require("./keyMirror");
@@ -16301,9 +17471,9 @@ var ReactPropTypeLocations = keyMirror({
module.exports = ReactPropTypeLocations;
-},{"./keyMirror":188}],120:[function(require,module,exports){
+},{"./keyMirror":197}],126:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -16313,12 +17483,12 @@ module.exports = ReactPropTypeLocations;
* @providesModule ReactPropTypes
*/
-"use strict";
+'use strict';
var ReactElement = require("./ReactElement");
+var ReactFragment = require("./ReactFragment");
var ReactPropTypeLocationNames = require("./ReactPropTypeLocationNames");
-var deprecated = require("./deprecated");
var emptyFunction = require("./emptyFunction");
/**
@@ -16389,22 +17559,7 @@ var ReactPropTypes = {
objectOf: createObjectOfTypeChecker,
oneOf: createEnumTypeChecker,
oneOfType: createUnionTypeChecker,
- shape: createShapeTypeChecker,
-
- component: deprecated(
- 'React.PropTypes',
- 'component',
- 'element',
- this,
- elementTypeChecker
- ),
- renderable: deprecated(
- 'React.PropTypes',
- 'renderable',
- 'node',
- this,
- nodeTypeChecker
- )
+ shape: createShapeTypeChecker
};
function createChainableTypeChecker(validate) {
@@ -16414,10 +17569,11 @@ function createChainableTypeChecker(validate) {
var locationName = ReactPropTypeLocationNames[location];
if (isRequired) {
return new Error(
- ("Required " + locationName + " `" + propName + "` was not specified in ")+
+ ("Required " + locationName + " `" + propName + "` was not specified in ") +
("`" + componentName + "`.")
);
}
+ return null;
} else {
return validate(props, propName, componentName, location);
}
@@ -16445,12 +17601,13 @@ function createPrimitiveTypeChecker(expectedType) {
("supplied to `" + componentName + "`, expected `" + expectedType + "`.")
);
}
+ return null;
}
return createChainableTypeChecker(validate);
}
function createAnyTypeChecker() {
- return createChainableTypeChecker(emptyFunction.thatReturns());
+ return createChainableTypeChecker(emptyFunction.thatReturns(null));
}
function createArrayOfTypeChecker(typeChecker) {
@@ -16470,6 +17627,7 @@ function createArrayOfTypeChecker(typeChecker) {
return error;
}
}
+ return null;
}
return createChainableTypeChecker(validate);
}
@@ -16483,6 +17641,7 @@ function createElementTypeChecker() {
("`" + componentName + "`, expected a ReactElement.")
);
}
+ return null;
}
return createChainableTypeChecker(validate);
}
@@ -16497,6 +17656,7 @@ function createInstanceTypeChecker(expectedClass) {
("`" + componentName + "`, expected instance of `" + expectedClassName + "`.")
);
}
+ return null;
}
return createChainableTypeChecker(validate);
}
@@ -16506,7 +17666,7 @@ function createEnumTypeChecker(expectedValues) {
var propValue = props[propName];
for (var i = 0; i < expectedValues.length; i++) {
if (propValue === expectedValues[i]) {
- return;
+ return null;
}
}
@@ -16539,6 +17699,7 @@ function createObjectOfTypeChecker(typeChecker) {
}
}
}
+ return null;
}
return createChainableTypeChecker(validate);
}
@@ -16548,7 +17709,7 @@ function createUnionTypeChecker(arrayOfTypeCheckers) {
for (var i = 0; i < arrayOfTypeCheckers.length; i++) {
var checker = arrayOfTypeCheckers[i];
if (checker(props, propName, componentName, location) == null) {
- return;
+ return null;
}
}
@@ -16570,6 +17731,7 @@ function createNodeChecker() {
("`" + componentName + "`, expected a ReactNode.")
);
}
+ return null;
}
return createChainableTypeChecker(validate);
}
@@ -16595,14 +17757,16 @@ function createShapeTypeChecker(shapeTypes) {
return error;
}
}
+ return null;
}
- return createChainableTypeChecker(validate, 'expected `object`');
+ return createChainableTypeChecker(validate);
}
function isNode(propValue) {
- switch(typeof propValue) {
+ switch (typeof propValue) {
case 'number':
case 'string':
+ case 'undefined':
return true;
case 'boolean':
return !propValue;
@@ -16610,9 +17774,10 @@ function isNode(propValue) {
if (Array.isArray(propValue)) {
return propValue.every(isNode);
}
- if (ReactElement.isValidElement(propValue)) {
+ if (propValue === null || ReactElement.isValidElement(propValue)) {
return true;
}
+ propValue = ReactFragment.extractIfFragment(propValue);
for (var k in propValue) {
if (!isNode(propValue[k])) {
return false;
@@ -16655,9 +17820,9 @@ function getPreciseType(propValue) {
module.exports = ReactPropTypes;
-},{"./ReactElement":99,"./ReactPropTypeLocationNames":118,"./deprecated":162,"./emptyFunction":163}],121:[function(require,module,exports){
+},{"./ReactElement":103,"./ReactFragment":109,"./ReactPropTypeLocationNames":124,"./emptyFunction":170}],127:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -16667,7 +17832,7 @@ module.exports = ReactPropTypes;
* @providesModule ReactPutListenerQueue
*/
-"use strict";
+'use strict';
var PooledClass = require("./PooledClass");
var ReactBrowserEventEmitter = require("./ReactBrowserEventEmitter");
@@ -16711,9 +17876,9 @@ PooledClass.addPoolingTo(ReactPutListenerQueue);
module.exports = ReactPutListenerQueue;
-},{"./Object.assign":70,"./PooledClass":71,"./ReactBrowserEventEmitter":74}],122:[function(require,module,exports){
+},{"./Object.assign":69,"./PooledClass":70,"./ReactBrowserEventEmitter":73}],128:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -16724,7 +17889,7 @@ module.exports = ReactPutListenerQueue;
* @typechecks static-only
*/
-"use strict";
+'use strict';
var CallbackQueue = require("./CallbackQueue");
var PooledClass = require("./PooledClass");
@@ -16887,9 +18052,204 @@ PooledClass.addPoolingTo(ReactReconcileTransaction);
module.exports = ReactReconcileTransaction;
-},{"./CallbackQueue":48,"./Object.assign":70,"./PooledClass":71,"./ReactBrowserEventEmitter":74,"./ReactInputSelection":106,"./ReactPutListenerQueue":121,"./Transaction":149}],123:[function(require,module,exports){
+},{"./CallbackQueue":47,"./Object.assign":69,"./PooledClass":70,"./ReactBrowserEventEmitter":73,"./ReactInputSelection":111,"./ReactPutListenerQueue":127,"./Transaction":157}],129:[function(require,module,exports){
+(function (process){
+/**
+ * Copyright 2013-2015, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ * @providesModule ReactReconciler
+ */
+
+'use strict';
+
+var ReactRef = require("./ReactRef");
+var ReactElementValidator = require("./ReactElementValidator");
+
+/**
+ * Helper to call ReactRef.attachRefs with this composite component, split out
+ * to avoid allocations in the transaction mount-ready queue.
+ */
+function attachRefs() {
+ ReactRef.attachRefs(this, this._currentElement);
+}
+
+var ReactReconciler = {
+
+ /**
+ * Initializes the component, renders markup, and registers event listeners.
+ *
+ * @param {ReactComponent} internalInstance
+ * @param {string} rootID DOM ID of the root node.
+ * @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction
+ * @return {?string} Rendered markup to be inserted into the DOM.
+ * @final
+ * @internal
+ */
+ mountComponent: function(internalInstance, rootID, transaction, context) {
+ var markup = internalInstance.mountComponent(rootID, transaction, context);
+ if ("production" !== process.env.NODE_ENV) {
+ ReactElementValidator.checkAndWarnForMutatedProps(
+ internalInstance._currentElement
+ );
+ }
+ transaction.getReactMountReady().enqueue(attachRefs, internalInstance);
+ return markup;
+ },
+
+ /**
+ * Releases any resources allocated by `mountComponent`.
+ *
+ * @final
+ * @internal
+ */
+ unmountComponent: function(internalInstance) {
+ ReactRef.detachRefs(internalInstance, internalInstance._currentElement);
+ internalInstance.unmountComponent();
+ },
+
+ /**
+ * Update a component using a new element.
+ *
+ * @param {ReactComponent} internalInstance
+ * @param {ReactElement} nextElement
+ * @param {ReactReconcileTransaction} transaction
+ * @param {object} context
+ * @internal
+ */
+ receiveComponent: function(
+ internalInstance, nextElement, transaction, context
+ ) {
+ var prevElement = internalInstance._currentElement;
+
+ if (nextElement === prevElement && nextElement._owner != null) {
+ // Since elements are immutable after the owner is rendered,
+ // we can do a cheap identity compare here to determine if this is a
+ // superfluous reconcile. It's possible for state to be mutable but such
+ // change should trigger an update of the owner which would recreate
+ // the element. We explicitly check for the existence of an owner since
+ // it's possible for an element created outside a composite to be
+ // deeply mutated and reused.
+ return;
+ }
+
+ if ("production" !== process.env.NODE_ENV) {
+ ReactElementValidator.checkAndWarnForMutatedProps(nextElement);
+ }
+
+ var refsChanged = ReactRef.shouldUpdateRefs(
+ prevElement,
+ nextElement
+ );
+
+ if (refsChanged) {
+ ReactRef.detachRefs(internalInstance, prevElement);
+ }
+
+ internalInstance.receiveComponent(nextElement, transaction, context);
+
+ if (refsChanged) {
+ transaction.getReactMountReady().enqueue(attachRefs, internalInstance);
+ }
+ },
+
+ /**
+ * Flush any dirty changes in a component.
+ *
+ * @param {ReactComponent} internalInstance
+ * @param {ReactReconcileTransaction} transaction
+ * @internal
+ */
+ performUpdateIfNecessary: function(
+ internalInstance,
+ transaction
+ ) {
+ internalInstance.performUpdateIfNecessary(transaction);
+ }
+
+};
+
+module.exports = ReactReconciler;
+
+}).call(this,require('_process'))
+},{"./ReactElementValidator":104,"./ReactRef":130,"_process":1}],130:[function(require,module,exports){
+/**
+ * Copyright 2013-2015, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ * @providesModule ReactRef
+ */
+
+'use strict';
+
+var ReactOwner = require("./ReactOwner");
+
+var ReactRef = {};
+
+function attachRef(ref, component, owner) {
+ if (typeof ref === 'function') {
+ ref(component.getPublicInstance());
+ } else {
+ // Legacy ref
+ ReactOwner.addComponentAsRefTo(component, ref, owner);
+ }
+}
+
+function detachRef(ref, component, owner) {
+ if (typeof ref === 'function') {
+ ref(null);
+ } else {
+ // Legacy ref
+ ReactOwner.removeComponentAsRefFrom(component, ref, owner);
+ }
+}
+
+ReactRef.attachRefs = function(instance, element) {
+ var ref = element.ref;
+ if (ref != null) {
+ attachRef(ref, instance, element._owner);
+ }
+};
+
+ReactRef.shouldUpdateRefs = function(prevElement, nextElement) {
+ // If either the owner or a `ref` has changed, make sure the newest owner
+ // has stored a reference to `this`, and the previous owner (if different)
+ // has forgotten the reference to `this`. We use the element instead
+ // of the public this.props because the post processing cannot determine
+ // a ref. The ref conceptually lives on the element.
+
+ // TODO: Should this even be possible? The owner cannot change because
+ // it's forbidden by shouldUpdateReactComponent. The ref can change
+ // if you swap the keys of but not the refs. Reconsider where this check
+ // is made. It probably belongs where the key checking and
+ // instantiateReactComponent is done.
+
+ return (
+ nextElement._owner !== prevElement._owner ||
+ nextElement.ref !== prevElement.ref
+ );
+};
+
+ReactRef.detachRefs = function(instance, element) {
+ var ref = element.ref;
+ if (ref != null) {
+ detachRef(ref, instance, element._owner);
+ }
+};
+
+module.exports = ReactRef;
+
+},{"./ReactOwner":121}],131:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -16900,7 +18260,7 @@ module.exports = ReactReconcileTransaction;
* @typechecks
*/
-"use strict";
+'use strict';
var ReactRootIndexInjection = {
/**
@@ -16918,10 +18278,10 @@ var ReactRootIndex = {
module.exports = ReactRootIndex;
-},{}],124:[function(require,module,exports){
+},{}],132:[function(require,module,exports){
(function (process){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -16931,7 +18291,7 @@ module.exports = ReactRootIndex;
* @typechecks static-only
* @providesModule ReactServerRendering
*/
-"use strict";
+'use strict';
var ReactElement = require("./ReactElement");
var ReactInstanceHandles = require("./ReactInstanceHandles");
@@ -16939,6 +18299,7 @@ var ReactMarkupChecksum = require("./ReactMarkupChecksum");
var ReactServerRenderingTransaction =
require("./ReactServerRenderingTransaction");
+var emptyObject = require("./emptyObject");
var instantiateReactComponent = require("./instantiateReactComponent");
var invariant = require("./invariant");
@@ -16959,7 +18320,8 @@ function renderToString(element) {
return transaction.perform(function() {
var componentInstance = instantiateReactComponent(element, null);
- var markup = componentInstance.mountComponent(id, transaction, 0);
+ var markup =
+ componentInstance.mountComponent(id, transaction, emptyObject);
return ReactMarkupChecksum.addChecksumToMarkup(markup);
}, null);
} finally {
@@ -16985,7 +18347,7 @@ function renderToStaticMarkup(element) {
return transaction.perform(function() {
var componentInstance = instantiateReactComponent(element, null);
- return componentInstance.mountComponent(id, transaction, 0);
+ return componentInstance.mountComponent(id, transaction, emptyObject);
}, null);
} finally {
ReactServerRenderingTransaction.release(transaction);
@@ -16998,9 +18360,9 @@ module.exports = {
};
}).call(this,require('_process'))
-},{"./ReactElement":99,"./ReactInstanceHandles":107,"./ReactMarkupChecksum":110,"./ReactServerRenderingTransaction":125,"./instantiateReactComponent":181,"./invariant":182,"_process":1}],125:[function(require,module,exports){
+},{"./ReactElement":103,"./ReactInstanceHandles":112,"./ReactMarkupChecksum":116,"./ReactServerRenderingTransaction":133,"./emptyObject":171,"./instantiateReactComponent":190,"./invariant":191,"_process":1}],133:[function(require,module,exports){
/**
- * Copyright 2014, Facebook, Inc.
+ * Copyright 2014-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -17011,7 +18373,7 @@ module.exports = {
* @typechecks
*/
-"use strict";
+'use strict';
var PooledClass = require("./PooledClass");
var CallbackQueue = require("./CallbackQueue");
@@ -17111,9 +18473,9 @@ PooledClass.addPoolingTo(ReactServerRenderingTransaction);
module.exports = ReactServerRenderingTransaction;
-},{"./CallbackQueue":48,"./Object.assign":70,"./PooledClass":71,"./ReactPutListenerQueue":121,"./Transaction":149,"./emptyFunction":163}],126:[function(require,module,exports){
+},{"./CallbackQueue":47,"./Object.assign":69,"./PooledClass":70,"./ReactPutListenerQueue":127,"./Transaction":157,"./emptyFunction":170}],134:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -17123,7 +18485,7 @@ module.exports = ReactServerRenderingTransaction;
* @providesModule ReactStateSetters
*/
-"use strict";
+'use strict';
var ReactStateSetters = {
/**
@@ -17217,9 +18579,9 @@ ReactStateSetters.Mixin = {
module.exports = ReactStateSetters;
-},{}],127:[function(require,module,exports){
+},{}],135:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -17229,16 +18591,19 @@ module.exports = ReactStateSetters;
* @providesModule ReactTestUtils
*/
-"use strict";
+'use strict';
var EventConstants = require("./EventConstants");
var EventPluginHub = require("./EventPluginHub");
var EventPropagators = require("./EventPropagators");
var React = require("./React");
var ReactElement = require("./ReactElement");
+var ReactEmptyComponent = require("./ReactEmptyComponent");
var ReactBrowserEventEmitter = require("./ReactBrowserEventEmitter");
+var ReactCompositeComponent = require("./ReactCompositeComponent");
+var ReactInstanceHandles = require("./ReactInstanceHandles");
+var ReactInstanceMap = require("./ReactInstanceMap");
var ReactMount = require("./ReactMount");
-var ReactTextComponent = require("./ReactTextComponent");
var ReactUpdates = require("./ReactUpdates");
var SyntheticEvent = require("./SyntheticEvent");
@@ -17275,12 +18640,14 @@ var ReactTestUtils = {
isElementOfType: function(inst, convenienceConstructor) {
return (
ReactElement.isValidElement(inst) &&
- inst.type === convenienceConstructor.type
+ inst.type === convenienceConstructor
);
},
isDOMComponent: function(inst) {
- return !!(inst && inst.mountComponent && inst.tagName);
+ // TODO: Fix this heuristic. It's just here because composites can currently
+ // pretend to be DOM components.
+ return !!(inst && inst.tagName && inst.getDOMNode);
},
isDOMComponentElement: function(inst) {
@@ -17296,7 +18663,7 @@ var ReactTestUtils = {
isCompositeComponentWithType: function(inst, type) {
return !!(ReactTestUtils.isCompositeComponent(inst) &&
- (inst.constructor === type.type));
+ (inst.constructor === type));
},
isCompositeComponentElement: function(inst) {
@@ -17317,8 +18684,12 @@ var ReactTestUtils = {
(inst.constructor === type));
},
- isTextComponent: function(inst) {
- return inst instanceof ReactTextComponent.type;
+ getRenderedChildOfCompositeComponent: function(inst) {
+ if (!ReactTestUtils.isCompositeComponent(inst)) {
+ return null;
+ }
+ var internalInstance = ReactInstanceMap.get(inst);
+ return internalInstance._renderedComponent.getPublicInstance();
},
findAllInRenderedTree: function(inst, test) {
@@ -17327,19 +18698,31 @@ var ReactTestUtils = {
}
var ret = test(inst) ? [inst] : [];
if (ReactTestUtils.isDOMComponent(inst)) {
- var renderedChildren = inst._renderedChildren;
+ var internalInstance = ReactInstanceMap.get(inst);
+ var renderedChildren = internalInstance
+ ._renderedComponent
+ ._renderedChildren;
var key;
for (key in renderedChildren) {
if (!renderedChildren.hasOwnProperty(key)) {
continue;
}
+ if (!renderedChildren[key].getPublicInstance) {
+ continue;
+ }
ret = ret.concat(
- ReactTestUtils.findAllInRenderedTree(renderedChildren[key], test)
+ ReactTestUtils.findAllInRenderedTree(
+ renderedChildren[key].getPublicInstance(),
+ test
+ )
);
}
} else if (ReactTestUtils.isCompositeComponent(inst)) {
ret = ret.concat(
- ReactTestUtils.findAllInRenderedTree(inst._renderedComponent, test)
+ ReactTestUtils.findAllInRenderedTree(
+ ReactTestUtils.getRenderedChildOfCompositeComponent(inst),
+ test
+ )
);
}
return ret;
@@ -17354,8 +18737,7 @@ var ReactTestUtils = {
return ReactTestUtils.findAllInRenderedTree(root, function(inst) {
var instClassName = inst.props.className;
return ReactTestUtils.isDOMComponent(inst) && (
- instClassName &&
- (' ' + instClassName + ' ').indexOf(' ' + className + ' ') !== -1
+ (instClassName && (' ' + instClassName + ' ').indexOf(' ' + className + ' ') !== -1)
);
});
},
@@ -17370,7 +18752,9 @@ var ReactTestUtils = {
var all =
ReactTestUtils.scryRenderedDOMComponentsWithClass(root, className);
if (all.length !== 1) {
- throw new Error('Did not find exactly one match for class:' + className);
+ throw new Error('Did not find exactly one match ' +
+ '(found: ' + all.length + ') for class:' + className
+ );
}
return all[0];
},
@@ -17451,21 +18835,14 @@ var ReactTestUtils = {
mockComponent: function(module, mockTagName) {
mockTagName = mockTagName || module.mockTagName || "div";
- var ConvenienceConstructor = React.createClass({displayName: 'ConvenienceConstructor',
- render: function() {
- return React.createElement(
- mockTagName,
- null,
- this.props.children
- );
- }
+ module.prototype.render.mockImplementation(function() {
+ return React.createElement(
+ mockTagName,
+ null,
+ this.props.children
+ );
});
- module.mockImplementation(ConvenienceConstructor);
-
- module.type = ConvenienceConstructor.type;
- module.isReactLegacyFactory = true;
-
return this;
},
@@ -17510,11 +18887,94 @@ var ReactTestUtils = {
};
},
+ createRenderer: function() {
+ return new ReactShallowRenderer();
+ },
+
Simulate: null,
SimulateNative: {}
};
/**
+ * @class ReactShallowRenderer
+ */
+var ReactShallowRenderer = function() {
+ this._instance = null;
+};
+
+ReactShallowRenderer.prototype.getRenderOutput = function() {
+ return (
+ (this._instance && this._instance._renderedComponent &&
+ this._instance._renderedComponent._renderedOutput)
+ || null
+ );
+};
+
+var NoopInternalComponent = function(element) {
+ this._renderedOutput = element;
+ this._currentElement = element === null || element === false ?
+ ReactEmptyComponent.emptyElement :
+ element;
+};
+
+NoopInternalComponent.prototype = {
+
+ mountComponent: function() {
+ },
+
+ receiveComponent: function(element) {
+ this._renderedOutput = element;
+ this._currentElement = element === null || element === false ?
+ ReactEmptyComponent.emptyElement :
+ element;
+ },
+
+ unmountComponent: function() {
+ }
+
+};
+
+var ShallowComponentWrapper = function() { };
+assign(
+ ShallowComponentWrapper.prototype,
+ ReactCompositeComponent.Mixin, {
+ _instantiateReactComponent: function(element) {
+ return new NoopInternalComponent(element);
+ },
+ _replaceNodeWithMarkupByID: function() {},
+ _renderValidatedComponent:
+ ReactCompositeComponent.Mixin.
+ _renderValidatedComponentWithoutOwnerOrContext
+ }
+);
+
+ReactShallowRenderer.prototype.render = function(element, context) {
+ var transaction = ReactUpdates.ReactReconcileTransaction.getPooled();
+ this._render(element, transaction, context);
+ ReactUpdates.ReactReconcileTransaction.release(transaction);
+};
+
+ReactShallowRenderer.prototype.unmount = function() {
+ if (this._instance) {
+ this._instance.unmountComponent();
+ }
+};
+
+ReactShallowRenderer.prototype._render = function(element, transaction, context) {
+ if (!this._instance) {
+ var rootID = ReactInstanceHandles.createReactRootID();
+ var instance = new ShallowComponentWrapper(element.type);
+ instance.construct(element);
+
+ instance.mountComponent(rootID, transaction, context);
+
+ this._instance = instance;
+ } else {
+ this._instance.receiveComponent(element, transaction, context);
+ }
+};
+
+/**
* Exports:
*
* - `ReactTestUtils.Simulate.click(Element/ReactDOMComponent)`
@@ -17629,115 +19089,9 @@ for (eventType in topLevelTypes) {
module.exports = ReactTestUtils;
-},{"./EventConstants":58,"./EventPluginHub":60,"./EventPropagators":63,"./Object.assign":70,"./React":72,"./ReactBrowserEventEmitter":74,"./ReactElement":99,"./ReactMount":111,"./ReactTextComponent":128,"./ReactUpdates":132,"./SyntheticEvent":141}],128:[function(require,module,exports){
+},{"./EventConstants":56,"./EventPluginHub":58,"./EventPropagators":61,"./Object.assign":69,"./React":71,"./ReactBrowserEventEmitter":73,"./ReactCompositeComponent":83,"./ReactElement":103,"./ReactEmptyComponent":105,"./ReactInstanceHandles":112,"./ReactInstanceMap":113,"./ReactMount":117,"./ReactUpdates":140,"./SyntheticEvent":149}],136:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
- * All rights reserved.
- *
- * This source code is licensed under the BSD-style license found in the
- * LICENSE file in the root directory of this source tree. An additional grant
- * of patent rights can be found in the PATENTS file in the same directory.
- *
- * @providesModule ReactTextComponent
- * @typechecks static-only
- */
-
-"use strict";
-
-var DOMPropertyOperations = require("./DOMPropertyOperations");
-var ReactComponent = require("./ReactComponent");
-var ReactElement = require("./ReactElement");
-
-var assign = require("./Object.assign");
-var escapeTextForBrowser = require("./escapeTextForBrowser");
-
-/**
- * Text nodes violate a couple assumptions that React makes about components:
- *
- * - When mounting text into the DOM, adjacent text nodes are merged.
- * - Text nodes cannot be assigned a React root ID.
- *
- * This component is used to wrap strings in elements so that they can undergo
- * the same reconciliation that is applied to elements.
- *
- * TODO: Investigate representing React components in the DOM with text nodes.
- *
- * @class ReactTextComponent
- * @extends ReactComponent
- * @internal
- */
-var ReactTextComponent = function(props) {
- // This constructor and it's argument is currently used by mocks.
-};
-
-assign(ReactTextComponent.prototype, ReactComponent.Mixin, {
-
- /**
- * Creates the markup for this text node. This node is not intended to have
- * any features besides containing text content.
- *
- * @param {string} rootID DOM ID of the root node.
- * @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction
- * @param {number} mountDepth number of components in the owner hierarchy
- * @return {string} Markup for this text node.
- * @internal
- */
- mountComponent: function(rootID, transaction, mountDepth) {
- ReactComponent.Mixin.mountComponent.call(
- this,
- rootID,
- transaction,
- mountDepth
- );
-
- var escapedText = escapeTextForBrowser(this.props);
-
- if (transaction.renderToStaticMarkup) {
- // Normally we'd wrap this in a `span` for the reasons stated above, but
- // since this is a situation where React won't take over (static pages),
- // we can simply return the text as it is.
- return escapedText;
- }
-
- return (
- '<span ' + DOMPropertyOperations.createMarkupForID(rootID) + '>' +
- escapedText +
- '</span>'
- );
- },
-
- /**
- * Updates this component by updating the text content.
- *
- * @param {object} nextComponent Contains the next text content.
- * @param {ReactReconcileTransaction} transaction
- * @internal
- */
- receiveComponent: function(nextComponent, transaction) {
- var nextProps = nextComponent.props;
- if (nextProps !== this.props) {
- this.props = nextProps;
- ReactComponent.BackendIDOperations.updateTextContentByID(
- this._rootNodeID,
- nextProps
- );
- }
- }
-
-});
-
-var ReactTextComponentFactory = function(text) {
- // Bypass validation and configuration
- return new ReactElement(ReactTextComponent, null, null, null, null, text);
-};
-
-ReactTextComponentFactory.type = ReactTextComponent;
-
-module.exports = ReactTextComponentFactory;
-
-},{"./DOMPropertyOperations":54,"./Object.assign":70,"./ReactComponent":78,"./ReactElement":99,"./escapeTextForBrowser":165}],129:[function(require,module,exports){
-/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -17748,9 +19102,10 @@ module.exports = ReactTextComponentFactory;
* @providesModule ReactTransitionChildMapping
*/
-"use strict";
+'use strict';
var ReactChildren = require("./ReactChildren");
+var ReactFragment = require("./ReactFragment");
var ReactTransitionChildMapping = {
/**
@@ -17761,9 +19116,12 @@ var ReactTransitionChildMapping = {
* @return {object} Mapping of key to child
*/
getChildMapping: function(children) {
- return ReactChildren.map(children, function(child) {
+ if (!children) {
+ return children;
+ }
+ return ReactFragment.extract(ReactChildren.map(children, function(child) {
return child;
- });
+ }));
},
/**
@@ -17836,9 +19194,9 @@ var ReactTransitionChildMapping = {
module.exports = ReactTransitionChildMapping;
-},{"./ReactChildren":77}],130:[function(require,module,exports){
+},{"./ReactChildren":77,"./ReactFragment":109}],137:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -17848,7 +19206,7 @@ module.exports = ReactTransitionChildMapping;
* @providesModule ReactTransitionEvents
*/
-"use strict";
+'use strict';
var ExecutionEnvironment = require("./ExecutionEnvironment");
@@ -17947,9 +19305,9 @@ var ReactTransitionEvents = {
module.exports = ReactTransitionEvents;
-},{"./ExecutionEnvironment":64}],131:[function(require,module,exports){
+},{"./ExecutionEnvironment":62}],138:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -17959,7 +19317,7 @@ module.exports = ReactTransitionEvents;
* @providesModule ReactTransitionGroup
*/
-"use strict";
+'use strict';
var React = require("./React");
var ReactTransitionChildMapping = require("./ReactTransitionChildMapping");
@@ -17989,6 +19347,21 @@ var ReactTransitionGroup = React.createClass({
};
},
+ componentWillMount: function() {
+ this.currentlyTransitioningKeys = {};
+ this.keysToEnter = [];
+ this.keysToLeave = [];
+ },
+
+ componentDidMount: function() {
+ var initialChildMapping = this.state.children;
+ for (var key in initialChildMapping) {
+ if (initialChildMapping[key]) {
+ this.performAppear(key);
+ }
+ }
+ },
+
componentWillReceiveProps: function(nextProps) {
var nextChildMapping = ReactTransitionChildMapping.getChildMapping(
nextProps.children
@@ -18023,12 +19396,6 @@ var ReactTransitionGroup = React.createClass({
// If we want to someday check for reordering, we could do it here.
},
- componentWillMount: function() {
- this.currentlyTransitioningKeys = {};
- this.keysToEnter = [];
- this.keysToLeave = [];
- },
-
componentDidUpdate: function() {
var keysToEnter = this.keysToEnter;
this.keysToEnter = [];
@@ -18039,6 +19406,38 @@ var ReactTransitionGroup = React.createClass({
keysToLeave.forEach(this.performLeave);
},
+ performAppear: function(key) {
+ this.currentlyTransitioningKeys[key] = true;
+
+ var component = this.refs[key];
+
+ if (component.componentWillAppear) {
+ component.componentWillAppear(
+ this._handleDoneAppearing.bind(this, key)
+ );
+ } else {
+ this._handleDoneAppearing(key);
+ }
+ },
+
+ _handleDoneAppearing: function(key) {
+ var component = this.refs[key];
+ if (component.componentDidAppear) {
+ component.componentDidAppear();
+ }
+
+ delete this.currentlyTransitioningKeys[key];
+
+ var currentChildMapping = ReactTransitionChildMapping.getChildMapping(
+ this.props.children
+ );
+
+ if (!currentChildMapping || !currentChildMapping.hasOwnProperty(key)) {
+ // This was removed before it had fully appeared. Remove it.
+ this.performLeave(key);
+ }
+ },
+
performEnter: function(key) {
this.currentlyTransitioningKeys[key] = true;
@@ -18111,7 +19510,7 @@ var ReactTransitionGroup = React.createClass({
render: function() {
// TODO: we could get rid of the need for the wrapper node
// by cloning a single child
- var childrenToRender = {};
+ var childrenToRender = [];
for (var key in this.state.children) {
var child = this.state.children[key];
if (child) {
@@ -18120,10 +19519,10 @@ var ReactTransitionGroup = React.createClass({
// already been removed. In case you need this behavior you can provide
// a childFactory function to wrap every child, even the ones that are
// leaving.
- childrenToRender[key] = cloneWithProps(
+ childrenToRender.push(cloneWithProps(
this.props.childFactory(child),
- {ref: key}
- );
+ {ref: key, key: key}
+ ));
}
}
return React.createElement(
@@ -18136,10 +19535,309 @@ var ReactTransitionGroup = React.createClass({
module.exports = ReactTransitionGroup;
-},{"./Object.assign":70,"./React":72,"./ReactTransitionChildMapping":129,"./cloneWithProps":155,"./emptyFunction":163}],132:[function(require,module,exports){
+},{"./Object.assign":69,"./React":71,"./ReactTransitionChildMapping":136,"./cloneWithProps":163,"./emptyFunction":170}],139:[function(require,module,exports){
(function (process){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2015, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ * @providesModule ReactUpdateQueue
+ */
+
+'use strict';
+
+var ReactLifeCycle = require("./ReactLifeCycle");
+var ReactCurrentOwner = require("./ReactCurrentOwner");
+var ReactElement = require("./ReactElement");
+var ReactInstanceMap = require("./ReactInstanceMap");
+var ReactUpdates = require("./ReactUpdates");
+
+var assign = require("./Object.assign");
+var invariant = require("./invariant");
+var warning = require("./warning");
+
+function enqueueUpdate(internalInstance) {
+ if (internalInstance !== ReactLifeCycle.currentlyMountingInstance) {
+ // If we're in a componentWillMount handler, don't enqueue a rerender
+ // because ReactUpdates assumes we're in a browser context (which is
+ // wrong for server rendering) and we're about to do a render anyway.
+ // See bug in #1740.
+ ReactUpdates.enqueueUpdate(internalInstance);
+ }
+}
+
+function getInternalInstanceReadyForUpdate(publicInstance, callerName) {
+ ("production" !== process.env.NODE_ENV ? invariant(
+ ReactCurrentOwner.current == null,
+ '%s(...): Cannot update during an existing state transition ' +
+ '(such as within `render`). Render methods should be a pure function ' +
+ 'of props and state.',
+ callerName
+ ) : invariant(ReactCurrentOwner.current == null));
+
+ var internalInstance = ReactInstanceMap.get(publicInstance);
+ if (!internalInstance) {
+ if ("production" !== process.env.NODE_ENV) {
+ // Only warn when we have a callerName. Otherwise we should be silent.
+ // We're probably calling from enqueueCallback. We don't want to warn
+ // there because we already warned for the corresponding lifecycle method.
+ ("production" !== process.env.NODE_ENV ? warning(
+ !callerName,
+ '%s(...): Can only update a mounted or mounting component. ' +
+ 'This usually means you called %s() on an unmounted ' +
+ 'component. This is a no-op.',
+ callerName,
+ callerName
+ ) : null);
+ }
+ return null;
+ }
+
+ if (internalInstance === ReactLifeCycle.currentlyUnmountingInstance) {
+ return null;
+ }
+
+ return internalInstance;
+}
+
+/**
+ * ReactUpdateQueue allows for state updates to be scheduled into a later
+ * reconciliation step.
+ */
+var ReactUpdateQueue = {
+
+ /**
+ * Enqueue a callback that will be executed after all the pending updates
+ * have processed.
+ *
+ * @param {ReactClass} publicInstance The instance to use as `this` context.
+ * @param {?function} callback Called after state is updated.
+ * @internal
+ */
+ enqueueCallback: function(publicInstance, callback) {
+ ("production" !== process.env.NODE_ENV ? invariant(
+ typeof callback === 'function',
+ 'enqueueCallback(...): You called `setProps`, `replaceProps`, ' +
+ '`setState`, `replaceState`, or `forceUpdate` with a callback that ' +
+ 'isn\'t callable.'
+ ) : invariant(typeof callback === 'function'));
+ var internalInstance = getInternalInstanceReadyForUpdate(publicInstance);
+
+ // Previously we would throw an error if we didn't have an internal
+ // instance. Since we want to make it a no-op instead, we mirror the same
+ // behavior we have in other enqueue* methods.
+ // We also need to ignore callbacks in componentWillMount. See
+ // enqueueUpdates.
+ if (!internalInstance ||
+ internalInstance === ReactLifeCycle.currentlyMountingInstance) {
+ return null;
+ }
+
+ if (internalInstance._pendingCallbacks) {
+ internalInstance._pendingCallbacks.push(callback);
+ } else {
+ internalInstance._pendingCallbacks = [callback];
+ }
+ // TODO: The callback here is ignored when setState is called from
+ // componentWillMount. Either fix it or disallow doing so completely in
+ // favor of getInitialState. Alternatively, we can disallow
+ // componentWillMount during server-side rendering.
+ enqueueUpdate(internalInstance);
+ },
+
+ enqueueCallbackInternal: function(internalInstance, callback) {
+ ("production" !== process.env.NODE_ENV ? invariant(
+ typeof callback === 'function',
+ 'enqueueCallback(...): You called `setProps`, `replaceProps`, ' +
+ '`setState`, `replaceState`, or `forceUpdate` with a callback that ' +
+ 'isn\'t callable.'
+ ) : invariant(typeof callback === 'function'));
+ if (internalInstance._pendingCallbacks) {
+ internalInstance._pendingCallbacks.push(callback);
+ } else {
+ internalInstance._pendingCallbacks = [callback];
+ }
+ enqueueUpdate(internalInstance);
+ },
+
+ /**
+ * Forces an update. This should only be invoked when it is known with
+ * certainty that we are **not** in a DOM transaction.
+ *
+ * You may want to call this when you know that some deeper aspect of the
+ * component's state has changed but `setState` was not called.
+ *
+ * This will not invoke `shouldUpdateComponent`, but it will invoke
+ * `componentWillUpdate` and `componentDidUpdate`.
+ *
+ * @param {ReactClass} publicInstance The instance that should rerender.
+ * @internal
+ */
+ enqueueForceUpdate: function(publicInstance) {
+ var internalInstance = getInternalInstanceReadyForUpdate(
+ publicInstance,
+ 'forceUpdate'
+ );
+
+ if (!internalInstance) {
+ return;
+ }
+
+ internalInstance._pendingForceUpdate = true;
+
+ enqueueUpdate(internalInstance);
+ },
+
+ /**
+ * Replaces all of the state. Always use this or `setState` to mutate state.
+ * You should treat `this.state` as immutable.
+ *
+ * There is no guarantee that `this.state` will be immediately updated, so
+ * accessing `this.state` after calling this method may return the old value.
+ *
+ * @param {ReactClass} publicInstance The instance that should rerender.
+ * @param {object} completeState Next state.
+ * @internal
+ */
+ enqueueReplaceState: function(publicInstance, completeState) {
+ var internalInstance = getInternalInstanceReadyForUpdate(
+ publicInstance,
+ 'replaceState'
+ );
+
+ if (!internalInstance) {
+ return;
+ }
+
+ internalInstance._pendingStateQueue = [completeState];
+ internalInstance._pendingReplaceState = true;
+
+ enqueueUpdate(internalInstance);
+ },
+
+ /**
+ * Sets a subset of the state. This only exists because _pendingState is
+ * internal. This provides a merging strategy that is not available to deep
+ * properties which is confusing. TODO: Expose pendingState or don't use it
+ * during the merge.
+ *
+ * @param {ReactClass} publicInstance The instance that should rerender.
+ * @param {object} partialState Next partial state to be merged with state.
+ * @internal
+ */
+ enqueueSetState: function(publicInstance, partialState) {
+ var internalInstance = getInternalInstanceReadyForUpdate(
+ publicInstance,
+ 'setState'
+ );
+
+ if (!internalInstance) {
+ return;
+ }
+
+ var queue =
+ internalInstance._pendingStateQueue ||
+ (internalInstance._pendingStateQueue = []);
+ queue.push(partialState);
+
+ enqueueUpdate(internalInstance);
+ },
+
+ /**
+ * Sets a subset of the props.
+ *
+ * @param {ReactClass} publicInstance The instance that should rerender.
+ * @param {object} partialProps Subset of the next props.
+ * @internal
+ */
+ enqueueSetProps: function(publicInstance, partialProps) {
+ var internalInstance = getInternalInstanceReadyForUpdate(
+ publicInstance,
+ 'setProps'
+ );
+
+ if (!internalInstance) {
+ return;
+ }
+
+ ("production" !== process.env.NODE_ENV ? invariant(
+ internalInstance._isTopLevel,
+ 'setProps(...): You called `setProps` on a ' +
+ 'component with a parent. This is an anti-pattern since props will ' +
+ 'get reactively updated when rendered. Instead, change the owner\'s ' +
+ '`render` method to pass the correct value as props to the component ' +
+ 'where it is created.'
+ ) : invariant(internalInstance._isTopLevel));
+
+ // Merge with the pending element if it exists, otherwise with existing
+ // element props.
+ var element = internalInstance._pendingElement ||
+ internalInstance._currentElement;
+ var props = assign({}, element.props, partialProps);
+ internalInstance._pendingElement = ReactElement.cloneAndReplaceProps(
+ element,
+ props
+ );
+
+ enqueueUpdate(internalInstance);
+ },
+
+ /**
+ * Replaces all of the props.
+ *
+ * @param {ReactClass} publicInstance The instance that should rerender.
+ * @param {object} props New props.
+ * @internal
+ */
+ enqueueReplaceProps: function(publicInstance, props) {
+ var internalInstance = getInternalInstanceReadyForUpdate(
+ publicInstance,
+ 'replaceProps'
+ );
+
+ if (!internalInstance) {
+ return;
+ }
+
+ ("production" !== process.env.NODE_ENV ? invariant(
+ internalInstance._isTopLevel,
+ 'replaceProps(...): You called `replaceProps` on a ' +
+ 'component with a parent. This is an anti-pattern since props will ' +
+ 'get reactively updated when rendered. Instead, change the owner\'s ' +
+ '`render` method to pass the correct value as props to the component ' +
+ 'where it is created.'
+ ) : invariant(internalInstance._isTopLevel));
+
+ // Merge with the pending element if it exists, otherwise with existing
+ // element props.
+ var element = internalInstance._pendingElement ||
+ internalInstance._currentElement;
+ internalInstance._pendingElement = ReactElement.cloneAndReplaceProps(
+ element,
+ props
+ );
+
+ enqueueUpdate(internalInstance);
+ },
+
+ enqueueElementInternal: function(internalInstance, newElement) {
+ internalInstance._pendingElement = newElement;
+ enqueueUpdate(internalInstance);
+ }
+
+};
+
+module.exports = ReactUpdateQueue;
+
+}).call(this,require('_process'))
+},{"./Object.assign":69,"./ReactCurrentOwner":85,"./ReactElement":103,"./ReactInstanceMap":113,"./ReactLifeCycle":114,"./ReactUpdates":140,"./invariant":191,"./warning":212,"_process":1}],140:[function(require,module,exports){
+(function (process){
+/**
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -18149,12 +19847,13 @@ module.exports = ReactTransitionGroup;
* @providesModule ReactUpdates
*/
-"use strict";
+'use strict';
var CallbackQueue = require("./CallbackQueue");
var PooledClass = require("./PooledClass");
var ReactCurrentOwner = require("./ReactCurrentOwner");
var ReactPerf = require("./ReactPerf");
+var ReactReconciler = require("./ReactReconciler");
var Transaction = require("./Transaction");
var assign = require("./Object.assign");
@@ -18244,20 +19943,20 @@ assign(
PooledClass.addPoolingTo(ReactUpdatesFlushTransaction);
-function batchedUpdates(callback, a, b) {
+function batchedUpdates(callback, a, b, c, d) {
ensureInjected();
- batchingStrategy.batchedUpdates(callback, a, b);
+ batchingStrategy.batchedUpdates(callback, a, b, c, d);
}
/**
- * Array comparator for ReactComponents by owner depth
+ * Array comparator for ReactComponents by mount ordering.
*
* @param {ReactComponent} c1 first component you're comparing
* @param {ReactComponent} c2 second component you're comparing
* @return {number} Return value usable by Array.prototype.sort().
*/
-function mountDepthComparator(c1, c2) {
- return c1._mountDepth - c2._mountDepth;
+function mountOrderComparator(c1, c2) {
+ return c1._mountOrder - c2._mountOrder;
}
function runBatchedUpdates(transaction) {
@@ -18273,69 +19972,68 @@ function runBatchedUpdates(transaction) {
// Since reconciling a component higher in the owner hierarchy usually (not
// always -- see shouldComponentUpdate()) will reconcile children, reconcile
// them before their children by sorting the array.
- dirtyComponents.sort(mountDepthComparator);
+ dirtyComponents.sort(mountOrderComparator);
for (var i = 0; i < len; i++) {
- // If a component is unmounted before pending changes apply, ignore them
- // TODO: Queue unmounts in the same list to avoid this happening at all
+ // If a component is unmounted before pending changes apply, it will still
+ // be here, but we assume that it has cleared its _pendingCallbacks and
+ // that performUpdateIfNecessary is a noop.
var component = dirtyComponents[i];
- if (component.isMounted()) {
- // If performUpdateIfNecessary happens to enqueue any new updates, we
- // shouldn't execute the callbacks until the next render happens, so
- // stash the callbacks first
- var callbacks = component._pendingCallbacks;
- component._pendingCallbacks = null;
- component.performUpdateIfNecessary(transaction.reconcileTransaction);
-
- if (callbacks) {
- for (var j = 0; j < callbacks.length; j++) {
- transaction.callbackQueue.enqueue(
- callbacks[j],
- component
- );
- }
+
+ // If performUpdateIfNecessary happens to enqueue any new updates, we
+ // shouldn't execute the callbacks until the next render happens, so
+ // stash the callbacks first
+ var callbacks = component._pendingCallbacks;
+ component._pendingCallbacks = null;
+
+ ReactReconciler.performUpdateIfNecessary(
+ component,
+ transaction.reconcileTransaction
+ );
+
+ if (callbacks) {
+ for (var j = 0; j < callbacks.length; j++) {
+ transaction.callbackQueue.enqueue(
+ callbacks[j],
+ component.getPublicInstance()
+ );
}
}
}
}
-var flushBatchedUpdates = ReactPerf.measure(
- 'ReactUpdates',
- 'flushBatchedUpdates',
- function() {
- // ReactUpdatesFlushTransaction's wrappers will clear the dirtyComponents
- // array and perform any updates enqueued by mount-ready handlers (i.e.,
- // componentDidUpdate) but we need to check here too in order to catch
- // updates enqueued by setState callbacks and asap calls.
- while (dirtyComponents.length || asapEnqueued) {
- if (dirtyComponents.length) {
- var transaction = ReactUpdatesFlushTransaction.getPooled();
- transaction.perform(runBatchedUpdates, null, transaction);
- ReactUpdatesFlushTransaction.release(transaction);
- }
+var flushBatchedUpdates = function() {
+ // ReactUpdatesFlushTransaction's wrappers will clear the dirtyComponents
+ // array and perform any updates enqueued by mount-ready handlers (i.e.,
+ // componentDidUpdate) but we need to check here too in order to catch
+ // updates enqueued by setState callbacks and asap calls.
+ while (dirtyComponents.length || asapEnqueued) {
+ if (dirtyComponents.length) {
+ var transaction = ReactUpdatesFlushTransaction.getPooled();
+ transaction.perform(runBatchedUpdates, null, transaction);
+ ReactUpdatesFlushTransaction.release(transaction);
+ }
- if (asapEnqueued) {
- asapEnqueued = false;
- var queue = asapCallbackQueue;
- asapCallbackQueue = CallbackQueue.getPooled();
- queue.notifyAll();
- CallbackQueue.release(queue);
- }
+ if (asapEnqueued) {
+ asapEnqueued = false;
+ var queue = asapCallbackQueue;
+ asapCallbackQueue = CallbackQueue.getPooled();
+ queue.notifyAll();
+ CallbackQueue.release(queue);
}
}
+};
+flushBatchedUpdates = ReactPerf.measure(
+ 'ReactUpdates',
+ 'flushBatchedUpdates',
+ flushBatchedUpdates
);
/**
* Mark a component as needing a rerender, adding an optional callback to a
* list of functions which will be executed once the rerender occurs.
*/
-function enqueueUpdate(component, callback) {
- ("production" !== process.env.NODE_ENV ? invariant(
- !callback || typeof callback === "function",
- 'enqueueUpdate(...): You called `setProps`, `replaceProps`, ' +
- '`setState`, `replaceState`, or `forceUpdate` with a callback that ' +
- 'isn\'t callable.'
- ) : invariant(!callback || typeof callback === "function"));
+function enqueueUpdate(component) {
ensureInjected();
// Various parts of our code (such as ReactCompositeComponent's
@@ -18352,19 +20050,11 @@ function enqueueUpdate(component, callback) {
) : null);
if (!batchingStrategy.isBatchingUpdates) {
- batchingStrategy.batchedUpdates(enqueueUpdate, component, callback);
+ batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
dirtyComponents.push(component);
-
- if (callback) {
- if (component._pendingCallbacks) {
- component._pendingCallbacks.push(callback);
- } else {
- component._pendingCallbacks = [callback];
- }
- }
}
/**
@@ -18426,10 +20116,10 @@ var ReactUpdates = {
module.exports = ReactUpdates;
}).call(this,require('_process'))
-},{"./CallbackQueue":48,"./Object.assign":70,"./PooledClass":71,"./ReactCurrentOwner":83,"./ReactPerf":116,"./Transaction":149,"./invariant":182,"./warning":202,"_process":1}],133:[function(require,module,exports){
+},{"./CallbackQueue":47,"./Object.assign":69,"./PooledClass":70,"./ReactCurrentOwner":85,"./ReactPerf":122,"./ReactReconciler":129,"./Transaction":157,"./invariant":191,"./warning":212,"_process":1}],141:[function(require,module,exports){
(function (process){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -18446,13 +20136,14 @@ module.exports = ReactUpdates;
* place to live inside React core.
*/
-"use strict";
+'use strict';
var LinkedStateMixin = require("./LinkedStateMixin");
var React = require("./React");
var ReactComponentWithPureRenderMixin =
require("./ReactComponentWithPureRenderMixin");
var ReactCSSTransitionGroup = require("./ReactCSSTransitionGroup");
+var ReactFragment = require("./ReactFragment");
var ReactTransitionGroup = require("./ReactTransitionGroup");
var ReactUpdates = require("./ReactUpdates");
@@ -18469,6 +20160,7 @@ React.addons = {
batchedUpdates: ReactUpdates.batchedUpdates,
classSet: cx,
cloneWithProps: cloneWithProps,
+ createFragment: ReactFragment.create,
update: update
};
@@ -18480,9 +20172,9 @@ if ("production" !== process.env.NODE_ENV) {
module.exports = React;
}).call(this,require('_process'))
-},{"./LinkedStateMixin":66,"./React":72,"./ReactCSSTransitionGroup":75,"./ReactComponentWithPureRenderMixin":80,"./ReactDefaultPerf":97,"./ReactTestUtils":127,"./ReactTransitionGroup":131,"./ReactUpdates":132,"./cloneWithProps":155,"./cx":160,"./update":201,"_process":1}],134:[function(require,module,exports){
+},{"./LinkedStateMixin":65,"./React":71,"./ReactCSSTransitionGroup":74,"./ReactComponentWithPureRenderMixin":82,"./ReactDefaultPerf":101,"./ReactFragment":109,"./ReactTestUtils":135,"./ReactTransitionGroup":138,"./ReactUpdates":140,"./cloneWithProps":163,"./cx":168,"./update":211,"_process":1}],142:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -18494,7 +20186,7 @@ module.exports = React;
/*jslint bitwise: true*/
-"use strict";
+'use strict';
var DOMProperty = require("./DOMProperty");
@@ -18572,9 +20264,9 @@ var SVGDOMPropertyConfig = {
module.exports = SVGDOMPropertyConfig;
-},{"./DOMProperty":53}],135:[function(require,module,exports){
+},{"./DOMProperty":51}],143:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -18584,7 +20276,7 @@ module.exports = SVGDOMPropertyConfig;
* @providesModule SelectEventPlugin
*/
-"use strict";
+'use strict';
var EventConstants = require("./EventConstants");
var EventPropagators = require("./EventPropagators");
@@ -18669,8 +20361,8 @@ function constructSelectEvent(nativeEvent) {
// won't dispatch.
if (mouseDown ||
activeElement == null ||
- activeElement != getActiveElement()) {
- return;
+ activeElement !== getActiveElement()) {
+ return null;
}
// Only fire when selection has actually changed.
@@ -18767,9 +20459,9 @@ var SelectEventPlugin = {
module.exports = SelectEventPlugin;
-},{"./EventConstants":58,"./EventPropagators":63,"./ReactInputSelection":106,"./SyntheticEvent":141,"./getActiveElement":169,"./isTextInputElement":185,"./keyOf":189,"./shallowEqual":197}],136:[function(require,module,exports){
+},{"./EventConstants":56,"./EventPropagators":61,"./ReactInputSelection":111,"./SyntheticEvent":149,"./getActiveElement":177,"./isTextInputElement":194,"./keyOf":198,"./shallowEqual":207}],144:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -18780,7 +20472,7 @@ module.exports = SelectEventPlugin;
* @typechecks
*/
-"use strict";
+'use strict';
/**
* Size of the reactRoot ID space. We generate random numbers for React root
@@ -18798,10 +20490,10 @@ var ServerReactRootIndex = {
module.exports = ServerReactRootIndex;
-},{}],137:[function(require,module,exports){
+},{}],145:[function(require,module,exports){
(function (process){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -18811,7 +20503,7 @@ module.exports = ServerReactRootIndex;
* @providesModule SimpleEventPlugin
*/
-"use strict";
+'use strict';
var EventConstants = require("./EventConstants");
var EventPluginUtils = require("./EventPluginUtils");
@@ -19087,8 +20779,8 @@ var topLevelEventsToDispatchConfig = {
topWheel: eventTypes.wheel
};
-for (var topLevelType in topLevelEventsToDispatchConfig) {
- topLevelEventsToDispatchConfig[topLevelType].dependencies = [topLevelType];
+for (var type in topLevelEventsToDispatchConfig) {
+ topLevelEventsToDispatchConfig[type].dependencies = [type];
}
var SimpleEventPlugin = {
@@ -19226,9 +20918,9 @@ var SimpleEventPlugin = {
module.exports = SimpleEventPlugin;
}).call(this,require('_process'))
-},{"./EventConstants":58,"./EventPluginUtils":62,"./EventPropagators":63,"./SyntheticClipboardEvent":138,"./SyntheticDragEvent":140,"./SyntheticEvent":141,"./SyntheticFocusEvent":142,"./SyntheticKeyboardEvent":144,"./SyntheticMouseEvent":145,"./SyntheticTouchEvent":146,"./SyntheticUIEvent":147,"./SyntheticWheelEvent":148,"./getEventCharCode":170,"./invariant":182,"./keyOf":189,"./warning":202,"_process":1}],138:[function(require,module,exports){
+},{"./EventConstants":56,"./EventPluginUtils":60,"./EventPropagators":61,"./SyntheticClipboardEvent":146,"./SyntheticDragEvent":148,"./SyntheticEvent":149,"./SyntheticFocusEvent":150,"./SyntheticKeyboardEvent":152,"./SyntheticMouseEvent":153,"./SyntheticTouchEvent":154,"./SyntheticUIEvent":155,"./SyntheticWheelEvent":156,"./getEventCharCode":178,"./invariant":191,"./keyOf":198,"./warning":212,"_process":1}],146:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -19239,7 +20931,7 @@ module.exports = SimpleEventPlugin;
* @typechecks static-only
*/
-"use strict";
+'use strict';
var SyntheticEvent = require("./SyntheticEvent");
@@ -19271,10 +20963,9 @@ SyntheticEvent.augmentClass(SyntheticClipboardEvent, ClipboardEventInterface);
module.exports = SyntheticClipboardEvent;
-
-},{"./SyntheticEvent":141}],139:[function(require,module,exports){
+},{"./SyntheticEvent":149}],147:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -19285,7 +20976,7 @@ module.exports = SyntheticClipboardEvent;
* @typechecks static-only
*/
-"use strict";
+'use strict';
var SyntheticEvent = require("./SyntheticEvent");
@@ -19317,10 +21008,9 @@ SyntheticEvent.augmentClass(
module.exports = SyntheticCompositionEvent;
-
-},{"./SyntheticEvent":141}],140:[function(require,module,exports){
+},{"./SyntheticEvent":149}],148:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -19331,7 +21021,7 @@ module.exports = SyntheticCompositionEvent;
* @typechecks static-only
*/
-"use strict";
+'use strict';
var SyntheticMouseEvent = require("./SyntheticMouseEvent");
@@ -19357,9 +21047,9 @@ SyntheticMouseEvent.augmentClass(SyntheticDragEvent, DragEventInterface);
module.exports = SyntheticDragEvent;
-},{"./SyntheticMouseEvent":145}],141:[function(require,module,exports){
+},{"./SyntheticMouseEvent":153}],149:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -19370,7 +21060,7 @@ module.exports = SyntheticDragEvent;
* @typechecks static-only
*/
-"use strict";
+'use strict';
var PooledClass = require("./PooledClass");
@@ -19448,13 +21138,21 @@ assign(SyntheticEvent.prototype, {
preventDefault: function() {
this.defaultPrevented = true;
var event = this.nativeEvent;
- event.preventDefault ? event.preventDefault() : event.returnValue = false;
+ if (event.preventDefault) {
+ event.preventDefault();
+ } else {
+ event.returnValue = false;
+ }
this.isDefaultPrevented = emptyFunction.thatReturnsTrue;
},
stopPropagation: function() {
var event = this.nativeEvent;
- event.stopPropagation ? event.stopPropagation() : event.cancelBubble = true;
+ if (event.stopPropagation) {
+ event.stopPropagation();
+ } else {
+ event.cancelBubble = true;
+ }
this.isPropagationStopped = emptyFunction.thatReturnsTrue;
},
@@ -19515,9 +21213,9 @@ PooledClass.addPoolingTo(SyntheticEvent, PooledClass.threeArgumentPooler);
module.exports = SyntheticEvent;
-},{"./Object.assign":70,"./PooledClass":71,"./emptyFunction":163,"./getEventTarget":173}],142:[function(require,module,exports){
+},{"./Object.assign":69,"./PooledClass":70,"./emptyFunction":170,"./getEventTarget":181}],150:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -19528,7 +21226,7 @@ module.exports = SyntheticEvent;
* @typechecks static-only
*/
-"use strict";
+'use strict';
var SyntheticUIEvent = require("./SyntheticUIEvent");
@@ -19554,9 +21252,9 @@ SyntheticUIEvent.augmentClass(SyntheticFocusEvent, FocusEventInterface);
module.exports = SyntheticFocusEvent;
-},{"./SyntheticUIEvent":147}],143:[function(require,module,exports){
+},{"./SyntheticUIEvent":155}],151:[function(require,module,exports){
/**
- * Copyright 2013 Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -19567,7 +21265,7 @@ module.exports = SyntheticFocusEvent;
* @typechecks static-only
*/
-"use strict";
+'use strict';
var SyntheticEvent = require("./SyntheticEvent");
@@ -19600,10 +21298,9 @@ SyntheticEvent.augmentClass(
module.exports = SyntheticInputEvent;
-
-},{"./SyntheticEvent":141}],144:[function(require,module,exports){
+},{"./SyntheticEvent":149}],152:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -19614,7 +21311,7 @@ module.exports = SyntheticInputEvent;
* @typechecks static-only
*/
-"use strict";
+'use strict';
var SyntheticUIEvent = require("./SyntheticUIEvent");
@@ -19688,9 +21385,9 @@ SyntheticUIEvent.augmentClass(SyntheticKeyboardEvent, KeyboardEventInterface);
module.exports = SyntheticKeyboardEvent;
-},{"./SyntheticUIEvent":147,"./getEventCharCode":170,"./getEventKey":171,"./getEventModifierState":172}],145:[function(require,module,exports){
+},{"./SyntheticUIEvent":155,"./getEventCharCode":178,"./getEventKey":179,"./getEventModifierState":180}],153:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -19701,7 +21398,7 @@ module.exports = SyntheticKeyboardEvent;
* @typechecks static-only
*/
-"use strict";
+'use strict';
var SyntheticUIEvent = require("./SyntheticUIEvent");
var ViewportMetrics = require("./ViewportMetrics");
@@ -19739,9 +21436,7 @@ var MouseEventInterface = {
buttons: null,
relatedTarget: function(event) {
return event.relatedTarget || (
- event.fromElement === event.srcElement ?
- event.toElement :
- event.fromElement
+ ((event.fromElement === event.srcElement ? event.toElement : event.fromElement))
);
},
// "Proprietary" Interface.
@@ -19771,9 +21466,9 @@ SyntheticUIEvent.augmentClass(SyntheticMouseEvent, MouseEventInterface);
module.exports = SyntheticMouseEvent;
-},{"./SyntheticUIEvent":147,"./ViewportMetrics":150,"./getEventModifierState":172}],146:[function(require,module,exports){
+},{"./SyntheticUIEvent":155,"./ViewportMetrics":158,"./getEventModifierState":180}],154:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -19784,7 +21479,7 @@ module.exports = SyntheticMouseEvent;
* @typechecks static-only
*/
-"use strict";
+'use strict';
var SyntheticUIEvent = require("./SyntheticUIEvent");
@@ -19819,9 +21514,9 @@ SyntheticUIEvent.augmentClass(SyntheticTouchEvent, TouchEventInterface);
module.exports = SyntheticTouchEvent;
-},{"./SyntheticUIEvent":147,"./getEventModifierState":172}],147:[function(require,module,exports){
+},{"./SyntheticUIEvent":155,"./getEventModifierState":180}],155:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -19832,7 +21527,7 @@ module.exports = SyntheticTouchEvent;
* @typechecks static-only
*/
-"use strict";
+'use strict';
var SyntheticEvent = require("./SyntheticEvent");
@@ -19881,9 +21576,9 @@ SyntheticEvent.augmentClass(SyntheticUIEvent, UIEventInterface);
module.exports = SyntheticUIEvent;
-},{"./SyntheticEvent":141,"./getEventTarget":173}],148:[function(require,module,exports){
+},{"./SyntheticEvent":149,"./getEventTarget":181}],156:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -19894,7 +21589,7 @@ module.exports = SyntheticUIEvent;
* @typechecks static-only
*/
-"use strict";
+'use strict';
var SyntheticMouseEvent = require("./SyntheticMouseEvent");
@@ -19942,10 +21637,10 @@ SyntheticMouseEvent.augmentClass(SyntheticWheelEvent, WheelEventInterface);
module.exports = SyntheticWheelEvent;
-},{"./SyntheticMouseEvent":145}],149:[function(require,module,exports){
+},{"./SyntheticMouseEvent":153}],157:[function(require,module,exports){
(function (process){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -19955,7 +21650,7 @@ module.exports = SyntheticWheelEvent;
* @providesModule Transaction
*/
-"use strict";
+'use strict';
var invariant = require("./invariant");
@@ -20007,7 +21702,7 @@ var invariant = require("./invariant");
* content.
* - (Future use case): Wrapping particular flushes of the `ReactWorker` queue
* to preserve the `scrollTop` (an automatic scroll aware DOM).
- * - (Future use case): Layout calculations before and after DOM upates.
+ * - (Future use case): Layout calculations before and after DOM updates.
*
* Transactional plugin API:
* - A module that has an `initialize` method that returns any precomputation.
@@ -20149,8 +21844,8 @@ var Mixin = {
// close -- if it's still set to true in the finally block, it means
// wrapper.close threw.
errorThrown = true;
- if (initData !== Transaction.OBSERVED_ERROR) {
- wrapper.close && wrapper.close.call(this, initData);
+ if (initData !== Transaction.OBSERVED_ERROR && wrapper.close) {
+ wrapper.close.call(this, initData);
}
errorThrown = false;
} finally {
@@ -20183,9 +21878,9 @@ var Transaction = {
module.exports = Transaction;
}).call(this,require('_process'))
-},{"./invariant":182,"_process":1}],150:[function(require,module,exports){
+},{"./invariant":191,"_process":1}],158:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -20195,9 +21890,7 @@ module.exports = Transaction;
* @providesModule ViewportMetrics
*/
-"use strict";
-
-var getUnboundedScrollPosition = require("./getUnboundedScrollPosition");
+'use strict';
var ViewportMetrics = {
@@ -20205,8 +21898,7 @@ var ViewportMetrics = {
currentScrollTop: 0,
- refreshScrollValues: function() {
- var scrollPosition = getUnboundedScrollPosition(window);
+ refreshScrollValues: function(scrollPosition) {
ViewportMetrics.currentScrollLeft = scrollPosition.x;
ViewportMetrics.currentScrollTop = scrollPosition.y;
}
@@ -20215,10 +21907,10 @@ var ViewportMetrics = {
module.exports = ViewportMetrics;
-},{"./getUnboundedScrollPosition":178}],151:[function(require,module,exports){
+},{}],159:[function(require,module,exports){
(function (process){
/**
- * Copyright 2014, Facebook, Inc.
+ * Copyright 2014-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -20228,7 +21920,7 @@ module.exports = ViewportMetrics;
* @providesModule accumulateInto
*/
-"use strict";
+'use strict';
var invariant = require("./invariant");
@@ -20281,9 +21973,9 @@ function accumulateInto(current, next) {
module.exports = accumulateInto;
}).call(this,require('_process'))
-},{"./invariant":182,"_process":1}],152:[function(require,module,exports){
+},{"./invariant":191,"_process":1}],160:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -20295,7 +21987,7 @@ module.exports = accumulateInto;
/* jslint bitwise:true */
-"use strict";
+'use strict';
var MOD = 65521;
@@ -20315,9 +22007,9 @@ function adler32(data) {
module.exports = adler32;
-},{}],153:[function(require,module,exports){
+},{}],161:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -20347,9 +22039,9 @@ function camelize(string) {
module.exports = camelize;
-},{}],154:[function(require,module,exports){
+},{}],162:[function(require,module,exports){
/**
- * Copyright 2014, Facebook, Inc.
+ * Copyright 2014-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -20389,21 +22081,21 @@ function camelizeStyleName(string) {
module.exports = camelizeStyleName;
-},{"./camelize":153}],155:[function(require,module,exports){
+},{"./camelize":161}],163:[function(require,module,exports){
(function (process){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
- * @typechecks
+ * @typechecks static-only
* @providesModule cloneWithProps
*/
-"use strict";
+'use strict';
var ReactElement = require("./ReactElement");
var ReactPropTransferer = require("./ReactPropTransferer");
@@ -20417,10 +22109,10 @@ var CHILDREN_PROP = keyOf({children: null});
* Sometimes you want to change the props of a child passed to you. Usually
* this is to add a CSS class.
*
- * @param {object} child child component you'd like to clone
- * @param {object} props props you'd like to modify. They will be merged
- * as if you used `transferPropsTo()`.
- * @return {object} a clone of child with props merged in.
+ * @param {ReactElement} child child element you'd like to clone
+ * @param {object} props props you'd like to modify. className and style will be
+ * merged automatically.
+ * @return {ReactElement} a clone of child with props merged in.
*/
function cloneWithProps(child, props) {
if ("production" !== process.env.NODE_ENV) {
@@ -20448,9 +22140,9 @@ function cloneWithProps(child, props) {
module.exports = cloneWithProps;
}).call(this,require('_process'))
-},{"./ReactElement":99,"./ReactPropTransferer":117,"./keyOf":189,"./warning":202,"_process":1}],156:[function(require,module,exports){
+},{"./ReactElement":103,"./ReactPropTransferer":123,"./keyOf":198,"./warning":212,"_process":1}],164:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -20492,16 +22184,16 @@ function containsNode(outerNode, innerNode) {
module.exports = containsNode;
-},{"./isTextNode":186}],157:[function(require,module,exports){
+},{"./isTextNode":195}],165:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
- * @providesModule createArrayFrom
+ * @providesModule createArrayFromMixed
* @typechecks
*/
@@ -20551,10 +22243,10 @@ function hasArrayNature(obj) {
*
* This is mostly useful idiomatically:
*
- * var createArrayFrom = require('createArrayFrom');
+ * var createArrayFromMixed = require('createArrayFromMixed');
*
* function takesOneOrMoreThings(things) {
- * things = createArrayFrom(things);
+ * things = createArrayFromMixed(things);
* ...
* }
*
@@ -20566,7 +22258,7 @@ function hasArrayNature(obj) {
* @param {*} obj
* @return {array}
*/
-function createArrayFrom(obj) {
+function createArrayFromMixed(obj) {
if (!hasArrayNature(obj)) {
return [obj];
} else if (Array.isArray(obj)) {
@@ -20576,12 +22268,12 @@ function createArrayFrom(obj) {
}
}
-module.exports = createArrayFrom;
+module.exports = createArrayFromMixed;
-},{"./toArray":199}],158:[function(require,module,exports){
+},{"./toArray":209}],166:[function(require,module,exports){
(function (process){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -20592,10 +22284,10 @@ module.exports = createArrayFrom;
* @typechecks
*/
-"use strict";
+'use strict';
// Defeat circular references by requiring this directly.
-var ReactCompositeComponent = require("./ReactCompositeComponent");
+var ReactClass = require("./ReactClass");
var ReactElement = require("./ReactElement");
var invariant = require("./invariant");
@@ -20614,7 +22306,8 @@ var invariant = require("./invariant");
function createFullPageComponent(tag) {
var elementFactory = ReactElement.createFactory(tag);
- var FullPageComponent = ReactCompositeComponent.createClass({
+ var FullPageComponent = ReactClass.createClass({
+ tagName: tag.toUpperCase(),
displayName: 'ReactFullPageComponent' + tag,
componentWillUnmount: function() {
@@ -20639,10 +22332,10 @@ function createFullPageComponent(tag) {
module.exports = createFullPageComponent;
}).call(this,require('_process'))
-},{"./ReactCompositeComponent":81,"./ReactElement":99,"./invariant":182,"_process":1}],159:[function(require,module,exports){
+},{"./ReactClass":78,"./ReactElement":103,"./invariant":191,"_process":1}],167:[function(require,module,exports){
(function (process){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -20657,7 +22350,7 @@ module.exports = createFullPageComponent;
var ExecutionEnvironment = require("./ExecutionEnvironment");
-var createArrayFrom = require("./createArrayFrom");
+var createArrayFromMixed = require("./createArrayFromMixed");
var getMarkupWrap = require("./getMarkupWrap");
var invariant = require("./invariant");
@@ -20716,10 +22409,10 @@ function createNodesFromMarkup(markup, handleScript) {
handleScript,
'createNodesFromMarkup(...): Unexpected <script> element rendered.'
) : invariant(handleScript));
- createArrayFrom(scripts).forEach(handleScript);
+ createArrayFromMixed(scripts).forEach(handleScript);
}
- var nodes = createArrayFrom(node.childNodes);
+ var nodes = createArrayFromMixed(node.childNodes);
while (node.lastChild) {
node.removeChild(node.lastChild);
}
@@ -20729,9 +22422,10 @@ function createNodesFromMarkup(markup, handleScript) {
module.exports = createNodesFromMarkup;
}).call(this,require('_process'))
-},{"./ExecutionEnvironment":64,"./createArrayFrom":157,"./getMarkupWrap":174,"./invariant":182,"_process":1}],160:[function(require,module,exports){
+},{"./ExecutionEnvironment":62,"./createArrayFromMixed":165,"./getMarkupWrap":183,"./invariant":191,"_process":1}],168:[function(require,module,exports){
+(function (process){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -20756,7 +22450,22 @@ module.exports = createNodesFromMarkup;
* @param [string ...] Variable list of classNames in the string case.
* @return string Renderable space-separated CSS className.
*/
+
+'use strict';
+var warning = require("./warning");
+
+var warned = false;
+
function cx(classNames) {
+ if ("production" !== process.env.NODE_ENV) {
+ ("production" !== process.env.NODE_ENV ? warning(
+ warned,
+ 'React.addons.classSet will be deprecated in a future version. See ' +
+ 'http://fb.me/react-addons-classset'
+ ) : null);
+ warned = true;
+ }
+
if (typeof classNames == 'object') {
return Object.keys(classNames).filter(function(className) {
return classNames[className];
@@ -20768,9 +22477,10 @@ function cx(classNames) {
module.exports = cx;
-},{}],161:[function(require,module,exports){
+}).call(this,require('_process'))
+},{"./warning":212,"_process":1}],169:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -20781,7 +22491,7 @@ module.exports = cx;
* @typechecks static-only
*/
-"use strict";
+'use strict';
var CSSProperty = require("./CSSProperty");
@@ -20826,60 +22536,9 @@ function dangerousStyleValue(name, value) {
module.exports = dangerousStyleValue;
-},{"./CSSProperty":46}],162:[function(require,module,exports){
-(function (process){
-/**
- * Copyright 2013-2014, Facebook, Inc.
- * All rights reserved.
- *
- * This source code is licensed under the BSD-style license found in the
- * LICENSE file in the root directory of this source tree. An additional grant
- * of patent rights can be found in the PATENTS file in the same directory.
- *
- * @providesModule deprecated
- */
-
-var assign = require("./Object.assign");
-var warning = require("./warning");
-
-/**
- * This will log a single deprecation notice per function and forward the call
- * on to the new API.
- *
- * @param {string} namespace The namespace of the call, eg 'React'
- * @param {string} oldName The old function name, eg 'renderComponent'
- * @param {string} newName The new function name, eg 'render'
- * @param {*} ctx The context this forwarded call should run in
- * @param {function} fn The function to forward on to
- * @return {*} Will be the value as returned from `fn`
- */
-function deprecated(namespace, oldName, newName, ctx, fn) {
- var warned = false;
- if ("production" !== process.env.NODE_ENV) {
- var newFn = function() {
- ("production" !== process.env.NODE_ENV ? warning(
- warned,
- (namespace + "." + oldName + " will be deprecated in a future version. ") +
- ("Use " + namespace + "." + newName + " instead.")
- ) : null);
- warned = true;
- return fn.apply(ctx, arguments);
- };
- newFn.displayName = (namespace + "_" + oldName);
- // We need to make sure all properties of the original fn are copied over.
- // In particular, this is needed to support PropTypes
- return assign(newFn, fn);
- }
-
- return fn;
-}
-
-module.exports = deprecated;
-
-}).call(this,require('_process'))
-},{"./Object.assign":70,"./warning":202,"_process":1}],163:[function(require,module,exports){
+},{"./CSSProperty":45}],170:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -20911,10 +22570,10 @@ emptyFunction.thatReturnsArgument = function(arg) { return arg; };
module.exports = emptyFunction;
-},{}],164:[function(require,module,exports){
+},{}],171:[function(require,module,exports){
(function (process){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -20935,27 +22594,26 @@ if ("production" !== process.env.NODE_ENV) {
module.exports = emptyObject;
}).call(this,require('_process'))
-},{"_process":1}],165:[function(require,module,exports){
+},{"_process":1}],172:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
- * @providesModule escapeTextForBrowser
- * @typechecks static-only
+ * @providesModule escapeTextContentForBrowser
*/
-"use strict";
+'use strict';
var ESCAPE_LOOKUP = {
- "&": "&amp;",
- ">": "&gt;",
- "<": "&lt;",
- "\"": "&quot;",
- "'": "&#x27;"
+ '&': '&amp;',
+ '>': '&gt;',
+ '<': '&lt;',
+ '"': '&quot;',
+ '\'': '&#x27;'
};
var ESCAPE_REGEX = /[&><"']/g;
@@ -20970,28 +22628,99 @@ function escaper(match) {
* @param {*} text Text value to escape.
* @return {string} An escaped string.
*/
-function escapeTextForBrowser(text) {
+function escapeTextContentForBrowser(text) {
return ('' + text).replace(ESCAPE_REGEX, escaper);
}
-module.exports = escapeTextForBrowser;
+module.exports = escapeTextContentForBrowser;
-},{}],166:[function(require,module,exports){
+},{}],173:[function(require,module,exports){
(function (process){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
- * @providesModule flattenChildren
+ * @providesModule findDOMNode
+ * @typechecks static-only
*/
-"use strict";
+'use strict';
+
+var ReactCurrentOwner = require("./ReactCurrentOwner");
+var ReactInstanceMap = require("./ReactInstanceMap");
+var ReactMount = require("./ReactMount");
+
+var invariant = require("./invariant");
+var isNode = require("./isNode");
+var warning = require("./warning");
+
+/**
+ * Returns the DOM node rendered by this element.
+ *
+ * @param {ReactComponent|DOMElement} componentOrElement
+ * @return {DOMElement} The root node of this element.
+ */
+function findDOMNode(componentOrElement) {
+ if ("production" !== process.env.NODE_ENV) {
+ var owner = ReactCurrentOwner.current;
+ if (owner !== null) {
+ ("production" !== process.env.NODE_ENV ? warning(
+ owner._warnedAboutRefsInRender,
+ '%s is accessing getDOMNode or findDOMNode inside its render(). ' +
+ 'render() should be a pure function of props and state. It should ' +
+ 'never access something that requires stale data from the previous ' +
+ 'render, such as refs. Move this logic to componentDidMount and ' +
+ 'componentDidUpdate instead.',
+ owner.getName() || 'A component'
+ ) : null);
+ owner._warnedAboutRefsInRender = true;
+ }
+ }
+ if (componentOrElement == null) {
+ return null;
+ }
+ if (isNode(componentOrElement)) {
+ return componentOrElement;
+ }
+ if (ReactInstanceMap.has(componentOrElement)) {
+ return ReactMount.getNodeFromInstance(componentOrElement);
+ }
+ ("production" !== process.env.NODE_ENV ? invariant(
+ componentOrElement.render == null ||
+ typeof componentOrElement.render !== 'function',
+ 'Component (with keys: %s) contains `render` method ' +
+ 'but is not mounted in the DOM',
+ Object.keys(componentOrElement)
+ ) : invariant(componentOrElement.render == null ||
+ typeof componentOrElement.render !== 'function'));
+ ("production" !== process.env.NODE_ENV ? invariant(
+ false,
+ 'Element appears to be neither ReactComponent nor DOMNode (keys: %s)',
+ Object.keys(componentOrElement)
+ ) : invariant(false));
+}
+
+module.exports = findDOMNode;
+
+}).call(this,require('_process'))
+},{"./ReactCurrentOwner":85,"./ReactInstanceMap":113,"./ReactMount":117,"./invariant":191,"./isNode":193,"./warning":212,"_process":1}],174:[function(require,module,exports){
+(function (process){
+/**
+ * Copyright 2013-2015, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ * @providesModule flattenChildren
+ */
-var ReactTextComponent = require("./ReactTextComponent");
+'use strict';
var traverseAllChildren = require("./traverseAllChildren");
var warning = require("./warning");
@@ -21005,26 +22734,17 @@ function flattenSingleChildIntoContext(traverseContext, child, name) {
// We found a component instance.
var result = traverseContext;
var keyUnique = !result.hasOwnProperty(name);
- ("production" !== process.env.NODE_ENV ? warning(
- keyUnique,
- 'flattenChildren(...): Encountered two children with the same key, ' +
- '`%s`. Child keys must be unique; when two children share a key, only ' +
- 'the first child will be used.',
- name
- ) : null);
+ if ("production" !== process.env.NODE_ENV) {
+ ("production" !== process.env.NODE_ENV ? warning(
+ keyUnique,
+ 'flattenChildren(...): Encountered two children with the same key, ' +
+ '`%s`. Child keys must be unique; when two children share a key, only ' +
+ 'the first child will be used.',
+ name
+ ) : null);
+ }
if (keyUnique && child != null) {
- var type = typeof child;
- var normalizedValue;
-
- if (type === 'string') {
- normalizedValue = ReactTextComponent(child);
- } else if (type === 'number') {
- normalizedValue = ReactTextComponent('' + child);
- } else {
- normalizedValue = child;
- }
-
- result[name] = normalizedValue;
+ result[name] = child;
}
}
@@ -21045,9 +22765,9 @@ function flattenChildren(children) {
module.exports = flattenChildren;
}).call(this,require('_process'))
-},{"./ReactTextComponent":128,"./traverseAllChildren":200,"./warning":202,"_process":1}],167:[function(require,module,exports){
+},{"./traverseAllChildren":210,"./warning":212,"_process":1}],175:[function(require,module,exports){
/**
- * Copyright 2014, Facebook, Inc.
+ * Copyright 2014-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -21074,9 +22794,9 @@ function focusNode(node) {
module.exports = focusNode;
-},{}],168:[function(require,module,exports){
+},{}],176:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -21086,7 +22806,7 @@ module.exports = focusNode;
* @providesModule forEachAccumulated
*/
-"use strict";
+'use strict';
/**
* @param {array} an "accumulation" of items which is either an Array or
@@ -21105,9 +22825,9 @@ var forEachAccumulated = function(arr, cb, scope) {
module.exports = forEachAccumulated;
-},{}],169:[function(require,module,exports){
+},{}],177:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -21134,9 +22854,9 @@ function getActiveElement() /*?DOMElement*/ {
module.exports = getActiveElement;
-},{}],170:[function(require,module,exports){
+},{}],178:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -21147,7 +22867,7 @@ module.exports = getActiveElement;
* @typechecks static-only
*/
-"use strict";
+'use strict';
/**
* `charCode` represents the actual "character code" and is safe to use with
@@ -21186,9 +22906,9 @@ function getEventCharCode(nativeEvent) {
module.exports = getEventCharCode;
-},{}],171:[function(require,module,exports){
+},{}],179:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -21199,7 +22919,7 @@ module.exports = getEventCharCode;
* @typechecks static-only
*/
-"use strict";
+'use strict';
var getEventCharCode = require("./getEventCharCode");
@@ -21291,9 +23011,9 @@ function getEventKey(nativeEvent) {
module.exports = getEventKey;
-},{"./getEventCharCode":170}],172:[function(require,module,exports){
+},{"./getEventCharCode":178}],180:[function(require,module,exports){
/**
- * Copyright 2013 Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -21304,7 +23024,7 @@ module.exports = getEventKey;
* @typechecks static-only
*/
-"use strict";
+'use strict';
/**
* Translation from modifier key to the associated property in the event.
@@ -21338,9 +23058,9 @@ function getEventModifierState(nativeEvent) {
module.exports = getEventModifierState;
-},{}],173:[function(require,module,exports){
+},{}],181:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -21351,7 +23071,7 @@ module.exports = getEventModifierState;
* @typechecks static-only
*/
-"use strict";
+'use strict';
/**
* Gets the target node from a native browser event by accounting for
@@ -21369,10 +23089,54 @@ function getEventTarget(nativeEvent) {
module.exports = getEventTarget;
-},{}],174:[function(require,module,exports){
+},{}],182:[function(require,module,exports){
+/**
+ * Copyright 2013-2015, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ * @providesModule getIteratorFn
+ * @typechecks static-only
+ */
+
+'use strict';
+
+/* global Symbol */
+var ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator;
+var FAUX_ITERATOR_SYMBOL = '@@iterator'; // Before Symbol spec.
+
+/**
+ * Returns the iterator method function contained on the iterable object.
+ *
+ * Be sure to invoke the function with the iterable as context:
+ *
+ * var iteratorFn = getIteratorFn(myIterable);
+ * if (iteratorFn) {
+ * var iterator = iteratorFn.call(myIterable);
+ * ...
+ * }
+ *
+ * @param {?object} maybeIterable
+ * @return {?function}
+ */
+function getIteratorFn(maybeIterable) {
+ var iteratorFn = maybeIterable && (
+ (ITERATOR_SYMBOL && maybeIterable[ITERATOR_SYMBOL] || maybeIterable[FAUX_ITERATOR_SYMBOL])
+ );
+ if (typeof iteratorFn === 'function') {
+ return iteratorFn;
+ }
+}
+
+module.exports = getIteratorFn;
+
+},{}],183:[function(require,module,exports){
(function (process){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -21486,9 +23250,9 @@ function getMarkupWrap(nodeName) {
module.exports = getMarkupWrap;
}).call(this,require('_process'))
-},{"./ExecutionEnvironment":64,"./invariant":182,"_process":1}],175:[function(require,module,exports){
+},{"./ExecutionEnvironment":62,"./invariant":191,"_process":1}],184:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -21498,7 +23262,7 @@ module.exports = getMarkupWrap;
* @providesModule getNodeForCharacterOffset
*/
-"use strict";
+'use strict';
/**
* Given any node return the first leaf node without children.
@@ -21542,7 +23306,7 @@ function getNodeForCharacterOffset(root, offset) {
var nodeEnd = 0;
while (node) {
- if (node.nodeType == 3) {
+ if (node.nodeType === 3) {
nodeEnd = nodeStart + node.textContent.length;
if (nodeStart <= offset && nodeEnd >= offset) {
@@ -21561,9 +23325,9 @@ function getNodeForCharacterOffset(root, offset) {
module.exports = getNodeForCharacterOffset;
-},{}],176:[function(require,module,exports){
+},{}],185:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -21573,7 +23337,7 @@ module.exports = getNodeForCharacterOffset;
* @providesModule getReactRootElementInContainer
*/
-"use strict";
+'use strict';
var DOC_NODE_TYPE = 9;
@@ -21596,9 +23360,9 @@ function getReactRootElementInContainer(container) {
module.exports = getReactRootElementInContainer;
-},{}],177:[function(require,module,exports){
+},{}],186:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -21608,7 +23372,7 @@ module.exports = getReactRootElementInContainer;
* @providesModule getTextContentAccessor
*/
-"use strict";
+'use strict';
var ExecutionEnvironment = require("./ExecutionEnvironment");
@@ -21633,9 +23397,9 @@ function getTextContentAccessor() {
module.exports = getTextContentAccessor;
-},{"./ExecutionEnvironment":64}],178:[function(require,module,exports){
+},{"./ExecutionEnvironment":62}],187:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -21673,9 +23437,9 @@ function getUnboundedScrollPosition(scrollable) {
module.exports = getUnboundedScrollPosition;
-},{}],179:[function(require,module,exports){
+},{}],188:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -21706,9 +23470,9 @@ function hyphenate(string) {
module.exports = hyphenate;
-},{}],180:[function(require,module,exports){
+},{}],189:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -21747,10 +23511,10 @@ function hyphenateStyleName(string) {
module.exports = hyphenateStyleName;
-},{"./hyphenate":179}],181:[function(require,module,exports){
+},{"./hyphenate":188}],190:[function(require,module,exports){
(function (process){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -21761,99 +23525,122 @@ module.exports = hyphenateStyleName;
* @typechecks static-only
*/
-"use strict";
+'use strict';
+var ReactCompositeComponent = require("./ReactCompositeComponent");
+var ReactEmptyComponent = require("./ReactEmptyComponent");
+var ReactNativeComponent = require("./ReactNativeComponent");
+
+var assign = require("./Object.assign");
+var invariant = require("./invariant");
var warning = require("./warning");
-var ReactElement = require("./ReactElement");
-var ReactLegacyElement = require("./ReactLegacyElement");
-var ReactNativeComponent = require("./ReactNativeComponent");
-var ReactEmptyComponent = require("./ReactEmptyComponent");
+// To avoid a cyclic dependency, we create the final class in this module
+var ReactCompositeComponentWrapper = function() { };
+assign(
+ ReactCompositeComponentWrapper.prototype,
+ ReactCompositeComponent.Mixin,
+ {
+ _instantiateReactComponent: instantiateReactComponent
+ }
+);
/**
- * Given an `element` create an instance that will actually be mounted.
+ * Check if the type reference is a known internal type. I.e. not a user
+ * provided composite type.
*
- * @param {object} element
+ * @param {function} type
+ * @return {boolean} Returns true if this is a valid internal type.
+ */
+function isInternalComponentType(type) {
+ return (
+ typeof type === 'function' &&
+ typeof type.prototype.mountComponent === 'function' &&
+ typeof type.prototype.receiveComponent === 'function'
+ );
+}
+
+/**
+ * Given a ReactNode, create an instance that will actually be mounted.
+ *
+ * @param {ReactNode} node
* @param {*} parentCompositeType The composite type that resolved this.
* @return {object} A new instance of the element's constructor.
* @protected
*/
-function instantiateReactComponent(element, parentCompositeType) {
+function instantiateReactComponent(node, parentCompositeType) {
var instance;
- if ("production" !== process.env.NODE_ENV) {
- ("production" !== process.env.NODE_ENV ? warning(
- element && (typeof element.type === 'function' ||
- typeof element.type === 'string'),
- 'Only functions or strings can be mounted as React components.'
- ) : null);
-
- // Resolve mock instances
- if (element.type._mockedReactClassConstructor) {
- // If this is a mocked class, we treat the legacy factory as if it was the
- // class constructor for future proofing unit tests. Because this might
- // be mocked as a legacy factory, we ignore any warnings triggerd by
- // this temporary hack.
- ReactLegacyElement._isLegacyCallWarningEnabled = false;
- try {
- instance = new element.type._mockedReactClassConstructor(
- element.props
- );
- } finally {
- ReactLegacyElement._isLegacyCallWarningEnabled = true;
- }
-
- // If the mock implementation was a legacy factory, then it returns a
- // element. We need to turn this into a real component instance.
- if (ReactElement.isValidElement(instance)) {
- instance = new instance.type(instance.props);
- }
+ if (node === null || node === false) {
+ node = ReactEmptyComponent.emptyElement;
+ }
- var render = instance.render;
- if (!render) {
- // For auto-mocked factories, the prototype isn't shimmed and therefore
- // there is no render function on the instance. We replace the whole
- // component with an empty component instance instead.
- element = ReactEmptyComponent.getEmptyComponent();
- } else {
- if (render._isMockFunction && !render._getMockImplementation()) {
- // Auto-mocked components may have a prototype with a mocked render
- // function. For those, we'll need to mock the result of the render
- // since we consider undefined to be invalid results from render.
- render.mockImplementation(
- ReactEmptyComponent.getEmptyComponent
- );
- }
- instance.construct(element);
- return instance;
- }
+ if (typeof node === 'object') {
+ var element = node;
+ if ("production" !== process.env.NODE_ENV) {
+ ("production" !== process.env.NODE_ENV ? warning(
+ element && (typeof element.type === 'function' ||
+ typeof element.type === 'string'),
+ 'Only functions or strings can be mounted as React components.'
+ ) : null);
}
- }
- // Special case string values
- if (typeof element.type === 'string') {
- instance = ReactNativeComponent.createInstanceForTag(
- element.type,
- element.props,
- parentCompositeType
- );
+ // Special case string values
+ if (parentCompositeType === element.type &&
+ typeof element.type === 'string') {
+ // Avoid recursion if the wrapper renders itself.
+ instance = ReactNativeComponent.createInternalComponent(element);
+ // All native components are currently wrapped in a composite so we're
+ // safe to assume that this is what we should instantiate.
+ } else if (isInternalComponentType(element.type)) {
+ // This is temporarily available for custom components that are not string
+ // represenations. I.e. ART. Once those are updated to use the string
+ // representation, we can drop this code path.
+ instance = new element.type(element);
+ } else {
+ instance = new ReactCompositeComponentWrapper();
+ }
+ } else if (typeof node === 'string' || typeof node === 'number') {
+ instance = ReactNativeComponent.createInstanceForText(node);
} else {
- // Normal case for non-mocks and non-strings
- instance = new element.type(element.props);
+ ("production" !== process.env.NODE_ENV ? invariant(
+ false,
+ 'Encountered invalid React node of type %s',
+ typeof node
+ ) : invariant(false));
}
if ("production" !== process.env.NODE_ENV) {
("production" !== process.env.NODE_ENV ? warning(
typeof instance.construct === 'function' &&
typeof instance.mountComponent === 'function' &&
- typeof instance.receiveComponent === 'function',
+ typeof instance.receiveComponent === 'function' &&
+ typeof instance.unmountComponent === 'function',
'Only React Components can be mounted.'
) : null);
}
- // This actually sets up the internal instance. This will become decoupled
- // from the public instance in a future diff.
- instance.construct(element);
+ // Sets up the instance. This can probably just move into the constructor now.
+ instance.construct(node);
+
+ // These two fields are used by the DOM and ART diffing algorithms
+ // respectively. Instead of using expandos on components, we should be
+ // storing the state needed by the diffing algorithms elsewhere.
+ instance._mountIndex = 0;
+ instance._mountImage = null;
+
+ if ("production" !== process.env.NODE_ENV) {
+ instance._isOwnerNecessary = false;
+ instance._warnedAboutRefsInRender = false;
+ }
+
+ // Internal instances should fully constructed at this point, so they should
+ // not get any new fields added to them at this point.
+ if ("production" !== process.env.NODE_ENV) {
+ if (Object.preventExtensions) {
+ Object.preventExtensions(instance);
+ }
+ }
return instance;
}
@@ -21861,10 +23648,10 @@ function instantiateReactComponent(element, parentCompositeType) {
module.exports = instantiateReactComponent;
}).call(this,require('_process'))
-},{"./ReactElement":99,"./ReactEmptyComponent":101,"./ReactLegacyElement":108,"./ReactNativeComponent":114,"./warning":202,"_process":1}],182:[function(require,module,exports){
+},{"./Object.assign":69,"./ReactCompositeComponent":83,"./ReactEmptyComponent":105,"./ReactNativeComponent":120,"./invariant":191,"./warning":212,"_process":1}],191:[function(require,module,exports){
(function (process){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -21918,9 +23705,9 @@ var invariant = function(condition, format, a, b, c, d, e, f) {
module.exports = invariant;
}).call(this,require('_process'))
-},{"_process":1}],183:[function(require,module,exports){
+},{"_process":1}],192:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -21930,7 +23717,7 @@ module.exports = invariant;
* @providesModule isEventSupported
*/
-"use strict";
+'use strict';
var ExecutionEnvironment = require("./ExecutionEnvironment");
@@ -21983,9 +23770,9 @@ function isEventSupported(eventNameSuffix, capture) {
module.exports = isEventSupported;
-},{"./ExecutionEnvironment":64}],184:[function(require,module,exports){
+},{"./ExecutionEnvironment":62}],193:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -22002,18 +23789,17 @@ module.exports = isEventSupported;
*/
function isNode(object) {
return !!(object && (
- typeof Node === 'function' ? object instanceof Node :
- typeof object === 'object' &&
- typeof object.nodeType === 'number' &&
- typeof object.nodeName === 'string'
+ ((typeof Node === 'function' ? object instanceof Node : typeof object === 'object' &&
+ typeof object.nodeType === 'number' &&
+ typeof object.nodeName === 'string'))
));
}
module.exports = isNode;
-},{}],185:[function(require,module,exports){
+},{}],194:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -22023,7 +23809,7 @@ module.exports = isNode;
* @providesModule isTextInputElement
*/
-"use strict";
+'use strict';
/**
* @see http://www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html#input-type-attr-summary
@@ -22048,16 +23834,15 @@ var supportedInputTypes = {
function isTextInputElement(elem) {
return elem && (
- (elem.nodeName === 'INPUT' && supportedInputTypes[elem.type]) ||
- elem.nodeName === 'TEXTAREA'
+ (elem.nodeName === 'INPUT' && supportedInputTypes[elem.type] || elem.nodeName === 'TEXTAREA')
);
}
module.exports = isTextInputElement;
-},{}],186:[function(require,module,exports){
+},{}],195:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -22080,9 +23865,9 @@ function isTextNode(object) {
module.exports = isTextNode;
-},{"./isNode":184}],187:[function(require,module,exports){
+},{"./isNode":193}],196:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -22093,7 +23878,7 @@ module.exports = isTextNode;
* @typechecks static-only
*/
-"use strict";
+'use strict';
/**
* Combines multiple className strings into one.
@@ -22121,10 +23906,10 @@ function joinClasses(className/*, ... */) {
module.exports = joinClasses;
-},{}],188:[function(require,module,exports){
+},{}],197:[function(require,module,exports){
(function (process){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -22135,7 +23920,7 @@ module.exports = joinClasses;
* @typechecks static-only
*/
-"use strict";
+'use strict';
var invariant = require("./invariant");
@@ -22176,9 +23961,9 @@ var keyMirror = function(obj) {
module.exports = keyMirror;
}).call(this,require('_process'))
-},{"./invariant":182,"_process":1}],189:[function(require,module,exports){
+},{"./invariant":191,"_process":1}],198:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -22212,9 +23997,9 @@ var keyOf = function(oneKeyObj) {
module.exports = keyOf;
-},{}],190:[function(require,module,exports){
+},{}],199:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -22265,9 +24050,9 @@ function mapObject(object, callback, context) {
module.exports = mapObject;
-},{}],191:[function(require,module,exports){
+},{}],200:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -22278,7 +24063,7 @@ module.exports = mapObject;
* @typechecks static-only
*/
-"use strict";
+'use strict';
/**
* Memoizes the return value of a function that accepts one string argument.
@@ -22289,54 +24074,19 @@ module.exports = mapObject;
function memoizeStringOnly(callback) {
var cache = {};
return function(string) {
- if (cache.hasOwnProperty(string)) {
- return cache[string];
- } else {
- return cache[string] = callback.call(this, string);
+ if (!cache.hasOwnProperty(string)) {
+ cache[string] = callback.call(this, string);
}
+ return cache[string];
};
}
module.exports = memoizeStringOnly;
-},{}],192:[function(require,module,exports){
+},{}],201:[function(require,module,exports){
(function (process){
/**
- * Copyright 2014, Facebook, Inc.
- * All rights reserved.
- *
- * This source code is licensed under the BSD-style license found in the
- * LICENSE file in the root directory of this source tree. An additional grant
- * of patent rights can be found in the PATENTS file in the same directory.
- *
- * @providesModule monitorCodeUse
- */
-
-"use strict";
-
-var invariant = require("./invariant");
-
-/**
- * Provides open-source compatible instrumentation for monitoring certain API
- * uses before we're ready to issue a warning or refactor. It accepts an event
- * name which may only contain the characters [a-z0-9_] and an optional data
- * object with further information.
- */
-
-function monitorCodeUse(eventName, data) {
- ("production" !== process.env.NODE_ENV ? invariant(
- eventName && !/[^a-z0-9_]/.test(eventName),
- 'You must provide an eventName using only the characters [a-z0-9_]'
- ) : invariant(eventName && !/[^a-z0-9_]/.test(eventName)));
-}
-
-module.exports = monitorCodeUse;
-
-}).call(this,require('_process'))
-},{"./invariant":182,"_process":1}],193:[function(require,module,exports){
-(function (process){
-/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -22345,7 +24095,7 @@ module.exports = monitorCodeUse;
*
* @providesModule onlyChild
*/
-"use strict";
+'use strict';
var ReactElement = require("./ReactElement");
@@ -22373,9 +24123,9 @@ function onlyChild(children) {
module.exports = onlyChild;
}).call(this,require('_process'))
-},{"./ReactElement":99,"./invariant":182,"_process":1}],194:[function(require,module,exports){
+},{"./ReactElement":103,"./invariant":191,"_process":1}],202:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -22401,9 +24151,9 @@ if (ExecutionEnvironment.canUseDOM) {
module.exports = performance || {};
-},{"./ExecutionEnvironment":64}],195:[function(require,module,exports){
+},{"./ExecutionEnvironment":62}],203:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -22429,9 +24179,37 @@ var performanceNow = performance.now.bind(performance);
module.exports = performanceNow;
-},{"./performance":194}],196:[function(require,module,exports){
+},{"./performance":202}],204:[function(require,module,exports){
+/**
+ * Copyright 2013-2015, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ * @providesModule quoteAttributeValueForBrowser
+ */
+
+'use strict';
+
+var escapeTextContentForBrowser = require("./escapeTextContentForBrowser");
+
+/**
+ * Escapes attribute value to prevent scripting attacks.
+ *
+ * @param {*} value Value to escape.
+ * @return {string} An escaped string.
+ */
+function quoteAttributeValueForBrowser(value) {
+ return '"' + escapeTextContentForBrowser(value) + '"';
+}
+
+module.exports = quoteAttributeValueForBrowser;
+
+},{"./escapeTextContentForBrowser":172}],205:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -22441,7 +24219,9 @@ module.exports = performanceNow;
* @providesModule setInnerHTML
*/
-"use strict";
+/* globals MSApp */
+
+'use strict';
var ExecutionEnvironment = require("./ExecutionEnvironment");
@@ -22460,6 +24240,15 @@ var setInnerHTML = function(node, html) {
node.innerHTML = html;
};
+// Win8 apps: Allow all html to be inserted
+if (typeof MSApp !== 'undefined' && MSApp.execUnsafeLocalFunction) {
+ setInnerHTML = function(node, html) {
+ MSApp.execUnsafeLocalFunction(function() {
+ node.innerHTML = html;
+ });
+ };
+}
+
if (ExecutionEnvironment.canUseDOM) {
// IE8: When updating a just created node with innerHTML only leading
// whitespace is removed. When updating an existing node with innerHTML
@@ -22507,9 +24296,51 @@ if (ExecutionEnvironment.canUseDOM) {
module.exports = setInnerHTML;
-},{"./ExecutionEnvironment":64}],197:[function(require,module,exports){
+},{"./ExecutionEnvironment":62}],206:[function(require,module,exports){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ * @providesModule setTextContent
+ */
+
+'use strict';
+
+var ExecutionEnvironment = require("./ExecutionEnvironment");
+var escapeTextContentForBrowser = require("./escapeTextContentForBrowser");
+var setInnerHTML = require("./setInnerHTML");
+
+/**
+ * Set the textContent property of a node, ensuring that whitespace is preserved
+ * even in IE8. innerText is a poor substitute for textContent and, among many
+ * issues, inserts <br> instead of the literal newline chars. innerHTML behaves
+ * as it should.
+ *
+ * @param {DOMElement} node
+ * @param {string} text
+ * @internal
+ */
+var setTextContent = function(node, text) {
+ node.textContent = text;
+};
+
+if (ExecutionEnvironment.canUseDOM) {
+ if (!('textContent' in document.documentElement)) {
+ setTextContent = function(node, text) {
+ setInnerHTML(node, escapeTextContentForBrowser(text));
+ };
+ }
+}
+
+module.exports = setTextContent;
+
+},{"./ExecutionEnvironment":62,"./escapeTextContentForBrowser":172,"./setInnerHTML":205}],207:[function(require,module,exports){
+/**
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -22519,7 +24350,7 @@ module.exports = setInnerHTML;
* @providesModule shallowEqual
*/
-"use strict";
+'use strict';
/**
* Performs equality by iterating through keys on an object and returning
@@ -22551,9 +24382,10 @@ function shallowEqual(objA, objB) {
module.exports = shallowEqual;
-},{}],198:[function(require,module,exports){
+},{}],208:[function(require,module,exports){
+(function (process){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -22564,7 +24396,9 @@ module.exports = shallowEqual;
* @typechecks static-only
*/
-"use strict";
+'use strict';
+
+var warning = require("./warning");
/**
* Given a `prevElement` and `nextElement`, determines if the existing
@@ -22578,21 +24412,84 @@ module.exports = shallowEqual;
* @protected
*/
function shouldUpdateReactComponent(prevElement, nextElement) {
- if (prevElement && nextElement &&
- prevElement.type === nextElement.type &&
- prevElement.key === nextElement.key &&
- prevElement._owner === nextElement._owner) {
- return true;
+ if (prevElement != null && nextElement != null) {
+ var prevType = typeof prevElement;
+ var nextType = typeof nextElement;
+ if (prevType === 'string' || prevType === 'number') {
+ return (nextType === 'string' || nextType === 'number');
+ } else {
+ if (nextType === 'object' &&
+ prevElement.type === nextElement.type &&
+ prevElement.key === nextElement.key) {
+ var ownersMatch = prevElement._owner === nextElement._owner;
+ var prevName = null;
+ var nextName = null;
+ var nextDisplayName = null;
+ if ("production" !== process.env.NODE_ENV) {
+ if (!ownersMatch) {
+ if (prevElement._owner != null &&
+ prevElement._owner.getPublicInstance() != null &&
+ prevElement._owner.getPublicInstance().constructor != null) {
+ prevName =
+ prevElement._owner.getPublicInstance().constructor.displayName;
+ }
+ if (nextElement._owner != null &&
+ nextElement._owner.getPublicInstance() != null &&
+ nextElement._owner.getPublicInstance().constructor != null) {
+ nextName =
+ nextElement._owner.getPublicInstance().constructor.displayName;
+ }
+ if (nextElement.type != null &&
+ nextElement.type.displayName != null) {
+ nextDisplayName = nextElement.type.displayName;
+ }
+ if (nextElement.type != null && typeof nextElement.type === 'string') {
+ nextDisplayName = nextElement.type;
+ }
+ if (typeof nextElement.type !== 'string' ||
+ nextElement.type === 'input' ||
+ nextElement.type === 'textarea') {
+ if ((prevElement._owner != null &&
+ prevElement._owner._isOwnerNecessary === false) ||
+ (nextElement._owner != null &&
+ nextElement._owner._isOwnerNecessary === false)) {
+ if (prevElement._owner != null) {
+ prevElement._owner._isOwnerNecessary = true;
+ }
+ if (nextElement._owner != null) {
+ nextElement._owner._isOwnerNecessary = true;
+ }
+ ("production" !== process.env.NODE_ENV ? warning(
+ false,
+ '<%s /> is being rendered by both %s and %s using the same ' +
+ 'key (%s) in the same place. Currently, this means that ' +
+ 'they don\'t preserve state. This behavior should be very ' +
+ 'rare so we\'re considering deprecating it. Please contact ' +
+ 'the React team and explain your use case so that we can ' +
+ 'take that into consideration.',
+ nextDisplayName || 'Unknown Component',
+ prevName || '[Unknown]',
+ nextName || '[Unknown]',
+ prevElement.key
+ ) : null);
+ }
+ }
+ }
+ }
+ return ownersMatch;
+ }
+ }
}
return false;
}
module.exports = shouldUpdateReactComponent;
-},{}],199:[function(require,module,exports){
+}).call(this,require('_process'))
+},{"./warning":212,"_process":1}],209:[function(require,module,exports){
(function (process){
/**
- * Copyright 2014, Facebook, Inc.
+ * Copyright 2014-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -22609,7 +24506,7 @@ var invariant = require("./invariant");
* Convert array-like objects to arrays.
*
* This API assumes the caller knows the contents of the data type. For less
- * well defined inputs use createArrayFrom.
+ * well defined inputs use createArrayFromMixed.
*
* @param {object|function|filelist} obj
* @return {array}
@@ -22661,10 +24558,10 @@ function toArray(obj) {
module.exports = toArray;
}).call(this,require('_process'))
-},{"./invariant":182,"_process":1}],200:[function(require,module,exports){
+},{"./invariant":191,"_process":1}],210:[function(require,module,exports){
(function (process){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -22674,22 +24571,22 @@ module.exports = toArray;
* @providesModule traverseAllChildren
*/
-"use strict";
+'use strict';
var ReactElement = require("./ReactElement");
+var ReactFragment = require("./ReactFragment");
var ReactInstanceHandles = require("./ReactInstanceHandles");
+var getIteratorFn = require("./getIteratorFn");
var invariant = require("./invariant");
+var warning = require("./warning");
var SEPARATOR = ReactInstanceHandles.SEPARATOR;
var SUBSEPARATOR = ':';
/**
- * TODO: Test that:
- * 1. `mapChildren` transforms strings and numbers into `ReactTextComponent`.
- * 2. it('should fail when supplied duplicate key', function() {
- * 3. That a single child and an array with one item have the same key pattern.
- * });
+ * TODO: Test that a single child and an array with one item have the same key
+ * pattern.
*/
var userProvidedKeyEscaperLookup = {
@@ -22700,6 +24597,8 @@ var userProvidedKeyEscaperLookup = {
var userProvidedKeyEscapeRegex = /[=.:]/g;
+var didWarnAboutMaps = false;
+
function userProvidedKeyEscaper(match) {
return userProvidedKeyEscaperLookup[match];
}
@@ -22753,58 +24652,99 @@ function wrapUserProvidedKey(key) {
* process.
* @return {!number} The number of children in this subtree.
*/
-var traverseAllChildrenImpl =
- function(children, nameSoFar, indexSoFar, callback, traverseContext) {
- var nextName, nextIndex;
- var subtreeCount = 0; // Count of children found in the current subtree.
- if (Array.isArray(children)) {
- for (var i = 0; i < children.length; i++) {
- var child = children[i];
- nextName = (
- nameSoFar +
- (nameSoFar ? SUBSEPARATOR : SEPARATOR) +
- getComponentKey(child, i)
- );
- nextIndex = indexSoFar + subtreeCount;
- subtreeCount += traverseAllChildrenImpl(
- child,
- nextName,
- nextIndex,
- callback,
- traverseContext
- );
- }
- } else {
- var type = typeof children;
- var isOnlyChild = nameSoFar === '';
+function traverseAllChildrenImpl(
+ children,
+ nameSoFar,
+ indexSoFar,
+ callback,
+ traverseContext
+) {
+ var type = typeof children;
+
+ if (type === 'undefined' || type === 'boolean') {
+ // All of the above are perceived as null.
+ children = null;
+ }
+
+ if (children === null ||
+ type === 'string' ||
+ type === 'number' ||
+ ReactElement.isValidElement(children)) {
+ callback(
+ traverseContext,
+ children,
// If it's the only child, treat the name as if it was wrapped in an array
- // so that it's consistent if the number of children grows
- var storageName =
- isOnlyChild ? SEPARATOR + getComponentKey(children, 0) : nameSoFar;
- if (children == null || type === 'boolean') {
- // All of the above are perceived as null.
- callback(traverseContext, null, storageName, indexSoFar);
- subtreeCount = 1;
- } else if (type === 'string' || type === 'number' ||
- ReactElement.isValidElement(children)) {
- callback(traverseContext, children, storageName, indexSoFar);
- subtreeCount = 1;
- } else if (type === 'object') {
- ("production" !== process.env.NODE_ENV ? invariant(
- !children || children.nodeType !== 1,
- 'traverseAllChildren(...): Encountered an invalid child; DOM ' +
- 'elements are not valid children of React components.'
- ) : invariant(!children || children.nodeType !== 1));
- for (var key in children) {
- if (children.hasOwnProperty(key)) {
+ // so that it's consistent if the number of children grows.
+ nameSoFar === '' ? SEPARATOR + getComponentKey(children, 0) : nameSoFar,
+ indexSoFar
+ );
+ return 1;
+ }
+
+ var child, nextName, nextIndex;
+ var subtreeCount = 0; // Count of children found in the current subtree.
+
+ if (Array.isArray(children)) {
+ for (var i = 0; i < children.length; i++) {
+ child = children[i];
+ nextName = (
+ (nameSoFar !== '' ? nameSoFar + SUBSEPARATOR : SEPARATOR) +
+ getComponentKey(child, i)
+ );
+ nextIndex = indexSoFar + subtreeCount;
+ subtreeCount += traverseAllChildrenImpl(
+ child,
+ nextName,
+ nextIndex,
+ callback,
+ traverseContext
+ );
+ }
+ } else {
+ var iteratorFn = getIteratorFn(children);
+ if (iteratorFn) {
+ var iterator = iteratorFn.call(children);
+ var step;
+ if (iteratorFn !== children.entries) {
+ var ii = 0;
+ while (!(step = iterator.next()).done) {
+ child = step.value;
+ nextName = (
+ (nameSoFar !== '' ? nameSoFar + SUBSEPARATOR : SEPARATOR) +
+ getComponentKey(child, ii++)
+ );
+ nextIndex = indexSoFar + subtreeCount;
+ subtreeCount += traverseAllChildrenImpl(
+ child,
+ nextName,
+ nextIndex,
+ callback,
+ traverseContext
+ );
+ }
+ } else {
+ if ("production" !== process.env.NODE_ENV) {
+ ("production" !== process.env.NODE_ENV ? warning(
+ didWarnAboutMaps,
+ 'Using Maps as children is not yet fully supported. It is an ' +
+ 'experimental feature that might be removed. Convert it to a ' +
+ 'sequence / iterable of keyed ReactElements instead.'
+ ) : null);
+ didWarnAboutMaps = true;
+ }
+ // Iterator will provide entry [k,v] tuples rather than values.
+ while (!(step = iterator.next()).done) {
+ var entry = step.value;
+ if (entry) {
+ child = entry[1];
nextName = (
- nameSoFar + (nameSoFar ? SUBSEPARATOR : SEPARATOR) +
- wrapUserProvidedKey(key) + SUBSEPARATOR +
- getComponentKey(children[key], 0)
+ (nameSoFar !== '' ? nameSoFar + SUBSEPARATOR : SEPARATOR) +
+ wrapUserProvidedKey(entry[0]) + SUBSEPARATOR +
+ getComponentKey(child, 0)
);
nextIndex = indexSoFar + subtreeCount;
subtreeCount += traverseAllChildrenImpl(
- children[key],
+ child,
nextName,
nextIndex,
callback,
@@ -22813,9 +24753,36 @@ var traverseAllChildrenImpl =
}
}
}
+ } else if (type === 'object') {
+ ("production" !== process.env.NODE_ENV ? invariant(
+ children.nodeType !== 1,
+ 'traverseAllChildren(...): Encountered an invalid child; DOM ' +
+ 'elements are not valid children of React components.'
+ ) : invariant(children.nodeType !== 1));
+ var fragment = ReactFragment.extract(children);
+ for (var key in fragment) {
+ if (fragment.hasOwnProperty(key)) {
+ child = fragment[key];
+ nextName = (
+ (nameSoFar !== '' ? nameSoFar + SUBSEPARATOR : SEPARATOR) +
+ wrapUserProvidedKey(key) + SUBSEPARATOR +
+ getComponentKey(child, 0)
+ );
+ nextIndex = indexSoFar + subtreeCount;
+ subtreeCount += traverseAllChildrenImpl(
+ child,
+ nextName,
+ nextIndex,
+ callback,
+ traverseContext
+ );
+ }
+ }
}
- return subtreeCount;
- };
+ }
+
+ return subtreeCount;
+}
/**
* Traverses children that are typically specified as `props.children`, but
@@ -22844,10 +24811,10 @@ function traverseAllChildren(children, callback, traverseContext) {
module.exports = traverseAllChildren;
}).call(this,require('_process'))
-},{"./ReactElement":99,"./ReactInstanceHandles":107,"./invariant":182,"_process":1}],201:[function(require,module,exports){
+},{"./ReactElement":103,"./ReactFragment":109,"./ReactInstanceHandles":112,"./getIteratorFn":182,"./invariant":191,"./warning":212,"_process":1}],211:[function(require,module,exports){
(function (process){
/**
- * Copyright 2013-2014, Facebook, Inc.
+ * Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -22857,7 +24824,7 @@ module.exports = traverseAllChildren;
* @providesModule update
*/
-"use strict";
+'use strict';
var assign = require("./Object.assign");
var keyOf = require("./keyOf");
@@ -23012,10 +24979,10 @@ function update(value, spec) {
module.exports = update;
}).call(this,require('_process'))
-},{"./Object.assign":70,"./invariant":182,"./keyOf":189,"_process":1}],202:[function(require,module,exports){
+},{"./Object.assign":69,"./invariant":191,"./keyOf":198,"_process":1}],212:[function(require,module,exports){
(function (process){
/**
- * Copyright 2014, Facebook, Inc.
+ * Copyright 2014-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -23039,7 +25006,7 @@ var emptyFunction = require("./emptyFunction");
var warning = emptyFunction;
if ("production" !== process.env.NODE_ENV) {
- warning = function(condition, format ) {var args=Array.prototype.slice.call(arguments,2);
+ warning = function(condition, format ) {for (var args=[],$__0=2,$__1=arguments.length;$__0<$__1;$__0++) args.push(arguments[$__0]);
if (format === undefined) {
throw new Error(
'`warning(condition, format, ...args)` requires a warning ' +
@@ -23047,9 +25014,27 @@ if ("production" !== process.env.NODE_ENV) {
);
}
+ if (format.length < 10 || /^[s\W]*$/.test(format)) {
+ throw new Error(
+ 'The warning format should be able to uniquely identify this ' +
+ 'warning. Please, use a more descriptive format than: ' + format
+ );
+ }
+
+ if (format.indexOf('Failed Composite propType: ') === 0) {
+ return; // Ignore CompositeComponent proptype check.
+ }
+
if (!condition) {
var argIndex = 0;
- console.warn('Warning: ' + format.replace(/%s/g, function() {return args[argIndex++];}));
+ var message = 'Warning: ' + format.replace(/%s/g, function() {return args[argIndex++];});
+ console.warn(message);
+ try {
+ // --- Welcome to debugging React ---
+ // This error was thrown as a convenience so that you can use this stack
+ // to find the callsite that caused this warning to fire.
+ throw new Error(message);
+ } catch(x) {}
}
};
}
@@ -23057,7 +25042,7 @@ if ("production" !== process.env.NODE_ENV) {
module.exports = warning;
}).call(this,require('_process'))
-},{"./emptyFunction":163,"_process":1}],"flux":[function(require,module,exports){
+},{"./emptyFunction":170,"_process":1}],"flux":[function(require,module,exports){
/**
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
@@ -32280,7 +34265,7 @@ return jQuery;
(function (global){
/**
* @license
- * lodash 3.5.0 (Custom Build) <https://lodash.com/>
+ * lodash 3.6.0 (Custom Build) <https://lodash.com/>
* Build: `lodash modern -d -o ./index.js`
* Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
* Based on Underscore.js 1.8.2 <http://underscorejs.org/LICENSE>
@@ -32293,7 +34278,7 @@ return jQuery;
var undefined;
/** Used as the semantic version number. */
- var VERSION = '3.5.0';
+ var VERSION = '3.6.0';
/** Used to compose bitmasks for wrapper metadata. */
var BIND_FLAG = 1,
@@ -32303,8 +34288,8 @@ return jQuery;
CURRY_RIGHT_FLAG = 16,
PARTIAL_FLAG = 32,
PARTIAL_RIGHT_FLAG = 64,
- REARG_FLAG = 128,
- ARY_FLAG = 256;
+ ARY_FLAG = 128,
+ REARG_FLAG = 256;
/** Used as default options for `_.trunc`. */
var DEFAULT_TRUNC_LENGTH = 30,
@@ -32368,18 +34353,18 @@ return jQuery;
reInterpolate = /<%=([\s\S]+?)%>/g;
/**
- * Used to match ES template delimiters.
- * See the [ES spec](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-template-literal-lexical-components)
- * for more details.
+ * Used to match [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks).
+ */
+ var reComboMarks = /[\u0300-\u036f\ufe20-\ufe23]/g;
+
+ /**
+ * Used to match [ES template delimiters](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-template-literal-lexical-components).
*/
var reEsTemplate = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g;
/** Used to match `RegExp` flags from their coerced string values. */
var reFlags = /\w*$/;
- /** Used to detect named functions. */
- var reFuncName = /^\s*function[ \n\r\t]+\w/;
-
/** Used to detect hexadecimal string values. */
var reHexPrefix = /^0[xX]/;
@@ -32393,16 +34378,13 @@ return jQuery;
var reNoMatch = /($^)/;
/**
- * Used to match `RegExp` special characters.
- * See this [article on `RegExp` characters](http://www.regular-expressions.info/characters.html#special)
- * for more details.
+ * Used to match `RegExp` [special characters](http://www.regular-expressions.info/characters.html#special).
+ * In addition to special characters the forward slash is escaped to allow for
+ * easier `eval` use and `Function` compilation.
*/
var reRegExpChars = /[.*+?^${}()|[\]\/\\]/g,
reHasRegExpChars = RegExp(reRegExpChars.source);
- /** Used to detect functions containing a `this` reference. */
- var reThis = /\bthis\b/;
-
/** Used to match unescaped characters in compiled string literals. */
var reUnescapedString = /['\n\r\u2028\u2029\\]/g;
@@ -32433,7 +34415,7 @@ return jQuery;
'Object', 'RegExp', 'Set', 'String', '_', 'clearTimeout', 'document',
'isFinite', 'parseInt', 'setTimeout', 'TypeError', 'Uint8Array',
'Uint8ClampedArray', 'Uint16Array', 'Uint32Array', 'WeakMap',
- 'window', 'WinRTError'
+ 'window'
];
/** Used to make template sourceURLs easier to identify. */
@@ -32542,8 +34524,11 @@ return jQuery;
/** Detect free variable `global` from Node.js. */
var freeGlobal = freeExports && freeModule && typeof global == 'object' && global;
+ /** Detect free variable `self`. */
+ var freeSelf = objectTypes[typeof self] && self && self.Object && self;
+
/** Detect free variable `window`. */
- var freeWindow = objectTypes[typeof window] && window;
+ var freeWindow = objectTypes[typeof window] && window && window.Object && window;
/** Detect the popular CommonJS extension `module.exports`. */
var moduleExports = freeModule && freeModule.exports === freeExports && freeExports;
@@ -32554,7 +34539,7 @@ return jQuery;
* The `this` value is used if it is the global object to avoid Greasemonkey's
* restricted `window` object, otherwise the `window` object is used.
*/
- var root = freeGlobal || ((freeWindow !== (this && this.window)) && freeWindow) || this;
+ var root = freeGlobal || ((freeWindow !== (this && this.window)) && freeWindow) || freeSelf || this;
/*--------------------------------------------------------------------------*/
@@ -32583,6 +34568,28 @@ return jQuery;
}
/**
+ * The base implementation of `_.findIndex` and `_.findLastIndex` without
+ * support for callback shorthands and `this` binding.
+ *
+ * @private
+ * @param {Array} array The array to search.
+ * @param {Function} predicate The function invoked per iteration.
+ * @param {boolean} [fromRight] Specify iterating from right to left.
+ * @returns {number} Returns the index of the matched value, else `-1`.
+ */
+ function baseFindIndex(array, predicate, fromRight) {
+ var length = array.length,
+ index = fromRight ? length : -1;
+
+ while ((fromRight ? index-- : ++index < length)) {
+ if (predicate(array[index], index, array)) {
+ return index;
+ }
+ }
+ return -1;
+ }
+
+ /**
* The base implementation of `_.indexOf` without support for binary searches.
*
* @private
@@ -32768,7 +34775,6 @@ return jQuery;
/**
* Gets the index at which the first occurrence of `NaN` is found in `array`.
- * If `fromRight` is provided elements of `array` are iterated from right to left.
*
* @private
* @param {Array} array The array to search.
@@ -32797,7 +34803,7 @@ return jQuery;
* @returns {boolean} Returns `true` if `value` is object-like, else `false`.
*/
function isObjectLike(value) {
- return (value && typeof value == 'object') || false;
+ return !!value && typeof value == 'object';
}
/**
@@ -32919,19 +34925,19 @@ return jQuery;
* @returns {Function} Returns a new `lodash` function.
* @example
*
- * _.mixin({ 'add': function(a, b) { return a + b; } });
+ * _.mixin({ 'foo': _.constant('foo') });
*
* var lodash = _.runInContext();
- * lodash.mixin({ 'sub': function(a, b) { return a - b; } });
+ * lodash.mixin({ 'bar': lodash.constant('bar') });
*
- * _.isFunction(_.add);
+ * _.isFunction(_.foo);
* // => true
- * _.isFunction(_.sub);
+ * _.isFunction(_.bar);
* // => false
*
- * lodash.isFunction(lodash.add);
+ * lodash.isFunction(lodash.foo);
* // => false
- * lodash.isFunction(lodash.sub);
+ * lodash.isFunction(lodash.bar);
* // => true
*
* // using `context` to mock `Date#getTime` use in `_.now`
@@ -32984,9 +34990,8 @@ return jQuery;
var idCounter = 0;
/**
- * Used to resolve the `toStringTag` of values.
- * See the [ES spec](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-object.prototype.tostring)
- * for more details.
+ * Used to resolve the [`toStringTag`](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-object.prototype.tostring)
+ * of values.
*/
var objToString = objectProto.toString;
@@ -33051,15 +35056,17 @@ return jQuery;
var FLOAT64_BYTES_PER_ELEMENT = Float64Array ? Float64Array.BYTES_PER_ELEMENT : 0;
/**
- * Used as the maximum length of an array-like value.
- * See the [ES spec](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-number.max_safe_integer)
- * for more details.
+ * Used as the [maximum length](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-number.max_safe_integer)
+ * of an array-like value.
*/
var MAX_SAFE_INTEGER = Math.pow(2, 53) - 1;
/** Used to store function metadata. */
var metaMap = WeakMap && new WeakMap;
+ /** Used to lookup unminified function names. */
+ var realNames = {};
+
/*------------------------------------------------------------------------*/
/**
@@ -33209,7 +35216,7 @@ return jQuery;
* @memberOf _.support
* @type boolean
*/
- support.funcDecomp = !isNative(context.WinRTError) && reThis.test(runInContext);
+ support.funcDecomp = /\bthis\b/.test(function() { return this; });
/**
* Detect if `Function#name` is supported (all but IE).
@@ -33585,7 +35592,7 @@ return jQuery;
/**
* A specialized version of `_.forEach` for arrays without support for callback
- * shorthands or `this` binding.
+ * shorthands and `this` binding.
*
* @private
* @param {Array} array The array to iterate over.
@@ -33606,7 +35613,7 @@ return jQuery;
/**
* A specialized version of `_.forEachRight` for arrays without support for
- * callback shorthands or `this` binding.
+ * callback shorthands and `this` binding.
*
* @private
* @param {Array} array The array to iterate over.
@@ -33626,7 +35633,7 @@ return jQuery;
/**
* A specialized version of `_.every` for arrays without support for callback
- * shorthands or `this` binding.
+ * shorthands and `this` binding.
*
* @private
* @param {Array} array The array to iterate over.
@@ -33648,7 +35655,7 @@ return jQuery;
/**
* A specialized version of `_.filter` for arrays without support for callback
- * shorthands or `this` binding.
+ * shorthands and `this` binding.
*
* @private
* @param {Array} array The array to iterate over.
@@ -33672,7 +35679,7 @@ return jQuery;
/**
* A specialized version of `_.map` for arrays without support for callback
- * shorthands or `this` binding.
+ * shorthands and `this` binding.
*
* @private
* @param {Array} array The array to iterate over.
@@ -33734,7 +35741,7 @@ return jQuery;
/**
* A specialized version of `_.reduce` for arrays without support for callback
- * shorthands or `this` binding.
+ * shorthands and `this` binding.
*
* @private
* @param {Array} array The array to iterate over.
@@ -33759,7 +35766,7 @@ return jQuery;
/**
* A specialized version of `_.reduceRight` for arrays without support for
- * callback shorthands or `this` binding.
+ * callback shorthands and `this` binding.
*
* @private
* @param {Array} array The array to iterate over.
@@ -33782,7 +35789,7 @@ return jQuery;
/**
* A specialized version of `_.some` for arrays without support for callback
- * shorthands or `this` binding.
+ * shorthands and `this` binding.
*
* @private
* @param {Array} array The array to iterate over.
@@ -33803,6 +35810,23 @@ return jQuery;
}
/**
+ * A specialized version of `_.sum` for arrays without support for iteratees.
+ *
+ * @private
+ * @param {Array} array The array to iterate over.
+ * @returns {number} Returns the sum.
+ */
+ function arraySum(array) {
+ var length = array.length,
+ result = 0;
+
+ while (length--) {
+ result += +array[length] || 0;
+ }
+ return result;
+ }
+
+ /**
* Used by `_.defaults` to customize its `_.assign` use.
*
* @private
@@ -33917,26 +35941,6 @@ return jQuery;
}
/**
- * The base implementation of `_.bindAll` without support for individual
- * method name arguments.
- *
- * @private
- * @param {Object} object The object to bind and assign the bound methods to.
- * @param {string[]} methodNames The object method names to bind.
- * @returns {Object} Returns `object`.
- */
- function baseBindAll(object, methodNames) {
- var index = -1,
- length = methodNames.length;
-
- while (++index < length) {
- var key = methodNames[index];
- object[key] = createWrapper(object[key], BIND_FLAG, object);
- }
- return object;
- }
-
- /**
* The base implementation of `_.callback` which supports specifying the
* number of arguments to provide to `func`.
*
@@ -33949,9 +35953,9 @@ return jQuery;
function baseCallback(func, thisArg, argCount) {
var type = typeof func;
if (type == 'function') {
- return (typeof thisArg != 'undefined' && isBindable(func))
- ? bindCallback(func, thisArg, argCount)
- : func;
+ return typeof thisArg == 'undefined'
+ ? func
+ : bindCallback(func, thisArg, argCount);
}
if (func == null) {
return identity;
@@ -34058,14 +36062,14 @@ return jQuery;
* @private
* @param {Function} func The function to delay.
* @param {number} wait The number of milliseconds to delay invocation.
- * @param {Object} args The `arguments` object to slice and provide to `func`.
+ * @param {Object} args The arguments provide to `func`.
* @returns {number} Returns the timer id.
*/
- function baseDelay(func, wait, args, fromIndex) {
+ function baseDelay(func, wait, args) {
if (typeof func != 'function') {
throw new TypeError(FUNC_ERROR_TEXT);
}
- return setTimeout(function() { func.apply(undefined, baseSlice(args, fromIndex)); }, wait);
+ return setTimeout(function() { func.apply(undefined, args); }, wait);
}
/**
@@ -34124,21 +36128,7 @@ return jQuery;
* @param {Function} iteratee The function invoked per iteration.
* @returns {Array|Object|string} Returns `collection`.
*/
- function baseEach(collection, iteratee) {
- var length = collection ? collection.length : 0;
- if (!isLength(length)) {
- return baseForOwn(collection, iteratee);
- }
- var index = -1,
- iterable = toObject(collection);
-
- while (++index < length) {
- if (iteratee(iterable[index], index, iterable) === false) {
- break;
- }
- }
- return collection;
- }
+ var baseEach = createBaseEach(baseForOwn);
/**
* The base implementation of `_.forEachRight` without support for callback
@@ -34149,23 +36139,11 @@ return jQuery;
* @param {Function} iteratee The function invoked per iteration.
* @returns {Array|Object|string} Returns `collection`.
*/
- function baseEachRight(collection, iteratee) {
- var length = collection ? collection.length : 0;
- if (!isLength(length)) {
- return baseForOwnRight(collection, iteratee);
- }
- var iterable = toObject(collection);
- while (length--) {
- if (iteratee(iterable[length], length, iterable) === false) {
- break;
- }
- }
- return collection;
- }
+ var baseEachRight = createBaseEach(baseForOwnRight, true);
/**
* The base implementation of `_.every` without support for callback
- * shorthands or `this` binding.
+ * shorthands and `this` binding.
*
* @private
* @param {Array|Object|string} collection The collection to iterate over.
@@ -34214,7 +36192,7 @@ return jQuery;
/**
* The base implementation of `_.filter` without support for callback
- * shorthands or `this` binding.
+ * shorthands and `this` binding.
*
* @private
* @param {Array|Object|string} collection The collection to iterate over.
@@ -34263,11 +36241,10 @@ return jQuery;
* @param {Array} array The array to flatten.
* @param {boolean} isDeep Specify a deep flatten.
* @param {boolean} isStrict Restrict flattening to arrays and `arguments` objects.
- * @param {number} fromIndex The index to start from.
* @returns {Array} Returns the new flattened array.
*/
- function baseFlatten(array, isDeep, isStrict, fromIndex) {
- var index = fromIndex - 1,
+ function baseFlatten(array, isDeep, isStrict) {
+ var index = -1,
length = array.length,
resIndex = -1,
result = [];
@@ -34278,7 +36255,7 @@ return jQuery;
if (isObjectLike(value) && isLength(value.length) && (isArray(value) || isArguments(value))) {
if (isDeep) {
// Recursively flatten arrays (susceptible to call stack limits).
- value = baseFlatten(value, isDeep, isStrict, 0);
+ value = baseFlatten(value, isDeep, isStrict);
}
var valIndex = -1,
valLength = value.length;
@@ -34306,20 +36283,7 @@ return jQuery;
* @param {Function} keysFunc The function to get the keys of `object`.
* @returns {Object} Returns `object`.
*/
- function baseFor(object, iteratee, keysFunc) {
- var index = -1,
- iterable = toObject(object),
- props = keysFunc(object),
- length = props.length;
-
- while (++index < length) {
- var key = props[index];
- if (iteratee(iterable[key], key, iterable) === false) {
- break;
- }
- }
- return object;
- }
+ var baseFor = createBaseFor();
/**
* This function is like `baseFor` except that it iterates over properties
@@ -34331,19 +36295,7 @@ return jQuery;
* @param {Function} keysFunc The function to get the keys of `object`.
* @returns {Object} Returns `object`.
*/
- function baseForRight(object, iteratee, keysFunc) {
- var iterable = toObject(object),
- props = keysFunc(object),
- length = props.length;
-
- while (length--) {
- var key = props[length];
- if (iteratee(iterable[key], key, iterable) === false) {
- break;
- }
- }
- return object;
- }
+ var baseForRight = createBaseFor(true);
/**
* The base implementation of `_.forIn` without support for callback
@@ -34409,30 +36361,6 @@ return jQuery;
}
/**
- * The base implementation of `_.invoke` which requires additional arguments
- * to be provided as an array of arguments rather than individually.
- *
- * @private
- * @param {Array|Object|string} collection The collection to iterate over.
- * @param {Function|string} methodName The name of the method to invoke or
- * the function invoked per iteration.
- * @param {Array} [args] The arguments to invoke the method with.
- * @returns {Array} Returns the array of results.
- */
- function baseInvoke(collection, methodName, args) {
- var index = -1,
- isFunc = typeof methodName == 'function',
- length = collection ? collection.length : 0,
- result = isLength(length) ? Array(length) : [];
-
- baseEach(collection, function(value) {
- var func = isFunc ? methodName : (value != null && value[methodName]);
- result[++index] = func ? func.apply(value, args) : undefined;
- });
- return result;
- }
-
- /**
* The base implementation of `_.isEqual` without support for `this` binding
* `customizer` functions.
*
@@ -34440,12 +36368,12 @@ return jQuery;
* @param {*} value The value to compare.
* @param {*} other The other value to compare.
* @param {Function} [customizer] The function to customize comparing values.
- * @param {boolean} [isWhere] Specify performing partial comparisons.
+ * @param {boolean} [isLoose] Specify performing partial comparisons.
* @param {Array} [stackA] Tracks traversed `value` objects.
* @param {Array} [stackB] Tracks traversed `other` objects.
* @returns {boolean} Returns `true` if the values are equivalent, else `false`.
*/
- function baseIsEqual(value, other, customizer, isWhere, stackA, stackB) {
+ function baseIsEqual(value, other, customizer, isLoose, stackA, stackB) {
// Exit early for identical values.
if (value === other) {
// Treat `+0` vs. `-0` as not equal.
@@ -34460,7 +36388,7 @@ return jQuery;
// Return `false` unless both values are `NaN`.
return value !== value && other !== other;
}
- return baseIsEqualDeep(value, other, baseIsEqual, customizer, isWhere, stackA, stackB);
+ return baseIsEqualDeep(value, other, baseIsEqual, customizer, isLoose, stackA, stackB);
}
/**
@@ -34473,12 +36401,12 @@ return jQuery;
* @param {Object} other The other object to compare.
* @param {Function} equalFunc The function to determine equivalents of values.
* @param {Function} [customizer] The function to customize comparing objects.
- * @param {boolean} [isWhere] Specify performing partial comparisons.
+ * @param {boolean} [isLoose] Specify performing partial comparisons.
* @param {Array} [stackA=[]] Tracks traversed `value` objects.
* @param {Array} [stackB=[]] Tracks traversed `other` objects.
* @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
*/
- function baseIsEqualDeep(object, other, equalFunc, customizer, isWhere, stackA, stackB) {
+ function baseIsEqualDeep(object, other, equalFunc, customizer, isLoose, stackA, stackB) {
var objIsArr = isArray(object),
othIsArr = isArray(other),
objTag = arrayTag,
@@ -34500,21 +36428,27 @@ return jQuery;
othIsArr = isTypedArray(other);
}
}
- var objIsObj = objTag == objectTag,
- othIsObj = othTag == objectTag,
+ var objIsObj = (objTag == objectTag || (isLoose && objTag == funcTag)),
+ othIsObj = (othTag == objectTag || (isLoose && othTag == funcTag)),
isSameTag = objTag == othTag;
if (isSameTag && !(objIsArr || objIsObj)) {
return equalByTag(object, other, objTag);
}
- var valWrapped = objIsObj && hasOwnProperty.call(object, '__wrapped__'),
- othWrapped = othIsObj && hasOwnProperty.call(other, '__wrapped__');
+ if (isLoose) {
+ if (!isSameTag && !(objIsObj && othIsObj)) {
+ return false;
+ }
+ } else {
+ var valWrapped = objIsObj && hasOwnProperty.call(object, '__wrapped__'),
+ othWrapped = othIsObj && hasOwnProperty.call(other, '__wrapped__');
- if (valWrapped || othWrapped) {
- return equalFunc(valWrapped ? object.value() : object, othWrapped ? other.value() : other, customizer, isWhere, stackA, stackB);
- }
- if (!isSameTag) {
- return false;
+ if (valWrapped || othWrapped) {
+ return equalFunc(valWrapped ? object.value() : object, othWrapped ? other.value() : other, customizer, isLoose, stackA, stackB);
+ }
+ if (!isSameTag) {
+ return false;
+ }
}
// Assume cyclic values are equal.
// For more information on detecting circular references see https://es5.github.io/#JO.
@@ -34531,7 +36465,7 @@ return jQuery;
stackA.push(object);
stackB.push(other);
- var result = (objIsArr ? equalArrays : equalObjects)(object, other, equalFunc, customizer, isWhere, stackA, stackB);
+ var result = (objIsArr ? equalArrays : equalObjects)(object, other, equalFunc, customizer, isLoose, stackA, stackB);
stackA.pop();
stackB.pop();
@@ -34541,7 +36475,7 @@ return jQuery;
/**
* The base implementation of `_.isMatch` without support for callback
- * shorthands or `this` binding.
+ * shorthands and `this` binding.
*
* @private
* @param {Object} object The object to inspect.
@@ -34552,30 +36486,27 @@ return jQuery;
* @returns {boolean} Returns `true` if `object` is a match, else `false`.
*/
function baseIsMatch(object, props, values, strictCompareFlags, customizer) {
- var length = props.length;
- if (object == null) {
- return !length;
- }
var index = -1,
+ length = props.length,
noCustomizer = !customizer;
while (++index < length) {
if ((noCustomizer && strictCompareFlags[index])
? values[index] !== object[props[index]]
- : !hasOwnProperty.call(object, props[index])
+ : !(props[index] in object)
) {
return false;
}
}
index = -1;
while (++index < length) {
- var key = props[index];
+ var key = props[index],
+ objValue = object[key],
+ srcValue = values[index];
+
if (noCustomizer && strictCompareFlags[index]) {
- var result = hasOwnProperty.call(object, key);
+ var result = typeof objValue != 'undefined' || (key in object);
} else {
- var objValue = object[key],
- srcValue = values[index];
-
result = customizer ? customizer(objValue, srcValue, key) : undefined;
if (typeof result == 'undefined') {
result = baseIsEqual(srcValue, objValue, customizer, true);
@@ -34590,7 +36521,7 @@ return jQuery;
/**
* The base implementation of `_.map` without support for callback shorthands
- * or `this` binding.
+ * and `this` binding.
*
* @private
* @param {Array|Object|string} collection The collection to iterate over.
@@ -34616,13 +36547,17 @@ return jQuery;
var props = keys(source),
length = props.length;
+ if (!length) {
+ return constant(true);
+ }
if (length == 1) {
var key = props[0],
value = source[key];
if (isStrictComparable(value)) {
return function(object) {
- return object != null && object[key] === value && hasOwnProperty.call(object, key);
+ return object != null && object[key] === value &&
+ (typeof value != 'undefined' || (key in toObject(object)));
};
}
}
@@ -34635,7 +36570,7 @@ return jQuery;
strictCompareFlags[length] = isStrictComparable(value);
}
return function(object) {
- return baseIsMatch(object, props, values, strictCompareFlags);
+ return object != null && baseIsMatch(toObject(object), props, values, strictCompareFlags);
};
}
@@ -34651,7 +36586,8 @@ return jQuery;
function baseMatchesProperty(key, value) {
if (isStrictComparable(value)) {
return function(object) {
- return object != null && object[key] === value;
+ return object != null && object[key] === value &&
+ (typeof value != 'undefined' || (key in toObject(object)));
};
}
return function(object) {
@@ -34731,7 +36667,7 @@ return jQuery;
if (isLength(srcValue.length) && (isArray(srcValue) || isTypedArray(srcValue))) {
result = isArray(value)
? value
- : (value ? arrayCopy(value) : []);
+ : ((value && value.length) ? arrayCopy(value) : []);
}
else if (isPlainObject(srcValue) || isArguments(srcValue)) {
result = isArguments(value)
@@ -34769,30 +36705,6 @@ return jQuery;
}
/**
- * The base implementation of `_.pullAt` without support for individual
- * index arguments.
- *
- * @private
- * @param {Array} array The array to modify.
- * @param {number[]} indexes The indexes of elements to remove.
- * @returns {Array} Returns the new array of removed elements.
- */
- function basePullAt(array, indexes) {
- var length = indexes.length,
- result = baseAt(array, indexes);
-
- indexes.sort(baseCompareAscending);
- while (length--) {
- var index = parseFloat(indexes[length]);
- if (index != previous && isIndex(index)) {
- var previous = index;
- splice.call(array, index, 1);
- }
- }
- return result;
- }
-
- /**
* The base implementation of `_.random` without support for argument juggling
* and returning floating-point numbers.
*
@@ -34807,7 +36719,7 @@ return jQuery;
/**
* The base implementation of `_.reduce` and `_.reduceRight` without support
- * for callback shorthands or `this` binding, which iterates over `collection`
+ * for callback shorthands and `this` binding, which iterates over `collection`
* using the provided `eachFunc`.
*
* @private
@@ -34874,7 +36786,7 @@ return jQuery;
/**
* The base implementation of `_.some` without support for callback shorthands
- * or `this` binding.
+ * and `this` binding.
*
* @private
* @param {Array|Object|string} collection The collection to iterate over.
@@ -34942,6 +36854,23 @@ return jQuery;
}
/**
+ * The base implementation of `_.sum` without support for callback shorthands
+ * and `this` binding.
+ *
+ * @private
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @returns {number} Returns the sum.
+ */
+ function baseSum(collection, iteratee) {
+ var result = 0;
+ baseEach(collection, function(value, index, collection) {
+ result += +iteratee(value, index, collection) || 0;
+ });
+ return result;
+ }
+
+ /**
* The base implementation of `_.uniq` without support for callback shorthands
* and `this` binding.
*
@@ -35015,6 +36944,27 @@ return jQuery;
}
/**
+ * The base implementation of `_.dropRightWhile`, `_.dropWhile`, `_.takeRightWhile`,
+ * and `_.takeWhile` without support for callback shorthands and `this` binding.
+ *
+ * @private
+ * @param {Array} array The array to query.
+ * @param {Function} predicate The function invoked per iteration.
+ * @param {boolean} [isDrop] Specify dropping elements instead of taking them.
+ * @param {boolean} [fromRight] Specify iterating from right to left.
+ * @returns {Array} Returns the slice of `array`.
+ */
+ function baseWhile(array, predicate, isDrop, fromRight) {
+ var length = array.length,
+ index = fromRight ? length : -1;
+
+ while ((fromRight ? index-- : ++index < length) && predicate(array[index], index, array)) {}
+ return isDrop
+ ? baseSlice(array, (fromRight ? 0 : index), (fromRight ? index + 1 : length))
+ : baseSlice(array, (fromRight ? index + 1 : 0), (fromRight ? length : index));
+ }
+
+ /**
* The base implementation of `wrapperValue` which returns the result of
* performing a sequence of actions on the unwrapped `value`, where each
* successive action is supplied the return value of the previous.
@@ -35022,7 +36972,7 @@ return jQuery;
* @private
* @param {*} value The unwrapped value.
* @param {Array} actions Actions to peform to resolve the unwrapped value.
- * @returns {*} Returns the resolved unwrapped value.
+ * @returns {*} Returns the resolved value.
*/
function baseWrapperValue(value, actions) {
var result = value;
@@ -35049,8 +36999,7 @@ return jQuery;
* @private
* @param {Array} array The sorted array to inspect.
* @param {*} value The value to evaluate.
- * @param {boolean} [retHighest] Specify returning the highest, instead
- * of the lowest, index at which a value should be inserted into `array`.
+ * @param {boolean} [retHighest] Specify returning the highest qualified index.
* @returns {number} Returns the index at which `value` should be inserted
* into `array`.
*/
@@ -35083,8 +37032,7 @@ return jQuery;
* @param {Array} array The sorted array to inspect.
* @param {*} value The value to evaluate.
* @param {Function} iteratee The function invoked per iteration.
- * @param {boolean} [retHighest] Specify returning the highest, instead
- * of the lowest, index at which a value should be inserted into `array`.
+ * @param {boolean} [retHighest] Specify returning the highest qualified index.
* @returns {number} Returns the index at which `value` should be inserted
* into `array`.
*/
@@ -35250,6 +37198,9 @@ return jQuery;
* object composed from the results of running each element in the collection
* through an iteratee.
*
+ * **Note:** This function is used to create `_.countBy`, `_.groupBy`, `_.indexBy`,
+ * and `_.partition`.
+ *
* @private
* @param {Function} setter The function to set keys and values of the accumulator object.
* @param {Function} [initializer] The function to initialize the accumulator object.
@@ -35281,6 +37232,8 @@ return jQuery;
* Creates a function that assigns properties of source object(s) to a given
* destination object.
*
+ * **Note:** This function is used to create `_.assign`, `_.defaults`, and `_.merge`.
+ *
* @private
* @param {Function} assigner The function to assign values.
* @returns {Function} Returns the new assigner function.
@@ -35321,6 +37274,56 @@ return jQuery;
}
/**
+ * Creates a `baseEach` or `baseEachRight` function.
+ *
+ * @private
+ * @param {Function} eachFunc The function to iterate over a collection.
+ * @param {boolean} [fromRight] Specify iterating from right to left.
+ * @returns {Function} Returns the new base function.
+ */
+ function createBaseEach(eachFunc, fromRight) {
+ return function(collection, iteratee) {
+ var length = collection ? collection.length : 0;
+ if (!isLength(length)) {
+ return eachFunc(collection, iteratee);
+ }
+ var index = fromRight ? length : -1,
+ iterable = toObject(collection);
+
+ while ((fromRight ? index-- : ++index < length)) {
+ if (iteratee(iterable[index], index, iterable) === false) {
+ break;
+ }
+ }
+ return collection;
+ };
+ }
+
+ /**
+ * Creates a base function for `_.forIn` or `_.forInRight`.
+ *
+ * @private
+ * @param {boolean} [fromRight] Specify iterating from right to left.
+ * @returns {Function} Returns the new base function.
+ */
+ function createBaseFor(fromRight) {
+ return function(object, iteratee, keysFunc) {
+ var iterable = toObject(object),
+ props = keysFunc(object),
+ length = props.length,
+ index = fromRight ? length : -1;
+
+ while ((fromRight ? index-- : ++index < length)) {
+ var key = props[index];
+ if (iteratee(iterable[key], key, iterable) === false) {
+ break;
+ }
+ }
+ return object;
+ };
+ }
+
+ /**
* Creates a function that wraps `func` and invokes it with the `this`
* binding of `thisArg`.
*
@@ -35351,41 +37354,6 @@ return jQuery;
};
/**
- * Creates a function to compose other functions into a single function.
- *
- * @private
- * @param {boolean} [fromRight] Specify iterating from right to left.
- * @returns {Function} Returns the new composer function.
- */
- function createComposer(fromRight) {
- return function() {
- var length = arguments.length,
- index = length,
- fromIndex = fromRight ? (length - 1) : 0;
-
- if (!length) {
- return function() { return arguments[0]; };
- }
- var funcs = Array(length);
- while (index--) {
- funcs[index] = arguments[index];
- if (typeof funcs[index] != 'function') {
- throw new TypeError(FUNC_ERROR_TEXT);
- }
- }
- return function() {
- var index = fromIndex,
- result = funcs[index].apply(this, arguments);
-
- while ((fromRight ? index-- : ++index < length)) {
- result = funcs[index].call(this, result);
- }
- return result;
- };
- };
- }
-
- /**
* Creates a function that produces compound words out of the words in a
* given string.
*
@@ -35427,7 +37395,26 @@ return jQuery;
}
/**
- * Creates a function that gets the extremum value of a collection.
+ * Creates a `_.curry` or `_.curryRight` function.
+ *
+ * @private
+ * @param {boolean} flag The curry bit flag.
+ * @returns {Function} Returns the new curry function.
+ */
+ function createCurry(flag) {
+ function curryFunc(func, arity, guard) {
+ if (guard && isIterateeCall(func, arity, guard)) {
+ arity = null;
+ }
+ var result = createWrapper(func, flag, null, null, null, null, null, arity);
+ result.placeholder = curryFunc.placeholder;
+ return result;
+ }
+ return curryFunc;
+ }
+
+ /**
+ * Creates a `_.max` or `_.min` function.
*
* @private
* @param {Function} arrayFunc The function to get the extremum value from an array.
@@ -35460,6 +37447,204 @@ return jQuery;
}
/**
+ * Creates a `_.find` or `_.findLast` function.
+ *
+ * @private
+ * @param {Function} eachFunc The function to iterate over a collection.
+ * @param {boolean} [fromRight] Specify iterating from right to left.
+ * @returns {Function} Returns the new find function.
+ */
+ function createFind(eachFunc, fromRight) {
+ return function(collection, predicate, thisArg) {
+ predicate = getCallback(predicate, thisArg, 3);
+ if (isArray(collection)) {
+ var index = baseFindIndex(collection, predicate, fromRight);
+ return index > -1 ? collection[index] : undefined;
+ }
+ return baseFind(collection, predicate, eachFunc);
+ }
+ }
+
+ /**
+ * Creates a `_.findIndex` or `_.findLastIndex` function.
+ *
+ * @private
+ * @param {boolean} [fromRight] Specify iterating from right to left.
+ * @returns {Function} Returns the new find function.
+ */
+ function createFindIndex(fromRight) {
+ return function(array, predicate, thisArg) {
+ if (!(array && array.length)) {
+ return -1;
+ }
+ predicate = getCallback(predicate, thisArg, 3);
+ return baseFindIndex(array, predicate, fromRight);
+ };
+ }
+
+ /**
+ * Creates a `_.findKey` or `_.findLastKey` function.
+ *
+ * @private
+ * @param {Function} objectFunc The function to iterate over an object.
+ * @returns {Function} Returns the new find function.
+ */
+ function createFindKey(objectFunc) {
+ return function(object, predicate, thisArg) {
+ predicate = getCallback(predicate, thisArg, 3);
+ return baseFind(object, predicate, objectFunc, true);
+ };
+ }
+
+ /**
+ * Creates a `_.flow` or `_.flowRight` function.
+ *
+ * @private
+ * @param {boolean} [fromRight] Specify iterating from right to left.
+ * @returns {Function} Returns the new flow function.
+ */
+ function createFlow(fromRight) {
+ return function() {
+ var length = arguments.length;
+ if (!length) {
+ return function() { return arguments[0]; };
+ }
+ var wrapper,
+ index = fromRight ? length : -1,
+ leftIndex = 0,
+ funcs = Array(length);
+
+ while ((fromRight ? index-- : ++index < length)) {
+ var func = funcs[leftIndex++] = arguments[index];
+ if (typeof func != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
+ var funcName = wrapper ? '' : getFuncName(func);
+ wrapper = funcName == 'wrapper' ? new LodashWrapper([]) : wrapper;
+ }
+ index = wrapper ? -1 : length;
+ while (++index < length) {
+ func = funcs[index];
+ funcName = getFuncName(func);
+
+ var data = funcName == 'wrapper' ? getData(func) : null;
+ if (data && isLaziable(data[0])) {
+ wrapper = wrapper[getFuncName(data[0])].apply(wrapper, data[3]);
+ } else {
+ wrapper = (func.length == 1 && isLaziable(func)) ? wrapper[funcName]() : wrapper.thru(func);
+ }
+ }
+ return function() {
+ var args = arguments;
+ if (wrapper && args.length == 1 && isArray(args[0])) {
+ return wrapper.plant(args[0]).value();
+ }
+ var index = 0,
+ result = funcs[index].apply(this, args);
+
+ while (++index < length) {
+ result = funcs[index].call(this, result);
+ }
+ return result;
+ };
+ };
+ }
+
+ /**
+ * Creates a function for `_.forEach` or `_.forEachRight`.
+ *
+ * @private
+ * @param {Function} arrayFunc The function to iterate over an array.
+ * @param {Function} eachFunc The function to iterate over a collection.
+ * @returns {Function} Returns the new each function.
+ */
+ function createForEach(arrayFunc, eachFunc) {
+ return function(collection, iteratee, thisArg) {
+ return (typeof iteratee == 'function' && typeof thisArg == 'undefined' && isArray(collection))
+ ? arrayFunc(collection, iteratee)
+ : eachFunc(collection, bindCallback(iteratee, thisArg, 3));
+ };
+ }
+
+ /**
+ * Creates a function for `_.forIn` or `_.forInRight`.
+ *
+ * @private
+ * @param {Function} objectFunc The function to iterate over an object.
+ * @returns {Function} Returns the new each function.
+ */
+ function createForIn(objectFunc) {
+ return function(object, iteratee, thisArg) {
+ if (typeof iteratee != 'function' || typeof thisArg != 'undefined') {
+ iteratee = bindCallback(iteratee, thisArg, 3);
+ }
+ return objectFunc(object, iteratee, keysIn);
+ };
+ }
+
+ /**
+ * Creates a function for `_.forOwn` or `_.forOwnRight`.
+ *
+ * @private
+ * @param {Function} objectFunc The function to iterate over an object.
+ * @returns {Function} Returns the new each function.
+ */
+ function createForOwn(objectFunc) {
+ return function(object, iteratee, thisArg) {
+ if (typeof iteratee != 'function' || typeof thisArg != 'undefined') {
+ iteratee = bindCallback(iteratee, thisArg, 3);
+ }
+ return objectFunc(object, iteratee);
+ };
+ }
+
+ /**
+ * Creates a function for `_.padLeft` or `_.padRight`.
+ *
+ * @private
+ * @param {boolean} [fromRight] Specify padding from the right.
+ * @returns {Function} Returns the new pad function.
+ */
+ function createPadDir(fromRight) {
+ return function(string, length, chars) {
+ string = baseToString(string);
+ return string && ((fromRight ? string : '') + createPadding(string, length, chars) + (fromRight ? '' : string));
+ };
+ }
+
+ /**
+ * Creates a `_.partial` or `_.partialRight` function.
+ *
+ * @private
+ * @param {boolean} flag The partial bit flag.
+ * @returns {Function} Returns the new partial function.
+ */
+ function createPartial(flag) {
+ var partialFunc = restParam(function(func, partials) {
+ var holders = replaceHolders(partials, partialFunc.placeholder);
+ return createWrapper(func, flag, null, partials, holders);
+ });
+ return partialFunc;
+ }
+
+ /**
+ * Creates a function for `_.reduce` or `_.reduceRight`.
+ *
+ * @private
+ * @param {Function} arrayFunc The function to iterate over an array.
+ * @param {Function} eachFunc The function to iterate over a collection.
+ * @returns {Function} Returns the new each function.
+ */
+ function createReduce(arrayFunc, eachFunc) {
+ return function(collection, iteratee, accumulator, thisArg) {
+ var initFromArray = arguments.length < 3;
+ return (typeof iteratee == 'function' && typeof thisArg == 'undefined' && isArray(collection))
+ ? arrayFunc(collection, iteratee, accumulator, initFromArray)
+ : baseReduce(collection, getCallback(iteratee, thisArg, 4), accumulator, initFromArray, eachFunc);
+ };
+ }
+
+ /**
* Creates a function that wraps `func` and invokes it with optional `this`
* binding of, partial application, and currying.
*
@@ -35522,7 +37707,12 @@ return jQuery;
if (!isCurryBound) {
bitmask &= ~(BIND_FLAG | BIND_KEY_FLAG);
}
- var result = createHybridWrapper(func, bitmask, thisArg, newPartials, newsHolders, newPartialsRight, newHoldersRight, newArgPos, ary, newArity);
+ var newData = [func, bitmask, thisArg, newPartials, newsHolders, newPartialsRight, newHoldersRight, newArgPos, ary, newArity],
+ result = createHybridWrapper.apply(undefined, newData);
+
+ if (isLaziable(func)) {
+ setData(result, newData);
+ }
result.placeholder = placeholder;
return result;
}
@@ -35544,9 +37734,8 @@ return jQuery;
}
/**
- * Creates the pad required for `string` based on the given padding length.
- * The `chars` string may be truncated if the number of padding characters
- * exceeds the padding length.
+ * Creates the padding required for `string` based on the given `length`.
+ * The `chars` string is truncated if the number of characters exceeds `length`.
*
* @private
* @param {string} string The string to create padding for.
@@ -35554,7 +37743,7 @@ return jQuery;
* @param {string} [chars=' '] The string used as padding.
* @returns {string} Returns the pad for `string`.
*/
- function createPad(string, length, chars) {
+ function createPadding(string, length, chars) {
var strLength = string.length;
length = +length;
@@ -35604,6 +37793,22 @@ return jQuery;
}
/**
+ * Creates a `_.sortedIndex` or `_.sortedLastIndex` function.
+ *
+ * @private
+ * @param {boolean} [retHighest] Specify returning the highest qualified index.
+ * @returns {Function} Returns the new index function.
+ */
+ function createSortedIndex(retHighest) {
+ return function(array, value, iteratee, thisArg) {
+ var func = getCallback(iteratee);
+ return (func === baseCallback && iteratee == null)
+ ? binaryIndex(array, value, retHighest)
+ : binaryIndexBy(array, value, func(iteratee, thisArg, 1), retHighest);
+ };
+ }
+
+ /**
* Creates a function that either curries or invokes `func` with optional
* `this` binding and partially applied arguments.
*
@@ -35645,10 +37850,10 @@ return jQuery;
partials = holders = null;
}
- var data = !isBindKey && getData(func),
+ var data = isBindKey ? null : getData(func),
newData = [func, bitmask, thisArg, partials, holders, partialsRight, holdersRight, argPos, ary, arity];
- if (data && data !== true) {
+ if (data) {
mergeData(newData, data);
bitmask = newData[1];
arity = newData[9];
@@ -35677,18 +37882,18 @@ return jQuery;
* @param {Array} other The other array to compare.
* @param {Function} equalFunc The function to determine equivalents of values.
* @param {Function} [customizer] The function to customize comparing arrays.
- * @param {boolean} [isWhere] Specify performing partial comparisons.
+ * @param {boolean} [isLoose] Specify performing partial comparisons.
* @param {Array} [stackA] Tracks traversed `value` objects.
* @param {Array} [stackB] Tracks traversed `other` objects.
* @returns {boolean} Returns `true` if the arrays are equivalent, else `false`.
*/
- function equalArrays(array, other, equalFunc, customizer, isWhere, stackA, stackB) {
+ function equalArrays(array, other, equalFunc, customizer, isLoose, stackA, stackB) {
var index = -1,
arrLength = array.length,
othLength = other.length,
result = true;
- if (arrLength != othLength && !(isWhere && othLength > arrLength)) {
+ if (arrLength != othLength && !(isLoose && othLength > arrLength)) {
return false;
}
// Deep compare the contents, ignoring non-numeric properties.
@@ -35698,23 +37903,23 @@ return jQuery;
result = undefined;
if (customizer) {
- result = isWhere
+ result = isLoose
? customizer(othValue, arrValue, index)
: customizer(arrValue, othValue, index);
}
if (typeof result == 'undefined') {
// Recursively compare arrays (susceptible to call stack limits).
- if (isWhere) {
+ if (isLoose) {
var othIndex = othLength;
while (othIndex--) {
othValue = other[othIndex];
- result = (arrValue && arrValue === othValue) || equalFunc(arrValue, othValue, customizer, isWhere, stackA, stackB);
+ result = (arrValue && arrValue === othValue) || equalFunc(arrValue, othValue, customizer, isLoose, stackA, stackB);
if (result) {
break;
}
}
} else {
- result = (arrValue && arrValue === othValue) || equalFunc(arrValue, othValue, customizer, isWhere, stackA, stackB);
+ result = (arrValue && arrValue === othValue) || equalFunc(arrValue, othValue, customizer, isLoose, stackA, stackB);
}
}
}
@@ -35770,26 +37975,26 @@ return jQuery;
* @param {Object} other The other object to compare.
* @param {Function} equalFunc The function to determine equivalents of values.
* @param {Function} [customizer] The function to customize comparing values.
- * @param {boolean} [isWhere] Specify performing partial comparisons.
+ * @param {boolean} [isLoose] Specify performing partial comparisons.
* @param {Array} [stackA] Tracks traversed `value` objects.
* @param {Array} [stackB] Tracks traversed `other` objects.
* @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
*/
- function equalObjects(object, other, equalFunc, customizer, isWhere, stackA, stackB) {
+ function equalObjects(object, other, equalFunc, customizer, isLoose, stackA, stackB) {
var objProps = keys(object),
objLength = objProps.length,
othProps = keys(other),
othLength = othProps.length;
- if (objLength != othLength && !isWhere) {
+ if (objLength != othLength && !isLoose) {
return false;
}
- var hasCtor,
+ var skipCtor = isLoose,
index = -1;
while (++index < objLength) {
var key = objProps[index],
- result = hasOwnProperty.call(other, key);
+ result = isLoose ? key in other : hasOwnProperty.call(other, key);
if (result) {
var objValue = object[key],
@@ -35797,21 +38002,21 @@ return jQuery;
result = undefined;
if (customizer) {
- result = isWhere
+ result = isLoose
? customizer(othValue, objValue, key)
: customizer(objValue, othValue, key);
}
if (typeof result == 'undefined') {
// Recursively compare objects (susceptible to call stack limits).
- result = (objValue && objValue === othValue) || equalFunc(objValue, othValue, customizer, isWhere, stackA, stackB);
+ result = (objValue && objValue === othValue) || equalFunc(objValue, othValue, customizer, isLoose, stackA, stackB);
}
}
if (!result) {
return false;
}
- hasCtor || (hasCtor = key == 'constructor');
+ skipCtor || (skipCtor = key == 'constructor');
}
- if (!hasCtor) {
+ if (!skipCtor) {
var objCtor = object.constructor,
othCtor = other.constructor;
@@ -35829,7 +38034,7 @@ return jQuery;
/**
* Gets the extremum value of `collection` invoking `iteratee` for each value
* in `collection` to generate the criterion by which the value is ranked.
- * The `iteratee` is invoked with three arguments; (value, index, collection).
+ * The `iteratee` is invoked with three arguments: (value, index, collection).
*
* @private
* @param {Array|Object|string} collection The collection to iterate over.
@@ -35881,6 +38086,37 @@ return jQuery;
};
/**
+ * Gets the name of `func`.
+ *
+ * @private
+ * @param {Function} func The function to query.
+ * @returns {string} Returns the function name.
+ */
+ var getFuncName = (function() {
+ if (!support.funcNames) {
+ return constant('');
+ }
+ if (constant.name == 'constant') {
+ return baseProperty('name');
+ }
+ return function(func) {
+ var result = func.name,
+ array = realNames[result],
+ length = array ? array.length : 0;
+
+ while (length--) {
+ var data = array[length],
+ otherFunc = data.func;
+
+ if (otherFunc == null || otherFunc == func) {
+ return data.name;
+ }
+ }
+ return result;
+ };
+ }());
+
+ /**
* Gets the appropriate "indexOf" function. If the `_.indexOf` method is
* customized this function returns the custom method, otherwise it returns
* the `baseIndexOf` function. If arguments are provided the chosen function
@@ -35998,31 +38234,6 @@ return jQuery;
}
/**
- * Checks if `func` is eligible for `this` binding.
- *
- * @private
- * @param {Function} func The function to check.
- * @returns {boolean} Returns `true` if `func` is eligible, else `false`.
- */
- function isBindable(func) {
- var support = lodash.support,
- result = !(support.funcNames ? func.name : support.funcDecomp);
-
- if (!result) {
- var source = fnToString.call(func);
- if (!support.funcNames) {
- result = !reFuncName.test(source);
- }
- if (!result) {
- // Check if `func` references the `this` keyword and store the result.
- result = reThis.test(source) || isNative(func);
- baseSetData(func, result);
- }
- }
- return result;
- }
-
- /**
* Checks if `value` is a valid array-like index.
*
* @private
@@ -36064,11 +38275,21 @@ return jQuery;
}
/**
+ * Checks if `func` has a lazy counterpart.
+ *
+ * @private
+ * @param {Function} func The function to check.
+ * @returns {boolean} Returns `true` if `func` has a lazy counterpart, else `false`.
+ */
+ function isLaziable(func) {
+ var funcName = getFuncName(func);
+ return !!funcName && func === lodash[funcName] && funcName in LazyWrapper.prototype;
+ }
+
+ /**
* Checks if `value` is a valid array-like length.
*
- * **Note:** This function is based on ES `ToLength`. See the
- * [ES spec](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength)
- * for more details.
+ * **Note:** This function is based on [`ToLength`](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength).
*
* @private
* @param {*} value The value to check.
@@ -36108,22 +38329,13 @@ return jQuery;
function mergeData(data, source) {
var bitmask = data[1],
srcBitmask = source[1],
- newBitmask = bitmask | srcBitmask;
+ newBitmask = bitmask | srcBitmask,
+ isCommon = newBitmask < ARY_FLAG;
- var arityFlags = ARY_FLAG | REARG_FLAG,
- bindFlags = BIND_FLAG | BIND_KEY_FLAG,
- comboFlags = arityFlags | bindFlags | CURRY_BOUND_FLAG | CURRY_RIGHT_FLAG;
-
- var isAry = bitmask & ARY_FLAG && !(srcBitmask & ARY_FLAG),
- isRearg = bitmask & REARG_FLAG && !(srcBitmask & REARG_FLAG),
- argPos = (isRearg ? data : source)[7],
- ary = (isAry ? data : source)[8];
-
- var isCommon = !(bitmask >= REARG_FLAG && srcBitmask > bindFlags) &&
- !(bitmask > bindFlags && srcBitmask >= REARG_FLAG);
-
- var isCombo = (newBitmask >= arityFlags && newBitmask <= comboFlags) &&
- (bitmask < REARG_FLAG || ((isRearg || isAry) && argPos.length <= ary));
+ var isCombo =
+ (srcBitmask == ARY_FLAG && bitmask == CURRY_FLAG) ||
+ (srcBitmask == ARY_FLAG && bitmask == REARG_FLAG && data[7].length <= source[8]) ||
+ (srcBitmask == (ARY_FLAG | REARG_FLAG) && bitmask == CURRY_FLAG);
// Exit early if metadata can't be merged.
if (!(isCommon || isCombo)) {
@@ -36442,10 +38654,9 @@ return jQuery;
* Creates an array excluding all values of the provided arrays using
* `SameValueZero` for equality comparisons.
*
- * **Note:** `SameValueZero` comparisons are like strict equality comparisons,
- * e.g. `===`, except that `NaN` matches `NaN`. See the
- * [ES spec](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-samevaluezero)
- * for more details.
+ * **Note:** [`SameValueZero`](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-samevaluezero)
+ * comparisons are like strict equality comparisons, e.g. `===`, except that
+ * `NaN` matches `NaN`.
*
* @static
* @memberOf _
@@ -36458,19 +38669,11 @@ return jQuery;
* _.difference([1, 2, 3], [4, 2]);
* // => [1, 3]
*/
- function difference() {
- var args = arguments,
- index = -1,
- length = args.length;
-
- while (++index < length) {
- var value = args[index];
- if (isArray(value) || isArguments(value)) {
- break;
- }
- }
- return baseDifference(value, baseFlatten(args, false, true, ++index));
- }
+ var difference = restParam(function(array, values) {
+ return (isArray(array) || isArguments(array))
+ ? baseDifference(array, baseFlatten(values, false, true))
+ : [];
+ });
/**
* Creates a slice of `array` with `n` elements dropped from the beginning.
@@ -36546,7 +38749,7 @@ return jQuery;
/**
* Creates a slice of `array` excluding elements dropped from the end.
* Elements are dropped until `predicate` returns falsey. The predicate is
- * bound to `thisArg` and invoked with three arguments; (value, index, array).
+ * bound to `thisArg` and invoked with three arguments: (value, index, array).
*
* If a property name is provided for `predicate` the created `_.property`
* style callback returns the property value of the given element.
@@ -36593,19 +38796,15 @@ return jQuery;
* // => ['barney', 'fred', 'pebbles']
*/
function dropRightWhile(array, predicate, thisArg) {
- var length = array ? array.length : 0;
- if (!length) {
- return [];
- }
- predicate = getCallback(predicate, thisArg, 3);
- while (length-- && predicate(array[length], length, array)) {}
- return baseSlice(array, 0, length + 1);
+ return (array && array.length)
+ ? baseWhile(array, getCallback(predicate, thisArg, 3), true, true)
+ : [];
}
/**
* Creates a slice of `array` excluding elements dropped from the beginning.
* Elements are dropped until `predicate` returns falsey. The predicate is
- * bound to `thisArg` and invoked with three arguments; (value, index, array).
+ * bound to `thisArg` and invoked with three arguments: (value, index, array).
*
* If a property name is provided for `predicate` the created `_.property`
* style callback returns the property value of the given element.
@@ -36652,14 +38851,9 @@ return jQuery;
* // => ['barney', 'fred', 'pebbles']
*/
function dropWhile(array, predicate, thisArg) {
- var length = array ? array.length : 0;
- if (!length) {
- return [];
- }
- var index = -1;
- predicate = getCallback(predicate, thisArg, 3);
- while (++index < length && predicate(array[index], index, array)) {}
- return baseSlice(array, index);
+ return (array && array.length)
+ ? baseWhile(array, getCallback(predicate, thisArg, 3), true)
+ : [];
}
/**
@@ -36676,6 +38870,19 @@ return jQuery;
* @param {number} [start=0] The start position.
* @param {number} [end=array.length] The end position.
* @returns {Array} Returns `array`.
+ * @example
+ *
+ * var array = [1, 2, 3];
+ *
+ * _.fill(array, 'a');
+ * console.log(array);
+ * // => ['a', 'a', 'a']
+ *
+ * _.fill(Array(3), 2);
+ * // => [2, 2, 2]
+ *
+ * _.fill([4, 6, 8], '*', 1, 2);
+ * // => [4, '*', 8]
*/
function fill(array, value, start, end) {
var length = array ? array.length : 0;
@@ -36691,7 +38898,7 @@ return jQuery;
/**
* This method is like `_.find` except that it returns the index of the first
- * element `predicate` returns truthy for, instead of the element itself.
+ * element `predicate` returns truthy for instead of the element itself.
*
* If a property name is provided for `predicate` the created `_.property`
* style callback returns the property value of the given element.
@@ -36737,18 +38944,7 @@ return jQuery;
* _.findIndex(users, 'active');
* // => 2
*/
- function findIndex(array, predicate, thisArg) {
- var index = -1,
- length = array ? array.length : 0;
-
- predicate = getCallback(predicate, thisArg, 3);
- while (++index < length) {
- if (predicate(array[index], index, array)) {
- return index;
- }
- }
- return -1;
- }
+ var findIndex = createFindIndex();
/**
* This method is like `_.findIndex` except that it iterates over elements
@@ -36798,16 +38994,7 @@ return jQuery;
* _.findLastIndex(users, 'active');
* // => 0
*/
- function findLastIndex(array, predicate, thisArg) {
- var length = array ? array.length : 0;
- predicate = getCallback(predicate, thisArg, 3);
- while (length--) {
- if (predicate(array[length], length, array)) {
- return length;
- }
- }
- return -1;
- }
+ var findLastIndex = createFindIndex(true);
/**
* Gets the first element of `array`.
@@ -36844,18 +39031,18 @@ return jQuery;
* @example
*
* _.flatten([1, [2, 3, [4]]]);
- * // => [1, 2, 3, [4]];
+ * // => [1, 2, 3, [4]]
*
* // using `isDeep`
* _.flatten([1, [2, 3, [4]]], true);
- * // => [1, 2, 3, 4];
+ * // => [1, 2, 3, 4]
*/
function flatten(array, isDeep, guard) {
var length = array ? array.length : 0;
if (guard && isIterateeCall(array, isDeep, guard)) {
isDeep = false;
}
- return length ? baseFlatten(array, isDeep, false, 0) : [];
+ return length ? baseFlatten(array, isDeep) : [];
}
/**
@@ -36869,11 +39056,11 @@ return jQuery;
* @example
*
* _.flattenDeep([1, [2, 3, [4]]]);
- * // => [1, 2, 3, 4];
+ * // => [1, 2, 3, 4]
*/
function flattenDeep(array) {
var length = array ? array.length : 0;
- return length ? baseFlatten(array, true, false, 0) : [];
+ return length ? baseFlatten(array, true) : [];
}
/**
@@ -36882,10 +39069,9 @@ return jQuery;
* it is used as the offset from the end of `array`. If `array` is sorted
* providing `true` for `fromIndex` performs a faster binary search.
*
- * **Note:** `SameValueZero` comparisons are like strict equality comparisons,
- * e.g. `===`, except that `NaN` matches `NaN`. See the
- * [ES spec](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-samevaluezero)
- * for more details.
+ * **Note:** [`SameValueZero`](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-samevaluezero)
+ * comparisons are like strict equality comparisons, e.g. `===`, except that
+ * `NaN` matches `NaN`.
*
* @static
* @memberOf _
@@ -36948,10 +39134,9 @@ return jQuery;
* Creates an array of unique values in all provided arrays using `SameValueZero`
* for equality comparisons.
*
- * **Note:** `SameValueZero` comparisons are like strict equality comparisons,
- * e.g. `===`, except that `NaN` matches `NaN`. See the
- * [ES spec](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-samevaluezero)
- * for more details.
+ * **Note:** [`SameValueZero`](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-samevaluezero)
+ * comparisons are like strict equality comparisons, e.g. `===`, except that
+ * `NaN` matches `NaN`.
*
* @static
* @memberOf _
@@ -37079,10 +39264,10 @@ return jQuery;
* comparisons.
*
* **Notes:**
- * - Unlike `_.without`, this method mutates `array`.
- * - `SameValueZero` comparisons are like strict equality comparisons, e.g. `===`,
- * except that `NaN` matches `NaN`. See the [ES spec](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-samevaluezero)
- * for more details.
+ * - Unlike `_.without`, this method mutates `array`
+ * - [`SameValueZero`](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-samevaluezero)
+ * comparisons are like strict equality comparisons, e.g. `===`, except
+ * that `NaN` matches `NaN`
*
* @static
* @memberOf _
@@ -37145,14 +39330,28 @@ return jQuery;
* console.log(evens);
* // => [10, 20]
*/
- function pullAt(array) {
- return basePullAt(array || [], baseFlatten(arguments, false, false, 1));
- }
+ var pullAt = restParam(function(array, indexes) {
+ array || (array = []);
+ indexes = baseFlatten(indexes);
+
+ var length = indexes.length,
+ result = baseAt(array, indexes);
+
+ indexes.sort(baseCompareAscending);
+ while (length--) {
+ var index = parseFloat(indexes[length]);
+ if (index != previous && isIndex(index)) {
+ var previous = index;
+ splice.call(array, index, 1);
+ }
+ }
+ return result;
+ });
/**
* Removes all elements from `array` that `predicate` returns truthy for
* and returns an array of the removed elements. The predicate is bound to
- * `thisArg` and invoked with three arguments; (value, index, array).
+ * `thisArg` and invoked with three arguments: (value, index, array).
*
* If a property name is provided for `predicate` the created `_.property`
* style callback returns the property value of the given element.
@@ -37256,14 +39455,14 @@ return jQuery;
* to compute their sort ranking. The iteratee is bound to `thisArg` and
* invoked with one argument; (value).
*
- * If a property name is provided for `predicate` the created `_.property`
+ * If a property name is provided for `iteratee` the created `_.property`
* style callback returns the property value of the given element.
*
* If a value is also provided for `thisArg` the created `_.matchesProperty`
* style callback returns `true` for elements that have a matching property
* value, else `false`.
*
- * If an object is provided for `predicate` the created `_.matches` style
+ * If an object is provided for `iteratee` the created `_.matches` style
* callback returns `true` for elements that have the properties of the given
* object, else `false`.
*
@@ -37297,12 +39496,7 @@ return jQuery;
* _.sortedIndex([{ 'x': 30 }, { 'x': 50 }], { 'x': 40 }, 'x');
* // => 1
*/
- function sortedIndex(array, value, iteratee, thisArg) {
- var func = getCallback(iteratee);
- return (func === baseCallback && iteratee == null)
- ? binaryIndex(array, value)
- : binaryIndexBy(array, value, func(iteratee, thisArg, 1));
- }
+ var sortedIndex = createSortedIndex();
/**
* This method is like `_.sortedIndex` except that it returns the highest
@@ -37324,12 +39518,7 @@ return jQuery;
* _.sortedLastIndex([4, 4, 5, 5], 5);
* // => 4
*/
- function sortedLastIndex(array, value, iteratee, thisArg) {
- var func = getCallback(iteratee);
- return (func === baseCallback && iteratee == null)
- ? binaryIndex(array, value, true)
- : binaryIndexBy(array, value, func(iteratee, thisArg, 1), true);
- }
+ var sortedLastIndex = createSortedIndex(true);
/**
* Creates a slice of `array` with `n` elements taken from the beginning.
@@ -37405,7 +39594,7 @@ return jQuery;
/**
* Creates a slice of `array` with elements taken from the end. Elements are
* taken until `predicate` returns falsey. The predicate is bound to `thisArg`
- * and invoked with three arguments; (value, index, array).
+ * and invoked with three arguments: (value, index, array).
*
* If a property name is provided for `predicate` the created `_.property`
* style callback returns the property value of the given element.
@@ -37452,19 +39641,15 @@ return jQuery;
* // => []
*/
function takeRightWhile(array, predicate, thisArg) {
- var length = array ? array.length : 0;
- if (!length) {
- return [];
- }
- predicate = getCallback(predicate, thisArg, 3);
- while (length-- && predicate(array[length], length, array)) {}
- return baseSlice(array, length + 1);
+ return (array && array.length)
+ ? baseWhile(array, getCallback(predicate, thisArg, 3), false, true)
+ : [];
}
/**
* Creates a slice of `array` with elements taken from the beginning. Elements
* are taken until `predicate` returns falsey. The predicate is bound to
- * `thisArg` and invoked with three arguments; (value, index, array).
+ * `thisArg` and invoked with three arguments: (value, index, array).
*
* If a property name is provided for `predicate` the created `_.property`
* style callback returns the property value of the given element.
@@ -37511,24 +39696,18 @@ return jQuery;
* // => []
*/
function takeWhile(array, predicate, thisArg) {
- var length = array ? array.length : 0;
- if (!length) {
- return [];
- }
- var index = -1;
- predicate = getCallback(predicate, thisArg, 3);
- while (++index < length && predicate(array[index], index, array)) {}
- return baseSlice(array, 0, index);
+ return (array && array.length)
+ ? baseWhile(array, getCallback(predicate, thisArg, 3))
+ : [];
}
/**
* Creates an array of unique values, in order, of the provided arrays using
* `SameValueZero` for equality comparisons.
*
- * **Note:** `SameValueZero` comparisons are like strict equality comparisons,
- * e.g. `===`, except that `NaN` matches `NaN`. See the
- * [ES spec](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-samevaluezero)
- * for more details.
+ * **Note:** [`SameValueZero`](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-samevaluezero)
+ * comparisons are like strict equality comparisons, e.g. `===`, except that
+ * `NaN` matches `NaN`.
*
* @static
* @memberOf _
@@ -37540,9 +39719,9 @@ return jQuery;
* _.union([1, 2], [4, 2], [2, 1]);
* // => [1, 2, 4]
*/
- function union() {
- return baseUniq(baseFlatten(arguments, false, true, 0));
- }
+ var union = restParam(function(arrays) {
+ return baseUniq(baseFlatten(arrays, false, true));
+ });
/**
* Creates a duplicate-value-free version of an array using `SameValueZero`
@@ -37550,23 +39729,22 @@ return jQuery;
* search algorithm for sorted arrays. If an iteratee function is provided it
* is invoked for each value in the array to generate the criterion by which
* uniqueness is computed. The `iteratee` is bound to `thisArg` and invoked
- * with three arguments; (value, index, array).
+ * with three arguments: (value, index, array).
*
- * If a property name is provided for `predicate` the created `_.property`
+ * If a property name is provided for `iteratee` the created `_.property`
* style callback returns the property value of the given element.
*
* If a value is also provided for `thisArg` the created `_.matchesProperty`
* style callback returns `true` for elements that have a matching property
* value, else `false`.
*
- * If an object is provided for `predicate` the created `_.matches` style
+ * If an object is provided for `iteratee` the created `_.matches` style
* callback returns `true` for elements that have the properties of the given
* object, else `false`.
*
- * **Note:** `SameValueZero` comparisons are like strict equality comparisons,
- * e.g. `===`, except that `NaN` matches `NaN`. See the
- * [ES spec](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-samevaluezero)
- * for more details.
+ * **Note:** [`SameValueZero`](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-samevaluezero)
+ * comparisons are like strict equality comparisons, e.g. `===`, except that
+ * `NaN` matches `NaN`.
*
* @static
* @memberOf _
@@ -37648,10 +39826,9 @@ return jQuery;
* Creates an array excluding all provided values using `SameValueZero` for
* equality comparisons.
*
- * **Note:** `SameValueZero` comparisons are like strict equality comparisons,
- * e.g. `===`, except that `NaN` matches `NaN`. See the
- * [ES spec](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-samevaluezero)
- * for more details.
+ * **Note:** [`SameValueZero`](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-samevaluezero)
+ * comparisons are like strict equality comparisons, e.g. `===`, except that
+ * `NaN` matches `NaN`.
*
* @static
* @memberOf _
@@ -37664,14 +39841,15 @@ return jQuery;
* _.without([1, 2, 1, 3], 1, 2);
* // => [3]
*/
- function without(array) {
- return baseDifference(array, baseSlice(arguments, 1));
- }
+ var without = restParam(function(array, values) {
+ return (isArray(array) || isArguments(array))
+ ? baseDifference(array, values)
+ : [];
+ });
/**
- * Creates an array that is the symmetric difference of the provided arrays.
- * See [Wikipedia](https://en.wikipedia.org/wiki/Symmetric_difference) for
- * more details.
+ * Creates an array that is the [symmetric difference](https://en.wikipedia.org/wiki/Symmetric_difference)
+ * of the provided arrays.
*
* @static
* @memberOf _
@@ -37713,20 +39891,13 @@ return jQuery;
* _.zip(['fred', 'barney'], [30, 40], [true, false]);
* // => [['fred', 30, true], ['barney', 40, false]]
*/
- function zip() {
- var length = arguments.length,
- array = Array(length);
-
- while (length--) {
- array[length] = arguments[length];
- }
- return unzip(array);
- }
+ var zip = restParam(unzip);
/**
- * Creates an object composed from arrays of property names and values. Provide
- * either a single two dimensional array, e.g. `[[key1, value1], [key2, value2]]`
- * or two arrays, one of property names and one of corresponding values.
+ * The inverse of `_.pairs`; this method returns an object composed from arrays
+ * of property names and values. Provide either a single two dimensional array,
+ * e.g. `[[key1, value1], [key2, value2]]` or two arrays, one of property names
+ * and one of corresponding values.
*
* @static
* @memberOf _
@@ -37737,6 +39908,9 @@ return jQuery;
* @returns {Object} Returns the new object.
* @example
*
+ * _.zipObject([['fred', 30], ['barney', 40]]);
+ * // => { 'fred': 30, 'barney': 40 }
+ *
* _.zipObject(['fred', 'barney'], [30, 40]);
* // => { 'fred': 30, 'barney': 40 }
*/
@@ -37833,13 +40007,14 @@ return jQuery;
* @returns {*} Returns the result of `interceptor`.
* @example
*
- * _([1, 2, 3])
- * .last()
+ * _(' abc ')
+ * .chain()
+ * .trim()
* .thru(function(value) {
* return [value];
* })
* .value();
- * // => [3]
+ * // => ['abc']
*/
function thru(value, interceptor, thisArg) {
return interceptor.call(thisArg, value);
@@ -38029,32 +40204,32 @@ return jQuery;
* _.at(['a', 'b', 'c'], [0, 2]);
* // => ['a', 'c']
*
- * _.at(['fred', 'barney', 'pebbles'], 0, 2);
- * // => ['fred', 'pebbles']
+ * _.at(['barney', 'fred', 'pebbles'], 0, 2);
+ * // => ['barney', 'pebbles']
*/
- function at(collection) {
+ var at = restParam(function(collection, props) {
var length = collection ? collection.length : 0;
if (isLength(length)) {
collection = toIterable(collection);
}
- return baseAt(collection, baseFlatten(arguments, false, false, 1));
- }
+ return baseAt(collection, baseFlatten(props));
+ });
/**
* Creates an object composed of keys generated from the results of running
* each element of `collection` through `iteratee`. The corresponding value
* of each key is the number of times the key was returned by `iteratee`.
- * The `iteratee` is bound to `thisArg` and invoked with three arguments;
+ * The `iteratee` is bound to `thisArg` and invoked with three arguments:
* (value, index|key, collection).
*
- * If a property name is provided for `predicate` the created `_.property`
+ * If a property name is provided for `iteratee` the created `_.property`
* style callback returns the property value of the given element.
*
* If a value is also provided for `thisArg` the created `_.matchesProperty`
* style callback returns `true` for elements that have a matching property
* value, else `false`.
*
- * If an object is provided for `predicate` the created `_.matches` style
+ * If an object is provided for `iteratee` the created `_.matches` style
* callback returns `true` for elements that have the properties of the given
* object, else `false`.
*
@@ -38087,7 +40262,7 @@ return jQuery;
/**
* Checks if `predicate` returns truthy for **all** elements of `collection`.
- * The predicate is bound to `thisArg` and invoked with three arguments;
+ * The predicate is bound to `thisArg` and invoked with three arguments:
* (value, index|key, collection).
*
* If a property name is provided for `predicate` the created `_.property`
@@ -38135,6 +40310,9 @@ return jQuery;
*/
function every(collection, predicate, thisArg) {
var func = isArray(collection) ? arrayEvery : baseEvery;
+ if (thisArg && isIterateeCall(collection, predicate, thisArg)) {
+ predicate = null;
+ }
if (typeof predicate != 'function' || typeof thisArg != 'undefined') {
predicate = getCallback(predicate, thisArg, 3);
}
@@ -38144,7 +40322,7 @@ return jQuery;
/**
* Iterates over elements of `collection`, returning an array of all elements
* `predicate` returns truthy for. The predicate is bound to `thisArg` and
- * invoked with three arguments; (value, index|key, collection).
+ * invoked with three arguments: (value, index|key, collection).
*
* If a property name is provided for `predicate` the created `_.property`
* style callback returns the property value of the given element.
@@ -38199,7 +40377,7 @@ return jQuery;
/**
* Iterates over elements of `collection`, returning the first element
* `predicate` returns truthy for. The predicate is bound to `thisArg` and
- * invoked with three arguments; (value, index|key, collection).
+ * invoked with three arguments: (value, index|key, collection).
*
* If a property name is provided for `predicate` the created `_.property`
* style callback returns the property value of the given element.
@@ -38246,14 +40424,7 @@ return jQuery;
* _.result(_.find(users, 'active'), 'user');
* // => 'barney'
*/
- function find(collection, predicate, thisArg) {
- if (isArray(collection)) {
- var index = findIndex(collection, predicate, thisArg);
- return index > -1 ? collection[index] : undefined;
- }
- predicate = getCallback(predicate, thisArg, 3);
- return baseFind(collection, predicate, baseEach);
- }
+ var find = createFind(baseEach);
/**
* This method is like `_.find` except that it iterates over elements of
@@ -38274,10 +40445,7 @@ return jQuery;
* });
* // => 3
*/
- function findLast(collection, predicate, thisArg) {
- predicate = getCallback(predicate, thisArg, 3);
- return baseFind(collection, predicate, baseEachRight);
- }
+ var findLast = createFind(baseEachRight, true);
/**
* Performs a deep comparison between each element in `collection` and the
@@ -38314,7 +40482,7 @@ return jQuery;
/**
* Iterates over elements of `collection` invoking `iteratee` for each element.
- * The `iteratee` is bound to `thisArg` and invoked with three arguments;
+ * The `iteratee` is bound to `thisArg` and invoked with three arguments:
* (value, index|key, collection). Iterator functions may exit iteration early
* by explicitly returning `false`.
*
@@ -38342,11 +40510,7 @@ return jQuery;
* });
* // => logs each value-key pair and returns the object (iteration order is not guaranteed)
*/
- function forEach(collection, iteratee, thisArg) {
- return (typeof iteratee == 'function' && typeof thisArg == 'undefined' && isArray(collection))
- ? arrayEach(collection, iteratee)
- : baseEach(collection, bindCallback(iteratee, thisArg, 3));
- }
+ var forEach = createForEach(arrayEach, baseEach);
/**
* This method is like `_.forEach` except that it iterates over elements of
@@ -38364,30 +40528,26 @@ return jQuery;
*
* _([1, 2]).forEachRight(function(n) {
* console.log(n);
- * }).join(',');
+ * }).value();
* // => logs each value from right to left and returns the array
*/
- function forEachRight(collection, iteratee, thisArg) {
- return (typeof iteratee == 'function' && typeof thisArg == 'undefined' && isArray(collection))
- ? arrayEachRight(collection, iteratee)
- : baseEachRight(collection, bindCallback(iteratee, thisArg, 3));
- }
+ var forEachRight = createForEach(arrayEachRight, baseEachRight);
/**
* Creates an object composed of keys generated from the results of running
* each element of `collection` through `iteratee`. The corresponding value
* of each key is an array of the elements responsible for generating the key.
- * The `iteratee` is bound to `thisArg` and invoked with three arguments;
+ * The `iteratee` is bound to `thisArg` and invoked with three arguments:
* (value, index|key, collection).
*
- * If a property name is provided for `predicate` the created `_.property`
+ * If a property name is provided for `iteratee` the created `_.property`
* style callback returns the property value of the given element.
*
* If a value is also provided for `thisArg` the created `_.matchesProperty`
* style callback returns `true` for elements that have a matching property
* value, else `false`.
*
- * If an object is provided for `predicate` the created `_.matches` style
+ * If an object is provided for `iteratee` the created `_.matches` style
* callback returns `true` for elements that have the properties of the given
* object, else `false`.
*
@@ -38428,10 +40588,9 @@ return jQuery;
* comparisons. If `fromIndex` is negative, it is used as the offset from
* the end of `collection`.
*
- * **Note:** `SameValueZero` comparisons are like strict equality comparisons,
- * e.g. `===`, except that `NaN` matches `NaN`. See the
- * [ES spec](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-samevaluezero)
- * for more details.
+ * **Note:** [`SameValueZero`](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-samevaluezero)
+ * comparisons are like strict equality comparisons, e.g. `===`, except that
+ * `NaN` matches `NaN`.
*
* @static
* @memberOf _
@@ -38440,6 +40599,7 @@ return jQuery;
* @param {Array|Object|string} collection The collection to search.
* @param {*} target The value to search for.
* @param {number} [fromIndex=0] The index to search from.
+ * @param- {Object} [guard] Enables use as a callback for functions like `_.reduce`.
* @returns {boolean} Returns `true` if a matching element is found, else `false`.
* @example
*
@@ -38455,7 +40615,7 @@ return jQuery;
* _.includes('pebbles', 'eb');
* // => true
*/
- function includes(collection, target, fromIndex) {
+ function includes(collection, target, fromIndex, guard) {
var length = collection ? collection.length : 0;
if (!isLength(length)) {
collection = values(collection);
@@ -38464,10 +40624,10 @@ return jQuery;
if (!length) {
return false;
}
- if (typeof fromIndex == 'number') {
- fromIndex = fromIndex < 0 ? nativeMax(length + fromIndex, 0) : (fromIndex || 0);
- } else {
+ if (typeof fromIndex != 'number' || (guard && isIterateeCall(target, fromIndex, guard))) {
fromIndex = 0;
+ } else {
+ fromIndex = fromIndex < 0 ? nativeMax(length + fromIndex, 0) : (fromIndex || 0);
}
return (typeof collection == 'string' || !isArray(collection) && isString(collection))
? (fromIndex < length && collection.indexOf(target, fromIndex) > -1)
@@ -38478,17 +40638,17 @@ return jQuery;
* Creates an object composed of keys generated from the results of running
* each element of `collection` through `iteratee`. The corresponding value
* of each key is the last element responsible for generating the key. The
- * iteratee function is bound to `thisArg` and invoked with three arguments;
+ * iteratee function is bound to `thisArg` and invoked with three arguments:
* (value, index|key, collection).
*
- * If a property name is provided for `predicate` the created `_.property`
+ * If a property name is provided for `iteratee` the created `_.property`
* style callback returns the property value of the given element.
*
* If a value is also provided for `thisArg` the created `_.matchesProperty`
* style callback returns `true` for elements that have a matching property
* value, else `false`.
*
- * If an object is provided for `predicate` the created `_.matches` style
+ * If an object is provided for `iteratee` the created `_.matches` style
* callback returns `true` for elements that have the properties of the given
* object, else `false`.
*
@@ -38546,23 +40706,32 @@ return jQuery;
* _.invoke([123, 456], String.prototype.split, '');
* // => [['1', '2', '3'], ['4', '5', '6']]
*/
- function invoke(collection, methodName) {
- return baseInvoke(collection, methodName, baseSlice(arguments, 2));
- }
+ var invoke = restParam(function(collection, methodName, args) {
+ var index = -1,
+ isFunc = typeof methodName == 'function',
+ length = collection ? collection.length : 0,
+ result = isLength(length) ? Array(length) : [];
+
+ baseEach(collection, function(value) {
+ var func = isFunc ? methodName : (value != null && value[methodName]);
+ result[++index] = func ? func.apply(value, args) : undefined;
+ });
+ return result;
+ });
/**
* Creates an array of values by running each element in `collection` through
* `iteratee`. The `iteratee` is bound to `thisArg` and invoked with three
- * arguments; (value, index|key, collection).
+ * arguments: (value, index|key, collection).
*
- * If a property name is provided for `predicate` the created `_.property`
+ * If a property name is provided for `iteratee` the created `_.property`
* style callback returns the property value of the given element.
*
* If a value is also provided for `thisArg` the created `_.matchesProperty`
* style callback returns `true` for elements that have a matching property
* value, else `false`.
*
- * If an object is provided for `predicate` the created `_.matches` style
+ * If an object is provided for `iteratee` the created `_.matches` style
* callback returns `true` for elements that have the properties of the given
* object, else `false`.
*
@@ -38571,9 +40740,9 @@ return jQuery;
*
* The guarded methods are:
* `ary`, `callback`, `chunk`, `clone`, `create`, `curry`, `curryRight`, `drop`,
- * `dropRight`, `fill`, `flatten`, `invert`, `max`, `min`, `parseInt`, `slice`,
- * `sortBy`, `take`, `takeRight`, `template`, `trim`, `trimLeft`, `trimRight`,
- * `trunc`, `random`, `range`, `sample`, `uniq`, and `words`
+ * `dropRight`, `every`, `fill`, `flatten`, `invert`, `max`, `min`, `parseInt`,
+ * `slice`, `sortBy`, `take`, `takeRight`, `template`, `trim`, `trimLeft`,
+ * `trimRight`, `trunc`, `random`, `range`, `sample`, `some`, `uniq`, and `words`
*
* @static
* @memberOf _
@@ -38616,7 +40785,7 @@ return jQuery;
* Creates an array of elements split into two groups, the first of which
* contains elements `predicate` returns truthy for, while the second of which
* contains elements `predicate` returns falsey for. The predicate is bound
- * to `thisArg` and invoked with three arguments; (value, index|key, collection).
+ * to `thisArg` and invoked with three arguments: (value, index|key, collection).
*
* If a property name is provided for `predicate` the created `_.property`
* style callback returns the property value of the given element.
@@ -38707,14 +40876,14 @@ return jQuery;
* each element in `collection` through `iteratee`, where each successive
* invocation is supplied the return value of the previous. If `accumulator`
* is not provided the first element of `collection` is used as the initial
- * value. The `iteratee` is bound to `thisArg`and invoked with four arguments;
+ * value. The `iteratee` is bound to `thisArg` and invoked with four arguments:
* (accumulator, value, index|key, collection).
*
* Many lodash methods are guarded to work as interatees for methods like
* `_.reduce`, `_.reduceRight`, and `_.transform`.
*
* The guarded methods are:
- * `assign`, `defaults`, `merge`, and `sortAllBy`
+ * `assign`, `defaults`, `includes`, `merge`, `sortByAll`, and `sortByOrder`
*
* @static
* @memberOf _
@@ -38738,10 +40907,7 @@ return jQuery;
* }, {});
* // => { 'a': 3, 'b': 6 } (iteration order is not guaranteed)
*/
- function reduce(collection, iteratee, accumulator, thisArg) {
- var func = isArray(collection) ? arrayReduce : baseReduce;
- return func(collection, getCallback(iteratee, thisArg, 4), accumulator, arguments.length < 3, baseEach);
- }
+ var reduce = createReduce(arrayReduce, baseEach);
/**
* This method is like `_.reduce` except that it iterates over elements of
@@ -38765,10 +40931,7 @@ return jQuery;
* }, []);
* // => [4, 5, 2, 3, 0, 1]
*/
- function reduceRight(collection, iteratee, accumulator, thisArg) {
- var func = isArray(collection) ? arrayReduceRight : baseReduce;
- return func(collection, getCallback(iteratee, thisArg, 4), accumulator, arguments.length < 3, baseEachRight);
- }
+ var reduceRight = createReduce(arrayReduceRight, baseEachRight);
/**
* The opposite of `_.filter`; this method returns the elements of `collection`
@@ -38855,9 +41018,8 @@ return jQuery;
}
/**
- * Creates an array of shuffled values, using a version of the Fisher-Yates
- * shuffle. See [Wikipedia](https://en.wikipedia.org/wiki/Fisher-Yates_shuffle)
- * for more details.
+ * Creates an array of shuffled values, using a version of the
+ * [Fisher-Yates shuffle](https://en.wikipedia.org/wiki/Fisher-Yates_shuffle).
*
* @static
* @memberOf _
@@ -38915,7 +41077,7 @@ return jQuery;
* Checks if `predicate` returns truthy for **any** element of `collection`.
* The function returns as soon as it finds a passing value and does not iterate
* over the entire collection. The predicate is bound to `thisArg` and invoked
- * with three arguments; (value, index|key, collection).
+ * with three arguments: (value, index|key, collection).
*
* If a property name is provided for `predicate` the created `_.property`
* style callback returns the property value of the given element.
@@ -38962,6 +41124,9 @@ return jQuery;
*/
function some(collection, predicate, thisArg) {
var func = isArray(collection) ? arraySome : baseSome;
+ if (thisArg && isIterateeCall(collection, predicate, thisArg)) {
+ predicate = null;
+ }
if (typeof predicate != 'function' || typeof thisArg != 'undefined') {
predicate = getCallback(predicate, thisArg, 3);
}
@@ -38972,17 +41137,17 @@ return jQuery;
* Creates an array of elements, sorted in ascending order by the results of
* running each element in a collection through `iteratee`. This method performs
* a stable sort, that is, it preserves the original sort order of equal elements.
- * The `iteratee` is bound to `thisArg` and invoked with three arguments;
+ * The `iteratee` is bound to `thisArg` and invoked with three arguments:
* (value, index|key, collection).
*
- * If a property name is provided for `predicate` the created `_.property`
+ * If a property name is provided for `iteratee` the created `_.property`
* style callback returns the property value of the given element.
*
* If a value is also provided for `thisArg` the created `_.matchesProperty`
* style callback returns `true` for elements that have a matching property
* value, else `false`.
*
- * If an object is provided for `predicate` the created `_.matches` style
+ * If an object is provided for `iteratee` the created `_.matches` style
* callback returns `true` for elements that have the properties of the given
* object, else `false`.
*
@@ -39058,17 +41223,24 @@ return jQuery;
* _.map(_.sortByAll(users, ['user', 'age']), _.values);
* // => [['barney', 26], ['barney', 36], ['fred', 30], ['fred', 40]]
*/
- function sortByAll(collection) {
+ function sortByAll() {
+ var args = arguments,
+ collection = args[0],
+ guard = args[3],
+ index = 0,
+ length = args.length - 1;
+
if (collection == null) {
return [];
}
- var args = arguments,
- guard = args[3];
-
+ var props = Array(length);
+ while (index < length) {
+ props[index] = args[++index];
+ }
if (guard && isIterateeCall(args[1], args[2], guard)) {
- args = [collection, args[1]];
+ props = args[1];
}
- return baseSortByOrder(collection, baseFlatten(args, false, false, 1), []);
+ return baseSortByOrder(collection, baseFlatten(props), []);
}
/**
@@ -39287,7 +41459,7 @@ return jQuery;
* @category Function
* @param {Function} func The function to bind.
* @param {*} thisArg The `this` binding of `func`.
- * @param {...*} [args] The arguments to be partially applied.
+ * @param {...*} [partials] The arguments to be partially applied.
* @returns {Function} Returns the new bound function.
* @example
*
@@ -39306,16 +41478,14 @@ return jQuery;
* bound('hi');
* // => 'hi fred!'
*/
- function bind(func, thisArg) {
+ var bind = restParam(function(func, thisArg, partials) {
var bitmask = BIND_FLAG;
- if (arguments.length > 2) {
- var partials = baseSlice(arguments, 2),
- holders = replaceHolders(partials, bind.placeholder);
-
+ if (partials.length) {
+ var holders = replaceHolders(partials, bind.placeholder);
bitmask |= PARTIAL_FLAG;
}
return createWrapper(func, bitmask, thisArg, partials, holders);
- }
+ });
/**
* Binds methods of an object to the object itself, overwriting the existing
@@ -39345,13 +41515,18 @@ return jQuery;
* jQuery('#docs').on('click', view.onClick);
* // => logs 'clicked docs' when the element is clicked
*/
- function bindAll(object) {
- return baseBindAll(object,
- arguments.length > 1
- ? baseFlatten(arguments, false, false, 1)
- : functions(object)
- );
- }
+ var bindAll = restParam(function(object, methodNames) {
+ methodNames = methodNames.length ? baseFlatten(methodNames) : functions(object);
+
+ var index = -1,
+ length = methodNames.length;
+
+ while (++index < length) {
+ var key = methodNames[index];
+ object[key] = createWrapper(object[key], BIND_FLAG, object);
+ }
+ return object;
+ });
/**
* Creates a function that invokes the method at `object[key]` and prepends
@@ -39370,7 +41545,7 @@ return jQuery;
* @category Function
* @param {Object} object The object the method belongs to.
* @param {string} key The key of the method.
- * @param {...*} [args] The arguments to be partially applied.
+ * @param {...*} [partials] The arguments to be partially applied.
* @returns {Function} Returns the new bound function.
* @example
*
@@ -39397,16 +41572,14 @@ return jQuery;
* bound('hi');
* // => 'hiya fred!'
*/
- function bindKey(object, key) {
+ var bindKey = restParam(function(object, key, partials) {
var bitmask = BIND_FLAG | BIND_KEY_FLAG;
- if (arguments.length > 2) {
- var partials = baseSlice(arguments, 2),
- holders = replaceHolders(partials, bindKey.placeholder);
-
+ if (partials.length) {
+ var holders = replaceHolders(partials, bindKey.placeholder);
bitmask |= PARTIAL_FLAG;
}
return createWrapper(key, bitmask, object, partials, holders);
- }
+ });
/**
* Creates a function that accepts one or more arguments of `func` that when
@@ -39448,14 +41621,7 @@ return jQuery;
* curried(1)(_, 3)(2);
* // => [1, 2, 3]
*/
- function curry(func, arity, guard) {
- if (guard && isIterateeCall(func, arity, guard)) {
- arity = null;
- }
- var result = createWrapper(func, CURRY_FLAG, null, null, null, null, null, arity);
- result.placeholder = curry.placeholder;
- return result;
- }
+ var curry = createCurry(CURRY_FLAG);
/**
* This method is like `_.curry` except that arguments are applied to `func`
@@ -39494,14 +41660,7 @@ return jQuery;
* curried(3)(1, _)(2);
* // => [1, 2, 3]
*/
- function curryRight(func, arity, guard) {
- if (guard && isIterateeCall(func, arity, guard)) {
- arity = null;
- }
- var result = createWrapper(func, CURRY_RIGHT_FLAG, null, null, null, null, null, arity);
- result.placeholder = curryRight.placeholder;
- return result;
- }
+ var curryRight = createCurry(CURRY_RIGHT_FLAG);
/**
* Creates a function that delays invoking `func` until after `wait` milliseconds
@@ -39696,9 +41855,9 @@ return jQuery;
* }, 'deferred');
* // logs 'deferred' after one or more milliseconds
*/
- function defer(func) {
- return baseDelay(func, 1, arguments, 1);
- }
+ var defer = restParam(function(func, args) {
+ return baseDelay(func, 1, args);
+ });
/**
* Invokes `func` after `wait` milliseconds. Any additional arguments are
@@ -39718,9 +41877,9 @@ return jQuery;
* }, 1000, 'later');
* // => logs 'later' after one second
*/
- function delay(func, wait) {
- return baseDelay(func, wait, arguments, 2);
- }
+ var delay = restParam(function(func, wait, args) {
+ return baseDelay(func, wait, args);
+ });
/**
* Creates a function that returns the result of invoking the provided
@@ -39742,7 +41901,7 @@ return jQuery;
* addSquare(1, 2);
* // => 9
*/
- var flow = createComposer();
+ var flow = createFlow();
/**
* This method is like `_.flow` except that it creates a function that
@@ -39764,7 +41923,7 @@ return jQuery;
* addSquare(1, 2);
* // => 9
*/
- var flowRight = createComposer(true);
+ var flowRight = createFlow(true);
/**
* Creates a function that memoizes the result of `func`. If `resolver` is
@@ -39776,10 +41935,8 @@ return jQuery;
*
* **Note:** The cache is exposed as the `cache` property on the memoized
* function. Its creation may be customized by replacing the `_.memoize.Cache`
- * constructor with one whose instances implement the ES `Map` method interface
- * of `get`, `has`, and `set`. See the
- * [ES spec](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-properties-of-the-map-prototype-object)
- * for more details.
+ * constructor with one whose instances implement the [`Map`](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-properties-of-the-map-prototype-object)
+ * method interface of `get`, `has`, and `set`.
*
* @static
* @memberOf _
@@ -39870,7 +42027,7 @@ return jQuery;
/**
* Creates a function that is restricted to invoking `func` once. Repeat calls
* to the function return the value of the first call. The `func` is invoked
- * with the `this` binding of the created function.
+ * with the `this` binding and arguments of the created function.
*
* @static
* @memberOf _
@@ -39903,7 +42060,7 @@ return jQuery;
* @memberOf _
* @category Function
* @param {Function} func The function to partially apply arguments to.
- * @param {...*} [args] The arguments to be partially applied.
+ * @param {...*} [partials] The arguments to be partially applied.
* @returns {Function} Returns the new partially applied function.
* @example
*
@@ -39920,12 +42077,7 @@ return jQuery;
* greetFred('hi');
* // => 'hi fred'
*/
- function partial(func) {
- var partials = baseSlice(arguments, 1),
- holders = replaceHolders(partials, partial.placeholder);
-
- return createWrapper(func, PARTIAL_FLAG, null, partials, holders);
- }
+ var partial = createPartial(PARTIAL_FLAG);
/**
* This method is like `_.partial` except that partially applied arguments
@@ -39941,7 +42093,7 @@ return jQuery;
* @memberOf _
* @category Function
* @param {Function} func The function to partially apply arguments to.
- * @param {...*} [args] The arguments to be partially applied.
+ * @param {...*} [partials] The arguments to be partially applied.
* @returns {Function} Returns the new partially applied function.
* @example
*
@@ -39958,12 +42110,7 @@ return jQuery;
* sayHelloTo('fred');
* // => 'hello fred'
*/
- function partialRight(func) {
- var partials = baseSlice(arguments, 1),
- holders = replaceHolders(partials, partialRight.placeholder);
-
- return createWrapper(func, PARTIAL_RIGHT_FLAG, null, partials, holders);
- }
+ var partialRight = createPartial(PARTIAL_RIGHT_FLAG);
/**
* Creates a function that invokes `func` with arguments arranged according
@@ -39993,29 +42140,80 @@ return jQuery;
* }, [1, 2, 3]);
* // => [3, 6, 9]
*/
- function rearg(func) {
- var indexes = baseFlatten(arguments, false, false, 1);
- return createWrapper(func, REARG_FLAG, null, null, null, indexes);
- }
+ var rearg = restParam(function(func, indexes) {
+ return createWrapper(func, REARG_FLAG, null, null, null, baseFlatten(indexes));
+ });
/**
* Creates a function that invokes `func` with the `this` binding of the
- * created function and the array of arguments provided to the created
- * function much like [Function#apply](http://es5.github.io/#x15.3.4.3).
+ * created function and arguments from `start` and beyond provided as an array.
+ *
+ * **Note:** This method is based on the [rest parameter](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters).
+ *
+ * @static
+ * @memberOf _
+ * @category Function
+ * @param {Function} func The function to apply a rest parameter to.
+ * @param {number} [start=func.length-1] The start position of the rest parameter.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * var say = _.restParam(function(what, names) {
+ * return what + ' ' + _.initial(names).join(', ') +
+ * (_.size(names) > 1 ? ', & ' : '') + _.last(names);
+ * });
+ *
+ * say('hello', 'fred', 'barney', 'pebbles');
+ * // => 'hello fred, barney, & pebbles'
+ */
+ function restParam(func, start) {
+ if (typeof func != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
+ start = nativeMax(typeof start == 'undefined' ? (func.length - 1) : (+start || 0), 0);
+ return function() {
+ var args = arguments,
+ index = -1,
+ length = nativeMax(args.length - start, 0),
+ rest = Array(length);
+
+ while (++index < length) {
+ rest[index] = args[start + index];
+ }
+ switch (start) {
+ case 0: return func.call(this, rest);
+ case 1: return func.call(this, args[0], rest);
+ case 2: return func.call(this, args[0], args[1], rest);
+ }
+ var otherArgs = Array(start + 1);
+ index = -1;
+ while (++index < start) {
+ otherArgs[index] = args[index];
+ }
+ otherArgs[start] = rest;
+ return func.apply(this, otherArgs);
+ };
+ }
+
+ /**
+ * Creates a function that invokes `func` with the `this` binding of the created
+ * function and an array of arguments much like [`Function#apply`](https://es5.github.io/#x15.3.4.3).
+ *
+ * **Note:** This method is based on the [spread operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator).
*
* @static
* @memberOf _
* @category Function
* @param {Function} func The function to spread arguments over.
- * @returns {*} Returns the new function.
+ * @returns {Function} Returns the new function.
* @example
*
- * var spread = _.spread(function(who, what) {
+ * var say = _.spread(function(who, what) {
* return who + ' says ' + what;
* });
*
- * spread(['Fred', 'hello']);
- * // => 'Fred says hello'
+ * say(['fred', 'hello']);
+ * // => 'fred says hello'
*
* // with a Promise
* var numbers = Promise.all([
@@ -40130,12 +42328,12 @@ return jQuery;
* cloning is handled by the method instead. The `customizer` is bound to
* `thisArg` and invoked with two argument; (value [, index|key, object]).
*
- * **Note:** This method is loosely based on the structured clone algorithm.
+ * **Note:** This method is loosely based on the
+ * [structured clone algorithm](http://www.w3.org/TR/html5/infrastructure.html#internal-structured-cloning-algorithm).
* The enumerable properties of `arguments` objects and objects created by
* constructors other than `Object` are cloned to plain `Object` objects. An
* empty object is returned for uncloneable values such as functions, DOM nodes,
- * Maps, Sets, and WeakMaps. See the [HTML5 specification](http://www.w3.org/TR/html5/infrastructure.html#internal-structured-cloning-algorithm)
- * for more details.
+ * Maps, Sets, and WeakMaps.
*
* @static
* @memberOf _
@@ -40193,12 +42391,12 @@ return jQuery;
* is handled by the method instead. The `customizer` is bound to `thisArg`
* and invoked with two argument; (value [, index|key, object]).
*
- * **Note:** This method is loosely based on the structured clone algorithm.
+ * **Note:** This method is loosely based on the
+ * [structured clone algorithm](http://www.w3.org/TR/html5/infrastructure.html#internal-structured-cloning-algorithm).
* The enumerable properties of `arguments` objects and objects created by
* constructors other than `Object` are cloned to plain `Object` objects. An
* empty object is returned for uncloneable values such as functions, DOM nodes,
- * Maps, Sets, and WeakMaps. See the [HTML5 specification](http://www.w3.org/TR/html5/infrastructure.html#internal-structured-cloning-algorithm)
- * for more details.
+ * Maps, Sets, and WeakMaps.
*
* @static
* @memberOf _
@@ -40255,7 +42453,7 @@ return jQuery;
*/
function isArguments(value) {
var length = isObjectLike(value) ? value.length : undefined;
- return (isLength(length) && objToString.call(value) == argsTag) || false;
+ return isLength(length) && objToString.call(value) == argsTag;
}
/**
@@ -40275,7 +42473,7 @@ return jQuery;
* // => false
*/
var isArray = nativeIsArray || function(value) {
- return (isObjectLike(value) && isLength(value.length) && objToString.call(value) == arrayTag) || false;
+ return isObjectLike(value) && isLength(value.length) && objToString.call(value) == arrayTag;
};
/**
@@ -40295,7 +42493,7 @@ return jQuery;
* // => false
*/
function isBoolean(value) {
- return (value === true || value === false || isObjectLike(value) && objToString.call(value) == boolTag) || false;
+ return value === true || value === false || (isObjectLike(value) && objToString.call(value) == boolTag);
}
/**
@@ -40315,7 +42513,7 @@ return jQuery;
* // => false
*/
function isDate(value) {
- return (isObjectLike(value) && objToString.call(value) == dateTag) || false;
+ return isObjectLike(value) && objToString.call(value) == dateTag;
}
/**
@@ -40335,13 +42533,13 @@ return jQuery;
* // => false
*/
function isElement(value) {
- return (value && value.nodeType === 1 && isObjectLike(value) &&
- (objToString.call(value).indexOf('Element') > -1)) || false;
+ return !!value && value.nodeType === 1 && isObjectLike(value) &&
+ (objToString.call(value).indexOf('Element') > -1);
}
// Fallback for environments without DOM support.
if (!support.dom) {
isElement = function(value) {
- return (value && value.nodeType === 1 && isObjectLike(value) && !isPlainObject(value)) || false;
+ return !!value && value.nodeType === 1 && isObjectLike(value) && !isPlainObject(value);
};
}
@@ -40389,7 +42587,7 @@ return jQuery;
* equivalent. If `customizer` is provided it is invoked to compare values.
* If `customizer` returns `undefined` comparisons are handled by the method
* instead. The `customizer` is bound to `thisArg` and invoked with three
- * arguments; (value, other [, index|key]).
+ * arguments: (value, other [, index|key]).
*
* **Note:** This method supports comparing arrays, booleans, `Date` objects,
* numbers, `Object` objects, regexes, and strings. Objects are compared by
@@ -40454,15 +42652,13 @@ return jQuery;
* // => false
*/
function isError(value) {
- return (isObjectLike(value) && typeof value.message == 'string' && objToString.call(value) == errorTag) || false;
+ return isObjectLike(value) && typeof value.message == 'string' && objToString.call(value) == errorTag;
}
/**
* Checks if `value` is a finite primitive number.
*
- * **Note:** This method is based on ES `Number.isFinite`. See the
- * [ES spec](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-number.isfinite)
- * for more details.
+ * **Note:** This method is based on [`Number.isFinite`](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-number.isfinite).
*
* @static
* @memberOf _
@@ -40514,11 +42710,9 @@ return jQuery;
};
/**
- * Checks if `value` is the language type of `Object`.
+ * Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`.
* (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
*
- * **Note:** See the [ES5 spec](https://es5.github.io/#x8) for more details.
- *
* @static
* @memberOf _
* @category Lang
@@ -40539,7 +42733,7 @@ return jQuery;
// Avoid a V8 JIT bug in Chrome 19-20.
// See https://code.google.com/p/v8/issues/detail?id=2291 for more details.
var type = typeof value;
- return type == 'function' || (value && type == 'object') || false;
+ return type == 'function' || (!!value && type == 'object');
}
/**
@@ -40547,7 +42741,7 @@ return jQuery;
* `object` contains equivalent property values. If `customizer` is provided
* it is invoked to compare values. If `customizer` returns `undefined`
* comparisons are handled by the method instead. The `customizer` is bound
- * to `thisArg` and invoked with three arguments; (value, other, index|key).
+ * to `thisArg` and invoked with three arguments: (value, other, index|key).
*
* **Note:** This method supports comparing properties of arrays, booleans,
* `Date` objects, numbers, `Object` objects, regexes, and strings. Functions
@@ -40585,13 +42779,19 @@ return jQuery;
var props = keys(source),
length = props.length;
+ if (!length) {
+ return true;
+ }
+ if (object == null) {
+ return false;
+ }
customizer = typeof customizer == 'function' && bindCallback(customizer, thisArg, 3);
if (!customizer && length == 1) {
var key = props[0],
value = source[key];
if (isStrictComparable(value)) {
- return object != null && value === object[key] && hasOwnProperty.call(object, key);
+ return value === object[key] && (typeof value != 'undefined' || (key in toObject(object)));
}
}
var values = Array(length),
@@ -40601,15 +42801,14 @@ return jQuery;
value = values[length] = source[props[length]];
strictCompareFlags[length] = isStrictComparable(value);
}
- return baseIsMatch(object, props, values, strictCompareFlags, customizer);
+ return baseIsMatch(toObject(object), props, values, strictCompareFlags, customizer);
}
/**
* Checks if `value` is `NaN`.
*
- * **Note:** This method is not the same as native `isNaN` which returns `true`
- * for `undefined` and other non-numeric values. See the [ES5 spec](https://es5.github.io/#x15.1.2.4)
- * for more details.
+ * **Note:** This method is not the same as [`isNaN`](https://es5.github.io/#x15.1.2.4)
+ * which returns `true` for `undefined` and other non-numeric values.
*
* @static
* @memberOf _
@@ -40659,7 +42858,7 @@ return jQuery;
if (objToString.call(value) == funcTag) {
return reNative.test(fnToString.call(value));
}
- return (isObjectLike(value) && reHostCtor.test(value)) || false;
+ return isObjectLike(value) && reHostCtor.test(value);
}
/**
@@ -40705,7 +42904,7 @@ return jQuery;
* // => false
*/
function isNumber(value) {
- return typeof value == 'number' || (isObjectLike(value) && objToString.call(value) == numberTag) || false;
+ return typeof value == 'number' || (isObjectLike(value) && objToString.call(value) == numberTag);
}
/**
@@ -40787,7 +42986,7 @@ return jQuery;
* // => false
*/
function isString(value) {
- return typeof value == 'string' || (isObjectLike(value) && objToString.call(value) == stringTag) || false;
+ return typeof value == 'string' || (isObjectLike(value) && objToString.call(value) == stringTag);
}
/**
@@ -40807,7 +43006,7 @@ return jQuery;
* // => false
*/
function isTypedArray(value) {
- return (isObjectLike(value) && isLength(value.length) && typedArrayTags[objToString.call(value)]) || false;
+ return isObjectLike(value) && isLength(value.length) && !!typedArrayTags[objToString.call(value)];
}
/**
@@ -40889,7 +43088,7 @@ return jQuery;
* Assigns own enumerable properties of source object(s) to the destination
* object. Subsequent sources overwrite property assignments of previous sources.
* If `customizer` is provided it is invoked to produce the assigned values.
- * The `customizer` is bound to `thisArg` and invoked with five arguments;
+ * The `customizer` is bound to `thisArg` and invoked with five arguments:
* (objectValue, sourceValue, key, object, source).
*
* @static
@@ -40974,18 +43173,18 @@ return jQuery;
* _.defaults({ 'user': 'barney' }, { 'age': 36 }, { 'user': 'fred' });
* // => { 'user': 'barney', 'age': 36 }
*/
- function defaults(object) {
+ var defaults = restParam(function(args) {
+ var object = args[0];
if (object == null) {
return object;
}
- var args = arrayCopy(arguments);
args.push(assignDefaults);
return assign.apply(undefined, args);
- }
+ });
/**
- * This method is like `_.findIndex` except that it returns the key of the
- * first element `predicate` returns truthy for, instead of the element itself.
+ * This method is like `_.find` except that it returns the key of the first
+ * element `predicate` returns truthy for instead of the element itself.
*
* If a property name is provided for `predicate` the created `_.property`
* style callback returns the property value of the given element.
@@ -41031,10 +43230,7 @@ return jQuery;
* _.findKey(users, 'active');
* // => 'barney'
*/
- function findKey(object, predicate, thisArg) {
- predicate = getCallback(predicate, thisArg, 3);
- return baseFind(object, predicate, baseForOwn, true);
- }
+ var findKey = createFindKey(baseForOwn);
/**
* This method is like `_.findKey` except that it iterates over elements of
@@ -41084,15 +43280,12 @@ return jQuery;
* _.findLastKey(users, 'active');
* // => 'pebbles'
*/
- function findLastKey(object, predicate, thisArg) {
- predicate = getCallback(predicate, thisArg, 3);
- return baseFind(object, predicate, baseForOwnRight, true);
- }
+ var findLastKey = createFindKey(baseForOwnRight);
/**
* Iterates over own and inherited enumerable properties of an object invoking
* `iteratee` for each property. The `iteratee` is bound to `thisArg` and invoked
- * with three arguments; (value, key, object). Iterator functions may exit
+ * with three arguments: (value, key, object). Iterator functions may exit
* iteration early by explicitly returning `false`.
*
* @static
@@ -41116,12 +43309,7 @@ return jQuery;
* });
* // => logs 'a', 'b', and 'c' (iteration order is not guaranteed)
*/
- function forIn(object, iteratee, thisArg) {
- if (typeof iteratee != 'function' || typeof thisArg != 'undefined') {
- iteratee = bindCallback(iteratee, thisArg, 3);
- }
- return baseFor(object, iteratee, keysIn);
- }
+ var forIn = createForIn(baseFor);
/**
* This method is like `_.forIn` except that it iterates over properties of
@@ -41148,15 +43336,12 @@ return jQuery;
* });
* // => logs 'c', 'b', and 'a' assuming `_.forIn ` logs 'a', 'b', and 'c'
*/
- function forInRight(object, iteratee, thisArg) {
- iteratee = bindCallback(iteratee, thisArg, 3);
- return baseForRight(object, iteratee, keysIn);
- }
+ var forInRight = createForIn(baseForRight);
/**
* Iterates over own enumerable properties of an object invoking `iteratee`
* for each property. The `iteratee` is bound to `thisArg` and invoked with
- * three arguments; (value, key, object). Iterator functions may exit iteration
+ * three arguments: (value, key, object). Iterator functions may exit iteration
* early by explicitly returning `false`.
*
* @static
@@ -41180,12 +43365,7 @@ return jQuery;
* });
* // => logs 'a' and 'b' (iteration order is not guaranteed)
*/
- function forOwn(object, iteratee, thisArg) {
- if (typeof iteratee != 'function' || typeof thisArg != 'undefined') {
- iteratee = bindCallback(iteratee, thisArg, 3);
- }
- return baseForOwn(object, iteratee);
- }
+ var forOwn = createForOwn(baseForOwn);
/**
* This method is like `_.forOwn` except that it iterates over properties of
@@ -41212,10 +43392,7 @@ return jQuery;
* });
* // => logs 'b' and 'a' assuming `_.forOwn` logs 'a' and 'b'
*/
- function forOwnRight(object, iteratee, thisArg) {
- iteratee = bindCallback(iteratee, thisArg, 3);
- return baseForRight(object, iteratee, keys);
- }
+ var forOwnRight = createForOwn(baseForOwnRight);
/**
* Creates an array of function property names from all enumerable properties,
@@ -41400,7 +43577,7 @@ return jQuery;
/**
* Creates an object with the same keys as `object` and values generated by
* running each own enumerable property of `object` through `iteratee`. The
- * iteratee function is bound to `thisArg` and invoked with three arguments;
+ * iteratee function is bound to `thisArg` and invoked with three arguments:
* (value, key, object).
*
* If a property name is provided for `iteratee` the created `_.property`
@@ -41455,7 +43632,7 @@ return jQuery;
* provided it is invoked to produce the merged values of the destination and
* source properties. If `customizer` returns `undefined` merging is handled
* by the method instead. The `customizer` is bound to `thisArg` and invoked
- * with five arguments; (objectValue, sourceValue, key, object, source).
+ * with five arguments: (objectValue, sourceValue, key, object, source).
*
* @static
* @memberOf _
@@ -41504,7 +43681,7 @@ return jQuery;
* Property names may be specified as individual arguments or as arrays of
* property names. If `predicate` is provided it is invoked for each property
* of `object` omitting the properties `predicate` returns truthy for. The
- * predicate is bound to `thisArg` and invoked with three arguments;
+ * predicate is bound to `thisArg` and invoked with three arguments:
* (value, key, object).
*
* @static
@@ -41526,19 +43703,19 @@ return jQuery;
* _.omit(object, _.isNumber);
* // => { 'user': 'fred' }
*/
- function omit(object, predicate, thisArg) {
+ var omit = restParam(function(object, props) {
if (object == null) {
return {};
}
- if (typeof predicate != 'function') {
- var props = arrayMap(baseFlatten(arguments, false, false, 1), String);
+ if (typeof props[0] != 'function') {
+ var props = arrayMap(baseFlatten(props), String);
return pickByArray(object, baseDifference(keysIn(object), props));
}
- predicate = bindCallback(predicate, thisArg, 3);
+ var predicate = bindCallback(props[0], props[1], 3);
return pickByCallback(object, function(value, key, object) {
return !predicate(value, key, object);
});
- }
+ });
/**
* Creates a two dimensional array of the key-value pairs for `object`,
@@ -41572,7 +43749,7 @@ return jQuery;
* names may be specified as individual arguments or as arrays of property
* names. If `predicate` is provided it is invoked for each property of `object`
* picking the properties `predicate` returns truthy for. The predicate is
- * bound to `thisArg` and invoked with three arguments; (value, key, object).
+ * bound to `thisArg` and invoked with three arguments: (value, key, object).
*
* @static
* @memberOf _
@@ -41593,14 +43770,14 @@ return jQuery;
* _.pick(object, _.isString);
* // => { 'user': 'fred' }
*/
- function pick(object, predicate, thisArg) {
+ var pick = restParam(function(object, props) {
if (object == null) {
return {};
}
- return typeof predicate == 'function'
- ? pickByCallback(object, bindCallback(predicate, thisArg, 3))
- : pickByArray(object, baseFlatten(arguments, false, false, 1));
- }
+ return typeof props[0] == 'function'
+ ? pickByCallback(object, bindCallback(props[0], props[1], 3))
+ : pickByArray(object, baseFlatten(props));
+ });
/**
* Resolves the value of property `key` on `object`. If the value of `key` is
@@ -41645,7 +43822,7 @@ return jQuery;
* `accumulator` object which is the result of running each of its own enumerable
* properties through `iteratee`, with each invocation potentially mutating
* the `accumulator` object. The `iteratee` is bound to `thisArg` and invoked
- * with four arguments; (accumulator, value, key, object). Iterator functions
+ * with four arguments: (accumulator, value, key, object). Iterator functions
* may exit iteration early by explicitly returning `false`.
*
* @static
@@ -41856,8 +44033,7 @@ return jQuery;
/*------------------------------------------------------------------------*/
/**
- * Converts `string` to camel case.
- * See [Wikipedia](https://en.wikipedia.org/wiki/CamelCase) for more details.
+ * Converts `string` to [camel case](https://en.wikipedia.org/wiki/CamelCase).
*
* @static
* @memberOf _
@@ -41899,9 +44075,8 @@ return jQuery;
}
/**
- * Deburrs `string` by converting latin-1 supplementary letters to basic latin letters.
- * See [Wikipedia](https://en.wikipedia.org/wiki/Latin-1_Supplement_(Unicode_block)#Character_table)
- * for more details.
+ * Deburrs `string` by converting [latin-1 supplementary letters](https://en.wikipedia.org/wiki/Latin-1_Supplement_(Unicode_block)#Character_table)
+ * to basic latin letters and removing [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks).
*
* @static
* @memberOf _
@@ -41915,7 +44090,7 @@ return jQuery;
*/
function deburr(string) {
string = baseToString(string);
- return string && string.replace(reLatin1, deburrLetter);
+ return string && string.replace(reLatin1, deburrLetter).replace(reComboMarks, '');
}
/**
@@ -41970,9 +44145,8 @@ return jQuery;
* [#108](https://html5sec.org/#108), and [#133](https://html5sec.org/#133) of
* the [HTML5 Security Cheatsheet](https://html5sec.org/) for more details.
*
- * When working with HTML you should always quote attribute values to reduce
- * XSS vectors. See [Ryan Grove's article](http://wonko.com/post/html-escaping)
- * for more details.
+ * When working with HTML you should always [quote attribute values](http://wonko.com/post/html-escaping)
+ * to reduce XSS vectors.
*
* @static
* @memberOf _
@@ -41993,8 +44167,8 @@ return jQuery;
}
/**
- * Escapes the `RegExp` special characters "\", "^", "$", ".", "|", "?", "*",
- * "+", "(", ")", "[", "]", "{" and "}" in `string`.
+ * Escapes the `RegExp` special characters "\", "/", "^", "$", ".", "|", "?",
+ * "*", "+", "(", ")", "[", "]", "{" and "}" in `string`.
*
* @static
* @memberOf _
@@ -42004,7 +44178,7 @@ return jQuery;
* @example
*
* _.escapeRegExp('[lodash](https://lodash.com/)');
- * // => '\[lodash\]\(https://lodash\.com/\)'
+ * // => '\[lodash\]\(https:\/\/lodash\.com\/\)'
*/
function escapeRegExp(string) {
string = baseToString(string);
@@ -42014,9 +44188,7 @@ return jQuery;
}
/**
- * Converts `string` to kebab case.
- * See [Wikipedia](https://en.wikipedia.org/wiki/Letter_case#Special_case_styles) for
- * more details.
+ * Converts `string` to [kebab case](https://en.wikipedia.org/wiki/Letter_case#Special_case_styles).
*
* @static
* @memberOf _
@@ -42039,9 +44211,8 @@ return jQuery;
});
/**
- * Pads `string` on the left and right sides if it is shorter then the given
- * padding length. The `chars` string may be truncated if the number of padding
- * characters can't be evenly divided by the padding length.
+ * Pads `string` on the left and right sides if it is shorter than `length`.
+ * Padding characters are truncated if they can't be evenly divided by `length`.
*
* @static
* @memberOf _
@@ -42073,14 +44244,13 @@ return jQuery;
leftLength = floor(mid),
rightLength = ceil(mid);
- chars = createPad('', rightLength, chars);
+ chars = createPadding('', rightLength, chars);
return chars.slice(0, leftLength) + string + chars;
}
/**
- * Pads `string` on the left side if it is shorter then the given padding
- * length. The `chars` string may be truncated if the number of padding
- * characters exceeds the padding length.
+ * Pads `string` on the left side if it is shorter than `length`. Padding
+ * characters are truncated if they exceed `length`.
*
* @static
* @memberOf _
@@ -42100,15 +44270,11 @@ return jQuery;
* _.padLeft('abc', 3);
* // => 'abc'
*/
- function padLeft(string, length, chars) {
- string = baseToString(string);
- return string && (createPad(string, length, chars) + string);
- }
+ var padLeft = createPadDir();
/**
- * Pads `string` on the right side if it is shorter then the given padding
- * length. The `chars` string may be truncated if the number of padding
- * characters exceeds the padding length.
+ * Pads `string` on the right side if it is shorter than `length`. Padding
+ * characters are truncated if they exceed `length`.
*
* @static
* @memberOf _
@@ -42128,18 +44294,15 @@ return jQuery;
* _.padRight('abc', 3);
* // => 'abc'
*/
- function padRight(string, length, chars) {
- string = baseToString(string);
- return string && (string + createPad(string, length, chars));
- }
+ var padRight = createPadDir(true);
/**
* Converts `string` to an integer of the specified radix. If `radix` is
* `undefined` or `0`, a `radix` of `10` is used unless `value` is a hexadecimal,
* in which case a `radix` of `16` is used.
*
- * **Note:** This method aligns with the ES5 implementation of `parseInt`.
- * See the [ES5 spec](https://es5.github.io/#E) for more details.
+ * **Note:** This method aligns with the [ES5 implementation](https://es5.github.io/#E)
+ * of `parseInt`.
*
* @static
* @memberOf _
@@ -42219,8 +44382,7 @@ return jQuery;
}
/**
- * Converts `string` to snake case.
- * See [Wikipedia](https://en.wikipedia.org/wiki/Snake_case) for more details.
+ * Converts `string` to [snake case](https://en.wikipedia.org/wiki/Snake_case).
*
* @static
* @memberOf _
@@ -42243,9 +44405,7 @@ return jQuery;
});
/**
- * Converts `string` to start case.
- * See [Wikipedia](https://en.wikipedia.org/wiki/Letter_case#Stylistic_or_specialised_usage)
- * for more details.
+ * Converts `string` to [start case](https://en.wikipedia.org/wiki/Letter_case#Stylistic_or_specialised_usage).
*
* @static
* @memberOf _
@@ -42304,9 +44464,9 @@ return jQuery;
* properties may be accessed as free variables in the template. If a setting
* object is provided it takes precedence over `_.templateSettings` values.
*
- * **Note:** In the development build `_.template` utilizes sourceURLs for easier debugging.
- * See the [HTML5 Rocks article on sourcemaps](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl)
- * for more details.
+ * **Note:** In the development build `_.template` utilizes
+ * [sourceURLs](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl)
+ * for easier debugging.
*
* For more information on precompiling templates see
* [lodash's custom builds documentation](https://lodash.com/custom-builds).
@@ -42518,7 +44678,7 @@ return jQuery;
* // => 'abc'
*
* _.map([' foo ', ' bar '], _.trim);
- * // => ['foo', 'bar]
+ * // => ['foo', 'bar']
*/
function trim(string, chars, guard) {
var value = string;
@@ -42626,7 +44786,7 @@ return jQuery;
* 'length': 24,
* 'separator': /,? +/
* });
- * //=> 'hi-diddly-ho there...'
+ * // => 'hi-diddly-ho there...'
*
* _.trunc('hi-diddly-ho there, neighborino', {
* 'omission': ' [...]'
@@ -42745,7 +44905,7 @@ return jQuery;
* @static
* @memberOf _
* @category Utility
- * @param {*} func The function to attempt.
+ * @param {Function} func The function to attempt.
* @returns {*} Returns the `func` result or error object.
* @example
*
@@ -42758,20 +44918,13 @@ return jQuery;
* elements = [];
* }
*/
- function attempt() {
- var func = arguments[0],
- length = arguments.length,
- args = Array(length ? (length - 1) : 0);
-
- while (--length > 0) {
- args[length - 1] = arguments[length];
- }
+ var attempt = restParam(function(func, args) {
try {
return func.apply(undefined, args);
} catch(e) {
return isError(e) ? e : new Error(e);
}
- }
+ });
/**
* Creates a function that invokes `func` with the `this` binding of `thisArg`
@@ -42908,12 +45061,11 @@ return jQuery;
*
* var users = [
* { 'user': 'barney' },
- * { 'user': 'fred' },
- * { 'user': 'pebbles' }
+ * { 'user': 'fred' }
* ];
*
* _.find(users, _.matchesProperty('user', 'fred'));
- * // => { 'user': 'fred', 'age': 40 }
+ * // => { 'user': 'fred' }
*/
function matchesProperty(key, value) {
return baseMatchesProperty(key + '', baseClone(value, true));
@@ -42924,6 +45076,9 @@ return jQuery;
* destination object. If `object` is a function then methods are added to
* its prototype as well.
*
+ * **Note:** Use `_.runInContext` to create a pristine `lodash` function
+ * for mixins to avoid conflicts caused by modifying the original.
+ *
* @static
* @memberOf _
* @category Utility
@@ -42941,7 +45096,7 @@ return jQuery;
* });
* }
*
- * // use `_.runInContext` to avoid potential conflicts (esp. in Node.js)
+ * // use `_.runInContext` to avoid conflicts (esp. in Node.js)
* var _ = require('lodash').runInContext();
*
* _.mixin({ 'vowels': vowels });
@@ -42991,12 +45146,10 @@ return jQuery;
return function() {
var chainAll = this.__chain__;
if (chain || chainAll) {
- var result = object(this.__wrapped__);
- (result.__actions__ = arrayCopy(this.__actions__)).push({
- 'func': func,
- 'args': arguments,
- 'thisArg': object
- });
+ var result = object(this.__wrapped__),
+ actions = result.__actions__ = arrayCopy(this.__actions__);
+
+ actions.push({ 'func': func, 'args': arguments, 'thisArg': object });
result.__chain__ = chainAll;
return result;
}
@@ -43063,7 +45216,7 @@ return jQuery;
* var getName = _.property('user');
*
* _.map(users, getName);
- * // => ['fred', barney']
+ * // => ['fred', 'barney']
*
* _.pluck(_.sortBy(users, getName), 'user');
* // => ['barney', 'fred']
@@ -43073,7 +45226,7 @@ return jQuery;
}
/**
- * The inverse of `_.property`; this method creates a function which returns
+ * The opposite of `_.property`; this method creates a function which returns
* the property value of a given key on `object`.
*
* @static
@@ -43251,16 +45404,16 @@ return jQuery;
* `-Infinity` is returned. If an iteratee function is provided it is invoked
* for each value in `collection` to generate the criterion by which the value
* is ranked. The `iteratee` is bound to `thisArg` and invoked with three
- * arguments; (value, index, collection).
+ * arguments: (value, index, collection).
*
- * If a property name is provided for `predicate` the created `_.property`
+ * If a property name is provided for `iteratee` the created `_.property`
* style callback returns the property value of the given element.
*
* If a value is also provided for `thisArg` the created `_.matchesProperty`
* style callback returns `true` for elements that have a matching property
* value, else `false`.
*
- * If an object is provided for `predicate` the created `_.matches` style
+ * If an object is provided for `iteratee` the created `_.matches` style
* callback returns `true` for elements that have the properties of the given
* object, else `false`.
*
@@ -43287,11 +45440,11 @@ return jQuery;
* _.max(users, function(chr) {
* return chr.age;
* });
- * // => { 'user': 'fred', 'age': 40 };
+ * // => { 'user': 'fred', 'age': 40 }
*
* // using the `_.property` callback shorthand
* _.max(users, 'age');
- * // => { 'user': 'fred', 'age': 40 };
+ * // => { 'user': 'fred', 'age': 40 }
*/
var max = createExtremum(arrayMax);
@@ -43300,16 +45453,16 @@ return jQuery;
* `Infinity` is returned. If an iteratee function is provided it is invoked
* for each value in `collection` to generate the criterion by which the value
* is ranked. The `iteratee` is bound to `thisArg` and invoked with three
- * arguments; (value, index, collection).
+ * arguments: (value, index, collection).
*
- * If a property name is provided for `predicate` the created `_.property`
+ * If a property name is provided for `iteratee` the created `_.property`
* style callback returns the property value of the given element.
*
* If a value is also provided for `thisArg` the created `_.matchesProperty`
* style callback returns `true` for elements that have a matching property
* value, else `false`.
*
- * If an object is provided for `predicate` the created `_.matches` style
+ * If an object is provided for `iteratee` the created `_.matches` style
* callback returns `true` for elements that have the properties of the given
* object, else `false`.
*
@@ -43336,11 +45489,11 @@ return jQuery;
* _.min(users, function(chr) {
* return chr.age;
* });
- * // => { 'user': 'barney', 'age': 36 };
+ * // => { 'user': 'barney', 'age': 36 }
*
* // using the `_.property` callback shorthand
* _.min(users, 'age');
- * // => { 'user': 'barney', 'age': 36 };
+ * // => { 'user': 'barney', 'age': 36 }
*/
var min = createExtremum(arrayMin, true);
@@ -43351,26 +45504,45 @@ return jQuery;
* @memberOf _
* @category Math
* @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function|Object|string} [iteratee] The function invoked per iteration.
+ * @param {*} [thisArg] The `this` binding of `iteratee`.
* @returns {number} Returns the sum.
* @example
*
- * _.sum([4, 6, 2]);
- * // => 12
+ * _.sum([4, 6]);
+ * // => 10
+ *
+ * _.sum({ 'a': 4, 'b': 6 });
+ * // => 10
*
- * _.sum({ 'a': 4, 'b': 6, 'c': 2 });
- * // => 12
+ * var objects = [
+ * { 'n': 4 },
+ * { 'n': 6 }
+ * ];
+ *
+ * _.sum(objects, function(object) {
+ * return object.n;
+ * });
+ * // => 10
+ *
+ * // using the `_.property` callback shorthand
+ * _.sum(objects, 'n');
+ * // => 10
*/
- function sum(collection) {
- if (!isArray(collection)) {
- collection = toIterable(collection);
+ function sum(collection, iteratee, thisArg) {
+ if (thisArg && isIterateeCall(collection, iteratee, thisArg)) {
+ iteratee = null;
}
- var length = collection.length,
- result = 0;
+ var func = getCallback(),
+ noIteratee = iteratee == null;
- while (length--) {
- result += +collection[length] || 0;
+ if (!(func === baseCallback && noIteratee)) {
+ noIteratee = false;
+ iteratee = func(iteratee, thisArg, 3);
}
- return result;
+ return noIteratee
+ ? arraySum(isArray(collection) ? collection : toIterable(collection))
+ : baseSum(collection, iteratee);
}
/*------------------------------------------------------------------------*/
@@ -43469,6 +45641,7 @@ return jQuery;
lodash.reject = reject;
lodash.remove = remove;
lodash.rest = rest;
+ lodash.restParam = restParam;
lodash.shuffle = shuffle;
lodash.slice = slice;
lodash.sortBy = sortBy;
@@ -43760,8 +45933,11 @@ return jQuery;
// Add `LazyWrapper` methods to `lodash.prototype`.
baseForOwn(LazyWrapper.prototype, function(func, methodName) {
- var lodashFunc = lodash[methodName],
- checkIteratee = /^(?:filter|map|reject)|While$/.test(methodName),
+ var lodashFunc = lodash[methodName];
+ if (!lodashFunc) {
+ return;
+ }
+ var checkIteratee = /^(?:filter|map|reject)|While$/.test(methodName),
retUnwrapped = /^(?:first|last)$/.test(methodName);
lodash.prototype[methodName] = function() {
@@ -43820,6 +45996,19 @@ return jQuery;
};
});
+ // Map minified function names to their real names.
+ baseForOwn(LazyWrapper.prototype, function(func, methodName) {
+ var lodashFunc = lodash[methodName];
+ if (lodashFunc) {
+ var key = lodashFunc.name,
+ names = realNames[key] || (realNames[key] = []);
+
+ names.push({ 'name': methodName, 'func': lodashFunc });
+ }
+ });
+
+ realNames[createHybridWrapper(null, BIND_KEY_FLAG).name] = [{ 'name': 'wrapper', 'func': null }];
+
// Add functions to the lazy wrapper.
LazyWrapper.prototype.clone = lazyClone;
LazyWrapper.prototype.reverse = lazyReverse;
@@ -43893,13 +46082,13 @@ exports.HashLocation = require("./locations/HashLocation");
exports.HistoryLocation = require("./locations/HistoryLocation");
exports.RefreshLocation = require("./locations/RefreshLocation");
exports.StaticLocation = require("./locations/StaticLocation");
+exports.TestLocation = require("./locations/TestLocation");
exports.ImitateBrowserBehavior = require("./behaviors/ImitateBrowserBehavior");
exports.ScrollToTopBehavior = require("./behaviors/ScrollToTopBehavior");
exports.History = require("./History");
exports.Navigation = require("./Navigation");
-exports.RouteHandlerMixin = require("./RouteHandlerMixin");
exports.State = require("./State");
exports.createRoute = require("./Route").createRoute;
@@ -43909,10 +46098,10 @@ exports.createRedirect = require("./Route").createRedirect;
exports.createRoutesFromReactChildren = require("./createRoutesFromReactChildren");
exports.create = require("./createRouter");
exports.run = require("./runRouter");
-},{"./History":6,"./Navigation":8,"./Route":13,"./RouteHandlerMixin":14,"./State":16,"./behaviors/ImitateBrowserBehavior":20,"./behaviors/ScrollToTopBehavior":21,"./components/DefaultRoute":22,"./components/Link":23,"./components/NotFoundRoute":24,"./components/Redirect":25,"./components/Route":26,"./components/RouteHandler":27,"./createRouter":28,"./createRoutesFromReactChildren":29,"./locations/HashLocation":32,"./locations/HistoryLocation":33,"./locations/RefreshLocation":34,"./locations/StaticLocation":35,"./runRouter":36}],"react/addons":[function(require,module,exports){
+},{"./History":10,"./Navigation":12,"./Route":16,"./State":18,"./behaviors/ImitateBrowserBehavior":21,"./behaviors/ScrollToTopBehavior":22,"./components/DefaultRoute":24,"./components/Link":25,"./components/NotFoundRoute":26,"./components/Redirect":27,"./components/Route":28,"./components/RouteHandler":29,"./createRouter":30,"./createRoutesFromReactChildren":31,"./locations/HashLocation":34,"./locations/HistoryLocation":35,"./locations/RefreshLocation":36,"./locations/StaticLocation":37,"./locations/TestLocation":38,"./runRouter":39}],"react/addons":[function(require,module,exports){
module.exports = require('./lib/ReactWithAddons');
-},{"./lib/ReactWithAddons":133}],"react":[function(require,module,exports){
+},{"./lib/ReactWithAddons":141}],"react":[function(require,module,exports){
module.exports = require('./lib/React');
-},{"./lib/React":72}]},{},["jquery","lodash","react","react-router","flux","react/addons"]);
+},{"./lib/React":71}]},{},["flux","jquery","lodash","react","react-router","react/addons"]);