add first release of viz

This commit is contained in:
Brian Picciano 2017-06-07 20:13:35 -06:00
parent 7d71d06fda
commit 656f2f0498
114 changed files with 109518 additions and 0 deletions

427
viz/css/normalize.css vendored Normal file
View File

@ -0,0 +1,427 @@
/*! normalize.css v3.0.2 | MIT License | git.io/normalize */
/**
* 1. Set default font family to sans-serif.
* 2. Prevent iOS text size adjust after orientation change, without disabling
* user zoom.
*/
html {
font-family: sans-serif; /* 1 */
-ms-text-size-adjust: 100%; /* 2 */
-webkit-text-size-adjust: 100%; /* 2 */
}
/**
* Remove default margin.
*/
body {
margin: 0;
}
/* HTML5 display definitions
========================================================================== */
/**
* Correct `block` display not defined for any HTML5 element in IE 8/9.
* Correct `block` display not defined for `details` or `summary` in IE 10/11
* and Firefox.
* Correct `block` display not defined for `main` in IE 11.
*/
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
main,
menu,
nav,
section,
summary {
display: block;
}
/**
* 1. Correct `inline-block` display not defined in IE 8/9.
* 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
*/
audio,
canvas,
progress,
video {
display: inline-block; /* 1 */
vertical-align: baseline; /* 2 */
}
/**
* Prevent modern browsers from displaying `audio` without controls.
* Remove excess height in iOS 5 devices.
*/
audio:not([controls]) {
display: none;
height: 0;
}
/**
* Address `[hidden]` styling not present in IE 8/9/10.
* Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22.
*/
[hidden],
template {
display: none;
}
/* Links
========================================================================== */
/**
* Remove the gray background color from active links in IE 10.
*/
a {
background-color: transparent;
}
/**
* Improve readability when focused and also mouse hovered in all browsers.
*/
a:active,
a:hover {
outline: 0;
}
/* Text-level semantics
========================================================================== */
/**
* Address styling not present in IE 8/9/10/11, Safari, and Chrome.
*/
abbr[title] {
border-bottom: 1px dotted;
}
/**
* Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
*/
b,
strong {
font-weight: bold;
}
/**
* Address styling not present in Safari and Chrome.
*/
dfn {
font-style: italic;
}
/**
* Address variable `h1` font-size and margin within `section` and `article`
* contexts in Firefox 4+, Safari, and Chrome.
*/
h1 {
font-size: 2em;
margin: 0.67em 0;
}
/**
* Address styling not present in IE 8/9.
*/
mark {
background: #ff0;
color: #000;
}
/**
* Address inconsistent and variable font size in all browsers.
*/
small {
font-size: 80%;
}
/**
* Prevent `sub` and `sup` affecting `line-height` in all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sup {
top: -0.5em;
}
sub {
bottom: -0.25em;
}
/* Embedded content
========================================================================== */
/**
* Remove border when inside `a` element in IE 8/9/10.
*/
img {
border: 0;
}
/**
* Correct overflow not hidden in IE 9/10/11.
*/
svg:not(:root) {
overflow: hidden;
}
/* Grouping content
========================================================================== */
/**
* Address margin not present in IE 8/9 and Safari.
*/
figure {
margin: 1em 40px;
}
/**
* Address differences between Firefox and other browsers.
*/
hr {
-moz-box-sizing: content-box;
box-sizing: content-box;
height: 0;
}
/**
* Contain overflow in all browsers.
*/
pre {
overflow: auto;
}
/**
* Address odd `em`-unit font size rendering in all browsers.
*/
code,
kbd,
pre,
samp {
font-family: monospace, monospace;
font-size: 1em;
}
/* Forms
========================================================================== */
/**
* Known limitation: by default, Chrome and Safari on OS X allow very limited
* styling of `select`, unless a `border` property is set.
*/
/**
* 1. Correct color not being inherited.
* Known issue: affects color of disabled elements.
* 2. Correct font properties not being inherited.
* 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
*/
button,
input,
optgroup,
select,
textarea {
color: inherit; /* 1 */
font: inherit; /* 2 */
margin: 0; /* 3 */
}
/**
* Address `overflow` set to `hidden` in IE 8/9/10/11.
*/
button {
overflow: visible;
}
/**
* Address inconsistent `text-transform` inheritance for `button` and `select`.
* All other form control elements do not inherit `text-transform` values.
* Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
* Correct `select` style inheritance in Firefox.
*/
button,
select {
text-transform: none;
}
/**
* 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
* and `video` controls.
* 2. Correct inability to style clickable `input` types in iOS.
* 3. Improve usability and consistency of cursor style between image-type
* `input` and others.
*/
button,
html input[type="button"], /* 1 */
input[type="reset"],
input[type="submit"] {
-webkit-appearance: button; /* 2 */
cursor: pointer; /* 3 */
}
/**
* Re-set default cursor for disabled elements.
*/
button[disabled],
html input[disabled] {
cursor: default;
}
/**
* Remove inner padding and border in Firefox 4+.
*/
button::-moz-focus-inner,
input::-moz-focus-inner {
border: 0;
padding: 0;
}
/**
* Address Firefox 4+ setting `line-height` on `input` using `!important` in
* the UA stylesheet.
*/
input {
line-height: normal;
}
/**
* It's recommended that you don't attempt to style these elements.
* Firefox's implementation doesn't respect box-sizing, padding, or width.
*
* 1. Address box sizing set to `content-box` in IE 8/9/10.
* 2. Remove excess padding in IE 8/9/10.
*/
input[type="checkbox"],
input[type="radio"] {
box-sizing: border-box; /* 1 */
padding: 0; /* 2 */
}
/**
* Fix the cursor style for Chrome's increment/decrement buttons. For certain
* `font-size` values of the `input`, it causes the cursor style of the
* decrement button to change from `default` to `text`.
*/
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
height: auto;
}
/**
* 1. Address `appearance` set to `searchfield` in Safari and Chrome.
* 2. Address `box-sizing` set to `border-box` in Safari and Chrome
* (include `-moz` to future-proof).
*/
input[type="search"] {
-webkit-appearance: textfield; /* 1 */
-moz-box-sizing: content-box;
-webkit-box-sizing: content-box; /* 2 */
box-sizing: content-box;
}
/**
* Remove inner padding and search cancel button in Safari and Chrome on OS X.
* Safari (but not Chrome) clips the cancel button when the search input has
* padding (and `textfield` appearance).
*/
input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
/**
* Define consistent border, margin, and padding.
*/
fieldset {
border: 1px solid #c0c0c0;
margin: 0 2px;
padding: 0.35em 0.625em 0.75em;
}
/**
* 1. Correct `color` not being inherited in IE 8/9/10/11.
* 2. Remove padding so people aren't caught out if they zero out fieldsets.
*/
legend {
border: 0; /* 1 */
padding: 0; /* 2 */
}
/**
* Remove default vertical scrollbar in IE 8/9/10/11.
*/
textarea {
overflow: auto;
}
/**
* Don't inherit the `font-weight` (applied by a rule above).
* NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
*/
optgroup {
font-weight: bold;
}
/* Tables
========================================================================== */
/**
* Remove most spacing between table cells.
*/
table {
border-collapse: collapse;
border-spacing: 0;
}
td,
th {
padding: 0;
}

418
viz/css/skeleton.css vendored Normal file
View File

@ -0,0 +1,418 @@
/*
* Skeleton V2.0.4
* Copyright 2014, Dave Gamache
* www.getskeleton.com
* Free to use under the MIT license.
* http://www.opensource.org/licenses/mit-license.php
* 12/29/2014
*/
/* Table of contents
- Grid
- Base Styles
- Typography
- Links
- Buttons
- Forms
- Lists
- Code
- Tables
- Spacing
- Utilities
- Clearing
- Media Queries
*/
/* Grid
*/
.container {
position: relative;
width: 100%;
max-width: 960px;
margin: 0 auto;
padding: 0 20px;
box-sizing: border-box; }
.column,
.columns {
width: 100%;
float: left;
box-sizing: border-box; }
/* For devices larger than 400px */
@media (min-width: 400px) {
.container {
width: 85%;
padding: 0; }
}
/* For devices larger than 550px */
@media (min-width: 550px) {
.container {
width: 80%; }
.column,
.columns {
margin-left: 4%; }
.column:first-child,
.columns:first-child {
margin-left: 0; }
.one.column,
.one.columns { width: 4.66666666667%; }
.two.columns { width: 13.3333333333%; }
.three.columns { width: 22%; }
.four.columns { width: 30.6666666667%; }
.five.columns { width: 39.3333333333%; }
.six.columns { width: 48%; }
.seven.columns { width: 56.6666666667%; }
.eight.columns { width: 65.3333333333%; }
.nine.columns { width: 74.0%; }
.ten.columns { width: 82.6666666667%; }
.eleven.columns { width: 91.3333333333%; }
.twelve.columns { width: 100%; margin-left: 0; }
.one-third.column { width: 30.6666666667%; }
.two-thirds.column { width: 65.3333333333%; }
.one-half.column { width: 48%; }
/* Offsets */
.offset-by-one.column,
.offset-by-one.columns { margin-left: 8.66666666667%; }
.offset-by-two.column,
.offset-by-two.columns { margin-left: 17.3333333333%; }
.offset-by-three.column,
.offset-by-three.columns { margin-left: 26%; }
.offset-by-four.column,
.offset-by-four.columns { margin-left: 34.6666666667%; }
.offset-by-five.column,
.offset-by-five.columns { margin-left: 43.3333333333%; }
.offset-by-six.column,
.offset-by-six.columns { margin-left: 52%; }
.offset-by-seven.column,
.offset-by-seven.columns { margin-left: 60.6666666667%; }
.offset-by-eight.column,
.offset-by-eight.columns { margin-left: 69.3333333333%; }
.offset-by-nine.column,
.offset-by-nine.columns { margin-left: 78.0%; }
.offset-by-ten.column,
.offset-by-ten.columns { margin-left: 86.6666666667%; }
.offset-by-eleven.column,
.offset-by-eleven.columns { margin-left: 95.3333333333%; }
.offset-by-one-third.column,
.offset-by-one-third.columns { margin-left: 34.6666666667%; }
.offset-by-two-thirds.column,
.offset-by-two-thirds.columns { margin-left: 69.3333333333%; }
.offset-by-one-half.column,
.offset-by-one-half.columns { margin-left: 52%; }
}
/* Base Styles
*/
/* NOTE
html is set to 62.5% so that all the REM measurements throughout Skeleton
are based on 10px sizing. So basically 1.5rem = 15px :) */
html {
font-size: 62.5%; }
body {
font-size: 1.5em; /* currently ems cause chrome bug misinterpreting rems on body element */
line-height: 1.6;
font-weight: 400;
font-family: "Raleway", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif;
color: #222; }
/* Typography
*/
h1, h2, h3, h4, h5, h6 {
margin-top: 0;
margin-bottom: 2rem;
font-weight: 300; }
h1 { font-size: 4.0rem; line-height: 1.2; letter-spacing: -.1rem;}
h2 { font-size: 3.6rem; line-height: 1.25; letter-spacing: -.1rem; }
h3 { font-size: 3.0rem; line-height: 1.3; letter-spacing: -.1rem; }
h4 { font-size: 2.4rem; line-height: 1.35; letter-spacing: -.08rem; }
h5 { font-size: 1.8rem; line-height: 1.5; letter-spacing: -.05rem; }
h6 { font-size: 1.5rem; line-height: 1.6; letter-spacing: 0; }
/* Larger than phablet */
@media (min-width: 550px) {
h1 { font-size: 5.0rem; }
h2 { font-size: 4.2rem; }
h3 { font-size: 3.6rem; }
h4 { font-size: 3.0rem; }
h5 { font-size: 2.4rem; }
h6 { font-size: 1.5rem; }
}
p {
margin-top: 0; }
/* Links
*/
a {
color: #1EAEDB; }
a:hover {
color: #0FA0CE; }
/* Buttons
*/
.button,
button,
input[type="submit"],
input[type="reset"],
input[type="button"] {
display: inline-block;
height: 38px;
padding: 0 30px;
color: #555;
text-align: center;
font-size: 11px;
font-weight: 600;
line-height: 38px;
letter-spacing: .1rem;
text-transform: uppercase;
text-decoration: none;
white-space: nowrap;
background-color: transparent;
border-radius: 4px;
border: 1px solid #bbb;
cursor: pointer;
box-sizing: border-box; }
.button:hover,
button:hover,
input[type="submit"]:hover,
input[type="reset"]:hover,
input[type="button"]:hover,
.button:focus,
button:focus,
input[type="submit"]:focus,
input[type="reset"]:focus,
input[type="button"]:focus {
color: #333;
border-color: #888;
outline: 0; }
.button.button-primary,
button.button-primary,
input[type="submit"].button-primary,
input[type="reset"].button-primary,
input[type="button"].button-primary {
color: #FFF;
background-color: #33C3F0;
border-color: #33C3F0; }
.button.button-primary:hover,
button.button-primary:hover,
input[type="submit"].button-primary:hover,
input[type="reset"].button-primary:hover,
input[type="button"].button-primary:hover,
.button.button-primary:focus,
button.button-primary:focus,
input[type="submit"].button-primary:focus,
input[type="reset"].button-primary:focus,
input[type="button"].button-primary:focus {
color: #FFF;
background-color: #1EAEDB;
border-color: #1EAEDB; }
/* Forms
*/
input[type="email"],
input[type="number"],
input[type="search"],
input[type="text"],
input[type="tel"],
input[type="url"],
input[type="password"],
textarea,
select {
height: 38px;
padding: 6px 10px; /* The 6px vertically centers text on FF, ignored by Webkit */
background-color: #fff;
border: 1px solid #D1D1D1;
border-radius: 4px;
box-shadow: none;
box-sizing: border-box; }
/* Removes awkward default styles on some inputs for iOS */
input[type="email"],
input[type="number"],
input[type="search"],
input[type="text"],
input[type="tel"],
input[type="url"],
input[type="password"],
textarea {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none; }
textarea {
min-height: 65px;
padding-top: 6px;
padding-bottom: 6px; }
input[type="email"]:focus,
input[type="number"]:focus,
input[type="search"]:focus,
input[type="text"]:focus,
input[type="tel"]:focus,
input[type="url"]:focus,
input[type="password"]:focus,
textarea:focus,
select:focus {
border: 1px solid #33C3F0;
outline: 0; }
label,
legend {
display: block;
margin-bottom: .5rem;
font-weight: 600; }
fieldset {
padding: 0;
border-width: 0; }
input[type="checkbox"],
input[type="radio"] {
display: inline; }
label > .label-body {
display: inline-block;
margin-left: .5rem;
font-weight: normal; }
/* Lists
*/
ul {
list-style: circle inside; }
ol {
list-style: decimal inside; }
ol, ul {
padding-left: 0;
margin-top: 0; }
ul ul,
ul ol,
ol ol,
ol ul {
margin: 1.5rem 0 1.5rem 3rem;
font-size: 90%; }
li {
margin-bottom: 1rem; }
/* Code
*/
code {
padding: .2rem .5rem;
margin: 0 .2rem;
font-size: 90%;
white-space: nowrap;
background: #F1F1F1;
border: 1px solid #E1E1E1;
border-radius: 4px; }
pre > code {
display: block;
padding: 1rem 1.5rem;
white-space: pre; }
/* Tables
*/
th,
td {
padding: 12px 15px;
text-align: left;
border-bottom: 1px solid #E1E1E1; }
th:first-child,
td:first-child {
padding-left: 0; }
th:last-child,
td:last-child {
padding-right: 0; }
/* Spacing
*/
button,
.button {
margin-bottom: 1rem; }
input,
textarea,
select,
fieldset {
margin-bottom: 1.5rem; }
pre,
blockquote,
dl,
figure,
table,
p,
ul,
ol,
form {
margin-bottom: 2.5rem; }
/* Utilities
*/
.u-full-width {
width: 100%;
box-sizing: border-box; }
.u-max-full-width {
max-width: 100%;
box-sizing: border-box; }
.u-pull-right {
float: right; }
.u-pull-left {
float: left; }
/* Misc
*/
hr {
margin-top: 3rem;
margin-bottom: 3.5rem;
border-width: 0;
border-top: 1px solid #E1E1E1; }
/* Clearing
*/
/* Self Clearing Goodness */
.container:after,
.row:after,
.u-cf {
content: "";
display: table;
clear: both; }
/* Media Queries
*/
/*
Note: The best way to structure the use of media queries is to create the queries
near the relevant code. For example, if you wanted to change the styles for buttons
on small devices, paste the mobile query code up in the buttons section and style it
there.
*/
/* Larger than mobile */
@media (min-width: 400px) {}
/* Larger than phablet (also point when grid becomes active) */
@media (min-width: 550px) {}
/* Larger than tablet */
@media (min-width: 750px) {}
/* Larger than desktop */
@media (min-width: 1000px) {}
/* Larger than Desktop HD */
@media (min-width: 1200px) {}

57
viz/index.html Normal file
View File

@ -0,0 +1,57 @@
<!DOCTYPE html>
<html>
<head>
<title>viz</title>
<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro" rel="stylesheet">
<link rel="stylesheet" href="css/normalize.css">
<link rel="stylesheet" href="css/skeleton.css">
<style type="text/css">
* {
font-family: 'Source Code Pro', monospace;
}
#viz {
//border: 1px solid grey;
}
</style>
<script src="js/main.js"></script>
</head>
<body>
<p align="center" style="margin-top:1%;"> <canvas id="viz"></canvas> </p>
<div class="container">
<div class="row" style="margin-top: 10%; margin-bottom:1%;">
<div class="twelve column">
<h1>1</h1>
<p>
Using clojurescript and
<a href="https://quil.info">quil</a>.
</p>
<p>
Any point can only be occupied by a single dot.</br>
A line cannot intersect another line.</br>
Every tick a point may spawn 0 or more adjacent points.
</p>
<p>
In this implementation adjacent points are chosen randomly,
both in number and direction. The result is somewhere between
Conway's Game of Life and white noise.
</p>
<p>
This implementation is hella slow, most time is spent drawing
the lines. This was optimized so that adjacent parallel lines
are drawn in a single operation, but that produced negligible
benefit.
</p>
<!-- (#_#) -->
<pre>
git clone https://github.com/mediocregopher/viz.git
git checkout 7e9e898466ce1663ef295deef0e3d0ac86a36de0
</pre>
</div>
</div>
</body>
</html>

5
viz/js/main.js Normal file
View File

@ -0,0 +1,5 @@
var CLOSURE_UNCOMPILED_DEFINES = null;
if(typeof goog == "undefined") document.write('<script src="out/goog/base.js"></script>');
document.write('<script src="out/cljs_deps.js"></script>');
document.write('<script>if (typeof goog == "undefined") console.warn("ClojureScript could not load :main, did you forget to specify :asset-path?");</script>');
document.write('<script>goog.require("viz.core");</script>');

10762
viz/out/cljs/core.cljs Normal file

File diff suppressed because it is too large Load Diff

35801
viz/out/cljs/core.js Normal file

File diff suppressed because it is too large Load Diff

1
viz/out/cljs/core.js.map Normal file

File diff suppressed because one or more lines are too long

17
viz/out/cljs_deps.js Normal file
View File

@ -0,0 +1,17 @@
goog.addDependency("base.js", ['goog'], []);
goog.addDependency("../cljs/core.js", ['cljs.core'], ['goog.string', 'goog.object', 'goog.math.Integer', 'goog.string.StringBuffer', 'goog.array', 'goog.math.Long']);
goog.addDependency("../viz/forest.js", ['viz.forest'], ['cljs.core']);
goog.addDependency("../processing.js", ['org.processingjs.Processing'], []);
goog.addDependency("../quil/middlewares/deprecated_options.js", ['quil.middlewares.deprecated_options'], ['cljs.core']);
goog.addDependency("../clojure/string.js", ['clojure.string'], ['goog.string', 'cljs.core', 'goog.string.StringBuffer']);
goog.addDependency("../quil/util.js", ['quil.util'], ['cljs.core', 'clojure.string']);
goog.addDependency("../quil/sketch.js", ['quil.sketch'], ['goog.dom', 'cljs.core', 'quil.middlewares.deprecated_options', 'goog.events.EventType', 'goog.events', 'quil.util']);
goog.addDependency("../quil/core.js", ['quil.core'], ['org.processingjs.Processing', 'quil.sketch', 'cljs.core', 'clojure.string', 'quil.util']);
goog.addDependency("../quil/middlewares/navigation_3d.js", ['quil.middlewares.navigation_3d'], ['cljs.core', 'quil.core']);
goog.addDependency("../quil/middlewares/navigation_2d.js", ['quil.middlewares.navigation_2d'], ['cljs.core', 'quil.core']);
goog.addDependency("../quil/middlewares/fun_mode.js", ['quil.middlewares.fun_mode'], ['cljs.core', 'quil.core']);
goog.addDependency("../quil/middleware.js", ['quil.middleware'], ['cljs.core', 'quil.middlewares.navigation_3d', 'quil.middlewares.navigation_2d', 'quil.middlewares.fun_mode']);
goog.addDependency("../clojure/set.js", ['clojure.set'], ['cljs.core']);
goog.addDependency("../viz/grid.js", ['viz.grid'], ['cljs.core']);
goog.addDependency("../viz/ghost.js", ['viz.ghost'], ['cljs.core', 'viz.forest', 'clojure.set', 'viz.grid']);
goog.addDependency("../viz/core.js", ['viz.core'], ['goog.string', 'cljs.core', 'viz.forest', 'quil.core', 'quil.middleware', 'goog.string.format', 'viz.ghost', 'viz.grid']);

161
viz/out/clojure/set.cljs Normal file
View File

@ -0,0 +1,161 @@
; Copyright (c) Rich Hickey. All rights reserved.
; The use and distribution terms for this software are covered by the
; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
; which can be found in the file epl-v10.html at the root of this distribution.
; By using this software in any fashion, you are agreeing to be bound by
; the terms of this license.
; You must not remove this notice, or any other, from this software.
(ns ^{:doc "Set operations such as union/intersection."
:author "Rich Hickey"}
clojure.set)
(defn- bubble-max-key [k coll]
"Move a maximal element of coll according to fn k (which returns a number)
to the front of coll."
(let [max (apply max-key k coll)]
(cons max (remove #(identical? max %) coll))))
(defn union
"Return a set that is the union of the input sets"
([] #{})
([s1] s1)
([s1 s2]
(if (< (count s1) (count s2))
(reduce conj s2 s1)
(reduce conj s1 s2)))
([s1 s2 & sets]
(let [bubbled-sets (bubble-max-key count (conj sets s2 s1))]
(reduce into (first bubbled-sets) (rest bubbled-sets)))))
(defn intersection
"Return a set that is the intersection of the input sets"
([s1] s1)
([s1 s2]
(if (< (count s2) (count s1))
(recur s2 s1)
(reduce (fn [result item]
(if (contains? s2 item)
result
(disj result item)))
s1 s1)))
([s1 s2 & sets]
(let [bubbled-sets (bubble-max-key #(- (count %)) (conj sets s2 s1))]
(reduce intersection (first bubbled-sets) (rest bubbled-sets)))))
(defn difference
"Return a set that is the first set without elements of the remaining sets"
([s1] s1)
([s1 s2]
(if (< (count s1) (count s2))
(reduce (fn [result item]
(if (contains? s2 item)
(disj result item)
result))
s1 s1)
(reduce disj s1 s2)))
([s1 s2 & sets]
(reduce difference s1 (conj sets s2))))
(defn select
"Returns a set of the elements for which pred is true"
[pred xset]
(reduce (fn [s k] (if (pred k) s (disj s k)))
xset xset))
(defn project
"Returns a rel of the elements of xrel with only the keys in ks"
[xrel ks]
(set (map #(select-keys % ks) xrel)))
(defn rename-keys
"Returns the map with the keys in kmap renamed to the vals in kmap"
[map kmap]
(reduce
(fn [m [old new]]
(if (contains? map old)
(assoc m new (get map old))
m))
(apply dissoc map (keys kmap)) kmap))
(defn rename
"Returns a rel of the maps in xrel with the keys in kmap renamed to the vals in kmap"
[xrel kmap]
(set (map #(rename-keys % kmap) xrel)))
(defn index
"Returns a map of the distinct values of ks in the xrel mapped to a
set of the maps in xrel with the corresponding values of ks."
[xrel ks]
(reduce
(fn [m x]
(let [ik (select-keys x ks)]
(assoc m ik (conj (get m ik #{}) x))))
{} xrel))
(defn map-invert
"Returns the map with the vals mapped to the keys."
[m] (reduce (fn [m [k v]] (assoc m v k)) {} m))
(defn join
"When passed 2 rels, returns the rel corresponding to the natural
join. When passed an additional keymap, joins on the corresponding
keys."
([xrel yrel] ;natural join
(if (and (seq xrel) (seq yrel))
(let [ks (intersection (set (keys (first xrel))) (set (keys (first yrel))))
[r s] (if (<= (count xrel) (count yrel))
[xrel yrel]
[yrel xrel])
idx (index r ks)]
(reduce (fn [ret x]
(let [found (idx (select-keys x ks))]
(if found
(reduce #(conj %1 (merge %2 x)) ret found)
ret)))
#{} s))
#{}))
([xrel yrel km] ;arbitrary key mapping
(let [[r s k] (if (<= (count xrel) (count yrel))
[xrel yrel (map-invert km)]
[yrel xrel km])
idx (index r (vals k))]
(reduce (fn [ret x]
(let [found (idx (rename-keys (select-keys x (keys k)) k))]
(if found
(reduce #(conj %1 (merge %2 x)) ret found)
ret)))
#{} s))))
(defn subset?
"Is set1 a subset of set2?"
[set1 set2]
(and (<= (count set1) (count set2))
(every? #(contains? set2 %) set1)))
(defn superset?
"Is set1 a superset of set2?"
[set1 set2]
(and (>= (count set1) (count set2))
(every? #(contains? set1 %) set2)))
(comment
(refer 'set)
(def xs #{{:a 11 :b 1 :c 1 :d 4}
{:a 2 :b 12 :c 2 :d 6}
{:a 3 :b 3 :c 3 :d 8 :f 42}})
(def ys #{{:a 11 :b 11 :c 11 :e 5}
{:a 12 :b 11 :c 12 :e 3}
{:a 3 :b 3 :c 3 :e 7 }})
(join xs ys)
(join xs (rename ys {:b :yb :c :yc}) {:a :a})
(union #{:a :b :c} #{:c :d :e })
(difference #{:a :b :c} #{:c :d :e})
(intersection #{:a :b :c} #{:c :d :e})
(index ys [:b]))

File diff suppressed because one or more lines are too long

400
viz/out/clojure/set.js Normal file
View File

@ -0,0 +1,400 @@
// Compiled by ClojureScript 1.9.473 {}
goog.provide('clojure.set');
goog.require('cljs.core');
clojure.set.bubble_max_key = (function clojure$set$bubble_max_key(k,coll){
var max = cljs.core.apply.call(null,cljs.core.max_key,k,coll);
return cljs.core.cons.call(null,max,cljs.core.remove.call(null,((function (max){
return (function (p1__7981_SHARP_){
return (max === p1__7981_SHARP_);
});})(max))
,coll));
});
/**
* Return a set that is the union of the input sets
*/
clojure.set.union = (function clojure$set$union(var_args){
var args7982 = [];
var len__7927__auto___7988 = arguments.length;
var i__7928__auto___7989 = (0);
while(true){
if((i__7928__auto___7989 < len__7927__auto___7988)){
args7982.push((arguments[i__7928__auto___7989]));
var G__7990 = (i__7928__auto___7989 + (1));
i__7928__auto___7989 = G__7990;
continue;
} else {
}
break;
}
var G__7987 = args7982.length;
switch (G__7987) {
case 0:
return clojure.set.union.cljs$core$IFn$_invoke$arity$0();
break;
case 1:
return clojure.set.union.cljs$core$IFn$_invoke$arity$1((arguments[(0)]));
break;
case 2:
return clojure.set.union.cljs$core$IFn$_invoke$arity$2((arguments[(0)]),(arguments[(1)]));
break;
default:
var argseq__7946__auto__ = (new cljs.core.IndexedSeq(args7982.slice((2)),(0),null));
return clojure.set.union.cljs$core$IFn$_invoke$arity$variadic((arguments[(0)]),(arguments[(1)]),argseq__7946__auto__);
}
});
clojure.set.union.cljs$core$IFn$_invoke$arity$0 = (function (){
return cljs.core.PersistentHashSet.EMPTY;
});
clojure.set.union.cljs$core$IFn$_invoke$arity$1 = (function (s1){
return s1;
});
clojure.set.union.cljs$core$IFn$_invoke$arity$2 = (function (s1,s2){
if((cljs.core.count.call(null,s1) < cljs.core.count.call(null,s2))){
return cljs.core.reduce.call(null,cljs.core.conj,s2,s1);
} else {
return cljs.core.reduce.call(null,cljs.core.conj,s1,s2);
}
});
clojure.set.union.cljs$core$IFn$_invoke$arity$variadic = (function (s1,s2,sets){
var bubbled_sets = clojure.set.bubble_max_key.call(null,cljs.core.count,cljs.core.conj.call(null,sets,s2,s1));
return cljs.core.reduce.call(null,cljs.core.into,cljs.core.first.call(null,bubbled_sets),cljs.core.rest.call(null,bubbled_sets));
});
clojure.set.union.cljs$lang$applyTo = (function (seq7983){
var G__7984 = cljs.core.first.call(null,seq7983);
var seq7983__$1 = cljs.core.next.call(null,seq7983);
var G__7985 = cljs.core.first.call(null,seq7983__$1);
var seq7983__$2 = cljs.core.next.call(null,seq7983__$1);
return clojure.set.union.cljs$core$IFn$_invoke$arity$variadic(G__7984,G__7985,seq7983__$2);
});
clojure.set.union.cljs$lang$maxFixedArity = (2);
/**
* Return a set that is the intersection of the input sets
*/
clojure.set.intersection = (function clojure$set$intersection(var_args){
var args7993 = [];
var len__7927__auto___7999 = arguments.length;
var i__7928__auto___8000 = (0);
while(true){
if((i__7928__auto___8000 < len__7927__auto___7999)){
args7993.push((arguments[i__7928__auto___8000]));
var G__8001 = (i__7928__auto___8000 + (1));
i__7928__auto___8000 = G__8001;
continue;
} else {
}
break;
}
var G__7998 = args7993.length;
switch (G__7998) {
case 1:
return clojure.set.intersection.cljs$core$IFn$_invoke$arity$1((arguments[(0)]));
break;
case 2:
return clojure.set.intersection.cljs$core$IFn$_invoke$arity$2((arguments[(0)]),(arguments[(1)]));
break;
default:
var argseq__7946__auto__ = (new cljs.core.IndexedSeq(args7993.slice((2)),(0),null));
return clojure.set.intersection.cljs$core$IFn$_invoke$arity$variadic((arguments[(0)]),(arguments[(1)]),argseq__7946__auto__);
}
});
clojure.set.intersection.cljs$core$IFn$_invoke$arity$1 = (function (s1){
return s1;
});
clojure.set.intersection.cljs$core$IFn$_invoke$arity$2 = (function (s1,s2){
while(true){
if((cljs.core.count.call(null,s2) < cljs.core.count.call(null,s1))){
var G__8003 = s2;
var G__8004 = s1;
s1 = G__8003;
s2 = G__8004;
continue;
} else {
return cljs.core.reduce.call(null,((function (s1,s2){
return (function (result,item){
if(cljs.core.contains_QMARK_.call(null,s2,item)){
return result;
} else {
return cljs.core.disj.call(null,result,item);
}
});})(s1,s2))
,s1,s1);
}
break;
}
});
clojure.set.intersection.cljs$core$IFn$_invoke$arity$variadic = (function (s1,s2,sets){
var bubbled_sets = clojure.set.bubble_max_key.call(null,(function (p1__7992_SHARP_){
return (- cljs.core.count.call(null,p1__7992_SHARP_));
}),cljs.core.conj.call(null,sets,s2,s1));
return cljs.core.reduce.call(null,clojure.set.intersection,cljs.core.first.call(null,bubbled_sets),cljs.core.rest.call(null,bubbled_sets));
});
clojure.set.intersection.cljs$lang$applyTo = (function (seq7994){
var G__7995 = cljs.core.first.call(null,seq7994);
var seq7994__$1 = cljs.core.next.call(null,seq7994);
var G__7996 = cljs.core.first.call(null,seq7994__$1);
var seq7994__$2 = cljs.core.next.call(null,seq7994__$1);
return clojure.set.intersection.cljs$core$IFn$_invoke$arity$variadic(G__7995,G__7996,seq7994__$2);
});
clojure.set.intersection.cljs$lang$maxFixedArity = (2);
/**
* Return a set that is the first set without elements of the remaining sets
*/
clojure.set.difference = (function clojure$set$difference(var_args){
var args8005 = [];
var len__7927__auto___8011 = arguments.length;
var i__7928__auto___8012 = (0);
while(true){
if((i__7928__auto___8012 < len__7927__auto___8011)){
args8005.push((arguments[i__7928__auto___8012]));
var G__8013 = (i__7928__auto___8012 + (1));
i__7928__auto___8012 = G__8013;
continue;
} else {
}
break;
}
var G__8010 = args8005.length;
switch (G__8010) {
case 1:
return clojure.set.difference.cljs$core$IFn$_invoke$arity$1((arguments[(0)]));
break;
case 2:
return clojure.set.difference.cljs$core$IFn$_invoke$arity$2((arguments[(0)]),(arguments[(1)]));
break;
default:
var argseq__7946__auto__ = (new cljs.core.IndexedSeq(args8005.slice((2)),(0),null));
return clojure.set.difference.cljs$core$IFn$_invoke$arity$variadic((arguments[(0)]),(arguments[(1)]),argseq__7946__auto__);
}
});
clojure.set.difference.cljs$core$IFn$_invoke$arity$1 = (function (s1){
return s1;
});
clojure.set.difference.cljs$core$IFn$_invoke$arity$2 = (function (s1,s2){
if((cljs.core.count.call(null,s1) < cljs.core.count.call(null,s2))){
return cljs.core.reduce.call(null,(function (result,item){
if(cljs.core.contains_QMARK_.call(null,s2,item)){
return cljs.core.disj.call(null,result,item);
} else {
return result;
}
}),s1,s1);
} else {
return cljs.core.reduce.call(null,cljs.core.disj,s1,s2);
}
});
clojure.set.difference.cljs$core$IFn$_invoke$arity$variadic = (function (s1,s2,sets){
return cljs.core.reduce.call(null,clojure.set.difference,s1,cljs.core.conj.call(null,sets,s2));
});
clojure.set.difference.cljs$lang$applyTo = (function (seq8006){
var G__8007 = cljs.core.first.call(null,seq8006);
var seq8006__$1 = cljs.core.next.call(null,seq8006);
var G__8008 = cljs.core.first.call(null,seq8006__$1);
var seq8006__$2 = cljs.core.next.call(null,seq8006__$1);
return clojure.set.difference.cljs$core$IFn$_invoke$arity$variadic(G__8007,G__8008,seq8006__$2);
});
clojure.set.difference.cljs$lang$maxFixedArity = (2);
/**
* Returns a set of the elements for which pred is true
*/
clojure.set.select = (function clojure$set$select(pred,xset){
return cljs.core.reduce.call(null,(function (s,k){
if(cljs.core.truth_(pred.call(null,k))){
return s;
} else {
return cljs.core.disj.call(null,s,k);
}
}),xset,xset);
});
/**
* Returns a rel of the elements of xrel with only the keys in ks
*/
clojure.set.project = (function clojure$set$project(xrel,ks){
return cljs.core.set.call(null,cljs.core.map.call(null,(function (p1__8015_SHARP_){
return cljs.core.select_keys.call(null,p1__8015_SHARP_,ks);
}),xrel));
});
/**
* Returns the map with the keys in kmap renamed to the vals in kmap
*/
clojure.set.rename_keys = (function clojure$set$rename_keys(map,kmap){
return cljs.core.reduce.call(null,(function (m,p__8020){
var vec__8021 = p__8020;
var old = cljs.core.nth.call(null,vec__8021,(0),null);
var new$ = cljs.core.nth.call(null,vec__8021,(1),null);
if(cljs.core.contains_QMARK_.call(null,map,old)){
return cljs.core.assoc.call(null,m,new$,cljs.core.get.call(null,map,old));
} else {
return m;
}
}),cljs.core.apply.call(null,cljs.core.dissoc,map,cljs.core.keys.call(null,kmap)),kmap);
});
/**
* Returns a rel of the maps in xrel with the keys in kmap renamed to the vals in kmap
*/
clojure.set.rename = (function clojure$set$rename(xrel,kmap){
return cljs.core.set.call(null,cljs.core.map.call(null,(function (p1__8024_SHARP_){
return clojure.set.rename_keys.call(null,p1__8024_SHARP_,kmap);
}),xrel));
});
/**
* Returns a map of the distinct values of ks in the xrel mapped to a
* set of the maps in xrel with the corresponding values of ks.
*/
clojure.set.index = (function clojure$set$index(xrel,ks){
return cljs.core.reduce.call(null,(function (m,x){
var ik = cljs.core.select_keys.call(null,x,ks);
return cljs.core.assoc.call(null,m,ik,cljs.core.conj.call(null,cljs.core.get.call(null,m,ik,cljs.core.PersistentHashSet.EMPTY),x));
}),cljs.core.PersistentArrayMap.EMPTY,xrel);
});
/**
* Returns the map with the vals mapped to the keys.
*/
clojure.set.map_invert = (function clojure$set$map_invert(m){
return cljs.core.reduce.call(null,(function (m__$1,p__8029){
var vec__8030 = p__8029;
var k = cljs.core.nth.call(null,vec__8030,(0),null);
var v = cljs.core.nth.call(null,vec__8030,(1),null);
return cljs.core.assoc.call(null,m__$1,v,k);
}),cljs.core.PersistentArrayMap.EMPTY,m);
});
/**
* When passed 2 rels, returns the rel corresponding to the natural
* join. When passed an additional keymap, joins on the corresponding
* keys.
*/
clojure.set.join = (function clojure$set$join(var_args){
var args8037 = [];
var len__7927__auto___8046 = arguments.length;
var i__7928__auto___8047 = (0);
while(true){
if((i__7928__auto___8047 < len__7927__auto___8046)){
args8037.push((arguments[i__7928__auto___8047]));
var G__8048 = (i__7928__auto___8047 + (1));
i__7928__auto___8047 = G__8048;
continue;
} else {
}
break;
}
var G__8039 = args8037.length;
switch (G__8039) {
case 2:
return clojure.set.join.cljs$core$IFn$_invoke$arity$2((arguments[(0)]),(arguments[(1)]));
break;
case 3:
return clojure.set.join.cljs$core$IFn$_invoke$arity$3((arguments[(0)]),(arguments[(1)]),(arguments[(2)]));
break;
default:
throw (new Error([cljs.core.str.cljs$core$IFn$_invoke$arity$1("Invalid arity: "),cljs.core.str.cljs$core$IFn$_invoke$arity$1(args8037.length)].join('')));
}
});
clojure.set.join.cljs$core$IFn$_invoke$arity$2 = (function (xrel,yrel){
if((cljs.core.seq.call(null,xrel)) && (cljs.core.seq.call(null,yrel))){
var ks = clojure.set.intersection.call(null,cljs.core.set.call(null,cljs.core.keys.call(null,cljs.core.first.call(null,xrel))),cljs.core.set.call(null,cljs.core.keys.call(null,cljs.core.first.call(null,yrel))));
var vec__8040 = (((cljs.core.count.call(null,xrel) <= cljs.core.count.call(null,yrel)))?new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, [xrel,yrel], null):new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, [yrel,xrel], null));
var r = cljs.core.nth.call(null,vec__8040,(0),null);
var s = cljs.core.nth.call(null,vec__8040,(1),null);
var idx = clojure.set.index.call(null,r,ks);
return cljs.core.reduce.call(null,((function (ks,vec__8040,r,s,idx){
return (function (ret,x){
var found = idx.call(null,cljs.core.select_keys.call(null,x,ks));
if(cljs.core.truth_(found)){
return cljs.core.reduce.call(null,((function (found,ks,vec__8040,r,s,idx){
return (function (p1__8033_SHARP_,p2__8034_SHARP_){
return cljs.core.conj.call(null,p1__8033_SHARP_,cljs.core.merge.call(null,p2__8034_SHARP_,x));
});})(found,ks,vec__8040,r,s,idx))
,ret,found);
} else {
return ret;
}
});})(ks,vec__8040,r,s,idx))
,cljs.core.PersistentHashSet.EMPTY,s);
} else {
return cljs.core.PersistentHashSet.EMPTY;
}
});
clojure.set.join.cljs$core$IFn$_invoke$arity$3 = (function (xrel,yrel,km){
var vec__8043 = (((cljs.core.count.call(null,xrel) <= cljs.core.count.call(null,yrel)))?new cljs.core.PersistentVector(null, 3, 5, cljs.core.PersistentVector.EMPTY_NODE, [xrel,yrel,clojure.set.map_invert.call(null,km)], null):new cljs.core.PersistentVector(null, 3, 5, cljs.core.PersistentVector.EMPTY_NODE, [yrel,xrel,km], null));
var r = cljs.core.nth.call(null,vec__8043,(0),null);
var s = cljs.core.nth.call(null,vec__8043,(1),null);
var k = cljs.core.nth.call(null,vec__8043,(2),null);
var idx = clojure.set.index.call(null,r,cljs.core.vals.call(null,k));
return cljs.core.reduce.call(null,((function (vec__8043,r,s,k,idx){
return (function (ret,x){
var found = idx.call(null,clojure.set.rename_keys.call(null,cljs.core.select_keys.call(null,x,cljs.core.keys.call(null,k)),k));
if(cljs.core.truth_(found)){
return cljs.core.reduce.call(null,((function (found,vec__8043,r,s,k,idx){
return (function (p1__8035_SHARP_,p2__8036_SHARP_){
return cljs.core.conj.call(null,p1__8035_SHARP_,cljs.core.merge.call(null,p2__8036_SHARP_,x));
});})(found,vec__8043,r,s,k,idx))
,ret,found);
} else {
return ret;
}
});})(vec__8043,r,s,k,idx))
,cljs.core.PersistentHashSet.EMPTY,s);
});
clojure.set.join.cljs$lang$maxFixedArity = 3;
/**
* Is set1 a subset of set2?
*/
clojure.set.subset_QMARK_ = (function clojure$set$subset_QMARK_(set1,set2){
return ((cljs.core.count.call(null,set1) <= cljs.core.count.call(null,set2))) && (cljs.core.every_QMARK_.call(null,(function (p1__8050_SHARP_){
return cljs.core.contains_QMARK_.call(null,set2,p1__8050_SHARP_);
}),set1));
});
/**
* Is set1 a superset of set2?
*/
clojure.set.superset_QMARK_ = (function clojure$set$superset_QMARK_(set1,set2){
return ((cljs.core.count.call(null,set1) >= cljs.core.count.call(null,set2))) && (cljs.core.every_QMARK_.call(null,(function (p1__8051_SHARP_){
return cljs.core.contains_QMARK_.call(null,set1,p1__8051_SHARP_);
}),set2));
});
//# sourceMappingURL=set.js.map

File diff suppressed because one or more lines are too long

258
viz/out/clojure/string.cljs Normal file
View File

@ -0,0 +1,258 @@
; Copyright (c) Rich Hickey. All rights reserved.
; The use and distribution terms for this software are covered by the
; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
; which can be found in the file epl-v10.html at the root of this distribution.
; By using this software in any fashion, you are agreeing to be bound by
; the terms of this license.
; You must not remove this notice, or any other, from this software.
(ns clojure.string
(:refer-clojure :exclude [replace reverse])
(:require [goog.string :as gstring])
(:import [goog.string StringBuffer]))
(defn- seq-reverse
[coll]
(reduce conj () coll))
(def ^:private re-surrogate-pair
(js/RegExp. "([\\uD800-\\uDBFF])([\\uDC00-\\uDFFF])" "g"))
(defn reverse
"Returns s with its characters reversed."
[s]
(-> (.replace s re-surrogate-pair "$2$1")
(.. (split "") (reverse) (join ""))))
(defn- replace-all
[s re replacement]
(.replace s (js/RegExp. (.-source re) "g") replacement))
(defn- replace-with
[f]
(fn [& args]
(let [matches (drop-last 2 args)]
(if (= (count matches) 1)
(f (first matches))
(f (vec matches))))))
(defn replace
"Replaces all instance of match with replacement in s.
match/replacement can be:
string / string
pattern / (string or function of match)."
[s match replacement]
(cond
(string? match)
(.replace s (js/RegExp. (gstring/regExpEscape match) "g") replacement)
(instance? js/RegExp match)
(if (string? replacement)
(replace-all s match replacement)
(replace-all s match (replace-with replacement)))
:else (throw (str "Invalid match arg: " match))))
(defn replace-first
"Replaces the first instance of match with replacement in s.
match/replacement can be:
string / string
pattern / (string or function of match)."
[s match replacement]
(.replace s match replacement))
(defn join
"Returns a string of all elements in coll, as returned by (seq coll),
separated by an optional separator."
([coll]
(loop [sb (StringBuffer.) coll (seq coll)]
(if-not (nil? coll)
(recur (. sb (append (str (first coll)))) (next coll))
(.toString sb))))
([separator coll]
(loop [sb (StringBuffer.) coll (seq coll)]
(if-not (nil? coll)
(do
(. sb (append (str (first coll))))
(let [coll (next coll)]
(when-not (nil? coll)
(. sb (append separator)))
(recur sb coll)))
(.toString sb)))))
(defn upper-case
"Converts string to all upper-case."
[s]
(.toUpperCase s))
(defn lower-case
"Converts string to all lower-case."
[s]
(.toLowerCase s))
(defn capitalize
"Converts first character of the string to upper-case, all other
characters to lower-case."
[s]
(if (< (count s) 2)
(upper-case s)
(str (upper-case (subs s 0 1))
(lower-case (subs s 1)))))
;; The JavaScript split function takes a limit argument but the return
;; value is not the same as the Java split function.
;;
;; Java: (.split "a-b-c" #"-" 2) => ["a" "b-c"]
;; JavaScript: (.split "a-b-c" #"-" 2) => ["a" "b"]
;;
;; For consistency, the three arg version has been implemented to
;; mimic Java's behavior.
(defn- pop-last-while-empty
[v]
(loop [v v]
(if (identical? "" (peek v))
(recur (pop v))
v)))
(defn- discard-trailing-if-needed
[limit v]
(if (and (== 0 limit) (< 1 (count v)))
(pop-last-while-empty v)
v))
(defn- split-with-empty-regex
[s limit]
(if (or (<= limit 0) (>= limit (+ 2 (count s))))
(conj (vec (cons "" (map str (seq s)))) "")
(condp == limit
1 (vector s)
2 (vector "" s)
(let [c (- limit 2)]
(conj (vec (cons "" (subvec (vec (map str (seq s))) 0 c))) (subs s c))))))
(defn split
"Splits string on a regular expression. Optional argument limit is
the maximum number of splits. Not lazy. Returns vector of the splits."
([s re]
(split s re 0))
([s re limit]
(discard-trailing-if-needed limit
(if (identical? "/(?:)/" (str re))
(split-with-empty-regex s limit)
(if (< limit 1)
(vec (.split (str s) re))
(loop [s s
limit limit
parts []]
(if (== 1 limit)
(conj parts s)
(let [m (re-find re s)]
(if-not (nil? m)
(let [index (.indexOf s m)]
(recur (.substring s (+ index (count m)))
(dec limit)
(conj parts (.substring s 0 index))))
(conj parts s))))))))))
(defn split-lines
"Splits s on \n or \r\n."
[s]
(split s #"\n|\r\n"))
(defn trim
"Removes whitespace from both ends of string."
[s]
(gstring/trim s))
(defn triml
"Removes whitespace from the left side of string."
[s]
(gstring/trimLeft s))
(defn trimr
"Removes whitespace from the right side of string."
[s]
(gstring/trimRight s))
(defn trim-newline
"Removes all trailing newline \\n or return \\r characters from
string. Similar to Perl's chomp."
[s]
(loop [index (.-length s)]
(if (zero? index)
""
(let [ch (get s (dec index))]
(if (or (identical? \newline ch)
(identical? \return ch))
(recur (dec index))
(.substring s 0 index))))))
(defn ^boolean blank?
"True is s is nil, empty, or contains only whitespace."
[s]
(gstring/isEmptySafe s))
(defn escape
"Return a new string, using cmap to escape each character ch
from s as follows:
If (cmap ch) is nil, append ch to the new string.
If (cmap ch) is non-nil, append (str (cmap ch)) instead."
[s cmap]
(let [buffer (StringBuffer.)
length (.-length s)]
(loop [index 0]
(if (== length index)
(. buffer (toString))
(let [ch (.charAt s index)
replacement (get cmap ch)]
(if-not (nil? replacement)
(.append buffer (str replacement))
(.append buffer ch))
(recur (inc index)))))))
(defn index-of
"Return index of value (string or char) in s, optionally searching
forward from from-index or nil if not found."
([s value]
(let [result (.indexOf s value)]
(if (neg? result)
nil
result)))
([s value from-index]
(let [result (.indexOf s value from-index)]
(if (neg? result)
nil
result))))
(defn last-index-of
"Return last index of value (string or char) in s, optionally
searching backward from from-index or nil if not found."
([s value]
(let [result (.lastIndexOf s value)]
(if (neg? result)
nil
result)))
([s value from-index]
(let [result (.lastIndexOf s value from-index)]
(if (neg? result)
nil
result))))
(defn ^boolean starts-with?
"True if s starts with substr."
[s substr]
(gstring/startsWith s substr))
(defn ^boolean ends-with?
"True if s ends with substr."
[s substr]
(gstring/endsWith s substr))
(defn ^boolean includes?
"True if s includes substr."
[s substr]
(gstring/contains s substr))

File diff suppressed because one or more lines are too long

505
viz/out/clojure/string.js Normal file
View File

@ -0,0 +1,505 @@
// Compiled by ClojureScript 1.9.473 {}
goog.provide('clojure.string');
goog.require('cljs.core');
goog.require('goog.string');
goog.require('goog.string.StringBuffer');
clojure.string.seq_reverse = (function clojure$string$seq_reverse(coll){
return cljs.core.reduce.call(null,cljs.core.conj,cljs.core.List.EMPTY,coll);
});
clojure.string.re_surrogate_pair = (new RegExp("([\\uD800-\\uDBFF])([\\uDC00-\\uDFFF])","g"));
/**
* Returns s with its characters reversed.
*/
clojure.string.reverse = (function clojure$string$reverse(s){
return s.replace(clojure.string.re_surrogate_pair,"$2$1").split("").reverse().join("");
});
clojure.string.replace_all = (function clojure$string$replace_all(s,re,replacement){
return s.replace((new RegExp(re.source,"g")),replacement);
});
clojure.string.replace_with = (function clojure$string$replace_with(f){
return (function() {
var G__8073__delegate = function (args){
var matches = cljs.core.drop_last.call(null,(2),args);
if(cljs.core._EQ_.call(null,cljs.core.count.call(null,matches),(1))){
return f.call(null,cljs.core.first.call(null,matches));
} else {
return f.call(null,cljs.core.vec.call(null,matches));
}
};
var G__8073 = function (var_args){
var args = null;
if (arguments.length > 0) {
var G__8074__i = 0, G__8074__a = new Array(arguments.length - 0);
while (G__8074__i < G__8074__a.length) {G__8074__a[G__8074__i] = arguments[G__8074__i + 0]; ++G__8074__i;}
args = new cljs.core.IndexedSeq(G__8074__a,0);
}
return G__8073__delegate.call(this,args);};
G__8073.cljs$lang$maxFixedArity = 0;
G__8073.cljs$lang$applyTo = (function (arglist__8075){
var args = cljs.core.seq(arglist__8075);
return G__8073__delegate(args);
});
G__8073.cljs$core$IFn$_invoke$arity$variadic = G__8073__delegate;
return G__8073;
})()
;
});
/**
* Replaces all instance of match with replacement in s.
* match/replacement can be:
*
* string / string
* pattern / (string or function of match).
*/
clojure.string.replace = (function clojure$string$replace(s,match,replacement){
if(typeof match === 'string'){
return s.replace((new RegExp(goog.string.regExpEscape(match),"g")),replacement);
} else {
if((match instanceof RegExp)){
if(typeof replacement === 'string'){
return clojure.string.replace_all.call(null,s,match,replacement);
} else {
return clojure.string.replace_all.call(null,s,match,clojure.string.replace_with.call(null,replacement));
}
} else {
throw [cljs.core.str.cljs$core$IFn$_invoke$arity$1("Invalid match arg: "),cljs.core.str.cljs$core$IFn$_invoke$arity$1(match)].join('');
}
}
});
/**
* Replaces the first instance of match with replacement in s.
* match/replacement can be:
*
* string / string
* pattern / (string or function of match).
*/
clojure.string.replace_first = (function clojure$string$replace_first(s,match,replacement){
return s.replace(match,replacement);
});
/**
* Returns a string of all elements in coll, as returned by (seq coll),
* separated by an optional separator.
*/
clojure.string.join = (function clojure$string$join(var_args){
var args8076 = [];
var len__7927__auto___8079 = arguments.length;
var i__7928__auto___8080 = (0);
while(true){
if((i__7928__auto___8080 < len__7927__auto___8079)){
args8076.push((arguments[i__7928__auto___8080]));
var G__8081 = (i__7928__auto___8080 + (1));
i__7928__auto___8080 = G__8081;
continue;
} else {
}
break;
}
var G__8078 = args8076.length;
switch (G__8078) {
case 1:
return clojure.string.join.cljs$core$IFn$_invoke$arity$1((arguments[(0)]));
break;
case 2:
return clojure.string.join.cljs$core$IFn$_invoke$arity$2((arguments[(0)]),(arguments[(1)]));
break;
default:
throw (new Error([cljs.core.str.cljs$core$IFn$_invoke$arity$1("Invalid arity: "),cljs.core.str.cljs$core$IFn$_invoke$arity$1(args8076.length)].join('')));
}
});
clojure.string.join.cljs$core$IFn$_invoke$arity$1 = (function (coll){
var sb = (new goog.string.StringBuffer());
var coll__$1 = cljs.core.seq.call(null,coll);
while(true){
if(!((coll__$1 == null))){
var G__8083 = sb.append([cljs.core.str.cljs$core$IFn$_invoke$arity$1(cljs.core.first.call(null,coll__$1))].join(''));
var G__8084 = cljs.core.next.call(null,coll__$1);
sb = G__8083;
coll__$1 = G__8084;
continue;
} else {
return sb.toString();
}
break;
}
});
clojure.string.join.cljs$core$IFn$_invoke$arity$2 = (function (separator,coll){
var sb = (new goog.string.StringBuffer());
var coll__$1 = cljs.core.seq.call(null,coll);
while(true){
if(!((coll__$1 == null))){
sb.append([cljs.core.str.cljs$core$IFn$_invoke$arity$1(cljs.core.first.call(null,coll__$1))].join(''));
var coll__$2 = cljs.core.next.call(null,coll__$1);
if((coll__$2 == null)){
} else {
sb.append(separator);
}
var G__8085 = sb;
var G__8086 = coll__$2;
sb = G__8085;
coll__$1 = G__8086;
continue;
} else {
return sb.toString();
}
break;
}
});
clojure.string.join.cljs$lang$maxFixedArity = 2;
/**
* Converts string to all upper-case.
*/
clojure.string.upper_case = (function clojure$string$upper_case(s){
return s.toUpperCase();
});
/**
* Converts string to all lower-case.
*/
clojure.string.lower_case = (function clojure$string$lower_case(s){
return s.toLowerCase();
});
/**
* Converts first character of the string to upper-case, all other
* characters to lower-case.
*/
clojure.string.capitalize = (function clojure$string$capitalize(s){
if((cljs.core.count.call(null,s) < (2))){
return clojure.string.upper_case.call(null,s);
} else {
return [cljs.core.str.cljs$core$IFn$_invoke$arity$1(clojure.string.upper_case.call(null,cljs.core.subs.call(null,s,(0),(1)))),cljs.core.str.cljs$core$IFn$_invoke$arity$1(clojure.string.lower_case.call(null,cljs.core.subs.call(null,s,(1))))].join('');
}
});
clojure.string.pop_last_while_empty = (function clojure$string$pop_last_while_empty(v){
var v__$1 = v;
while(true){
if(("" === cljs.core.peek.call(null,v__$1))){
var G__8087 = cljs.core.pop.call(null,v__$1);
v__$1 = G__8087;
continue;
} else {
return v__$1;
}
break;
}
});
clojure.string.discard_trailing_if_needed = (function clojure$string$discard_trailing_if_needed(limit,v){
if((((0) === limit)) && (((1) < cljs.core.count.call(null,v)))){
return clojure.string.pop_last_while_empty.call(null,v);
} else {
return v;
}
});
clojure.string.split_with_empty_regex = (function clojure$string$split_with_empty_regex(s,limit){
if(((limit <= (0))) || ((limit >= ((2) + cljs.core.count.call(null,s))))){
return cljs.core.conj.call(null,cljs.core.vec.call(null,cljs.core.cons.call(null,"",cljs.core.map.call(null,cljs.core.str,cljs.core.seq.call(null,s)))),"");
} else {
var pred__8091 = cljs.core._EQ__EQ_;
var expr__8092 = limit;
if(cljs.core.truth_(pred__8091.call(null,(1),expr__8092))){
return (new cljs.core.PersistentVector(null,1,(5),cljs.core.PersistentVector.EMPTY_NODE,[s],null));
} else {
if(cljs.core.truth_(pred__8091.call(null,(2),expr__8092))){
return (new cljs.core.PersistentVector(null,2,(5),cljs.core.PersistentVector.EMPTY_NODE,["",s],null));
} else {
var c = (limit - (2));
return cljs.core.conj.call(null,cljs.core.vec.call(null,cljs.core.cons.call(null,"",cljs.core.subvec.call(null,cljs.core.vec.call(null,cljs.core.map.call(null,cljs.core.str,cljs.core.seq.call(null,s))),(0),c))),cljs.core.subs.call(null,s,c));
}
}
}
});
/**
* Splits string on a regular expression. Optional argument limit is
* the maximum number of splits. Not lazy. Returns vector of the splits.
*/
clojure.string.split = (function clojure$string$split(var_args){
var args8094 = [];
var len__7927__auto___8097 = arguments.length;
var i__7928__auto___8098 = (0);
while(true){
if((i__7928__auto___8098 < len__7927__auto___8097)){
args8094.push((arguments[i__7928__auto___8098]));
var G__8099 = (i__7928__auto___8098 + (1));
i__7928__auto___8098 = G__8099;
continue;
} else {
}
break;
}
var G__8096 = args8094.length;
switch (G__8096) {
case 2:
return clojure.string.split.cljs$core$IFn$_invoke$arity$2((arguments[(0)]),(arguments[(1)]));
break;
case 3:
return clojure.string.split.cljs$core$IFn$_invoke$arity$3((arguments[(0)]),(arguments[(1)]),(arguments[(2)]));
break;
default:
throw (new Error([cljs.core.str.cljs$core$IFn$_invoke$arity$1("Invalid arity: "),cljs.core.str.cljs$core$IFn$_invoke$arity$1(args8094.length)].join('')));
}
});
clojure.string.split.cljs$core$IFn$_invoke$arity$2 = (function (s,re){
return clojure.string.split.call(null,s,re,(0));
});
clojure.string.split.cljs$core$IFn$_invoke$arity$3 = (function (s,re,limit){
return clojure.string.discard_trailing_if_needed.call(null,limit,((("/(?:)/" === [cljs.core.str.cljs$core$IFn$_invoke$arity$1(re)].join('')))?clojure.string.split_with_empty_regex.call(null,s,limit):(((limit < (1)))?cljs.core.vec.call(null,[cljs.core.str.cljs$core$IFn$_invoke$arity$1(s)].join('').split(re)):(function (){var s__$1 = s;
var limit__$1 = limit;
var parts = cljs.core.PersistentVector.EMPTY;
while(true){
if(((1) === limit__$1)){
return cljs.core.conj.call(null,parts,s__$1);
} else {
var m = cljs.core.re_find.call(null,re,s__$1);
if(!((m == null))){
var index = s__$1.indexOf(m);
var G__8101 = s__$1.substring((index + cljs.core.count.call(null,m)));
var G__8102 = (limit__$1 - (1));
var G__8103 = cljs.core.conj.call(null,parts,s__$1.substring((0),index));
s__$1 = G__8101;
limit__$1 = G__8102;
parts = G__8103;
continue;
} else {
return cljs.core.conj.call(null,parts,s__$1);
}
}
break;
}
})())));
});
clojure.string.split.cljs$lang$maxFixedArity = 3;
/**
* Splits s on
* or
* .
*/
clojure.string.split_lines = (function clojure$string$split_lines(s){
return clojure.string.split.call(null,s,/\n|\r\n/);
});
/**
* Removes whitespace from both ends of string.
*/
clojure.string.trim = (function clojure$string$trim(s){
return goog.string.trim(s);
});
/**
* Removes whitespace from the left side of string.
*/
clojure.string.triml = (function clojure$string$triml(s){
return goog.string.trimLeft(s);
});
/**
* Removes whitespace from the right side of string.
*/
clojure.string.trimr = (function clojure$string$trimr(s){
return goog.string.trimRight(s);
});
/**
* Removes all trailing newline \n or return \r characters from
* string. Similar to Perl's chomp.
*/
clojure.string.trim_newline = (function clojure$string$trim_newline(s){
var index = s.length;
while(true){
if((index === (0))){
return "";
} else {
var ch = cljs.core.get.call(null,s,(index - (1)));
if((("\n" === ch)) || (("\r" === ch))){
var G__8104 = (index - (1));
index = G__8104;
continue;
} else {
return s.substring((0),index);
}
}
break;
}
});
/**
* True is s is nil, empty, or contains only whitespace.
*/
clojure.string.blank_QMARK_ = (function clojure$string$blank_QMARK_(s){
return goog.string.isEmptySafe(s);
});
/**
* Return a new string, using cmap to escape each character ch
* from s as follows:
*
* If (cmap ch) is nil, append ch to the new string.
* If (cmap ch) is non-nil, append (str (cmap ch)) instead.
*/
clojure.string.escape = (function clojure$string$escape(s,cmap){
var buffer = (new goog.string.StringBuffer());
var length = s.length;
var index = (0);
while(true){
if((length === index)){
return buffer.toString();
} else {
var ch = s.charAt(index);
var replacement = cljs.core.get.call(null,cmap,ch);
if(!((replacement == null))){
buffer.append([cljs.core.str.cljs$core$IFn$_invoke$arity$1(replacement)].join(''));
} else {
buffer.append(ch);
}
var G__8105 = (index + (1));
index = G__8105;
continue;
}
break;
}
});
/**
* Return index of value (string or char) in s, optionally searching
* forward from from-index or nil if not found.
*/
clojure.string.index_of = (function clojure$string$index_of(var_args){
var args8106 = [];
var len__7927__auto___8109 = arguments.length;
var i__7928__auto___8110 = (0);
while(true){
if((i__7928__auto___8110 < len__7927__auto___8109)){
args8106.push((arguments[i__7928__auto___8110]));
var G__8111 = (i__7928__auto___8110 + (1));
i__7928__auto___8110 = G__8111;
continue;
} else {
}
break;
}
var G__8108 = args8106.length;
switch (G__8108) {
case 2:
return clojure.string.index_of.cljs$core$IFn$_invoke$arity$2((arguments[(0)]),(arguments[(1)]));
break;
case 3:
return clojure.string.index_of.cljs$core$IFn$_invoke$arity$3((arguments[(0)]),(arguments[(1)]),(arguments[(2)]));
break;
default:
throw (new Error([cljs.core.str.cljs$core$IFn$_invoke$arity$1("Invalid arity: "),cljs.core.str.cljs$core$IFn$_invoke$arity$1(args8106.length)].join('')));
}
});
clojure.string.index_of.cljs$core$IFn$_invoke$arity$2 = (function (s,value){
var result = s.indexOf(value);
if((result < (0))){
return null;
} else {
return result;
}
});
clojure.string.index_of.cljs$core$IFn$_invoke$arity$3 = (function (s,value,from_index){
var result = s.indexOf(value,from_index);
if((result < (0))){
return null;
} else {
return result;
}
});
clojure.string.index_of.cljs$lang$maxFixedArity = 3;
/**
* Return last index of value (string or char) in s, optionally
* searching backward from from-index or nil if not found.
*/
clojure.string.last_index_of = (function clojure$string$last_index_of(var_args){
var args8113 = [];
var len__7927__auto___8116 = arguments.length;
var i__7928__auto___8117 = (0);
while(true){
if((i__7928__auto___8117 < len__7927__auto___8116)){
args8113.push((arguments[i__7928__auto___8117]));
var G__8118 = (i__7928__auto___8117 + (1));
i__7928__auto___8117 = G__8118;
continue;
} else {
}
break;
}
var G__8115 = args8113.length;
switch (G__8115) {
case 2:
return clojure.string.last_index_of.cljs$core$IFn$_invoke$arity$2((arguments[(0)]),(arguments[(1)]));
break;
case 3:
return clojure.string.last_index_of.cljs$core$IFn$_invoke$arity$3((arguments[(0)]),(arguments[(1)]),(arguments[(2)]));
break;
default:
throw (new Error([cljs.core.str.cljs$core$IFn$_invoke$arity$1("Invalid arity: "),cljs.core.str.cljs$core$IFn$_invoke$arity$1(args8113.length)].join('')));
}
});
clojure.string.last_index_of.cljs$core$IFn$_invoke$arity$2 = (function (s,value){
var result = s.lastIndexOf(value);
if((result < (0))){
return null;
} else {
return result;
}
});
clojure.string.last_index_of.cljs$core$IFn$_invoke$arity$3 = (function (s,value,from_index){
var result = s.lastIndexOf(value,from_index);
if((result < (0))){
return null;
} else {
return result;
}
});
clojure.string.last_index_of.cljs$lang$maxFixedArity = 3;
/**
* True if s starts with substr.
*/
clojure.string.starts_with_QMARK_ = (function clojure$string$starts_with_QMARK_(s,substr){
return goog.string.startsWith(s,substr);
});
/**
* True if s ends with substr.
*/
clojure.string.ends_with_QMARK_ = (function clojure$string$ends_with_QMARK_(s,substr){
return goog.string.endsWith(s,substr);
});
/**
* True if s includes substr.
*/
clojure.string.includes_QMARK_ = (function clojure$string$includes_QMARK_(s,substr){
return goog.string.contains(s,substr);
});
//# sourceMappingURL=string.js.map

File diff suppressed because one or more lines are too long

1665
viz/out/goog/array/array.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,369 @@
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Utilities to check the preconditions, postconditions and
* invariants runtime.
*
* Methods in this package should be given special treatment by the compiler
* for type-inference. For example, <code>goog.asserts.assert(foo)</code>
* will restrict <code>foo</code> to a truthy value.
*
* The compiler has an option to disable asserts. So code like:
* <code>
* var x = goog.asserts.assert(foo()); goog.asserts.assert(bar());
* </code>
* will be transformed into:
* <code>
* var x = foo();
* </code>
* The compiler will leave in foo() (because its return value is used),
* but it will remove bar() because it assumes it does not have side-effects.
*
* @author agrieve@google.com (Andrew Grieve)
*/
goog.provide('goog.asserts');
goog.provide('goog.asserts.AssertionError');
goog.require('goog.debug.Error');
goog.require('goog.dom.NodeType');
goog.require('goog.string');
/**
* @define {boolean} Whether to strip out asserts or to leave them in.
*/
goog.define('goog.asserts.ENABLE_ASSERTS', goog.DEBUG);
/**
* Error object for failed assertions.
* @param {string} messagePattern The pattern that was used to form message.
* @param {!Array<*>} messageArgs The items to substitute into the pattern.
* @constructor
* @extends {goog.debug.Error}
* @final
*/
goog.asserts.AssertionError = function(messagePattern, messageArgs) {
messageArgs.unshift(messagePattern);
goog.debug.Error.call(this, goog.string.subs.apply(null, messageArgs));
// Remove the messagePattern afterwards to avoid permanently modifying the
// passed in array.
messageArgs.shift();
/**
* The message pattern used to format the error message. Error handlers can
* use this to uniquely identify the assertion.
* @type {string}
*/
this.messagePattern = messagePattern;
};
goog.inherits(goog.asserts.AssertionError, goog.debug.Error);
/** @override */
goog.asserts.AssertionError.prototype.name = 'AssertionError';
/**
* The default error handler.
* @param {!goog.asserts.AssertionError} e The exception to be handled.
*/
goog.asserts.DEFAULT_ERROR_HANDLER = function(e) {
throw e;
};
/**
* The handler responsible for throwing or logging assertion errors.
* @private {function(!goog.asserts.AssertionError)}
*/
goog.asserts.errorHandler_ = goog.asserts.DEFAULT_ERROR_HANDLER;
/**
* Throws an exception with the given message and "Assertion failed" prefixed
* onto it.
* @param {string} defaultMessage The message to use if givenMessage is empty.
* @param {Array<*>} defaultArgs The substitution arguments for defaultMessage.
* @param {string|undefined} givenMessage Message supplied by the caller.
* @param {Array<*>} givenArgs The substitution arguments for givenMessage.
* @throws {goog.asserts.AssertionError} When the value is not a number.
* @private
*/
goog.asserts.doAssertFailure_ = function(
defaultMessage, defaultArgs, givenMessage, givenArgs) {
var message = 'Assertion failed';
if (givenMessage) {
message += ': ' + givenMessage;
var args = givenArgs;
} else if (defaultMessage) {
message += ': ' + defaultMessage;
args = defaultArgs;
}
// The '' + works around an Opera 10 bug in the unit tests. Without it,
// a stack trace is added to var message above. With this, a stack trace is
// not added until this line (it causes the extra garbage to be added after
// the assertion message instead of in the middle of it).
var e = new goog.asserts.AssertionError('' + message, args || []);
goog.asserts.errorHandler_(e);
};
/**
* Sets a custom error handler that can be used to customize the behavior of
* assertion failures, for example by turning all assertion failures into log
* messages.
* @param {function(!goog.asserts.AssertionError)} errorHandler
*/
goog.asserts.setErrorHandler = function(errorHandler) {
if (goog.asserts.ENABLE_ASSERTS) {
goog.asserts.errorHandler_ = errorHandler;
}
};
/**
* Checks if the condition evaluates to true if goog.asserts.ENABLE_ASSERTS is
* true.
* @template T
* @param {T} condition The condition to check.
* @param {string=} opt_message Error message in case of failure.
* @param {...*} var_args The items to substitute into the failure message.
* @return {T} The value of the condition.
* @throws {goog.asserts.AssertionError} When the condition evaluates to false.
*/
goog.asserts.assert = function(condition, opt_message, var_args) {
if (goog.asserts.ENABLE_ASSERTS && !condition) {
goog.asserts.doAssertFailure_(
'', null, opt_message, Array.prototype.slice.call(arguments, 2));
}
return condition;
};
/**
* Fails if goog.asserts.ENABLE_ASSERTS is true. This function is useful in case
* when we want to add a check in the unreachable area like switch-case
* statement:
*
* <pre>
* switch(type) {
* case FOO: doSomething(); break;
* case BAR: doSomethingElse(); break;
* default: goog.asserts.fail('Unrecognized type: ' + type);
* // We have only 2 types - "default:" section is unreachable code.
* }
* </pre>
*
* @param {string=} opt_message Error message in case of failure.
* @param {...*} var_args The items to substitute into the failure message.
* @throws {goog.asserts.AssertionError} Failure.
*/
goog.asserts.fail = function(opt_message, var_args) {
if (goog.asserts.ENABLE_ASSERTS) {
goog.asserts.errorHandler_(
new goog.asserts.AssertionError(
'Failure' + (opt_message ? ': ' + opt_message : ''),
Array.prototype.slice.call(arguments, 1)));
}
};
/**
* Checks if the value is a number if goog.asserts.ENABLE_ASSERTS is true.
* @param {*} value The value to check.
* @param {string=} opt_message Error message in case of failure.
* @param {...*} var_args The items to substitute into the failure message.
* @return {number} The value, guaranteed to be a number when asserts enabled.
* @throws {goog.asserts.AssertionError} When the value is not a number.
*/
goog.asserts.assertNumber = function(value, opt_message, var_args) {
if (goog.asserts.ENABLE_ASSERTS && !goog.isNumber(value)) {
goog.asserts.doAssertFailure_(
'Expected number but got %s: %s.', [goog.typeOf(value), value],
opt_message, Array.prototype.slice.call(arguments, 2));
}
return /** @type {number} */ (value);
};
/**
* Checks if the value is a string if goog.asserts.ENABLE_ASSERTS is true.
* @param {*} value The value to check.
* @param {string=} opt_message Error message in case of failure.
* @param {...*} var_args The items to substitute into the failure message.
* @return {string} The value, guaranteed to be a string when asserts enabled.
* @throws {goog.asserts.AssertionError} When the value is not a string.
*/
goog.asserts.assertString = function(value, opt_message, var_args) {
if (goog.asserts.ENABLE_ASSERTS && !goog.isString(value)) {
goog.asserts.doAssertFailure_(
'Expected string but got %s: %s.', [goog.typeOf(value), value],
opt_message, Array.prototype.slice.call(arguments, 2));
}
return /** @type {string} */ (value);
};
/**
* Checks if the value is a function if goog.asserts.ENABLE_ASSERTS is true.
* @param {*} value The value to check.
* @param {string=} opt_message Error message in case of failure.
* @param {...*} var_args The items to substitute into the failure message.
* @return {!Function} The value, guaranteed to be a function when asserts
* enabled.
* @throws {goog.asserts.AssertionError} When the value is not a function.
*/
goog.asserts.assertFunction = function(value, opt_message, var_args) {
if (goog.asserts.ENABLE_ASSERTS && !goog.isFunction(value)) {
goog.asserts.doAssertFailure_(
'Expected function but got %s: %s.', [goog.typeOf(value), value],
opt_message, Array.prototype.slice.call(arguments, 2));
}
return /** @type {!Function} */ (value);
};
/**
* Checks if the value is an Object if goog.asserts.ENABLE_ASSERTS is true.
* @param {*} value The value to check.
* @param {string=} opt_message Error message in case of failure.
* @param {...*} var_args The items to substitute into the failure message.
* @return {!Object} The value, guaranteed to be a non-null object.
* @throws {goog.asserts.AssertionError} When the value is not an object.
*/
goog.asserts.assertObject = function(value, opt_message, var_args) {
if (goog.asserts.ENABLE_ASSERTS && !goog.isObject(value)) {
goog.asserts.doAssertFailure_(
'Expected object but got %s: %s.', [goog.typeOf(value), value],
opt_message, Array.prototype.slice.call(arguments, 2));
}
return /** @type {!Object} */ (value);
};
/**
* Checks if the value is an Array if goog.asserts.ENABLE_ASSERTS is true.
* @param {*} value The value to check.
* @param {string=} opt_message Error message in case of failure.
* @param {...*} var_args The items to substitute into the failure message.
* @return {!Array<?>} The value, guaranteed to be a non-null array.
* @throws {goog.asserts.AssertionError} When the value is not an array.
*/
goog.asserts.assertArray = function(value, opt_message, var_args) {
if (goog.asserts.ENABLE_ASSERTS && !goog.isArray(value)) {
goog.asserts.doAssertFailure_(
'Expected array but got %s: %s.', [goog.typeOf(value), value],
opt_message, Array.prototype.slice.call(arguments, 2));
}
return /** @type {!Array<?>} */ (value);
};
/**
* Checks if the value is a boolean if goog.asserts.ENABLE_ASSERTS is true.
* @param {*} value The value to check.
* @param {string=} opt_message Error message in case of failure.
* @param {...*} var_args The items to substitute into the failure message.
* @return {boolean} The value, guaranteed to be a boolean when asserts are
* enabled.
* @throws {goog.asserts.AssertionError} When the value is not a boolean.
*/
goog.asserts.assertBoolean = function(value, opt_message, var_args) {
if (goog.asserts.ENABLE_ASSERTS && !goog.isBoolean(value)) {
goog.asserts.doAssertFailure_(
'Expected boolean but got %s: %s.', [goog.typeOf(value), value],
opt_message, Array.prototype.slice.call(arguments, 2));
}
return /** @type {boolean} */ (value);
};
/**
* Checks if the value is a DOM Element if goog.asserts.ENABLE_ASSERTS is true.
* @param {*} value The value to check.
* @param {string=} opt_message Error message in case of failure.
* @param {...*} var_args The items to substitute into the failure message.
* @return {!Element} The value, likely to be a DOM Element when asserts are
* enabled.
* @throws {goog.asserts.AssertionError} When the value is not an Element.
*/
goog.asserts.assertElement = function(value, opt_message, var_args) {
if (goog.asserts.ENABLE_ASSERTS &&
(!goog.isObject(value) || value.nodeType != goog.dom.NodeType.ELEMENT)) {
goog.asserts.doAssertFailure_(
'Expected Element but got %s: %s.', [goog.typeOf(value), value],
opt_message, Array.prototype.slice.call(arguments, 2));
}
return /** @type {!Element} */ (value);
};
/**
* Checks if the value is an instance of the user-defined type if
* goog.asserts.ENABLE_ASSERTS is true.
*
* The compiler may tighten the type returned by this function.
*
* @param {?} value The value to check.
* @param {function(new: T, ...)} type A user-defined constructor.
* @param {string=} opt_message Error message in case of failure.
* @param {...*} var_args The items to substitute into the failure message.
* @throws {goog.asserts.AssertionError} When the value is not an instance of
* type.
* @return {T}
* @template T
*/
goog.asserts.assertInstanceof = function(value, type, opt_message, var_args) {
if (goog.asserts.ENABLE_ASSERTS && !(value instanceof type)) {
goog.asserts.doAssertFailure_(
'Expected instanceof %s but got %s.',
[goog.asserts.getType_(type), goog.asserts.getType_(value)],
opt_message, Array.prototype.slice.call(arguments, 3));
}
return value;
};
/**
* Checks that no enumerable keys are present in Object.prototype. Such keys
* would break most code that use {@code for (var ... in ...)} loops.
*/
goog.asserts.assertObjectPrototypeIsIntact = function() {
for (var key in Object.prototype) {
goog.asserts.fail(key + ' should not be enumerable in Object.prototype.');
}
};
/**
* Returns the type of a value. If a constructor is passed, and a suitable
* string cannot be found, 'unknown type name' will be returned.
* @param {*} value A constructor, object, or primitive.
* @return {string} The best display name for the value, or 'unknown type name'.
* @private
*/
goog.asserts.getType_ = function(value) {
if (value instanceof Function) {
return value.displayName || value.name || 'unknown type name';
} else if (value instanceof Object) {
return value.constructor.displayName || value.constructor.name ||
Object.prototype.toString.call(value);
} else {
return value === null ? 'null' : typeof value;
}
};

2727
viz/out/goog/base.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,159 @@
// Copyright 2010 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview A global registry for entry points into a program,
* so that they can be instrumented. Each module should register their
* entry points with this registry. Designed to be compiled out
* if no instrumentation is requested.
*
* Entry points may be registered before or after a call to
* goog.debug.entryPointRegistry.monitorAll. If an entry point is registered
* later, the existing monitor will instrument the new entry point.
*
* @author nicksantos@google.com (Nick Santos)
*/
goog.provide('goog.debug.EntryPointMonitor');
goog.provide('goog.debug.entryPointRegistry');
goog.require('goog.asserts');
/**
* @interface
*/
goog.debug.EntryPointMonitor = function() {};
/**
* Instruments a function.
*
* @param {!Function} fn A function to instrument.
* @return {!Function} The instrumented function.
*/
goog.debug.EntryPointMonitor.prototype.wrap;
/**
* Try to remove an instrumentation wrapper created by this monitor.
* If the function passed to unwrap is not a wrapper created by this
* monitor, then we will do nothing.
*
* Notice that some wrappers may not be unwrappable. For example, if other
* monitors have applied their own wrappers, then it will be impossible to
* unwrap them because their wrappers will have captured our wrapper.
*
* So it is important that entry points are unwrapped in the reverse
* order that they were wrapped.
*
* @param {!Function} fn A function to unwrap.
* @return {!Function} The unwrapped function, or {@code fn} if it was not
* a wrapped function created by this monitor.
*/
goog.debug.EntryPointMonitor.prototype.unwrap;
/**
* An array of entry point callbacks.
* @type {!Array<function(!Function)>}
* @private
*/
goog.debug.entryPointRegistry.refList_ = [];
/**
* Monitors that should wrap all the entry points.
* @type {!Array<!goog.debug.EntryPointMonitor>}
* @private
*/
goog.debug.entryPointRegistry.monitors_ = [];
/**
* Whether goog.debug.entryPointRegistry.monitorAll has ever been called.
* Checking this allows the compiler to optimize out the registrations.
* @type {boolean}
* @private
*/
goog.debug.entryPointRegistry.monitorsMayExist_ = false;
/**
* Register an entry point with this module.
*
* The entry point will be instrumented when a monitor is passed to
* goog.debug.entryPointRegistry.monitorAll. If this has already occurred, the
* entry point is instrumented immediately.
*
* @param {function(!Function)} callback A callback function which is called
* with a transforming function to instrument the entry point. The callback
* is responsible for wrapping the relevant entry point with the
* transforming function.
*/
goog.debug.entryPointRegistry.register = function(callback) {
// Don't use push(), so that this can be compiled out.
goog.debug.entryPointRegistry
.refList_[goog.debug.entryPointRegistry.refList_.length] = callback;
// If no one calls monitorAll, this can be compiled out.
if (goog.debug.entryPointRegistry.monitorsMayExist_) {
var monitors = goog.debug.entryPointRegistry.monitors_;
for (var i = 0; i < monitors.length; i++) {
callback(goog.bind(monitors[i].wrap, monitors[i]));
}
}
};
/**
* Configures a monitor to wrap all entry points.
*
* Entry points that have already been registered are immediately wrapped by
* the monitor. When an entry point is registered in the future, it will also
* be wrapped by the monitor when it is registered.
*
* @param {!goog.debug.EntryPointMonitor} monitor An entry point monitor.
*/
goog.debug.entryPointRegistry.monitorAll = function(monitor) {
goog.debug.entryPointRegistry.monitorsMayExist_ = true;
var transformer = goog.bind(monitor.wrap, monitor);
for (var i = 0; i < goog.debug.entryPointRegistry.refList_.length; i++) {
goog.debug.entryPointRegistry.refList_[i](transformer);
}
goog.debug.entryPointRegistry.monitors_.push(monitor);
};
/**
* Try to unmonitor all the entry points that have already been registered. If
* an entry point is registered in the future, it will not be wrapped by the
* monitor when it is registered. Note that this may fail if the entry points
* have additional wrapping.
*
* @param {!goog.debug.EntryPointMonitor} monitor The last monitor to wrap
* the entry points.
* @throws {Error} If the monitor is not the most recently configured monitor.
*/
goog.debug.entryPointRegistry.unmonitorAllIfPossible = function(monitor) {
var monitors = goog.debug.entryPointRegistry.monitors_;
goog.asserts.assert(
monitor == monitors[monitors.length - 1],
'Only the most recent monitor can be unwrapped.');
var transformer = goog.bind(monitor.unwrap, monitor);
for (var i = 0; i < goog.debug.entryPointRegistry.refList_.length; i++) {
goog.debug.entryPointRegistry.refList_[i](transformer);
}
monitors.length--;
};

View File

@ -0,0 +1,63 @@
// Copyright 2009 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Provides a base class for custom Error objects such that the
* stack is correctly maintained.
*
* You should never need to throw goog.debug.Error(msg) directly, Error(msg) is
* sufficient.
*
*/
goog.provide('goog.debug.Error');
/**
* Base class for custom error objects.
* @param {*=} opt_msg The message associated with the error.
* @constructor
* @extends {Error}
*/
goog.debug.Error = function(opt_msg) {
// Attempt to ensure there is a stack trace.
if (Error.captureStackTrace) {
Error.captureStackTrace(this, goog.debug.Error);
} else {
var stack = new Error().stack;
if (stack) {
this.stack = stack;
}
}
if (opt_msg) {
this.message = String(opt_msg);
}
/**
* Whether to report this error to the server. Setting this to false will
* cause the error reporter to not report the error back to the server,
* which can be useful if the client knows that the error has already been
* logged on the server.
* @type {boolean}
*/
this.reportErrorToServer = true;
};
goog.inherits(goog.debug.Error, Error);
/** @override */
goog.debug.Error.prototype.name = 'CustomError';

1508
viz/out/goog/deps.js Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,308 @@
// Copyright 2005 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Implements the disposable interface. The dispose method is used
* to clean up references and resources.
* @author arv@google.com (Erik Arvidsson)
*/
goog.provide('goog.Disposable');
/** @suppress {extraProvide} */
goog.provide('goog.dispose');
/** @suppress {extraProvide} */
goog.provide('goog.disposeAll');
goog.require('goog.disposable.IDisposable');
/**
* Class that provides the basic implementation for disposable objects. If your
* class holds one or more references to COM objects, DOM nodes, or other
* disposable objects, it should extend this class or implement the disposable
* interface (defined in goog.disposable.IDisposable).
* @constructor
* @implements {goog.disposable.IDisposable}
*/
goog.Disposable = function() {
if (goog.Disposable.MONITORING_MODE != goog.Disposable.MonitoringMode.OFF) {
if (goog.Disposable.INCLUDE_STACK_ON_CREATION) {
this.creationStack = new Error().stack;
}
goog.Disposable.instances_[goog.getUid(this)] = this;
}
// Support sealing
this.disposed_ = this.disposed_;
this.onDisposeCallbacks_ = this.onDisposeCallbacks_;
};
/**
* @enum {number} Different monitoring modes for Disposable.
*/
goog.Disposable.MonitoringMode = {
/**
* No monitoring.
*/
OFF: 0,
/**
* Creating and disposing the goog.Disposable instances is monitored. All
* disposable objects need to call the {@code goog.Disposable} base
* constructor. The PERMANENT mode must be switched on before creating any
* goog.Disposable instances.
*/
PERMANENT: 1,
/**
* INTERACTIVE mode can be switched on and off on the fly without producing
* errors. It also doesn't warn if the disposable objects don't call the
* {@code goog.Disposable} base constructor.
*/
INTERACTIVE: 2
};
/**
* @define {number} The monitoring mode of the goog.Disposable
* instances. Default is OFF. Switching on the monitoring is only
* recommended for debugging because it has a significant impact on
* performance and memory usage. If switched off, the monitoring code
* compiles down to 0 bytes.
*/
goog.define('goog.Disposable.MONITORING_MODE', 0);
/**
* @define {boolean} Whether to attach creation stack to each created disposable
* instance; This is only relevant for when MonitoringMode != OFF.
*/
goog.define('goog.Disposable.INCLUDE_STACK_ON_CREATION', true);
/**
* Maps the unique ID of every undisposed {@code goog.Disposable} object to
* the object itself.
* @type {!Object<number, !goog.Disposable>}
* @private
*/
goog.Disposable.instances_ = {};
/**
* @return {!Array<!goog.Disposable>} All {@code goog.Disposable} objects that
* haven't been disposed of.
*/
goog.Disposable.getUndisposedObjects = function() {
var ret = [];
for (var id in goog.Disposable.instances_) {
if (goog.Disposable.instances_.hasOwnProperty(id)) {
ret.push(goog.Disposable.instances_[Number(id)]);
}
}
return ret;
};
/**
* Clears the registry of undisposed objects but doesn't dispose of them.
*/
goog.Disposable.clearUndisposedObjects = function() {
goog.Disposable.instances_ = {};
};
/**
* Whether the object has been disposed of.
* @type {boolean}
* @private
*/
goog.Disposable.prototype.disposed_ = false;
/**
* Callbacks to invoke when this object is disposed.
* @type {Array<!Function>}
* @private
*/
goog.Disposable.prototype.onDisposeCallbacks_;
/**
* If monitoring the goog.Disposable instances is enabled, stores the creation
* stack trace of the Disposable instance.
* @const {string}
*/
goog.Disposable.prototype.creationStack;
/**
* @return {boolean} Whether the object has been disposed of.
* @override
*/
goog.Disposable.prototype.isDisposed = function() {
return this.disposed_;
};
/**
* @return {boolean} Whether the object has been disposed of.
* @deprecated Use {@link #isDisposed} instead.
*/
goog.Disposable.prototype.getDisposed = goog.Disposable.prototype.isDisposed;
/**
* Disposes of the object. If the object hasn't already been disposed of, calls
* {@link #disposeInternal}. Classes that extend {@code goog.Disposable} should
* override {@link #disposeInternal} in order to delete references to COM
* objects, DOM nodes, and other disposable objects. Reentrant.
*
* @return {void} Nothing.
* @override
*/
goog.Disposable.prototype.dispose = function() {
if (!this.disposed_) {
// Set disposed_ to true first, in case during the chain of disposal this
// gets disposed recursively.
this.disposed_ = true;
this.disposeInternal();
if (goog.Disposable.MONITORING_MODE != goog.Disposable.MonitoringMode.OFF) {
var uid = goog.getUid(this);
if (goog.Disposable.MONITORING_MODE ==
goog.Disposable.MonitoringMode.PERMANENT &&
!goog.Disposable.instances_.hasOwnProperty(uid)) {
throw Error(
this + ' did not call the goog.Disposable base ' +
'constructor or was disposed of after a clearUndisposedObjects ' +
'call');
}
delete goog.Disposable.instances_[uid];
}
}
};
/**
* Associates a disposable object with this object so that they will be disposed
* together.
* @param {goog.disposable.IDisposable} disposable that will be disposed when
* this object is disposed.
*/
goog.Disposable.prototype.registerDisposable = function(disposable) {
this.addOnDisposeCallback(goog.partial(goog.dispose, disposable));
};
/**
* Invokes a callback function when this object is disposed. Callbacks are
* invoked in the order in which they were added. If a callback is added to
* an already disposed Disposable, it will be called immediately.
* @param {function(this:T):?} callback The callback function.
* @param {T=} opt_scope An optional scope to call the callback in.
* @template T
*/
goog.Disposable.prototype.addOnDisposeCallback = function(callback, opt_scope) {
if (this.disposed_) {
callback.call(opt_scope);
return;
}
if (!this.onDisposeCallbacks_) {
this.onDisposeCallbacks_ = [];
}
this.onDisposeCallbacks_.push(
goog.isDef(opt_scope) ? goog.bind(callback, opt_scope) : callback);
};
/**
* Deletes or nulls out any references to COM objects, DOM nodes, or other
* disposable objects. Classes that extend {@code goog.Disposable} should
* override this method.
* Not reentrant. To avoid calling it twice, it must only be called from the
* subclass' {@code disposeInternal} method. Everywhere else the public
* {@code dispose} method must be used.
* For example:
* <pre>
* mypackage.MyClass = function() {
* mypackage.MyClass.base(this, 'constructor');
* // Constructor logic specific to MyClass.
* ...
* };
* goog.inherits(mypackage.MyClass, goog.Disposable);
*
* mypackage.MyClass.prototype.disposeInternal = function() {
* // Dispose logic specific to MyClass.
* ...
* // Call superclass's disposeInternal at the end of the subclass's, like
* // in C++, to avoid hard-to-catch issues.
* mypackage.MyClass.base(this, 'disposeInternal');
* };
* </pre>
* @protected
*/
goog.Disposable.prototype.disposeInternal = function() {
if (this.onDisposeCallbacks_) {
while (this.onDisposeCallbacks_.length) {
this.onDisposeCallbacks_.shift()();
}
}
};
/**
* Returns True if we can verify the object is disposed.
* Calls {@code isDisposed} on the argument if it supports it. If obj
* is not an object with an isDisposed() method, return false.
* @param {*} obj The object to investigate.
* @return {boolean} True if we can verify the object is disposed.
*/
goog.Disposable.isDisposed = function(obj) {
if (obj && typeof obj.isDisposed == 'function') {
return obj.isDisposed();
}
return false;
};
/**
* Calls {@code dispose} on the argument if it supports it. If obj is not an
* object with a dispose() method, this is a no-op.
* @param {*} obj The object to dispose of.
*/
goog.dispose = function(obj) {
if (obj && typeof obj.dispose == 'function') {
obj.dispose();
}
};
/**
* Calls {@code dispose} on each member of the list that supports it. (If the
* member is an ArrayLike, then {@code goog.disposeAll()} will be called
* recursively on each of its members.) If the member is not an object with a
* {@code dispose()} method, then it is ignored.
* @param {...*} var_args The list.
*/
goog.disposeAll = function(var_args) {
for (var i = 0, len = arguments.length; i < len; ++i) {
var disposable = arguments[i];
if (goog.isArrayLike(disposable)) {
goog.disposeAll.apply(null, disposable);
} else {
goog.dispose(disposable);
}
}
};

View File

@ -0,0 +1,45 @@
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Definition of the disposable interface. A disposable object
* has a dispose method to to clean up references and resources.
* @author nnaze@google.com (Nathan Naze)
*/
goog.provide('goog.disposable.IDisposable');
/**
* Interface for a disposable object. If a instance requires cleanup
* (references COM objects, DOM notes, or other disposable objects), it should
* implement this interface (it may subclass goog.Disposable).
* @interface
*/
goog.disposable.IDisposable = function() {};
/**
* Disposes of the object and its resources.
* @return {void} Nothing.
*/
goog.disposable.IDisposable.prototype.dispose = goog.abstractMethod;
/**
* @return {boolean} Whether the object has been disposed of.
*/
goog.disposable.IDisposable.prototype.isDisposed = goog.abstractMethod;

View File

@ -0,0 +1,73 @@
// Copyright 2010 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Browser capability checks for the dom package.
*
*/
goog.provide('goog.dom.BrowserFeature');
goog.require('goog.userAgent');
/**
* Enum of browser capabilities.
* @enum {boolean}
*/
goog.dom.BrowserFeature = {
/**
* Whether attributes 'name' and 'type' can be added to an element after it's
* created. False in Internet Explorer prior to version 9.
*/
CAN_ADD_NAME_OR_TYPE_ATTRIBUTES:
!goog.userAgent.IE || goog.userAgent.isDocumentModeOrHigher(9),
/**
* Whether we can use element.children to access an element's Element
* children. Available since Gecko 1.9.1, IE 9. (IE<9 also includes comment
* nodes in the collection.)
*/
CAN_USE_CHILDREN_ATTRIBUTE: !goog.userAgent.GECKO && !goog.userAgent.IE ||
goog.userAgent.IE && goog.userAgent.isDocumentModeOrHigher(9) ||
goog.userAgent.GECKO && goog.userAgent.isVersionOrHigher('1.9.1'),
/**
* Opera, Safari 3, and Internet Explorer 9 all support innerText but they
* include text nodes in script and style tags. Not document-mode-dependent.
*/
CAN_USE_INNER_TEXT:
(goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('9')),
/**
* MSIE, Opera, and Safari>=4 support element.parentElement to access an
* element's parent if it is an Element.
*/
CAN_USE_PARENT_ELEMENT_PROPERTY:
goog.userAgent.IE || goog.userAgent.OPERA || goog.userAgent.WEBKIT,
/**
* Whether NoScope elements need a scoped element written before them in
* innerHTML.
* MSDN: http://msdn.microsoft.com/en-us/library/ms533897(VS.85).aspx#1
*/
INNER_HTML_NEEDS_SCOPED_ELEMENT: goog.userAgent.IE,
/**
* Whether we use legacy IE range API.
*/
LEGACY_IE_RANGES:
goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9)
};

2992
viz/out/goog/dom/dom.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,48 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Definition of goog.dom.NodeType.
*/
goog.provide('goog.dom.NodeType');
/**
* Constants for the nodeType attribute in the Node interface.
*
* These constants match those specified in the Node interface. These are
* usually present on the Node object in recent browsers, but not in older
* browsers (specifically, early IEs) and thus are given here.
*
* In some browsers (early IEs), these are not defined on the Node object,
* so they are provided here.
*
* See http://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-1950641247
* @enum {number}
*/
goog.dom.NodeType = {
ELEMENT: 1,
ATTRIBUTE: 2,
TEXT: 3,
CDATA_SECTION: 4,
ENTITY_REFERENCE: 5,
ENTITY: 6,
PROCESSING_INSTRUCTION: 7,
COMMENT: 8,
DOCUMENT: 9,
DOCUMENT_TYPE: 10,
DOCUMENT_FRAGMENT: 11,
NOTATION: 12
};

372
viz/out/goog/dom/safe.js Normal file
View File

@ -0,0 +1,372 @@
// Copyright 2013 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Type-safe wrappers for unsafe DOM APIs.
*
* This file provides type-safe wrappers for DOM APIs that can result in
* cross-site scripting (XSS) vulnerabilities, if the API is supplied with
* untrusted (attacker-controlled) input. Instead of plain strings, the type
* safe wrappers consume values of types from the goog.html package whose
* contract promises that values are safe to use in the corresponding context.
*
* Hence, a program that exclusively uses the wrappers in this file (i.e., whose
* only reference to security-sensitive raw DOM APIs are in this file) is
* guaranteed to be free of XSS due to incorrect use of such DOM APIs (modulo
* correctness of code that produces values of the respective goog.html types,
* and absent code that violates type safety).
*
* For example, assigning to an element's .innerHTML property a string that is
* derived (even partially) from untrusted input typically results in an XSS
* vulnerability. The type-safe wrapper goog.html.setInnerHtml consumes a value
* of type goog.html.SafeHtml, whose contract states that using its values in a
* HTML context will not result in XSS. Hence a program that is free of direct
* assignments to any element's innerHTML property (with the exception of the
* assignment to .innerHTML in this file) is guaranteed to be free of XSS due to
* assignment of untrusted strings to the innerHTML property.
*/
goog.provide('goog.dom.safe');
goog.provide('goog.dom.safe.InsertAdjacentHtmlPosition');
goog.require('goog.asserts');
goog.require('goog.html.SafeHtml');
goog.require('goog.html.SafeUrl');
goog.require('goog.html.TrustedResourceUrl');
goog.require('goog.string');
goog.require('goog.string.Const');
/** @enum {string} */
goog.dom.safe.InsertAdjacentHtmlPosition = {
AFTERBEGIN: 'afterbegin',
AFTEREND: 'afterend',
BEFOREBEGIN: 'beforebegin',
BEFOREEND: 'beforeend'
};
/**
* Inserts known-safe HTML into a Node, at the specified position.
* @param {!Node} node The node on which to call insertAdjacentHTML.
* @param {!goog.dom.safe.InsertAdjacentHtmlPosition} position Position where
* to insert the HTML.
* @param {!goog.html.SafeHtml} html The known-safe HTML to insert.
*/
goog.dom.safe.insertAdjacentHtml = function(node, position, html) {
node.insertAdjacentHTML(position, goog.html.SafeHtml.unwrap(html));
};
/**
* Assigns known-safe HTML to an element's innerHTML property.
* @param {!Element} elem The element whose innerHTML is to be assigned to.
* @param {!goog.html.SafeHtml} html The known-safe HTML to assign.
*/
goog.dom.safe.setInnerHtml = function(elem, html) {
elem.innerHTML = goog.html.SafeHtml.unwrap(html);
};
/**
* Assigns known-safe HTML to an element's outerHTML property.
* @param {!Element} elem The element whose outerHTML is to be assigned to.
* @param {!goog.html.SafeHtml} html The known-safe HTML to assign.
*/
goog.dom.safe.setOuterHtml = function(elem, html) {
elem.outerHTML = goog.html.SafeHtml.unwrap(html);
};
/**
* Writes known-safe HTML to a document.
* @param {!Document} doc The document to be written to.
* @param {!goog.html.SafeHtml} html The known-safe HTML to assign.
*/
goog.dom.safe.documentWrite = function(doc, html) {
doc.write(goog.html.SafeHtml.unwrap(html));
};
/**
* Safely assigns a URL to an anchor element's href property.
*
* If url is of type goog.html.SafeUrl, its value is unwrapped and assigned to
* anchor's href property. If url is of type string however, it is first
* sanitized using goog.html.SafeUrl.sanitize.
*
* Example usage:
* goog.dom.safe.setAnchorHref(anchorEl, url);
* which is a safe alternative to
* anchorEl.href = url;
* The latter can result in XSS vulnerabilities if url is a
* user-/attacker-controlled value.
*
* @param {!HTMLAnchorElement} anchor The anchor element whose href property
* is to be assigned to.
* @param {string|!goog.html.SafeUrl} url The URL to assign.
* @see goog.html.SafeUrl#sanitize
*/
goog.dom.safe.setAnchorHref = function(anchor, url) {
/** @type {!goog.html.SafeUrl} */
var safeUrl;
if (url instanceof goog.html.SafeUrl) {
safeUrl = url;
} else {
safeUrl = goog.html.SafeUrl.sanitize(url);
}
anchor.href = goog.html.SafeUrl.unwrap(safeUrl);
};
/**
* Safely assigns a URL to an image element's src property.
*
* If url is of type goog.html.SafeUrl, its value is unwrapped and assigned to
* image's src property. If url is of type string however, it is first
* sanitized using goog.html.SafeUrl.sanitize.
*
* @param {!HTMLImageElement} imageElement The image element whose src property
* is to be assigned to.
* @param {string|!goog.html.SafeUrl} url The URL to assign.
* @see goog.html.SafeUrl#sanitize
*/
goog.dom.safe.setImageSrc = function(imageElement, url) {
/** @type {!goog.html.SafeUrl} */
var safeUrl;
if (url instanceof goog.html.SafeUrl) {
safeUrl = url;
} else {
safeUrl = goog.html.SafeUrl.sanitize(url);
}
imageElement.src = goog.html.SafeUrl.unwrap(safeUrl);
};
/**
* Safely assigns a URL to an embed element's src property.
*
* Example usage:
* goog.dom.safe.setEmbedSrc(embedEl, url);
* which is a safe alternative to
* embedEl.src = url;
* The latter can result in loading untrusted code unless it is ensured that
* the URL refers to a trustworthy resource.
*
* @param {!HTMLEmbedElement} embed The embed element whose src property
* is to be assigned to.
* @param {!goog.html.TrustedResourceUrl} url The URL to assign.
*/
goog.dom.safe.setEmbedSrc = function(embed, url) {
embed.src = goog.html.TrustedResourceUrl.unwrap(url);
};
/**
* Safely assigns a URL to a frame element's src property.
*
* Example usage:
* goog.dom.safe.setFrameSrc(frameEl, url);
* which is a safe alternative to
* frameEl.src = url;
* The latter can result in loading untrusted code unless it is ensured that
* the URL refers to a trustworthy resource.
*
* @param {!HTMLFrameElement} frame The frame element whose src property
* is to be assigned to.
* @param {!goog.html.TrustedResourceUrl} url The URL to assign.
*/
goog.dom.safe.setFrameSrc = function(frame, url) {
frame.src = goog.html.TrustedResourceUrl.unwrap(url);
};
/**
* Safely assigns a URL to an iframe element's src property.
*
* Example usage:
* goog.dom.safe.setIframeSrc(iframeEl, url);
* which is a safe alternative to
* iframeEl.src = url;
* The latter can result in loading untrusted code unless it is ensured that
* the URL refers to a trustworthy resource.
*
* @param {!HTMLIFrameElement} iframe The iframe element whose src property
* is to be assigned to.
* @param {!goog.html.TrustedResourceUrl} url The URL to assign.
*/
goog.dom.safe.setIframeSrc = function(iframe, url) {
iframe.src = goog.html.TrustedResourceUrl.unwrap(url);
};
/**
* Safely sets a link element's href and rel properties. Whether or not
* the URL assigned to href has to be a goog.html.TrustedResourceUrl
* depends on the value of the rel property. If rel contains "stylesheet"
* then a TrustedResourceUrl is required.
*
* Example usage:
* goog.dom.safe.setLinkHrefAndRel(linkEl, url, 'stylesheet');
* which is a safe alternative to
* linkEl.rel = 'stylesheet';
* linkEl.href = url;
* The latter can result in loading untrusted code unless it is ensured that
* the URL refers to a trustworthy resource.
*
* @param {!HTMLLinkElement} link The link element whose href property
* is to be assigned to.
* @param {string|!goog.html.SafeUrl|!goog.html.TrustedResourceUrl} url The URL
* to assign to the href property. Must be a TrustedResourceUrl if the
* value assigned to rel contains "stylesheet". A string value is
* sanitized with goog.html.SafeUrl.sanitize.
* @param {string} rel The value to assign to the rel property.
* @throws {Error} if rel contains "stylesheet" and url is not a
* TrustedResourceUrl
* @see goog.html.SafeUrl#sanitize
*/
goog.dom.safe.setLinkHrefAndRel = function(link, url, rel) {
link.rel = rel;
if (goog.string.caseInsensitiveContains(rel, 'stylesheet')) {
goog.asserts.assert(
url instanceof goog.html.TrustedResourceUrl,
'URL must be TrustedResourceUrl because "rel" contains "stylesheet"');
link.href = goog.html.TrustedResourceUrl.unwrap(url);
} else if (url instanceof goog.html.TrustedResourceUrl) {
link.href = goog.html.TrustedResourceUrl.unwrap(url);
} else if (url instanceof goog.html.SafeUrl) {
link.href = goog.html.SafeUrl.unwrap(url);
} else { // string
// SafeUrl.sanitize must return legitimate SafeUrl when passed a string.
link.href = goog.html.SafeUrl.sanitize(url).getTypedStringValue();
}
};
/**
* Safely assigns a URL to an object element's data property.
*
* Example usage:
* goog.dom.safe.setObjectData(objectEl, url);
* which is a safe alternative to
* objectEl.data = url;
* The latter can result in loading untrusted code unless setit is ensured that
* the URL refers to a trustworthy resource.
*
* @param {!HTMLObjectElement} object The object element whose data property
* is to be assigned to.
* @param {!goog.html.TrustedResourceUrl} url The URL to assign.
*/
goog.dom.safe.setObjectData = function(object, url) {
object.data = goog.html.TrustedResourceUrl.unwrap(url);
};
/**
* Safely assigns a URL to an iframe element's src property.
*
* Example usage:
* goog.dom.safe.setScriptSrc(scriptEl, url);
* which is a safe alternative to
* scriptEl.src = url;
* The latter can result in loading untrusted code unless it is ensured that
* the URL refers to a trustworthy resource.
*
* @param {!HTMLScriptElement} script The script element whose src property
* is to be assigned to.
* @param {!goog.html.TrustedResourceUrl} url The URL to assign.
*/
goog.dom.safe.setScriptSrc = function(script, url) {
script.src = goog.html.TrustedResourceUrl.unwrap(url);
};
/**
* Safely assigns a URL to a Location object's href property.
*
* If url is of type goog.html.SafeUrl, its value is unwrapped and assigned to
* loc's href property. If url is of type string however, it is first sanitized
* using goog.html.SafeUrl.sanitize.
*
* Example usage:
* goog.dom.safe.setLocationHref(document.location, redirectUrl);
* which is a safe alternative to
* document.location.href = redirectUrl;
* The latter can result in XSS vulnerabilities if redirectUrl is a
* user-/attacker-controlled value.
*
* @param {!Location} loc The Location object whose href property is to be
* assigned to.
* @param {string|!goog.html.SafeUrl} url The URL to assign.
* @see goog.html.SafeUrl#sanitize
*/
goog.dom.safe.setLocationHref = function(loc, url) {
/** @type {!goog.html.SafeUrl} */
var safeUrl;
if (url instanceof goog.html.SafeUrl) {
safeUrl = url;
} else {
safeUrl = goog.html.SafeUrl.sanitize(url);
}
loc.href = goog.html.SafeUrl.unwrap(safeUrl);
};
/**
* Safely opens a URL in a new window (via window.open).
*
* If url is of type goog.html.SafeUrl, its value is unwrapped and passed in to
* window.open. If url is of type string however, it is first sanitized
* using goog.html.SafeUrl.sanitize.
*
* Note that this function does not prevent leakages via the referer that is
* sent by window.open. It is advised to only use this to open 1st party URLs.
*
* Example usage:
* goog.dom.safe.openInWindow(url);
* which is a safe alternative to
* window.open(url);
* The latter can result in XSS vulnerabilities if redirectUrl is a
* user-/attacker-controlled value.
*
* @param {string|!goog.html.SafeUrl} url The URL to open.
* @param {Window=} opt_openerWin Window of which to call the .open() method.
* Defaults to the global window.
* @param {!goog.string.Const=} opt_name Name of the window to open in. Can be
* _top, etc as allowed by window.open().
* @param {string=} opt_specs Comma-separated list of specifications, same as
* in window.open().
* @param {boolean=} opt_replace Whether to replace the current entry in browser
* history, same as in window.open().
* @return {Window} Window the url was opened in.
*/
goog.dom.safe.openInWindow = function(
url, opt_openerWin, opt_name, opt_specs, opt_replace) {
/** @type {!goog.html.SafeUrl} */
var safeUrl;
if (url instanceof goog.html.SafeUrl) {
safeUrl = url;
} else {
safeUrl = goog.html.SafeUrl.sanitize(url);
}
var win = opt_openerWin || window;
return win.open(
goog.html.SafeUrl.unwrap(safeUrl),
// If opt_name is undefined, simply passing that in to open() causes IE to
// reuse the current window instead of opening a new one. Thus we pass ''
// in instead, which according to spec opens a new window. See
// https://html.spec.whatwg.org/multipage/browsers.html#dom-open .
opt_name ? goog.string.Const.unwrap(opt_name) : '', opt_specs,
opt_replace);
};

160
viz/out/goog/dom/tagname.js Normal file
View File

@ -0,0 +1,160 @@
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Defines the goog.dom.TagName enum. This enumerates
* all HTML tag names specified in either the the W3C HTML 4.01 index of
* elements or the HTML5 draft specification.
*
* References:
* http://www.w3.org/TR/html401/index/elements.html
* http://dev.w3.org/html5/spec/section-index.html
*
*/
goog.provide('goog.dom.TagName');
/**
* Enum of all html tag names specified by the W3C HTML4.01 and HTML5
* specifications.
* @enum {string}
*/
goog.dom.TagName = {
A: 'A',
ABBR: 'ABBR',
ACRONYM: 'ACRONYM',
ADDRESS: 'ADDRESS',
APPLET: 'APPLET',
AREA: 'AREA',
ARTICLE: 'ARTICLE',
ASIDE: 'ASIDE',
AUDIO: 'AUDIO',
B: 'B',
BASE: 'BASE',
BASEFONT: 'BASEFONT',
BDI: 'BDI',
BDO: 'BDO',
BIG: 'BIG',
BLOCKQUOTE: 'BLOCKQUOTE',
BODY: 'BODY',
BR: 'BR',
BUTTON: 'BUTTON',
CANVAS: 'CANVAS',
CAPTION: 'CAPTION',
CENTER: 'CENTER',
CITE: 'CITE',
CODE: 'CODE',
COL: 'COL',
COLGROUP: 'COLGROUP',
COMMAND: 'COMMAND',
DATA: 'DATA',
DATALIST: 'DATALIST',
DD: 'DD',
DEL: 'DEL',
DETAILS: 'DETAILS',
DFN: 'DFN',
DIALOG: 'DIALOG',
DIR: 'DIR',
DIV: 'DIV',
DL: 'DL',
DT: 'DT',
EM: 'EM',
EMBED: 'EMBED',
FIELDSET: 'FIELDSET',
FIGCAPTION: 'FIGCAPTION',
FIGURE: 'FIGURE',
FONT: 'FONT',
FOOTER: 'FOOTER',
FORM: 'FORM',
FRAME: 'FRAME',
FRAMESET: 'FRAMESET',
H1: 'H1',
H2: 'H2',
H3: 'H3',
H4: 'H4',
H5: 'H5',
H6: 'H6',
HEAD: 'HEAD',
HEADER: 'HEADER',
HGROUP: 'HGROUP',
HR: 'HR',
HTML: 'HTML',
I: 'I',
IFRAME: 'IFRAME',
IMG: 'IMG',
INPUT: 'INPUT',
INS: 'INS',
ISINDEX: 'ISINDEX',
KBD: 'KBD',
KEYGEN: 'KEYGEN',
LABEL: 'LABEL',
LEGEND: 'LEGEND',
LI: 'LI',
LINK: 'LINK',
MAP: 'MAP',
MARK: 'MARK',
MATH: 'MATH',
MENU: 'MENU',
META: 'META',
METER: 'METER',
NAV: 'NAV',
NOFRAMES: 'NOFRAMES',
NOSCRIPT: 'NOSCRIPT',
OBJECT: 'OBJECT',
OL: 'OL',
OPTGROUP: 'OPTGROUP',
OPTION: 'OPTION',
OUTPUT: 'OUTPUT',
P: 'P',
PARAM: 'PARAM',
PRE: 'PRE',
PROGRESS: 'PROGRESS',
Q: 'Q',
RP: 'RP',
RT: 'RT',
RUBY: 'RUBY',
S: 'S',
SAMP: 'SAMP',
SCRIPT: 'SCRIPT',
SECTION: 'SECTION',
SELECT: 'SELECT',
SMALL: 'SMALL',
SOURCE: 'SOURCE',
SPAN: 'SPAN',
STRIKE: 'STRIKE',
STRONG: 'STRONG',
STYLE: 'STYLE',
SUB: 'SUB',
SUMMARY: 'SUMMARY',
SUP: 'SUP',
SVG: 'SVG',
TABLE: 'TABLE',
TBODY: 'TBODY',
TD: 'TD',
TEMPLATE: 'TEMPLATE',
TEXTAREA: 'TEXTAREA',
TFOOT: 'TFOOT',
TH: 'TH',
THEAD: 'THEAD',
TIME: 'TIME',
TITLE: 'TITLE',
TR: 'TR',
TRACK: 'TRACK',
TT: 'TT',
U: 'U',
UL: 'UL',
VAR: 'VAR',
VIDEO: 'VIDEO',
WBR: 'WBR'
};

41
viz/out/goog/dom/tags.js Normal file
View File

@ -0,0 +1,41 @@
// Copyright 2014 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Utilities for HTML element tag names.
*/
goog.provide('goog.dom.tags');
goog.require('goog.object');
/**
* The void elements specified by
* http://www.w3.org/TR/html-markup/syntax.html#void-elements.
* @const @private {!Object<string, boolean>}
*/
goog.dom.tags.VOID_TAGS_ = goog.object.createSet(
'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input',
'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr');
/**
* Checks whether the tag is void (with no contents allowed and no legal end
* tag), for example 'br'.
* @param {string} tagName The tag name in lower case.
* @return {boolean}
*/
goog.dom.tags.isVoidTag = function(tagName) {
return goog.dom.tags.VOID_TAGS_[tagName] === true;
};

View File

@ -0,0 +1,402 @@
// Copyright 2005 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview A patched, standardized event object for browser events.
*
* <pre>
* The patched event object contains the following members:
* - type {string} Event type, e.g. 'click'
* - target {Object} The element that actually triggered the event
* - currentTarget {Object} The element the listener is attached to
* - relatedTarget {Object} For mouseover and mouseout, the previous object
* - offsetX {number} X-coordinate relative to target
* - offsetY {number} Y-coordinate relative to target
* - clientX {number} X-coordinate relative to viewport
* - clientY {number} Y-coordinate relative to viewport
* - screenX {number} X-coordinate relative to the edge of the screen
* - screenY {number} Y-coordinate relative to the edge of the screen
* - button {number} Mouse button. Use isButton() to test.
* - keyCode {number} Key-code
* - ctrlKey {boolean} Was ctrl key depressed
* - altKey {boolean} Was alt key depressed
* - shiftKey {boolean} Was shift key depressed
* - metaKey {boolean} Was meta key depressed
* - defaultPrevented {boolean} Whether the default action has been prevented
* - state {Object} History state object
*
* NOTE: The keyCode member contains the raw browser keyCode. For normalized
* key and character code use {@link goog.events.KeyHandler}.
* </pre>
*
* @author arv@google.com (Erik Arvidsson)
*/
goog.provide('goog.events.BrowserEvent');
goog.provide('goog.events.BrowserEvent.MouseButton');
goog.require('goog.events.BrowserFeature');
goog.require('goog.events.Event');
goog.require('goog.events.EventType');
goog.require('goog.reflect');
goog.require('goog.userAgent');
/**
* Accepts a browser event object and creates a patched, cross browser event
* object.
* The content of this object will not be initialized if no event object is
* provided. If this is the case, init() needs to be invoked separately.
* @param {Event=} opt_e Browser event object.
* @param {EventTarget=} opt_currentTarget Current target for event.
* @constructor
* @extends {goog.events.Event}
*/
goog.events.BrowserEvent = function(opt_e, opt_currentTarget) {
goog.events.BrowserEvent.base(this, 'constructor', opt_e ? opt_e.type : '');
/**
* Target that fired the event.
* @override
* @type {Node}
*/
this.target = null;
/**
* Node that had the listener attached.
* @override
* @type {Node|undefined}
*/
this.currentTarget = null;
/**
* For mouseover and mouseout events, the related object for the event.
* @type {Node}
*/
this.relatedTarget = null;
/**
* X-coordinate relative to target.
* @type {number}
*/
this.offsetX = 0;
/**
* Y-coordinate relative to target.
* @type {number}
*/
this.offsetY = 0;
/**
* X-coordinate relative to the window.
* @type {number}
*/
this.clientX = 0;
/**
* Y-coordinate relative to the window.
* @type {number}
*/
this.clientY = 0;
/**
* X-coordinate relative to the monitor.
* @type {number}
*/
this.screenX = 0;
/**
* Y-coordinate relative to the monitor.
* @type {number}
*/
this.screenY = 0;
/**
* Which mouse button was pressed.
* @type {number}
*/
this.button = 0;
/**
* Keycode of key press.
* @type {number}
*/
this.keyCode = 0;
/**
* Keycode of key press.
* @type {number}
*/
this.charCode = 0;
/**
* Whether control was pressed at time of event.
* @type {boolean}
*/
this.ctrlKey = false;
/**
* Whether alt was pressed at time of event.
* @type {boolean}
*/
this.altKey = false;
/**
* Whether shift was pressed at time of event.
* @type {boolean}
*/
this.shiftKey = false;
/**
* Whether the meta key was pressed at time of event.
* @type {boolean}
*/
this.metaKey = false;
/**
* History state object, only set for PopState events where it's a copy of the
* state object provided to pushState or replaceState.
* @type {Object}
*/
this.state = null;
/**
* Whether the default platform modifier key was pressed at time of event.
* (This is control for all platforms except Mac, where it's Meta.)
* @type {boolean}
*/
this.platformModifierKey = false;
/**
* The browser event object.
* @private {Event}
*/
this.event_ = null;
if (opt_e) {
this.init(opt_e, opt_currentTarget);
}
};
goog.inherits(goog.events.BrowserEvent, goog.events.Event);
/**
* Normalized button constants for the mouse.
* @enum {number}
*/
goog.events.BrowserEvent.MouseButton = {
LEFT: 0,
MIDDLE: 1,
RIGHT: 2
};
/**
* Static data for mapping mouse buttons.
* @type {!Array<number>}
*/
goog.events.BrowserEvent.IEButtonMap = [
1, // LEFT
4, // MIDDLE
2 // RIGHT
];
/**
* Accepts a browser event object and creates a patched, cross browser event
* object.
* @param {Event} e Browser event object.
* @param {EventTarget=} opt_currentTarget Current target for event.
*/
goog.events.BrowserEvent.prototype.init = function(e, opt_currentTarget) {
var type = this.type = e.type;
/**
* On touch devices use the first "changed touch" as the relevant touch.
* @type {Touch}
*/
var relevantTouch = e.changedTouches ? e.changedTouches[0] : null;
// TODO(nicksantos): Change this.target to type EventTarget.
this.target = /** @type {Node} */ (e.target) || e.srcElement;
// TODO(nicksantos): Change this.currentTarget to type EventTarget.
this.currentTarget = /** @type {Node} */ (opt_currentTarget);
var relatedTarget = /** @type {Node} */ (e.relatedTarget);
if (relatedTarget) {
// There's a bug in FireFox where sometimes, relatedTarget will be a
// chrome element, and accessing any property of it will get a permission
// denied exception. See:
// https://bugzilla.mozilla.org/show_bug.cgi?id=497780
if (goog.userAgent.GECKO) {
if (!goog.reflect.canAccessProperty(relatedTarget, 'nodeName')) {
relatedTarget = null;
}
}
// TODO(arv): Use goog.events.EventType when it has been refactored into its
// own file.
} else if (type == goog.events.EventType.MOUSEOVER) {
relatedTarget = e.fromElement;
} else if (type == goog.events.EventType.MOUSEOUT) {
relatedTarget = e.toElement;
}
this.relatedTarget = relatedTarget;
if (!goog.isNull(relevantTouch)) {
this.clientX = relevantTouch.clientX !== undefined ? relevantTouch.clientX :
relevantTouch.pageX;
this.clientY = relevantTouch.clientY !== undefined ? relevantTouch.clientY :
relevantTouch.pageY;
this.screenX = relevantTouch.screenX || 0;
this.screenY = relevantTouch.screenY || 0;
} else {
// Webkit emits a lame warning whenever layerX/layerY is accessed.
// http://code.google.com/p/chromium/issues/detail?id=101733
this.offsetX = (goog.userAgent.WEBKIT || e.offsetX !== undefined) ?
e.offsetX :
e.layerX;
this.offsetY = (goog.userAgent.WEBKIT || e.offsetY !== undefined) ?
e.offsetY :
e.layerY;
this.clientX = e.clientX !== undefined ? e.clientX : e.pageX;
this.clientY = e.clientY !== undefined ? e.clientY : e.pageY;
this.screenX = e.screenX || 0;
this.screenY = e.screenY || 0;
}
this.button = e.button;
this.keyCode = e.keyCode || 0;
this.charCode = e.charCode || (type == 'keypress' ? e.keyCode : 0);
this.ctrlKey = e.ctrlKey;
this.altKey = e.altKey;
this.shiftKey = e.shiftKey;
this.metaKey = e.metaKey;
this.platformModifierKey = goog.userAgent.MAC ? e.metaKey : e.ctrlKey;
this.state = e.state;
this.event_ = e;
if (e.defaultPrevented) {
this.preventDefault();
}
};
/**
* Tests to see which button was pressed during the event. This is really only
* useful in IE and Gecko browsers. And in IE, it's only useful for
* mousedown/mouseup events, because click only fires for the left mouse button.
*
* Safari 2 only reports the left button being clicked, and uses the value '1'
* instead of 0. Opera only reports a mousedown event for the middle button, and
* no mouse events for the right button. Opera has default behavior for left and
* middle click that can only be overridden via a configuration setting.
*
* There's a nice table of this mess at http://www.unixpapa.com/js/mouse.html.
*
* @param {goog.events.BrowserEvent.MouseButton} button The button
* to test for.
* @return {boolean} True if button was pressed.
*/
goog.events.BrowserEvent.prototype.isButton = function(button) {
if (!goog.events.BrowserFeature.HAS_W3C_BUTTON) {
if (this.type == 'click') {
return button == goog.events.BrowserEvent.MouseButton.LEFT;
} else {
return !!(
this.event_.button & goog.events.BrowserEvent.IEButtonMap[button]);
}
} else {
return this.event_.button == button;
}
};
/**
* Whether this has an "action"-producing mouse button.
*
* By definition, this includes left-click on windows/linux, and left-click
* without the ctrl key on Macs.
*
* @return {boolean} The result.
*/
goog.events.BrowserEvent.prototype.isMouseActionButton = function() {
// Webkit does not ctrl+click to be a right-click, so we
// normalize it to behave like Gecko and Opera.
return this.isButton(goog.events.BrowserEvent.MouseButton.LEFT) &&
!(goog.userAgent.WEBKIT && goog.userAgent.MAC && this.ctrlKey);
};
/**
* @override
*/
goog.events.BrowserEvent.prototype.stopPropagation = function() {
goog.events.BrowserEvent.superClass_.stopPropagation.call(this);
if (this.event_.stopPropagation) {
this.event_.stopPropagation();
} else {
this.event_.cancelBubble = true;
}
};
/**
* @override
*/
goog.events.BrowserEvent.prototype.preventDefault = function() {
goog.events.BrowserEvent.superClass_.preventDefault.call(this);
var be = this.event_;
if (!be.preventDefault) {
be.returnValue = false;
if (goog.events.BrowserFeature.SET_KEY_CODE_TO_PREVENT_DEFAULT) {
/** @preserveTry */
try {
// Most keys can be prevented using returnValue. Some special keys
// require setting the keyCode to -1 as well:
//
// In IE7:
// F3, F5, F10, F11, Ctrl+P, Crtl+O, Ctrl+F (these are taken from IE6)
//
// In IE8:
// Ctrl+P, Crtl+O, Ctrl+F (F1-F12 cannot be stopped through the event)
//
// We therefore do this for all function keys as well as when Ctrl key
// is pressed.
var VK_F1 = 112;
var VK_F12 = 123;
if (be.ctrlKey || be.keyCode >= VK_F1 && be.keyCode <= VK_F12) {
be.keyCode = -1;
}
} catch (ex) {
// IE throws an 'access denied' exception when trying to change
// keyCode in some situations (e.g. srcElement is input[type=file],
// or srcElement is an anchor tag rewritten by parent's innerHTML).
// Do nothing in this case.
}
}
} else {
be.preventDefault();
}
};
/**
* @return {Event} The underlying browser event object.
*/
goog.events.BrowserEvent.prototype.getBrowserEvent = function() {
return this.event_;
};

View File

@ -0,0 +1,84 @@
// Copyright 2010 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Browser capability checks for the events package.
*
*/
goog.provide('goog.events.BrowserFeature');
goog.require('goog.userAgent');
/**
* Enum of browser capabilities.
* @enum {boolean}
*/
goog.events.BrowserFeature = {
/**
* Whether the button attribute of the event is W3C compliant. False in
* Internet Explorer prior to version 9; document-version dependent.
*/
HAS_W3C_BUTTON:
!goog.userAgent.IE || goog.userAgent.isDocumentModeOrHigher(9),
/**
* Whether the browser supports full W3C event model.
*/
HAS_W3C_EVENT_SUPPORT:
!goog.userAgent.IE || goog.userAgent.isDocumentModeOrHigher(9),
/**
* To prevent default in IE7-8 for certain keydown events we need set the
* keyCode to -1.
*/
SET_KEY_CODE_TO_PREVENT_DEFAULT:
goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('9'),
/**
* Whether the {@code navigator.onLine} property is supported.
*/
HAS_NAVIGATOR_ONLINE_PROPERTY:
!goog.userAgent.WEBKIT || goog.userAgent.isVersionOrHigher('528'),
/**
* Whether HTML5 network online/offline events are supported.
*/
HAS_HTML5_NETWORK_EVENT_SUPPORT:
goog.userAgent.GECKO && goog.userAgent.isVersionOrHigher('1.9b') ||
goog.userAgent.IE && goog.userAgent.isVersionOrHigher('8') ||
goog.userAgent.OPERA && goog.userAgent.isVersionOrHigher('9.5') ||
goog.userAgent.WEBKIT && goog.userAgent.isVersionOrHigher('528'),
/**
* Whether HTML5 network events fire on document.body, or otherwise the
* window.
*/
HTML5_NETWORK_EVENTS_FIRE_ON_BODY:
goog.userAgent.GECKO && !goog.userAgent.isVersionOrHigher('8') ||
goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('9'),
/**
* Whether touch is enabled in the browser.
*/
TOUCH_ENABLED:
('ontouchstart' in goog.global ||
!!(goog.global['document'] && document.documentElement &&
'ontouchstart' in document.documentElement) ||
// IE10 uses non-standard touch events, so it has a different check.
!!(goog.global['navigator'] &&
goog.global['navigator']['msMaxTouchPoints']))
};

View File

@ -0,0 +1,145 @@
// Copyright 2005 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview A base class for event objects.
*
*/
goog.provide('goog.events.Event');
goog.provide('goog.events.EventLike');
/**
* goog.events.Event no longer depends on goog.Disposable. Keep requiring
* goog.Disposable here to not break projects which assume this dependency.
* @suppress {extraRequire}
*/
goog.require('goog.Disposable');
goog.require('goog.events.EventId');
/**
* A typedef for event like objects that are dispatchable via the
* goog.events.dispatchEvent function. strings are treated as the type for a
* goog.events.Event. Objects are treated as an extension of a new
* goog.events.Event with the type property of the object being used as the type
* of the Event.
* @typedef {string|Object|goog.events.Event|goog.events.EventId}
*/
goog.events.EventLike;
/**
* A base class for event objects, so that they can support preventDefault and
* stopPropagation.
*
* @suppress {underscore} Several properties on this class are technically
* public, but referencing these properties outside this package is strongly
* discouraged.
*
* @param {string|!goog.events.EventId} type Event Type.
* @param {Object=} opt_target Reference to the object that is the target of
* this event. It has to implement the {@code EventTarget} interface
* declared at {@link http://developer.mozilla.org/en/DOM/EventTarget}.
* @constructor
*/
goog.events.Event = function(type, opt_target) {
/**
* Event type.
* @type {string}
*/
this.type = type instanceof goog.events.EventId ? String(type) : type;
/**
* TODO(tbreisacher): The type should probably be
* EventTarget|goog.events.EventTarget.
*
* Target of the event.
* @type {Object|undefined}
*/
this.target = opt_target;
/**
* Object that had the listener attached.
* @type {Object|undefined}
*/
this.currentTarget = this.target;
/**
* Whether to cancel the event in internal capture/bubble processing for IE.
* @type {boolean}
* @public
*/
this.propagationStopped_ = false;
/**
* Whether the default action has been prevented.
* This is a property to match the W3C specification at
* {@link http://www.w3.org/TR/DOM-Level-3-Events/
* #events-event-type-defaultPrevented}.
* Must be treated as read-only outside the class.
* @type {boolean}
*/
this.defaultPrevented = false;
/**
* Return value for in internal capture/bubble processing for IE.
* @type {boolean}
* @public
* @suppress {underscore|visibility} Technically public, but referencing this
* outside this package is strongly discouraged.
*/
this.returnValue_ = true;
};
/**
* Stops event propagation.
*/
goog.events.Event.prototype.stopPropagation = function() {
this.propagationStopped_ = true;
};
/**
* Prevents the default action, for example a link redirecting to a url.
*/
goog.events.Event.prototype.preventDefault = function() {
this.defaultPrevented = true;
this.returnValue_ = false;
};
/**
* Stops the propagation of the event. It is equivalent to
* {@code e.stopPropagation()}, but can be used as the callback argument of
* {@link goog.events.listen} without declaring another function.
* @param {!goog.events.Event} e An event.
*/
goog.events.Event.stopPropagation = function(e) {
e.stopPropagation();
};
/**
* Prevents the default action. It is equivalent to
* {@code e.preventDefault()}, but can be used as the callback argument of
* {@link goog.events.listen} without declaring another function.
* @param {!goog.events.Event} e An event.
*/
goog.events.Event.preventDefault = function(e) {
e.preventDefault();
};

View File

@ -0,0 +1,46 @@
// Copyright 2013 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('goog.events.EventId');
/**
* A templated class that is used when registering for events. Typical usage:
*
* /** @type {goog.events.EventId<MyEventObj>} *\
* var myEventId = new goog.events.EventId(
* goog.events.getUniqueId(('someEvent'));
*
* // No need to cast or declare here since the compiler knows the
* // correct type of 'evt' (MyEventObj).
* something.listen(myEventId, function(evt) {});
*
* @param {string} eventId
* @template T
* @constructor
* @struct
* @final
*/
goog.events.EventId = function(eventId) {
/** @const */ this.id = eventId;
};
/**
* @override
*/
goog.events.EventId.prototype.toString = function() {
return this.id;
};

View File

@ -0,0 +1,989 @@
// Copyright 2005 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview An event manager for both native browser event
* targets and custom JavaScript event targets
* ({@code goog.events.Listenable}). This provides an abstraction
* over browsers' event systems.
*
* It also provides a simulation of W3C event model's capture phase in
* Internet Explorer (IE 8 and below). Caveat: the simulation does not
* interact well with listeners registered directly on the elements
* (bypassing goog.events) or even with listeners registered via
* goog.events in a separate JS binary. In these cases, we provide
* no ordering guarantees.
*
* The listeners will receive a "patched" event object. Such event object
* contains normalized values for certain event properties that differs in
* different browsers.
*
* Example usage:
* <pre>
* goog.events.listen(myNode, 'click', function(e) { alert('woo') });
* goog.events.listen(myNode, 'mouseover', mouseHandler, true);
* goog.events.unlisten(myNode, 'mouseover', mouseHandler, true);
* goog.events.removeAll(myNode);
* </pre>
*
* in IE and event object patching]
* @author arv@google.com (Erik Arvidsson)
*
* @see ../demos/events.html
* @see ../demos/event-propagation.html
* @see ../demos/stopevent.html
*/
// IMPLEMENTATION NOTES:
// goog.events stores an auxiliary data structure on each EventTarget
// source being listened on. This allows us to take advantage of GC,
// having the data structure GC'd when the EventTarget is GC'd. This
// GC behavior is equivalent to using W3C DOM Events directly.
goog.provide('goog.events');
goog.provide('goog.events.CaptureSimulationMode');
goog.provide('goog.events.Key');
goog.provide('goog.events.ListenableType');
goog.require('goog.asserts');
goog.require('goog.debug.entryPointRegistry');
goog.require('goog.events.BrowserEvent');
goog.require('goog.events.BrowserFeature');
goog.require('goog.events.Listenable');
goog.require('goog.events.ListenerMap');
goog.forwardDeclare('goog.debug.ErrorHandler');
goog.forwardDeclare('goog.events.EventWrapper');
/**
* @typedef {number|goog.events.ListenableKey}
*/
goog.events.Key;
/**
* @typedef {EventTarget|goog.events.Listenable}
*/
goog.events.ListenableType;
/**
* Property name on a native event target for the listener map
* associated with the event target.
* @private @const {string}
*/
goog.events.LISTENER_MAP_PROP_ = 'closure_lm_' + ((Math.random() * 1e6) | 0);
/**
* String used to prepend to IE event types.
* @const
* @private
*/
goog.events.onString_ = 'on';
/**
* Map of computed "on<eventname>" strings for IE event types. Caching
* this removes an extra object allocation in goog.events.listen which
* improves IE6 performance.
* @const
* @dict
* @private
*/
goog.events.onStringMap_ = {};
/**
* @enum {number} Different capture simulation mode for IE8-.
*/
goog.events.CaptureSimulationMode = {
/**
* Does not perform capture simulation. Will asserts in IE8- when you
* add capture listeners.
*/
OFF_AND_FAIL: 0,
/**
* Does not perform capture simulation, silently ignore capture
* listeners.
*/
OFF_AND_SILENT: 1,
/**
* Performs capture simulation.
*/
ON: 2
};
/**
* @define {number} The capture simulation mode for IE8-. By default,
* this is ON.
*/
goog.define('goog.events.CAPTURE_SIMULATION_MODE', 2);
/**
* Estimated count of total native listeners.
* @private {number}
*/
goog.events.listenerCountEstimate_ = 0;
/**
* Adds an event listener for a specific event on a native event
* target (such as a DOM element) or an object that has implemented
* {@link goog.events.Listenable}. A listener can only be added once
* to an object and if it is added again the key for the listener is
* returned. Note that if the existing listener is a one-off listener
* (registered via listenOnce), it will no longer be a one-off
* listener after a call to listen().
*
* @param {EventTarget|goog.events.Listenable} src The node to listen
* to events on.
* @param {string|Array<string>|
* !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}
* type Event type or array of event types.
* @param {function(this:T, EVENTOBJ):?|{handleEvent:function(?):?}|null}
* listener Callback method, or an object with a handleEvent function.
* WARNING: passing an Object is now softly deprecated.
* @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
* false).
* @param {T=} opt_handler Element in whose scope to call the listener.
* @return {goog.events.Key} Unique key for the listener.
* @template T,EVENTOBJ
*/
goog.events.listen = function(src, type, listener, opt_capt, opt_handler) {
if (goog.isArray(type)) {
for (var i = 0; i < type.length; i++) {
goog.events.listen(src, type[i], listener, opt_capt, opt_handler);
}
return null;
}
listener = goog.events.wrapListener(listener);
if (goog.events.Listenable.isImplementedBy(src)) {
return src.listen(
/** @type {string|!goog.events.EventId} */ (type), listener, opt_capt,
opt_handler);
} else {
return goog.events.listen_(
/** @type {!EventTarget} */ (src),
/** @type {string|!goog.events.EventId} */ (type), listener,
/* callOnce */ false, opt_capt, opt_handler);
}
};
/**
* Adds an event listener for a specific event on a native event
* target. A listener can only be added once to an object and if it
* is added again the key for the listener is returned.
*
* Note that a one-off listener will not change an existing listener,
* if any. On the other hand a normal listener will change existing
* one-off listener to become a normal listener.
*
* @param {EventTarget} src The node to listen to events on.
* @param {string|!goog.events.EventId} type Event type.
* @param {!Function} listener Callback function.
* @param {boolean} callOnce Whether the listener is a one-off
* listener or otherwise.
* @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
* false).
* @param {Object=} opt_handler Element in whose scope to call the listener.
* @return {goog.events.ListenableKey} Unique key for the listener.
* @private
*/
goog.events.listen_ = function(
src, type, listener, callOnce, opt_capt, opt_handler) {
if (!type) {
throw Error('Invalid event type');
}
var capture = !!opt_capt;
if (capture && !goog.events.BrowserFeature.HAS_W3C_EVENT_SUPPORT) {
if (goog.events.CAPTURE_SIMULATION_MODE ==
goog.events.CaptureSimulationMode.OFF_AND_FAIL) {
goog.asserts.fail('Can not register capture listener in IE8-.');
return null;
} else if (
goog.events.CAPTURE_SIMULATION_MODE ==
goog.events.CaptureSimulationMode.OFF_AND_SILENT) {
return null;
}
}
var listenerMap = goog.events.getListenerMap_(src);
if (!listenerMap) {
src[goog.events.LISTENER_MAP_PROP_] = listenerMap =
new goog.events.ListenerMap(src);
}
var listenerObj =
listenerMap.add(type, listener, callOnce, opt_capt, opt_handler);
// If the listenerObj already has a proxy, it has been set up
// previously. We simply return.
if (listenerObj.proxy) {
return listenerObj;
}
var proxy = goog.events.getProxy();
listenerObj.proxy = proxy;
proxy.src = src;
proxy.listener = listenerObj;
// Attach the proxy through the browser's API
if (src.addEventListener) {
src.addEventListener(type.toString(), proxy, capture);
} else if (src.attachEvent) {
// The else if above used to be an unconditional else. It would call
// exception on IE11, spoiling the day of some callers. The previous
// incarnation of this code, from 2007, indicates that it replaced an
// earlier still version that caused excess allocations on IE6.
src.attachEvent(goog.events.getOnString_(type.toString()), proxy);
} else {
throw Error('addEventListener and attachEvent are unavailable.');
}
goog.events.listenerCountEstimate_++;
return listenerObj;
};
/**
* Helper function for returning a proxy function.
* @return {!Function} A new or reused function object.
*/
goog.events.getProxy = function() {
var proxyCallbackFunction = goog.events.handleBrowserEvent_;
// Use a local var f to prevent one allocation.
var f =
goog.events.BrowserFeature.HAS_W3C_EVENT_SUPPORT ? function(eventObject) {
return proxyCallbackFunction.call(f.src, f.listener, eventObject);
} : function(eventObject) {
var v = proxyCallbackFunction.call(f.src, f.listener, eventObject);
// NOTE(chrishenry): In IE, we hack in a capture phase. However, if
// there is inline event handler which tries to prevent default (for
// example <a href="..." onclick="return false">...</a>) in a
// descendant element, the prevent default will be overridden
// by this listener if this listener were to return true. Hence, we
// return undefined.
if (!v) return v;
};
return f;
};
/**
* Adds an event listener for a specific event on a native event
* target (such as a DOM element) or an object that has implemented
* {@link goog.events.Listenable}. After the event has fired the event
* listener is removed from the target.
*
* If an existing listener already exists, listenOnce will do
* nothing. In particular, if the listener was previously registered
* via listen(), listenOnce() will not turn the listener into a
* one-off listener. Similarly, if there is already an existing
* one-off listener, listenOnce does not modify the listeners (it is
* still a once listener).
*
* @param {EventTarget|goog.events.Listenable} src The node to listen
* to events on.
* @param {string|Array<string>|
* !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}
* type Event type or array of event types.
* @param {function(this:T, EVENTOBJ):?|{handleEvent:function(?):?}|null}
* listener Callback method.
* @param {boolean=} opt_capt Fire in capture phase?.
* @param {T=} opt_handler Element in whose scope to call the listener.
* @return {goog.events.Key} Unique key for the listener.
* @template T,EVENTOBJ
*/
goog.events.listenOnce = function(src, type, listener, opt_capt, opt_handler) {
if (goog.isArray(type)) {
for (var i = 0; i < type.length; i++) {
goog.events.listenOnce(src, type[i], listener, opt_capt, opt_handler);
}
return null;
}
listener = goog.events.wrapListener(listener);
if (goog.events.Listenable.isImplementedBy(src)) {
return src.listenOnce(
/** @type {string|!goog.events.EventId} */ (type), listener, opt_capt,
opt_handler);
} else {
return goog.events.listen_(
/** @type {!EventTarget} */ (src),
/** @type {string|!goog.events.EventId} */ (type), listener,
/* callOnce */ true, opt_capt, opt_handler);
}
};
/**
* Adds an event listener with a specific event wrapper on a DOM Node or an
* object that has implemented {@link goog.events.Listenable}. A listener can
* only be added once to an object.
*
* @param {EventTarget|goog.events.Listenable} src The target to
* listen to events on.
* @param {goog.events.EventWrapper} wrapper Event wrapper to use.
* @param {function(this:T, ?):?|{handleEvent:function(?):?}|null} listener
* Callback method, or an object with a handleEvent function.
* @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
* false).
* @param {T=} opt_handler Element in whose scope to call the listener.
* @template T
*/
goog.events.listenWithWrapper = function(
src, wrapper, listener, opt_capt, opt_handler) {
wrapper.listen(src, listener, opt_capt, opt_handler);
};
/**
* Removes an event listener which was added with listen().
*
* @param {EventTarget|goog.events.Listenable} src The target to stop
* listening to events on.
* @param {string|Array<string>|
* !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}
* type Event type or array of event types to unlisten to.
* @param {function(?):?|{handleEvent:function(?):?}|null} listener The
* listener function to remove.
* @param {boolean=} opt_capt In DOM-compliant browsers, this determines
* whether the listener is fired during the capture or bubble phase of the
* event.
* @param {Object=} opt_handler Element in whose scope to call the listener.
* @return {?boolean} indicating whether the listener was there to remove.
* @template EVENTOBJ
*/
goog.events.unlisten = function(src, type, listener, opt_capt, opt_handler) {
if (goog.isArray(type)) {
for (var i = 0; i < type.length; i++) {
goog.events.unlisten(src, type[i], listener, opt_capt, opt_handler);
}
return null;
}
listener = goog.events.wrapListener(listener);
if (goog.events.Listenable.isImplementedBy(src)) {
return src.unlisten(
/** @type {string|!goog.events.EventId} */ (type), listener, opt_capt,
opt_handler);
}
if (!src) {
// TODO(chrishenry): We should tighten the API to only accept
// non-null objects, or add an assertion here.
return false;
}
var capture = !!opt_capt;
var listenerMap = goog.events.getListenerMap_(
/** @type {!EventTarget} */ (src));
if (listenerMap) {
var listenerObj = listenerMap.getListener(
/** @type {string|!goog.events.EventId} */ (type), listener, capture,
opt_handler);
if (listenerObj) {
return goog.events.unlistenByKey(listenerObj);
}
}
return false;
};
/**
* Removes an event listener which was added with listen() by the key
* returned by listen().
*
* @param {goog.events.Key} key The key returned by listen() for this
* event listener.
* @return {boolean} indicating whether the listener was there to remove.
*/
goog.events.unlistenByKey = function(key) {
// TODO(chrishenry): Remove this check when tests that rely on this
// are fixed.
if (goog.isNumber(key)) {
return false;
}
var listener = key;
if (!listener || listener.removed) {
return false;
}
var src = listener.src;
if (goog.events.Listenable.isImplementedBy(src)) {
return /** @type {!goog.events.Listenable} */ (src).unlistenByKey(listener);
}
var type = listener.type;
var proxy = listener.proxy;
if (src.removeEventListener) {
src.removeEventListener(type, proxy, listener.capture);
} else if (src.detachEvent) {
src.detachEvent(goog.events.getOnString_(type), proxy);
}
goog.events.listenerCountEstimate_--;
var listenerMap = goog.events.getListenerMap_(
/** @type {!EventTarget} */ (src));
// TODO(chrishenry): Try to remove this conditional and execute the
// first branch always. This should be safe.
if (listenerMap) {
listenerMap.removeByKey(listener);
if (listenerMap.getTypeCount() == 0) {
// Null the src, just because this is simple to do (and useful
// for IE <= 7).
listenerMap.src = null;
// We don't use delete here because IE does not allow delete
// on a window object.
src[goog.events.LISTENER_MAP_PROP_] = null;
}
} else {
listener.markAsRemoved();
}
return true;
};
/**
* Removes an event listener which was added with listenWithWrapper().
*
* @param {EventTarget|goog.events.Listenable} src The target to stop
* listening to events on.
* @param {goog.events.EventWrapper} wrapper Event wrapper to use.
* @param {function(?):?|{handleEvent:function(?):?}|null} listener The
* listener function to remove.
* @param {boolean=} opt_capt In DOM-compliant browsers, this determines
* whether the listener is fired during the capture or bubble phase of the
* event.
* @param {Object=} opt_handler Element in whose scope to call the listener.
*/
goog.events.unlistenWithWrapper = function(
src, wrapper, listener, opt_capt, opt_handler) {
wrapper.unlisten(src, listener, opt_capt, opt_handler);
};
/**
* Removes all listeners from an object. You can also optionally
* remove listeners of a particular type.
*
* @param {Object|undefined} obj Object to remove listeners from. Must be an
* EventTarget or a goog.events.Listenable.
* @param {string|!goog.events.EventId=} opt_type Type of event to remove.
* Default is all types.
* @return {number} Number of listeners removed.
*/
goog.events.removeAll = function(obj, opt_type) {
// TODO(chrishenry): Change the type of obj to
// (!EventTarget|!goog.events.Listenable).
if (!obj) {
return 0;
}
if (goog.events.Listenable.isImplementedBy(obj)) {
return /** @type {?} */ (obj).removeAllListeners(opt_type);
}
var listenerMap = goog.events.getListenerMap_(
/** @type {!EventTarget} */ (obj));
if (!listenerMap) {
return 0;
}
var count = 0;
var typeStr = opt_type && opt_type.toString();
for (var type in listenerMap.listeners) {
if (!typeStr || type == typeStr) {
// Clone so that we don't need to worry about unlistenByKey
// changing the content of the ListenerMap.
var listeners = listenerMap.listeners[type].concat();
for (var i = 0; i < listeners.length; ++i) {
if (goog.events.unlistenByKey(listeners[i])) {
++count;
}
}
}
}
return count;
};
/**
* Gets the listeners for a given object, type and capture phase.
*
* @param {Object} obj Object to get listeners for.
* @param {string|!goog.events.EventId} type Event type.
* @param {boolean} capture Capture phase?.
* @return {Array<!goog.events.Listener>} Array of listener objects.
*/
goog.events.getListeners = function(obj, type, capture) {
if (goog.events.Listenable.isImplementedBy(obj)) {
return /** @type {!goog.events.Listenable} */ (obj).getListeners(
type, capture);
} else {
if (!obj) {
// TODO(chrishenry): We should tighten the API to accept
// !EventTarget|goog.events.Listenable, and add an assertion here.
return [];
}
var listenerMap = goog.events.getListenerMap_(
/** @type {!EventTarget} */ (obj));
return listenerMap ? listenerMap.getListeners(type, capture) : [];
}
};
/**
* Gets the goog.events.Listener for the event or null if no such listener is
* in use.
*
* @param {EventTarget|goog.events.Listenable} src The target from
* which to get listeners.
* @param {?string|!goog.events.EventId<EVENTOBJ>} type The type of the event.
* @param {function(EVENTOBJ):?|{handleEvent:function(?):?}|null} listener The
* listener function to get.
* @param {boolean=} opt_capt In DOM-compliant browsers, this determines
* whether the listener is fired during the
* capture or bubble phase of the event.
* @param {Object=} opt_handler Element in whose scope to call the listener.
* @return {goog.events.ListenableKey} the found listener or null if not found.
* @template EVENTOBJ
*/
goog.events.getListener = function(src, type, listener, opt_capt, opt_handler) {
// TODO(chrishenry): Change type from ?string to string, or add assertion.
type = /** @type {string} */ (type);
listener = goog.events.wrapListener(listener);
var capture = !!opt_capt;
if (goog.events.Listenable.isImplementedBy(src)) {
return src.getListener(type, listener, capture, opt_handler);
}
if (!src) {
// TODO(chrishenry): We should tighten the API to only accept
// non-null objects, or add an assertion here.
return null;
}
var listenerMap = goog.events.getListenerMap_(
/** @type {!EventTarget} */ (src));
if (listenerMap) {
return listenerMap.getListener(type, listener, capture, opt_handler);
}
return null;
};
/**
* Returns whether an event target has any active listeners matching the
* specified signature. If either the type or capture parameters are
* unspecified, the function will match on the remaining criteria.
*
* @param {EventTarget|goog.events.Listenable} obj Target to get
* listeners for.
* @param {string|!goog.events.EventId=} opt_type Event type.
* @param {boolean=} opt_capture Whether to check for capture or bubble-phase
* listeners.
* @return {boolean} Whether an event target has one or more listeners matching
* the requested type and/or capture phase.
*/
goog.events.hasListener = function(obj, opt_type, opt_capture) {
if (goog.events.Listenable.isImplementedBy(obj)) {
return obj.hasListener(opt_type, opt_capture);
}
var listenerMap = goog.events.getListenerMap_(
/** @type {!EventTarget} */ (obj));
return !!listenerMap && listenerMap.hasListener(opt_type, opt_capture);
};
/**
* Provides a nice string showing the normalized event objects public members
* @param {Object} e Event Object.
* @return {string} String of the public members of the normalized event object.
*/
goog.events.expose = function(e) {
var str = [];
for (var key in e) {
if (e[key] && e[key].id) {
str.push(key + ' = ' + e[key] + ' (' + e[key].id + ')');
} else {
str.push(key + ' = ' + e[key]);
}
}
return str.join('\n');
};
/**
* Returns a string with on prepended to the specified type. This is used for IE
* which expects "on" to be prepended. This function caches the string in order
* to avoid extra allocations in steady state.
* @param {string} type Event type.
* @return {string} The type string with 'on' prepended.
* @private
*/
goog.events.getOnString_ = function(type) {
if (type in goog.events.onStringMap_) {
return goog.events.onStringMap_[type];
}
return goog.events.onStringMap_[type] = goog.events.onString_ + type;
};
/**
* Fires an object's listeners of a particular type and phase
*
* @param {Object} obj Object whose listeners to call.
* @param {string|!goog.events.EventId} type Event type.
* @param {boolean} capture Which event phase.
* @param {Object} eventObject Event object to be passed to listener.
* @return {boolean} True if all listeners returned true else false.
*/
goog.events.fireListeners = function(obj, type, capture, eventObject) {
if (goog.events.Listenable.isImplementedBy(obj)) {
return /** @type {!goog.events.Listenable} */ (obj).fireListeners(
type, capture, eventObject);
}
return goog.events.fireListeners_(obj, type, capture, eventObject);
};
/**
* Fires an object's listeners of a particular type and phase.
* @param {Object} obj Object whose listeners to call.
* @param {string|!goog.events.EventId} type Event type.
* @param {boolean} capture Which event phase.
* @param {Object} eventObject Event object to be passed to listener.
* @return {boolean} True if all listeners returned true else false.
* @private
*/
goog.events.fireListeners_ = function(obj, type, capture, eventObject) {
/** @type {boolean} */
var retval = true;
var listenerMap = goog.events.getListenerMap_(
/** @type {EventTarget} */ (obj));
if (listenerMap) {
// TODO(chrishenry): Original code avoids array creation when there
// is no listener, so we do the same. If this optimization turns
// out to be not required, we can replace this with
// listenerMap.getListeners(type, capture) instead, which is simpler.
var listenerArray = listenerMap.listeners[type.toString()];
if (listenerArray) {
listenerArray = listenerArray.concat();
for (var i = 0; i < listenerArray.length; i++) {
var listener = listenerArray[i];
// We might not have a listener if the listener was removed.
if (listener && listener.capture == capture && !listener.removed) {
var result = goog.events.fireListener(listener, eventObject);
retval = retval && (result !== false);
}
}
}
}
return retval;
};
/**
* Fires a listener with a set of arguments
*
* @param {goog.events.Listener} listener The listener object to call.
* @param {Object} eventObject The event object to pass to the listener.
* @return {boolean} Result of listener.
*/
goog.events.fireListener = function(listener, eventObject) {
var listenerFn = listener.listener;
var listenerHandler = listener.handler || listener.src;
if (listener.callOnce) {
goog.events.unlistenByKey(listener);
}
return listenerFn.call(listenerHandler, eventObject);
};
/**
* Gets the total number of listeners currently in the system.
* @return {number} Number of listeners.
* @deprecated This returns estimated count, now that Closure no longer
* stores a central listener registry. We still return an estimation
* to keep existing listener-related tests passing. In the near future,
* this function will be removed.
*/
goog.events.getTotalListenerCount = function() {
return goog.events.listenerCountEstimate_;
};
/**
* Dispatches an event (or event like object) and calls all listeners
* listening for events of this type. The type of the event is decided by the
* type property on the event object.
*
* If any of the listeners returns false OR calls preventDefault then this
* function will return false. If one of the capture listeners calls
* stopPropagation, then the bubble listeners won't fire.
*
* @param {goog.events.Listenable} src The event target.
* @param {goog.events.EventLike} e Event object.
* @return {boolean} If anyone called preventDefault on the event object (or
* if any of the handlers returns false) this will also return false.
* If there are no handlers, or if all handlers return true, this returns
* true.
*/
goog.events.dispatchEvent = function(src, e) {
goog.asserts.assert(
goog.events.Listenable.isImplementedBy(src),
'Can not use goog.events.dispatchEvent with ' +
'non-goog.events.Listenable instance.');
return src.dispatchEvent(e);
};
/**
* Installs exception protection for the browser event entry point using the
* given error handler.
*
* @param {goog.debug.ErrorHandler} errorHandler Error handler with which to
* protect the entry point.
*/
goog.events.protectBrowserEventEntryPoint = function(errorHandler) {
goog.events.handleBrowserEvent_ =
errorHandler.protectEntryPoint(goog.events.handleBrowserEvent_);
};
/**
* Handles an event and dispatches it to the correct listeners. This
* function is a proxy for the real listener the user specified.
*
* @param {goog.events.Listener} listener The listener object.
* @param {Event=} opt_evt Optional event object that gets passed in via the
* native event handlers.
* @return {boolean} Result of the event handler.
* @this {EventTarget} The object or Element that fired the event.
* @private
*/
goog.events.handleBrowserEvent_ = function(listener, opt_evt) {
if (listener.removed) {
return true;
}
// Synthesize event propagation if the browser does not support W3C
// event model.
if (!goog.events.BrowserFeature.HAS_W3C_EVENT_SUPPORT) {
var ieEvent = opt_evt ||
/** @type {Event} */ (goog.getObjectByName('window.event'));
var evt = new goog.events.BrowserEvent(ieEvent, this);
/** @type {boolean} */
var retval = true;
if (goog.events.CAPTURE_SIMULATION_MODE ==
goog.events.CaptureSimulationMode.ON) {
// If we have not marked this event yet, we should perform capture
// simulation.
if (!goog.events.isMarkedIeEvent_(ieEvent)) {
goog.events.markIeEvent_(ieEvent);
var ancestors = [];
for (var parent = evt.currentTarget; parent;
parent = parent.parentNode) {
ancestors.push(parent);
}
// Fire capture listeners.
var type = listener.type;
for (var i = ancestors.length - 1; !evt.propagationStopped_ && i >= 0;
i--) {
evt.currentTarget = ancestors[i];
var result =
goog.events.fireListeners_(ancestors[i], type, true, evt);
retval = retval && result;
}
// Fire bubble listeners.
//
// We can technically rely on IE to perform bubble event
// propagation. However, it turns out that IE fires events in
// opposite order of attachEvent registration, which broke
// some code and tests that rely on the order. (While W3C DOM
// Level 2 Events TR leaves the event ordering unspecified,
// modern browsers and W3C DOM Level 3 Events Working Draft
// actually specify the order as the registration order.)
for (var i = 0; !evt.propagationStopped_ && i < ancestors.length; i++) {
evt.currentTarget = ancestors[i];
var result =
goog.events.fireListeners_(ancestors[i], type, false, evt);
retval = retval && result;
}
}
} else {
retval = goog.events.fireListener(listener, evt);
}
return retval;
}
// Otherwise, simply fire the listener.
return goog.events.fireListener(
listener, new goog.events.BrowserEvent(opt_evt, this));
};
/**
* This is used to mark the IE event object so we do not do the Closure pass
* twice for a bubbling event.
* @param {Event} e The IE browser event.
* @private
*/
goog.events.markIeEvent_ = function(e) {
// Only the keyCode and the returnValue can be changed. We use keyCode for
// non keyboard events.
// event.returnValue is a bit more tricky. It is undefined by default. A
// boolean false prevents the default action. In a window.onbeforeunload and
// the returnValue is non undefined it will be alerted. However, we will only
// modify the returnValue for keyboard events. We can get a problem if non
// closure events sets the keyCode or the returnValue
var useReturnValue = false;
if (e.keyCode == 0) {
// We cannot change the keyCode in case that srcElement is input[type=file].
// We could test that that is the case but that would allocate 3 objects.
// If we use try/catch we will only allocate extra objects in the case of a
// failure.
/** @preserveTry */
try {
e.keyCode = -1;
return;
} catch (ex) {
useReturnValue = true;
}
}
if (useReturnValue ||
/** @type {boolean|undefined} */ (e.returnValue) == undefined) {
e.returnValue = true;
}
};
/**
* This is used to check if an IE event has already been handled by the Closure
* system so we do not do the Closure pass twice for a bubbling event.
* @param {Event} e The IE browser event.
* @return {boolean} True if the event object has been marked.
* @private
*/
goog.events.isMarkedIeEvent_ = function(e) {
return e.keyCode < 0 || e.returnValue != undefined;
};
/**
* Counter to create unique event ids.
* @private {number}
*/
goog.events.uniqueIdCounter_ = 0;
/**
* Creates a unique event id.
*
* @param {string} identifier The identifier.
* @return {string} A unique identifier.
* @idGenerator {unique}
*/
goog.events.getUniqueId = function(identifier) {
return identifier + '_' + goog.events.uniqueIdCounter_++;
};
/**
* @param {EventTarget} src The source object.
* @return {goog.events.ListenerMap} A listener map for the given
* source object, or null if none exists.
* @private
*/
goog.events.getListenerMap_ = function(src) {
var listenerMap = src[goog.events.LISTENER_MAP_PROP_];
// IE serializes the property as well (e.g. when serializing outer
// HTML). So we must check that the value is of the correct type.
return listenerMap instanceof goog.events.ListenerMap ? listenerMap : null;
};
/**
* Expando property for listener function wrapper for Object with
* handleEvent.
* @private @const {string}
*/
goog.events.LISTENER_WRAPPER_PROP_ =
'__closure_events_fn_' + ((Math.random() * 1e9) >>> 0);
/**
* @param {Object|Function} listener The listener function or an
* object that contains handleEvent method.
* @return {!Function} Either the original function or a function that
* calls obj.handleEvent. If the same listener is passed to this
* function more than once, the same function is guaranteed to be
* returned.
*/
goog.events.wrapListener = function(listener) {
goog.asserts.assert(listener, 'Listener can not be null.');
if (goog.isFunction(listener)) {
return listener;
}
goog.asserts.assert(
listener.handleEvent, 'An object listener must have handleEvent method.');
if (!listener[goog.events.LISTENER_WRAPPER_PROP_]) {
listener[goog.events.LISTENER_WRAPPER_PROP_] = function(e) {
return /** @type {?} */ (listener).handleEvent(e);
};
}
return listener[goog.events.LISTENER_WRAPPER_PROP_];
};
// Register the browser event handler as an entry point, so that
// it can be monitored for exception handling, etc.
goog.debug.entryPointRegistry.register(
/**
* @param {function(!Function): !Function} transformer The transforming
* function.
*/
function(transformer) {
goog.events.handleBrowserEvent_ =
transformer(goog.events.handleBrowserEvent_);
});

View File

@ -0,0 +1,239 @@
// Copyright 2010 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Event Types.
*
* @author arv@google.com (Erik Arvidsson)
*/
goog.provide('goog.events.EventType');
goog.require('goog.userAgent');
/**
* Returns a prefixed event name for the current browser.
* @param {string} eventName The name of the event.
* @return {string} The prefixed event name.
* @suppress {missingRequire|missingProvide}
* @private
*/
goog.events.getVendorPrefixedName_ = function(eventName) {
return goog.userAgent.WEBKIT ?
'webkit' + eventName :
(goog.userAgent.OPERA ? 'o' + eventName.toLowerCase() :
eventName.toLowerCase());
};
/**
* Constants for event names.
* @enum {string}
*/
goog.events.EventType = {
// Mouse events
CLICK: 'click',
RIGHTCLICK: 'rightclick',
DBLCLICK: 'dblclick',
MOUSEDOWN: 'mousedown',
MOUSEUP: 'mouseup',
MOUSEOVER: 'mouseover',
MOUSEOUT: 'mouseout',
MOUSEMOVE: 'mousemove',
MOUSEENTER: 'mouseenter',
MOUSELEAVE: 'mouseleave',
// Select start is non-standard.
// See http://msdn.microsoft.com/en-us/library/ie/ms536969(v=vs.85).aspx.
SELECTSTART: 'selectstart', // IE, Safari, Chrome
// Wheel events
// http://www.w3.org/TR/DOM-Level-3-Events/#events-wheelevents
WHEEL: 'wheel',
// Key events
KEYPRESS: 'keypress',
KEYDOWN: 'keydown',
KEYUP: 'keyup',
// Focus
BLUR: 'blur',
FOCUS: 'focus',
DEACTIVATE: 'deactivate', // IE only
// NOTE: The following two events are not stable in cross-browser usage.
// WebKit and Opera implement DOMFocusIn/Out.
// IE implements focusin/out.
// Gecko implements neither see bug at
// https://bugzilla.mozilla.org/show_bug.cgi?id=396927.
// The DOM Events Level 3 Draft deprecates DOMFocusIn in favor of focusin:
// http://dev.w3.org/2006/webapi/DOM-Level-3-Events/html/DOM3-Events.html
// You can use FOCUS in Capture phase until implementations converge.
FOCUSIN: goog.userAgent.IE ? 'focusin' : 'DOMFocusIn',
FOCUSOUT: goog.userAgent.IE ? 'focusout' : 'DOMFocusOut',
// Forms
CHANGE: 'change',
RESET: 'reset',
SELECT: 'select',
SUBMIT: 'submit',
INPUT: 'input',
PROPERTYCHANGE: 'propertychange', // IE only
// Drag and drop
DRAGSTART: 'dragstart',
DRAG: 'drag',
DRAGENTER: 'dragenter',
DRAGOVER: 'dragover',
DRAGLEAVE: 'dragleave',
DROP: 'drop',
DRAGEND: 'dragend',
// Touch events
// Note that other touch events exist, but we should follow the W3C list here.
// http://www.w3.org/TR/touch-events/#list-of-touchevent-types
TOUCHSTART: 'touchstart',
TOUCHMOVE: 'touchmove',
TOUCHEND: 'touchend',
TOUCHCANCEL: 'touchcancel',
// Misc
BEFOREUNLOAD: 'beforeunload',
CONSOLEMESSAGE: 'consolemessage',
CONTEXTMENU: 'contextmenu',
DOMCONTENTLOADED: 'DOMContentLoaded',
ERROR: 'error',
HELP: 'help',
LOAD: 'load',
LOSECAPTURE: 'losecapture',
ORIENTATIONCHANGE: 'orientationchange',
READYSTATECHANGE: 'readystatechange',
RESIZE: 'resize',
SCROLL: 'scroll',
TIMEUPDATE: 'timeupdate',
UNLOAD: 'unload',
// HTML 5 History events
// See http://www.w3.org/TR/html5/browsers.html#event-definitions-0
HASHCHANGE: 'hashchange',
PAGEHIDE: 'pagehide',
PAGESHOW: 'pageshow',
POPSTATE: 'popstate',
// Copy and Paste
// Support is limited. Make sure it works on your favorite browser
// before using.
// http://www.quirksmode.org/dom/events/cutcopypaste.html
COPY: 'copy',
PASTE: 'paste',
CUT: 'cut',
BEFORECOPY: 'beforecopy',
BEFORECUT: 'beforecut',
BEFOREPASTE: 'beforepaste',
// HTML5 online/offline events.
// http://www.w3.org/TR/offline-webapps/#related
ONLINE: 'online',
OFFLINE: 'offline',
// HTML 5 worker events
MESSAGE: 'message',
CONNECT: 'connect',
// CSS animation events.
/** @suppress {missingRequire} */
ANIMATIONSTART: goog.events.getVendorPrefixedName_('AnimationStart'),
/** @suppress {missingRequire} */
ANIMATIONEND: goog.events.getVendorPrefixedName_('AnimationEnd'),
/** @suppress {missingRequire} */
ANIMATIONITERATION: goog.events.getVendorPrefixedName_('AnimationIteration'),
// CSS transition events. Based on the browser support described at:
// https://developer.mozilla.org/en/css/css_transitions#Browser_compatibility
/** @suppress {missingRequire} */
TRANSITIONEND: goog.events.getVendorPrefixedName_('TransitionEnd'),
// W3C Pointer Events
// http://www.w3.org/TR/pointerevents/
POINTERDOWN: 'pointerdown',
POINTERUP: 'pointerup',
POINTERCANCEL: 'pointercancel',
POINTERMOVE: 'pointermove',
POINTEROVER: 'pointerover',
POINTEROUT: 'pointerout',
POINTERENTER: 'pointerenter',
POINTERLEAVE: 'pointerleave',
GOTPOINTERCAPTURE: 'gotpointercapture',
LOSTPOINTERCAPTURE: 'lostpointercapture',
// IE specific events.
// See http://msdn.microsoft.com/en-us/library/ie/hh772103(v=vs.85).aspx
// Note: these events will be supplanted in IE11.
MSGESTURECHANGE: 'MSGestureChange',
MSGESTUREEND: 'MSGestureEnd',
MSGESTUREHOLD: 'MSGestureHold',
MSGESTURESTART: 'MSGestureStart',
MSGESTURETAP: 'MSGestureTap',
MSGOTPOINTERCAPTURE: 'MSGotPointerCapture',
MSINERTIASTART: 'MSInertiaStart',
MSLOSTPOINTERCAPTURE: 'MSLostPointerCapture',
MSPOINTERCANCEL: 'MSPointerCancel',
MSPOINTERDOWN: 'MSPointerDown',
MSPOINTERENTER: 'MSPointerEnter',
MSPOINTERHOVER: 'MSPointerHover',
MSPOINTERLEAVE: 'MSPointerLeave',
MSPOINTERMOVE: 'MSPointerMove',
MSPOINTEROUT: 'MSPointerOut',
MSPOINTEROVER: 'MSPointerOver',
MSPOINTERUP: 'MSPointerUp',
// Native IMEs/input tools events.
TEXT: 'text',
TEXTINPUT: 'textInput',
COMPOSITIONSTART: 'compositionstart',
COMPOSITIONUPDATE: 'compositionupdate',
COMPOSITIONEND: 'compositionend',
// Webview tag events
// See http://developer.chrome.com/dev/apps/webview_tag.html
EXIT: 'exit',
LOADABORT: 'loadabort',
LOADCOMMIT: 'loadcommit',
LOADREDIRECT: 'loadredirect',
LOADSTART: 'loadstart',
LOADSTOP: 'loadstop',
RESPONSIVE: 'responsive',
SIZECHANGED: 'sizechanged',
UNRESPONSIVE: 'unresponsive',
// HTML5 Page Visibility API. See details at
// {@code goog.labs.dom.PageVisibilityMonitor}.
VISIBILITYCHANGE: 'visibilitychange',
// LocalStorage event.
STORAGE: 'storage',
// DOM Level 2 mutation events (deprecated).
DOMSUBTREEMODIFIED: 'DOMSubtreeModified',
DOMNODEINSERTED: 'DOMNodeInserted',
DOMNODEREMOVED: 'DOMNodeRemoved',
DOMNODEREMOVEDFROMDOCUMENT: 'DOMNodeRemovedFromDocument',
DOMNODEINSERTEDINTODOCUMENT: 'DOMNodeInsertedIntoDocument',
DOMATTRMODIFIED: 'DOMAttrModified',
DOMCHARACTERDATAMODIFIED: 'DOMCharacterDataModified',
// Print events.
BEFOREPRINT: 'beforeprint',
AFTERPRINT: 'afterprint'
};

View File

@ -0,0 +1,338 @@
// Copyright 2012 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview An interface for a listenable JavaScript object.
* @author chrishenry@google.com (Chris Henry)
*/
goog.provide('goog.events.Listenable');
goog.provide('goog.events.ListenableKey');
/** @suppress {extraRequire} */
goog.require('goog.events.EventId');
goog.forwardDeclare('goog.events.EventLike');
goog.forwardDeclare('goog.events.EventTarget');
/**
* A listenable interface. A listenable is an object with the ability
* to dispatch/broadcast events to "event listeners" registered via
* listen/listenOnce.
*
* The interface allows for an event propagation mechanism similar
* to one offered by native browser event targets, such as
* capture/bubble mechanism, stopping propagation, and preventing
* default actions. Capture/bubble mechanism depends on the ancestor
* tree constructed via {@code #getParentEventTarget}; this tree
* must be directed acyclic graph. The meaning of default action(s)
* in preventDefault is specific to a particular use case.
*
* Implementations that do not support capture/bubble or can not have
* a parent listenable can simply not implement any ability to set the
* parent listenable (and have {@code #getParentEventTarget} return
* null).
*
* Implementation of this class can be used with or independently from
* goog.events.
*
* Implementation must call {@code #addImplementation(implClass)}.
*
* @interface
* @see goog.events
* @see http://www.w3.org/TR/DOM-Level-2-Events/events.html
*/
goog.events.Listenable = function() {};
/**
* An expando property to indicate that an object implements
* goog.events.Listenable.
*
* See addImplementation/isImplementedBy.
*
* @type {string}
* @const
*/
goog.events.Listenable.IMPLEMENTED_BY_PROP =
'closure_listenable_' + ((Math.random() * 1e6) | 0);
/**
* Marks a given class (constructor) as an implementation of
* Listenable, do that we can query that fact at runtime. The class
* must have already implemented the interface.
* @param {!Function} cls The class constructor. The corresponding
* class must have already implemented the interface.
*/
goog.events.Listenable.addImplementation = function(cls) {
cls.prototype[goog.events.Listenable.IMPLEMENTED_BY_PROP] = true;
};
/**
* @param {Object} obj The object to check.
* @return {boolean} Whether a given instance implements Listenable. The
* class/superclass of the instance must call addImplementation.
*/
goog.events.Listenable.isImplementedBy = function(obj) {
return !!(obj && obj[goog.events.Listenable.IMPLEMENTED_BY_PROP]);
};
/**
* Adds an event listener. A listener can only be added once to an
* object and if it is added again the key for the listener is
* returned. Note that if the existing listener is a one-off listener
* (registered via listenOnce), it will no longer be a one-off
* listener after a call to listen().
*
* @param {string|!goog.events.EventId<EVENTOBJ>} type The event type id.
* @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener Callback
* method.
* @param {boolean=} opt_useCapture Whether to fire in capture phase
* (defaults to false).
* @param {SCOPE=} opt_listenerScope Object in whose scope to call the
* listener.
* @return {!goog.events.ListenableKey} Unique key for the listener.
* @template SCOPE,EVENTOBJ
*/
goog.events.Listenable.prototype.listen;
/**
* Adds an event listener that is removed automatically after the
* listener fired once.
*
* If an existing listener already exists, listenOnce will do
* nothing. In particular, if the listener was previously registered
* via listen(), listenOnce() will not turn the listener into a
* one-off listener. Similarly, if there is already an existing
* one-off listener, listenOnce does not modify the listeners (it is
* still a once listener).
*
* @param {string|!goog.events.EventId<EVENTOBJ>} type The event type id.
* @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener Callback
* method.
* @param {boolean=} opt_useCapture Whether to fire in capture phase
* (defaults to false).
* @param {SCOPE=} opt_listenerScope Object in whose scope to call the
* listener.
* @return {!goog.events.ListenableKey} Unique key for the listener.
* @template SCOPE,EVENTOBJ
*/
goog.events.Listenable.prototype.listenOnce;
/**
* Removes an event listener which was added with listen() or listenOnce().
*
* @param {string|!goog.events.EventId<EVENTOBJ>} type The event type id.
* @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener Callback
* method.
* @param {boolean=} opt_useCapture Whether to fire in capture phase
* (defaults to false).
* @param {SCOPE=} opt_listenerScope Object in whose scope to call
* the listener.
* @return {boolean} Whether any listener was removed.
* @template SCOPE,EVENTOBJ
*/
goog.events.Listenable.prototype.unlisten;
/**
* Removes an event listener which was added with listen() by the key
* returned by listen().
*
* @param {!goog.events.ListenableKey} key The key returned by
* listen() or listenOnce().
* @return {boolean} Whether any listener was removed.
*/
goog.events.Listenable.prototype.unlistenByKey;
/**
* Dispatches an event (or event like object) and calls all listeners
* listening for events of this type. The type of the event is decided by the
* type property on the event object.
*
* If any of the listeners returns false OR calls preventDefault then this
* function will return false. If one of the capture listeners calls
* stopPropagation, then the bubble listeners won't fire.
*
* @param {goog.events.EventLike} e Event object.
* @return {boolean} If anyone called preventDefault on the event object (or
* if any of the listeners returns false) this will also return false.
*/
goog.events.Listenable.prototype.dispatchEvent;
/**
* Removes all listeners from this listenable. If type is specified,
* it will only remove listeners of the particular type. otherwise all
* registered listeners will be removed.
*
* @param {string=} opt_type Type of event to remove, default is to
* remove all types.
* @return {number} Number of listeners removed.
*/
goog.events.Listenable.prototype.removeAllListeners;
/**
* Returns the parent of this event target to use for capture/bubble
* mechanism.
*
* NOTE(chrishenry): The name reflects the original implementation of
* custom event target ({@code goog.events.EventTarget}). We decided
* that changing the name is not worth it.
*
* @return {goog.events.Listenable} The parent EventTarget or null if
* there is no parent.
*/
goog.events.Listenable.prototype.getParentEventTarget;
/**
* Fires all registered listeners in this listenable for the given
* type and capture mode, passing them the given eventObject. This
* does not perform actual capture/bubble. Only implementors of the
* interface should be using this.
*
* @param {string|!goog.events.EventId<EVENTOBJ>} type The type of the
* listeners to fire.
* @param {boolean} capture The capture mode of the listeners to fire.
* @param {EVENTOBJ} eventObject The event object to fire.
* @return {boolean} Whether all listeners succeeded without
* attempting to prevent default behavior. If any listener returns
* false or called goog.events.Event#preventDefault, this returns
* false.
* @template EVENTOBJ
*/
goog.events.Listenable.prototype.fireListeners;
/**
* Gets all listeners in this listenable for the given type and
* capture mode.
*
* @param {string|!goog.events.EventId} type The type of the listeners to fire.
* @param {boolean} capture The capture mode of the listeners to fire.
* @return {!Array<!goog.events.ListenableKey>} An array of registered
* listeners.
* @template EVENTOBJ
*/
goog.events.Listenable.prototype.getListeners;
/**
* Gets the goog.events.ListenableKey for the event or null if no such
* listener is in use.
*
* @param {string|!goog.events.EventId<EVENTOBJ>} type The name of the event
* without the 'on' prefix.
* @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener The
* listener function to get.
* @param {boolean} capture Whether the listener is a capturing listener.
* @param {SCOPE=} opt_listenerScope Object in whose scope to call the
* listener.
* @return {goog.events.ListenableKey} the found listener or null if not found.
* @template SCOPE,EVENTOBJ
*/
goog.events.Listenable.prototype.getListener;
/**
* Whether there is any active listeners matching the specified
* signature. If either the type or capture parameters are
* unspecified, the function will match on the remaining criteria.
*
* @param {string|!goog.events.EventId<EVENTOBJ>=} opt_type Event type.
* @param {boolean=} opt_capture Whether to check for capture or bubble
* listeners.
* @return {boolean} Whether there is any active listeners matching
* the requested type and/or capture phase.
* @template EVENTOBJ
*/
goog.events.Listenable.prototype.hasListener;
/**
* An interface that describes a single registered listener.
* @interface
*/
goog.events.ListenableKey = function() {};
/**
* Counter used to create a unique key
* @type {number}
* @private
*/
goog.events.ListenableKey.counter_ = 0;
/**
* Reserves a key to be used for ListenableKey#key field.
* @return {number} A number to be used to fill ListenableKey#key
* field.
*/
goog.events.ListenableKey.reserveKey = function() {
return ++goog.events.ListenableKey.counter_;
};
/**
* The source event target.
* @type {Object|goog.events.Listenable|goog.events.EventTarget}
*/
goog.events.ListenableKey.prototype.src;
/**
* The event type the listener is listening to.
* @type {string}
*/
goog.events.ListenableKey.prototype.type;
/**
* The listener function.
* @type {function(?):?|{handleEvent:function(?):?}|null}
*/
goog.events.ListenableKey.prototype.listener;
/**
* Whether the listener works on capture phase.
* @type {boolean}
*/
goog.events.ListenableKey.prototype.capture;
/**
* The 'this' object for the listener function's scope.
* @type {Object|undefined}
*/
goog.events.ListenableKey.prototype.handler;
/**
* A globally unique number to identify the key.
* @type {number}
*/
goog.events.ListenableKey.prototype.key;

View File

@ -0,0 +1,131 @@
// Copyright 2005 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Listener object.
* @see ../demos/events.html
*/
goog.provide('goog.events.Listener');
goog.require('goog.events.ListenableKey');
/**
* Simple class that stores information about a listener
* @param {!Function} listener Callback function.
* @param {Function} proxy Wrapper for the listener that patches the event.
* @param {EventTarget|goog.events.Listenable} src Source object for
* the event.
* @param {string} type Event type.
* @param {boolean} capture Whether in capture or bubble phase.
* @param {Object=} opt_handler Object in whose context to execute the callback.
* @implements {goog.events.ListenableKey}
* @constructor
*/
goog.events.Listener = function(
listener, proxy, src, type, capture, opt_handler) {
if (goog.events.Listener.ENABLE_MONITORING) {
this.creationStack = new Error().stack;
}
/**
* Callback function.
* @type {Function}
*/
this.listener = listener;
/**
* A wrapper over the original listener. This is used solely to
* handle native browser events (it is used to simulate the capture
* phase and to patch the event object).
* @type {Function}
*/
this.proxy = proxy;
/**
* Object or node that callback is listening to
* @type {EventTarget|goog.events.Listenable}
*/
this.src = src;
/**
* The event type.
* @const {string}
*/
this.type = type;
/**
* Whether the listener is being called in the capture or bubble phase
* @const {boolean}
*/
this.capture = !!capture;
/**
* Optional object whose context to execute the listener in
* @type {Object|undefined}
*/
this.handler = opt_handler;
/**
* The key of the listener.
* @const {number}
* @override
*/
this.key = goog.events.ListenableKey.reserveKey();
/**
* Whether to remove the listener after it has been called.
* @type {boolean}
*/
this.callOnce = false;
/**
* Whether the listener has been removed.
* @type {boolean}
*/
this.removed = false;
};
/**
* @define {boolean} Whether to enable the monitoring of the
* goog.events.Listener instances. Switching on the monitoring is only
* recommended for debugging because it has a significant impact on
* performance and memory usage. If switched off, the monitoring code
* compiles down to 0 bytes.
*/
goog.define('goog.events.Listener.ENABLE_MONITORING', false);
/**
* If monitoring the goog.events.Listener instances is enabled, stores the
* creation stack trace of the Disposable instance.
* @type {string}
*/
goog.events.Listener.prototype.creationStack;
/**
* Marks this listener as removed. This also remove references held by
* this listener object (such as listener and event source).
*/
goog.events.Listener.prototype.markAsRemoved = function() {
this.removed = true;
this.listener = null;
this.proxy = null;
this.src = null;
this.handler = null;
};

View File

@ -0,0 +1,306 @@
// Copyright 2013 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview A map of listeners that provides utility functions to
* deal with listeners on an event target. Used by
* {@code goog.events.EventTarget}.
*
* WARNING: Do not use this class from outside goog.events package.
*
* @visibility {//closure/goog/bin/sizetests:__pkg__}
* @visibility {//closure/goog/events:__pkg__}
* @visibility {//closure/goog/labs/events:__pkg__}
*/
goog.provide('goog.events.ListenerMap');
goog.require('goog.array');
goog.require('goog.events.Listener');
goog.require('goog.object');
/**
* Creates a new listener map.
* @param {EventTarget|goog.events.Listenable} src The src object.
* @constructor
* @final
*/
goog.events.ListenerMap = function(src) {
/** @type {EventTarget|goog.events.Listenable} */
this.src = src;
/**
* Maps of event type to an array of listeners.
* @type {!Object<string, !Array<!goog.events.Listener>>}
*/
this.listeners = {};
/**
* The count of types in this map that have registered listeners.
* @private {number}
*/
this.typeCount_ = 0;
};
/**
* @return {number} The count of event types in this map that actually
* have registered listeners.
*/
goog.events.ListenerMap.prototype.getTypeCount = function() {
return this.typeCount_;
};
/**
* @return {number} Total number of registered listeners.
*/
goog.events.ListenerMap.prototype.getListenerCount = function() {
var count = 0;
for (var type in this.listeners) {
count += this.listeners[type].length;
}
return count;
};
/**
* Adds an event listener. A listener can only be added once to an
* object and if it is added again the key for the listener is
* returned.
*
* Note that a one-off listener will not change an existing listener,
* if any. On the other hand a normal listener will change existing
* one-off listener to become a normal listener.
*
* @param {string|!goog.events.EventId} type The listener event type.
* @param {!Function} listener This listener callback method.
* @param {boolean} callOnce Whether the listener is a one-off
* listener.
* @param {boolean=} opt_useCapture The capture mode of the listener.
* @param {Object=} opt_listenerScope Object in whose scope to call the
* listener.
* @return {!goog.events.ListenableKey} Unique key for the listener.
*/
goog.events.ListenerMap.prototype.add = function(
type, listener, callOnce, opt_useCapture, opt_listenerScope) {
var typeStr = type.toString();
var listenerArray = this.listeners[typeStr];
if (!listenerArray) {
listenerArray = this.listeners[typeStr] = [];
this.typeCount_++;
}
var listenerObj;
var index = goog.events.ListenerMap.findListenerIndex_(
listenerArray, listener, opt_useCapture, opt_listenerScope);
if (index > -1) {
listenerObj = listenerArray[index];
if (!callOnce) {
// Ensure that, if there is an existing callOnce listener, it is no
// longer a callOnce listener.
listenerObj.callOnce = false;
}
} else {
listenerObj = new goog.events.Listener(
listener, null, this.src, typeStr, !!opt_useCapture, opt_listenerScope);
listenerObj.callOnce = callOnce;
listenerArray.push(listenerObj);
}
return listenerObj;
};
/**
* Removes a matching listener.
* @param {string|!goog.events.EventId} type The listener event type.
* @param {!Function} listener This listener callback method.
* @param {boolean=} opt_useCapture The capture mode of the listener.
* @param {Object=} opt_listenerScope Object in whose scope to call the
* listener.
* @return {boolean} Whether any listener was removed.
*/
goog.events.ListenerMap.prototype.remove = function(
type, listener, opt_useCapture, opt_listenerScope) {
var typeStr = type.toString();
if (!(typeStr in this.listeners)) {
return false;
}
var listenerArray = this.listeners[typeStr];
var index = goog.events.ListenerMap.findListenerIndex_(
listenerArray, listener, opt_useCapture, opt_listenerScope);
if (index > -1) {
var listenerObj = listenerArray[index];
listenerObj.markAsRemoved();
goog.array.removeAt(listenerArray, index);
if (listenerArray.length == 0) {
delete this.listeners[typeStr];
this.typeCount_--;
}
return true;
}
return false;
};
/**
* Removes the given listener object.
* @param {!goog.events.ListenableKey} listener The listener to remove.
* @return {boolean} Whether the listener is removed.
*/
goog.events.ListenerMap.prototype.removeByKey = function(listener) {
var type = listener.type;
if (!(type in this.listeners)) {
return false;
}
var removed = goog.array.remove(this.listeners[type], listener);
if (removed) {
listener.markAsRemoved();
if (this.listeners[type].length == 0) {
delete this.listeners[type];
this.typeCount_--;
}
}
return removed;
};
/**
* Removes all listeners from this map. If opt_type is provided, only
* listeners that match the given type are removed.
* @param {string|!goog.events.EventId=} opt_type Type of event to remove.
* @return {number} Number of listeners removed.
*/
goog.events.ListenerMap.prototype.removeAll = function(opt_type) {
var typeStr = opt_type && opt_type.toString();
var count = 0;
for (var type in this.listeners) {
if (!typeStr || type == typeStr) {
var listenerArray = this.listeners[type];
for (var i = 0; i < listenerArray.length; i++) {
++count;
listenerArray[i].markAsRemoved();
}
delete this.listeners[type];
this.typeCount_--;
}
}
return count;
};
/**
* Gets all listeners that match the given type and capture mode. The
* returned array is a copy (but the listener objects are not).
* @param {string|!goog.events.EventId} type The type of the listeners
* to retrieve.
* @param {boolean} capture The capture mode of the listeners to retrieve.
* @return {!Array<!goog.events.ListenableKey>} An array of matching
* listeners.
*/
goog.events.ListenerMap.prototype.getListeners = function(type, capture) {
var listenerArray = this.listeners[type.toString()];
var rv = [];
if (listenerArray) {
for (var i = 0; i < listenerArray.length; ++i) {
var listenerObj = listenerArray[i];
if (listenerObj.capture == capture) {
rv.push(listenerObj);
}
}
}
return rv;
};
/**
* Gets the goog.events.ListenableKey for the event or null if no such
* listener is in use.
*
* @param {string|!goog.events.EventId} type The type of the listener
* to retrieve.
* @param {!Function} listener The listener function to get.
* @param {boolean} capture Whether the listener is a capturing listener.
* @param {Object=} opt_listenerScope Object in whose scope to call the
* listener.
* @return {goog.events.ListenableKey} the found listener or null if not found.
*/
goog.events.ListenerMap.prototype.getListener = function(
type, listener, capture, opt_listenerScope) {
var listenerArray = this.listeners[type.toString()];
var i = -1;
if (listenerArray) {
i = goog.events.ListenerMap.findListenerIndex_(
listenerArray, listener, capture, opt_listenerScope);
}
return i > -1 ? listenerArray[i] : null;
};
/**
* Whether there is a matching listener. If either the type or capture
* parameters are unspecified, the function will match on the
* remaining criteria.
*
* @param {string|!goog.events.EventId=} opt_type The type of the listener.
* @param {boolean=} opt_capture The capture mode of the listener.
* @return {boolean} Whether there is an active listener matching
* the requested type and/or capture phase.
*/
goog.events.ListenerMap.prototype.hasListener = function(
opt_type, opt_capture) {
var hasType = goog.isDef(opt_type);
var typeStr = hasType ? opt_type.toString() : '';
var hasCapture = goog.isDef(opt_capture);
return goog.object.some(this.listeners, function(listenerArray, type) {
for (var i = 0; i < listenerArray.length; ++i) {
if ((!hasType || listenerArray[i].type == typeStr) &&
(!hasCapture || listenerArray[i].capture == opt_capture)) {
return true;
}
}
return false;
});
};
/**
* Finds the index of a matching goog.events.Listener in the given
* listenerArray.
* @param {!Array<!goog.events.Listener>} listenerArray Array of listener.
* @param {!Function} listener The listener function.
* @param {boolean=} opt_useCapture The capture flag for the listener.
* @param {Object=} opt_listenerScope The listener scope.
* @return {number} The index of the matching listener within the
* listenerArray.
* @private
*/
goog.events.ListenerMap.findListenerIndex_ = function(
listenerArray, listener, opt_useCapture, opt_listenerScope) {
for (var i = 0; i < listenerArray.length; ++i) {
var listenerObj = listenerArray[i];
if (!listenerObj.removed && listenerObj.listener == listener &&
listenerObj.capture == !!opt_useCapture &&
listenerObj.handler == opt_listenerScope) {
return i;
}
}
return -1;
};

110
viz/out/goog/fs/url.js Normal file
View File

@ -0,0 +1,110 @@
// Copyright 2015 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Wrapper for URL and its createObjectUrl and revokeObjectUrl
* methods that are part of the HTML5 File API.
*/
goog.provide('goog.fs.url');
/**
* Creates a blob URL for a blob object.
* Throws an error if the browser does not support Object Urls.
*
* @param {!Blob} blob The object for which to create the URL.
* @return {string} The URL for the object.
*/
goog.fs.url.createObjectUrl = function(blob) {
return goog.fs.url.getUrlObject_().createObjectURL(blob);
};
/**
* Revokes a URL created by {@link goog.fs.url.createObjectUrl}.
* Throws an error if the browser does not support Object Urls.
*
* @param {string} url The URL to revoke.
*/
goog.fs.url.revokeObjectUrl = function(url) {
goog.fs.url.getUrlObject_().revokeObjectURL(url);
};
/**
* @typedef {{createObjectURL: (function(!Blob): string),
* revokeObjectURL: function(string): void}}
*/
goog.fs.url.UrlObject_;
/**
* Get the object that has the createObjectURL and revokeObjectURL functions for
* this browser.
*
* @return {goog.fs.url.UrlObject_} The object for this browser.
* @private
*/
goog.fs.url.getUrlObject_ = function() {
var urlObject = goog.fs.url.findUrlObject_();
if (urlObject != null) {
return urlObject;
} else {
throw Error('This browser doesn\'t seem to support blob URLs');
}
};
/**
* Finds the object that has the createObjectURL and revokeObjectURL functions
* for this browser.
*
* @return {?goog.fs.url.UrlObject_} The object for this browser or null if the
* browser does not support Object Urls.
* @suppress {unnecessaryCasts} Depending on how the code is compiled, casting
* goog.global to UrlObject_ may result in unnecessary cast warning.
* However, the cast cannot be removed because with different set of
* compiler flags, the cast is indeed necessary. As such, silencing it.
* @private
*/
goog.fs.url.findUrlObject_ = function() {
// This is what the spec says to do
// http://dev.w3.org/2006/webapi/FileAPI/#dfn-createObjectURL
if (goog.isDef(goog.global.URL) &&
goog.isDef(goog.global.URL.createObjectURL)) {
return /** @type {goog.fs.url.UrlObject_} */ (goog.global.URL);
// This is what Chrome does (as of 10.0.648.6 dev)
} else if (
goog.isDef(goog.global.webkitURL) &&
goog.isDef(goog.global.webkitURL.createObjectURL)) {
return /** @type {goog.fs.url.UrlObject_} */ (goog.global.webkitURL);
// This is what the spec used to say to do
} else if (goog.isDef(goog.global.createObjectURL)) {
return /** @type {goog.fs.url.UrlObject_} */ (goog.global);
} else {
return null;
}
};
/**
* Checks whether this browser supports Object Urls. If not, calls to
* createObjectUrl and revokeObjectUrl will result in an error.
*
* @return {boolean} True if this browser supports Object Urls.
*/
goog.fs.url.browserSupportsObjectUrls = function() {
return goog.fs.url.findUrlObject_() != null;
};

View File

@ -0,0 +1,948 @@
// Copyright 2013 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview The SafeHtml type and its builders.
*
* TODO(xtof): Link to document stating type contract.
*/
goog.provide('goog.html.SafeHtml');
goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.dom.TagName');
goog.require('goog.dom.tags');
goog.require('goog.html.SafeStyle');
goog.require('goog.html.SafeStyleSheet');
goog.require('goog.html.SafeUrl');
goog.require('goog.html.TrustedResourceUrl');
goog.require('goog.i18n.bidi.Dir');
goog.require('goog.i18n.bidi.DirectionalString');
goog.require('goog.labs.userAgent.browser');
goog.require('goog.object');
goog.require('goog.string');
goog.require('goog.string.Const');
goog.require('goog.string.TypedString');
/**
* A string that is safe to use in HTML context in DOM APIs and HTML documents.
*
* A SafeHtml is a string-like object that carries the security type contract
* that its value as a string will not cause untrusted script execution when
* evaluated as HTML in a browser.
*
* Values of this type are guaranteed to be safe to use in HTML contexts,
* such as, assignment to the innerHTML DOM property, or interpolation into
* a HTML template in HTML PC_DATA context, in the sense that the use will not
* result in a Cross-Site-Scripting vulnerability.
*
* Instances of this type must be created via the factory methods
* ({@code goog.html.SafeHtml.create}, {@code goog.html.SafeHtml.htmlEscape}),
* etc and not by invoking its constructor. The constructor intentionally
* takes no parameters and the type is immutable; hence only a default instance
* corresponding to the empty string can be obtained via constructor invocation.
*
* @see goog.html.SafeHtml#create
* @see goog.html.SafeHtml#htmlEscape
* @constructor
* @final
* @struct
* @implements {goog.i18n.bidi.DirectionalString}
* @implements {goog.string.TypedString}
*/
goog.html.SafeHtml = function() {
/**
* The contained value of this SafeHtml. The field has a purposely ugly
* name to make (non-compiled) code that attempts to directly access this
* field stand out.
* @private {string}
*/
this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ = '';
/**
* A type marker used to implement additional run-time type checking.
* @see goog.html.SafeHtml#unwrap
* @const
* @private
*/
this.SAFE_HTML_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ =
goog.html.SafeHtml.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_;
/**
* This SafeHtml's directionality, or null if unknown.
* @private {?goog.i18n.bidi.Dir}
*/
this.dir_ = null;
};
/**
* @override
* @const
*/
goog.html.SafeHtml.prototype.implementsGoogI18nBidiDirectionalString = true;
/** @override */
goog.html.SafeHtml.prototype.getDirection = function() {
return this.dir_;
};
/**
* @override
* @const
*/
goog.html.SafeHtml.prototype.implementsGoogStringTypedString = true;
/**
* Returns this SafeHtml's value as string.
*
* IMPORTANT: In code where it is security relevant that an object's type is
* indeed {@code SafeHtml}, use {@code goog.html.SafeHtml.unwrap} instead of
* this method. If in doubt, assume that it's security relevant. In particular,
* note that goog.html functions which return a goog.html type do not guarantee
* that the returned instance is of the right type. For example:
*
* <pre>
* var fakeSafeHtml = new String('fake');
* fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype;
* var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml);
* // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by
* // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml
* // instanceof goog.html.SafeHtml.
* </pre>
*
* @see goog.html.SafeHtml#unwrap
* @override
*/
goog.html.SafeHtml.prototype.getTypedStringValue = function() {
return this.privateDoNotAccessOrElseSafeHtmlWrappedValue_;
};
if (goog.DEBUG) {
/**
* Returns a debug string-representation of this value.
*
* To obtain the actual string value wrapped in a SafeHtml, use
* {@code goog.html.SafeHtml.unwrap}.
*
* @see goog.html.SafeHtml#unwrap
* @override
*/
goog.html.SafeHtml.prototype.toString = function() {
return 'SafeHtml{' + this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ +
'}';
};
}
/**
* Performs a runtime check that the provided object is indeed a SafeHtml
* object, and returns its value.
* @param {!goog.html.SafeHtml} safeHtml The object to extract from.
* @return {string} The SafeHtml object's contained string, unless the run-time
* type check fails. In that case, {@code unwrap} returns an innocuous
* string, or, if assertions are enabled, throws
* {@code goog.asserts.AssertionError}.
*/
goog.html.SafeHtml.unwrap = function(safeHtml) {
// Perform additional run-time type-checking to ensure that safeHtml is indeed
// an instance of the expected type. This provides some additional protection
// against security bugs due to application code that disables type checks.
// Specifically, the following checks are performed:
// 1. The object is an instance of the expected type.
// 2. The object is not an instance of a subclass.
// 3. The object carries a type marker for the expected type. "Faking" an
// object requires a reference to the type marker, which has names intended
// to stand out in code reviews.
if (safeHtml instanceof goog.html.SafeHtml &&
safeHtml.constructor === goog.html.SafeHtml &&
safeHtml.SAFE_HTML_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ ===
goog.html.SafeHtml.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) {
return safeHtml.privateDoNotAccessOrElseSafeHtmlWrappedValue_;
} else {
goog.asserts.fail('expected object of type SafeHtml, got \'' +
safeHtml + '\' of type ' + goog.typeOf(safeHtml));
return 'type_error:SafeHtml';
}
};
/**
* Shorthand for union of types that can sensibly be converted to strings
* or might already be SafeHtml (as SafeHtml is a goog.string.TypedString).
* @private
* @typedef {string|number|boolean|!goog.string.TypedString|
* !goog.i18n.bidi.DirectionalString}
*/
goog.html.SafeHtml.TextOrHtml_;
/**
* Returns HTML-escaped text as a SafeHtml object.
*
* If text is of a type that implements
* {@code goog.i18n.bidi.DirectionalString}, the directionality of the new
* {@code SafeHtml} object is set to {@code text}'s directionality, if known.
* Otherwise, the directionality of the resulting SafeHtml is unknown (i.e.,
* {@code null}).
*
* @param {!goog.html.SafeHtml.TextOrHtml_} textOrHtml The text to escape. If
* the parameter is of type SafeHtml it is returned directly (no escaping
* is done).
* @return {!goog.html.SafeHtml} The escaped text, wrapped as a SafeHtml.
*/
goog.html.SafeHtml.htmlEscape = function(textOrHtml) {
if (textOrHtml instanceof goog.html.SafeHtml) {
return textOrHtml;
}
var dir = null;
if (textOrHtml.implementsGoogI18nBidiDirectionalString) {
dir = textOrHtml.getDirection();
}
var textAsString;
if (textOrHtml.implementsGoogStringTypedString) {
textAsString = textOrHtml.getTypedStringValue();
} else {
textAsString = String(textOrHtml);
}
return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
goog.string.htmlEscape(textAsString), dir);
};
/**
* Returns HTML-escaped text as a SafeHtml object, with newlines changed to
* &lt;br&gt;.
* @param {!goog.html.SafeHtml.TextOrHtml_} textOrHtml The text to escape. If
* the parameter is of type SafeHtml it is returned directly (no escaping
* is done).
* @return {!goog.html.SafeHtml} The escaped text, wrapped as a SafeHtml.
*/
goog.html.SafeHtml.htmlEscapePreservingNewlines = function(textOrHtml) {
if (textOrHtml instanceof goog.html.SafeHtml) {
return textOrHtml;
}
var html = goog.html.SafeHtml.htmlEscape(textOrHtml);
return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
goog.string.newLineToBr(goog.html.SafeHtml.unwrap(html)),
html.getDirection());
};
/**
* Returns HTML-escaped text as a SafeHtml object, with newlines changed to
* &lt;br&gt; and escaping whitespace to preserve spatial formatting. Character
* entity #160 is used to make it safer for XML.
* @param {!goog.html.SafeHtml.TextOrHtml_} textOrHtml The text to escape. If
* the parameter is of type SafeHtml it is returned directly (no escaping
* is done).
* @return {!goog.html.SafeHtml} The escaped text, wrapped as a SafeHtml.
*/
goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces = function(
textOrHtml) {
if (textOrHtml instanceof goog.html.SafeHtml) {
return textOrHtml;
}
var html = goog.html.SafeHtml.htmlEscape(textOrHtml);
return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
goog.string.whitespaceEscape(goog.html.SafeHtml.unwrap(html)),
html.getDirection());
};
/**
* Coerces an arbitrary object into a SafeHtml object.
*
* If {@code textOrHtml} is already of type {@code goog.html.SafeHtml}, the same
* object is returned. Otherwise, {@code textOrHtml} is coerced to string, and
* HTML-escaped. If {@code textOrHtml} is of a type that implements
* {@code goog.i18n.bidi.DirectionalString}, its directionality, if known, is
* preserved.
*
* @param {!goog.html.SafeHtml.TextOrHtml_} textOrHtml The text or SafeHtml to
* coerce.
* @return {!goog.html.SafeHtml} The resulting SafeHtml object.
* @deprecated Use goog.html.SafeHtml.htmlEscape.
*/
goog.html.SafeHtml.from = goog.html.SafeHtml.htmlEscape;
/**
* @const
* @private
*/
goog.html.SafeHtml.VALID_NAMES_IN_TAG_ = /^[a-zA-Z0-9-]+$/;
/**
* Set of attributes containing URL as defined at
* http://www.w3.org/TR/html5/index.html#attributes-1.
* @private @const {!Object<string,boolean>}
*/
goog.html.SafeHtml.URL_ATTRIBUTES_ = goog.object.createSet(
'action', 'cite', 'data', 'formaction', 'href', 'manifest', 'poster',
'src');
/**
* Tags which are unsupported via create(). They might be supported via a
* tag-specific create method. These are tags which might require a
* TrustedResourceUrl in one of their attributes or a restricted type for
* their content.
* @private @const {!Object<string,boolean>}
*/
goog.html.SafeHtml.NOT_ALLOWED_TAG_NAMES_ = goog.object.createSet(
goog.dom.TagName.APPLET, goog.dom.TagName.BASE, goog.dom.TagName.EMBED,
goog.dom.TagName.IFRAME, goog.dom.TagName.LINK, goog.dom.TagName.MATH,
goog.dom.TagName.META, goog.dom.TagName.OBJECT, goog.dom.TagName.SCRIPT,
goog.dom.TagName.STYLE, goog.dom.TagName.SVG, goog.dom.TagName.TEMPLATE);
/**
* @typedef {string|number|goog.string.TypedString|
* goog.html.SafeStyle.PropertyMap}
*/
goog.html.SafeHtml.AttributeValue;
/**
* Creates a SafeHtml content consisting of a tag with optional attributes and
* optional content.
*
* For convenience tag names and attribute names are accepted as regular
* strings, instead of goog.string.Const. Nevertheless, you should not pass
* user-controlled values to these parameters. Note that these parameters are
* syntactically validated at runtime, and invalid values will result in
* an exception.
*
* Example usage:
*
* goog.html.SafeHtml.create('br');
* goog.html.SafeHtml.create('div', {'class': 'a'});
* goog.html.SafeHtml.create('p', {}, 'a');
* goog.html.SafeHtml.create('p', {}, goog.html.SafeHtml.create('br'));
*
* goog.html.SafeHtml.create('span', {
* 'style': {'margin': '0'}
* });
*
* To guarantee SafeHtml's type contract is upheld there are restrictions on
* attribute values and tag names.
*
* - For attributes which contain script code (on*), a goog.string.Const is
* required.
* - For attributes which contain style (style), a goog.html.SafeStyle or a
* goog.html.SafeStyle.PropertyMap is required.
* - For attributes which are interpreted as URLs (e.g. src, href) a
* goog.html.SafeUrl, goog.string.Const or string is required. If a string
* is passed, it will be sanitized with SafeUrl.sanitize().
* - For tags which can load code or set security relevant page metadata,
* more specific goog.html.SafeHtml.create*() functions must be used. Tags
* which are not supported by this function are applet, base, embed, iframe,
* link, math, object, script, style, svg, and template.
*
* @param {string} tagName The name of the tag. Only tag names consisting of
* [a-zA-Z0-9-] are allowed. Tag names documented above are disallowed.
* @param {?Object<string, ?goog.html.SafeHtml.AttributeValue>=} opt_attributes
* Mapping from attribute names to their values. Only attribute names
* consisting of [a-zA-Z0-9-] are allowed. Value of null or undefined causes
* the attribute to be omitted.
* @param {!goog.html.SafeHtml.TextOrHtml_|
* !Array<!goog.html.SafeHtml.TextOrHtml_>=} opt_content Content to
* HTML-escape and put inside the tag. This must be empty for void tags
* like <br>. Array elements are concatenated.
* @return {!goog.html.SafeHtml} The SafeHtml content with the tag.
* @throws {Error} If invalid tag name, attribute name, or attribute value is
* provided.
* @throws {goog.asserts.AssertionError} If content for void tag is provided.
*/
goog.html.SafeHtml.create = function(tagName, opt_attributes, opt_content) {
goog.html.SafeHtml.verifyTagName(tagName);
return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse(
tagName, opt_attributes, opt_content);
};
/**
* Verifies if the tag name is valid and if it doesn't change the context.
* E.g. STRONG is fine but SCRIPT throws because it changes context. See
* goog.html.SafeHtml.create for an explanation of allowed tags.
* @param {string} tagName
* @throws {Error} If invalid tag name is provided.
* @package
*/
goog.html.SafeHtml.verifyTagName = function(tagName) {
if (!goog.html.SafeHtml.VALID_NAMES_IN_TAG_.test(tagName)) {
throw Error('Invalid tag name <' + tagName + '>.');
}
if (tagName.toUpperCase() in goog.html.SafeHtml.NOT_ALLOWED_TAG_NAMES_) {
throw Error('Tag name <' + tagName + '> is not allowed for SafeHtml.');
}
};
/**
* Creates a SafeHtml representing an iframe tag.
*
* This by default restricts the iframe as much as possible by setting the
* sandbox attribute to the empty string. If the iframe requires less
* restrictions, set the sandbox attribute as tight as possible, but do not rely
* on the sandbox as a security feature because it is not supported by older
* browsers. If a sandbox is essential to security (e.g. for third-party
* frames), use createSandboxIframe which checks for browser support.
*
* @see https://developer.mozilla.org/en/docs/Web/HTML/Element/iframe#attr-sandbox
*
* @param {?goog.html.TrustedResourceUrl=} opt_src The value of the src
* attribute. If null or undefined src will not be set.
* @param {?goog.html.SafeHtml=} opt_srcdoc The value of the srcdoc attribute.
* If null or undefined srcdoc will not be set.
* @param {?Object<string, ?goog.html.SafeHtml.AttributeValue>=} opt_attributes
* Mapping from attribute names to their values. Only attribute names
* consisting of [a-zA-Z0-9-] are allowed. Value of null or undefined causes
* the attribute to be omitted.
* @param {!goog.html.SafeHtml.TextOrHtml_|
* !Array<!goog.html.SafeHtml.TextOrHtml_>=} opt_content Content to
* HTML-escape and put inside the tag. Array elements are concatenated.
* @return {!goog.html.SafeHtml} The SafeHtml content with the tag.
* @throws {Error} If invalid tag name, attribute name, or attribute value is
* provided. If opt_attributes contains the src or srcdoc attributes.
*/
goog.html.SafeHtml.createIframe = function(
opt_src, opt_srcdoc, opt_attributes, opt_content) {
if (opt_src) {
// Check whether this is really TrustedResourceUrl.
goog.html.TrustedResourceUrl.unwrap(opt_src);
}
var fixedAttributes = {};
fixedAttributes['src'] = opt_src || null;
fixedAttributes['srcdoc'] =
opt_srcdoc && goog.html.SafeHtml.unwrap(opt_srcdoc);
var defaultAttributes = {'sandbox': ''};
var attributes = goog.html.SafeHtml.combineAttributes(
fixedAttributes, defaultAttributes, opt_attributes);
return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse(
'iframe', attributes, opt_content);
};
/**
* Creates a SafeHtml representing a sandboxed iframe tag.
*
* The sandbox attribute is enforced in its most restrictive mode, an empty
* string. Consequently, the security requirements for the src and srcdoc
* attributes are relaxed compared to SafeHtml.createIframe. This function
* will throw on browsers that do not support the sandbox attribute, as
* determined by SafeHtml.canUseSandboxIframe.
*
* The SafeHtml returned by this function can trigger downloads with no
* user interaction on Chrome (though only a few, further attempts are blocked).
* Firefox and IE will block all downloads from the sandbox.
*
* @see https://developer.mozilla.org/en/docs/Web/HTML/Element/iframe#attr-sandbox
* @see https://lists.w3.org/Archives/Public/public-whatwg-archive/2013Feb/0112.html
*
* @param {string|!goog.html.SafeUrl=} opt_src The value of the src
* attribute. If null or undefined src will not be set.
* @param {string=} opt_srcdoc The value of the srcdoc attribute.
* If null or undefined srcdoc will not be set. Will not be sanitized.
* @param {!Object<string, ?goog.html.SafeHtml.AttributeValue>=} opt_attributes
* Mapping from attribute names to their values. Only attribute names
* consisting of [a-zA-Z0-9-] are allowed. Value of null or undefined causes
* the attribute to be omitted.
* @param {!goog.html.SafeHtml.TextOrHtml_|
* !Array<!goog.html.SafeHtml.TextOrHtml_>=} opt_content Content to
* HTML-escape and put inside the tag. Array elements are concatenated.
* @return {!goog.html.SafeHtml} The SafeHtml content with the tag.
* @throws {Error} If invalid tag name, attribute name, or attribute value is
* provided. If opt_attributes contains the src, srcdoc or sandbox
* attributes. If browser does not support the sandbox attribute on iframe.
*/
goog.html.SafeHtml.createSandboxIframe = function(
opt_src, opt_srcdoc, opt_attributes, opt_content) {
if (!goog.html.SafeHtml.canUseSandboxIframe()) {
throw new Error('The browser does not support sandboxed iframes.');
}
var fixedAttributes = {};
if (opt_src) {
// Note that sanitize is a no-op on SafeUrl.
fixedAttributes['src'] =
goog.html.SafeUrl.unwrap(goog.html.SafeUrl.sanitize(opt_src));
} else {
fixedAttributes['src'] = null;
}
fixedAttributes['srcdoc'] = opt_srcdoc || null;
fixedAttributes['sandbox'] = '';
var attributes =
goog.html.SafeHtml.combineAttributes(fixedAttributes, {}, opt_attributes);
return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse(
'iframe', attributes, opt_content);
};
/**
* Checks if the user agent supports sandboxed iframes.
* @return {boolean}
*/
goog.html.SafeHtml.canUseSandboxIframe = function() {
return goog.global['HTMLIFrameElement'] &&
('sandbox' in goog.global['HTMLIFrameElement'].prototype);
};
/**
* Creates a SafeHtml representing a script tag with the src attribute.
* @param {!goog.html.TrustedResourceUrl} src The value of the src
* attribute.
* @param {?Object<string, ?goog.html.SafeHtml.AttributeValue>=}
* opt_attributes
* Mapping from attribute names to their values. Only attribute names
* consisting of [a-zA-Z0-9-] are allowed. Value of null or undefined
* causes the attribute to be omitted.
* @return {!goog.html.SafeHtml} The SafeHtml content with the tag.
* @throws {Error} If invalid attribute name or value is provided. If
* opt_attributes contains the src attribute.
*/
goog.html.SafeHtml.createScriptSrc = function(src, opt_attributes) {
// Check whether this is really TrustedResourceUrl.
goog.html.TrustedResourceUrl.unwrap(src);
var fixedAttributes = {'src': src};
var defaultAttributes = {};
var attributes = goog.html.SafeHtml.combineAttributes(
fixedAttributes, defaultAttributes, opt_attributes);
return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse(
'script', attributes);
};
/**
* Creates a SafeHtml representing a style tag. The type attribute is set
* to "text/css".
* @param {!goog.html.SafeStyleSheet|!Array<!goog.html.SafeStyleSheet>}
* styleSheet Content to put inside the tag. Array elements are
* concatenated.
* @param {?Object<string, ?goog.html.SafeHtml.AttributeValue>=} opt_attributes
* Mapping from attribute names to their values. Only attribute names
* consisting of [a-zA-Z0-9-] are allowed. Value of null or undefined causes
* the attribute to be omitted.
* @return {!goog.html.SafeHtml} The SafeHtml content with the tag.
* @throws {Error} If invalid attribute name or attribute value is provided. If
* opt_attributes contains the type attribute.
*/
goog.html.SafeHtml.createStyle = function(styleSheet, opt_attributes) {
var fixedAttributes = {'type': 'text/css'};
var defaultAttributes = {};
var attributes = goog.html.SafeHtml.combineAttributes(
fixedAttributes, defaultAttributes, opt_attributes);
var content = '';
styleSheet = goog.array.concat(styleSheet);
for (var i = 0; i < styleSheet.length; i++) {
content += goog.html.SafeStyleSheet.unwrap(styleSheet[i]);
}
// Convert to SafeHtml so that it's not HTML-escaped. This is safe because
// as part of its contract, SafeStyleSheet should have no dangerous '<'.
var htmlContent =
goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
content, goog.i18n.bidi.Dir.NEUTRAL);
return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse(
'style', attributes, htmlContent);
};
/**
* Creates a SafeHtml representing a meta refresh tag.
* @param {!goog.html.SafeUrl|string} url Where to redirect. If a string is
* passed, it will be sanitized with SafeUrl.sanitize().
* @param {number=} opt_secs Number of seconds until the page should be
* reloaded. Will be set to 0 if unspecified.
* @return {!goog.html.SafeHtml} The SafeHtml content with the tag.
*/
goog.html.SafeHtml.createMetaRefresh = function(url, opt_secs) {
// Note that sanitize is a no-op on SafeUrl.
var unwrappedUrl = goog.html.SafeUrl.unwrap(goog.html.SafeUrl.sanitize(url));
if (goog.labs.userAgent.browser.isIE() ||
goog.labs.userAgent.browser.isEdge()) {
// IE/EDGE can't parse the content attribute if the url contains a
// semicolon. We can fix this by adding quotes around the url, but then we
// can't parse quotes in the URL correctly. Also, it seems that IE/EDGE
// did not unescape semicolons in these URLs at some point in the past. We
// take a best-effort approach.
//
// If the URL has semicolons (which may happen in some cases, see
// http://www.w3.org/TR/1999/REC-html401-19991224/appendix/notes.html#h-B.2
// for instance), wrap it in single quotes to protect the semicolons.
// If the URL has semicolons and single quotes, url-encode the single quotes
// as well.
//
// This is imperfect. Notice that both ' and ; are reserved characters in
// URIs, so this could do the wrong thing, but at least it will do the wrong
// thing in only rare cases.
if (goog.string.contains(unwrappedUrl, ';')) {
unwrappedUrl = "'" + unwrappedUrl.replace(/'/g, '%27') + "'";
}
}
var attributes = {
'http-equiv': 'refresh',
'content': (opt_secs || 0) + '; url=' + unwrappedUrl
};
// This function will handle the HTML escaping for attributes.
return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse(
'meta', attributes);
};
/**
* @param {string} tagName The tag name.
* @param {string} name The attribute name.
* @param {!goog.html.SafeHtml.AttributeValue} value The attribute value.
* @return {string} A "name=value" string.
* @throws {Error} If attribute value is unsafe for the given tag and attribute.
* @private
*/
goog.html.SafeHtml.getAttrNameAndValue_ = function(tagName, name, value) {
// If it's goog.string.Const, allow any valid attribute name.
if (value instanceof goog.string.Const) {
value = goog.string.Const.unwrap(value);
} else if (name.toLowerCase() == 'style') {
value = goog.html.SafeHtml.getStyleValue_(value);
} else if (/^on/i.test(name)) {
// TODO(jakubvrana): Disallow more attributes with a special meaning.
throw Error(
'Attribute "' + name + '" requires goog.string.Const value, "' + value +
'" given.');
// URL attributes handled differently accroding to tag.
} else if (name.toLowerCase() in goog.html.SafeHtml.URL_ATTRIBUTES_) {
if (value instanceof goog.html.TrustedResourceUrl) {
value = goog.html.TrustedResourceUrl.unwrap(value);
} else if (value instanceof goog.html.SafeUrl) {
value = goog.html.SafeUrl.unwrap(value);
} else if (goog.isString(value)) {
value = goog.html.SafeUrl.sanitize(value).getTypedStringValue();
} else {
throw Error(
'Attribute "' + name + '" on tag "' + tagName +
'" requires goog.html.SafeUrl, goog.string.Const, or string,' +
' value "' + value + '" given.');
}
}
// Accept SafeUrl, TrustedResourceUrl, etc. for attributes which only require
// HTML-escaping.
if (value.implementsGoogStringTypedString) {
// Ok to call getTypedStringValue() since there's no reliance on the type
// contract for security here.
value = value.getTypedStringValue();
}
goog.asserts.assert(
goog.isString(value) || goog.isNumber(value),
'String or number value expected, got ' + (typeof value) +
' with value: ' + value);
return name + '="' + goog.string.htmlEscape(String(value)) + '"';
};
/**
* Gets value allowed in "style" attribute.
* @param {!goog.html.SafeHtml.AttributeValue} value It could be SafeStyle or a
* map which will be passed to goog.html.SafeStyle.create.
* @return {string} Unwrapped value.
* @throws {Error} If string value is given.
* @private
*/
goog.html.SafeHtml.getStyleValue_ = function(value) {
if (!goog.isObject(value)) {
throw Error(
'The "style" attribute requires goog.html.SafeStyle or map ' +
'of style properties, ' + (typeof value) + ' given: ' + value);
}
if (!(value instanceof goog.html.SafeStyle)) {
// Process the property bag into a style object.
value = goog.html.SafeStyle.create(value);
}
return goog.html.SafeStyle.unwrap(value);
};
/**
* Creates a SafeHtml content with known directionality consisting of a tag with
* optional attributes and optional content.
* @param {!goog.i18n.bidi.Dir} dir Directionality.
* @param {string} tagName
* @param {?Object<string, ?goog.html.SafeHtml.AttributeValue>=} opt_attributes
* @param {!goog.html.SafeHtml.TextOrHtml_|
* !Array<!goog.html.SafeHtml.TextOrHtml_>=} opt_content
* @return {!goog.html.SafeHtml} The SafeHtml content with the tag.
*/
goog.html.SafeHtml.createWithDir = function(
dir, tagName, opt_attributes, opt_content) {
var html = goog.html.SafeHtml.create(tagName, opt_attributes, opt_content);
html.dir_ = dir;
return html;
};
/**
* Creates a new SafeHtml object by concatenating values.
* @param {...(!goog.html.SafeHtml.TextOrHtml_|
* !Array<!goog.html.SafeHtml.TextOrHtml_>)} var_args Values to concatenate.
* @return {!goog.html.SafeHtml}
*/
goog.html.SafeHtml.concat = function(var_args) {
var dir = goog.i18n.bidi.Dir.NEUTRAL;
var content = '';
/**
* @param {!goog.html.SafeHtml.TextOrHtml_|
* !Array<!goog.html.SafeHtml.TextOrHtml_>} argument
*/
var addArgument = function(argument) {
if (goog.isArray(argument)) {
goog.array.forEach(argument, addArgument);
} else {
var html = goog.html.SafeHtml.htmlEscape(argument);
content += goog.html.SafeHtml.unwrap(html);
var htmlDir = html.getDirection();
if (dir == goog.i18n.bidi.Dir.NEUTRAL) {
dir = htmlDir;
} else if (htmlDir != goog.i18n.bidi.Dir.NEUTRAL && dir != htmlDir) {
dir = null;
}
}
};
goog.array.forEach(arguments, addArgument);
return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
content, dir);
};
/**
* Creates a new SafeHtml object with known directionality by concatenating the
* values.
* @param {!goog.i18n.bidi.Dir} dir Directionality.
* @param {...(!goog.html.SafeHtml.TextOrHtml_|
* !Array<!goog.html.SafeHtml.TextOrHtml_>)} var_args Elements of array
* arguments would be processed recursively.
* @return {!goog.html.SafeHtml}
*/
goog.html.SafeHtml.concatWithDir = function(dir, var_args) {
var html = goog.html.SafeHtml.concat(goog.array.slice(arguments, 1));
html.dir_ = dir;
return html;
};
/**
* Type marker for the SafeHtml type, used to implement additional run-time
* type checking.
* @const {!Object}
* @private
*/
goog.html.SafeHtml.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {};
/**
* Package-internal utility method to create SafeHtml instances.
*
* @param {string} html The string to initialize the SafeHtml object with.
* @param {?goog.i18n.bidi.Dir} dir The directionality of the SafeHtml to be
* constructed, or null if unknown.
* @return {!goog.html.SafeHtml} The initialized SafeHtml object.
* @package
*/
goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse = function(
html, dir) {
return new goog.html.SafeHtml().initSecurityPrivateDoNotAccessOrElse_(
html, dir);
};
/**
* Called from createSafeHtmlSecurityPrivateDoNotAccessOrElse(). This
* method exists only so that the compiler can dead code eliminate static
* fields (like EMPTY) when they're not accessed.
* @param {string} html
* @param {?goog.i18n.bidi.Dir} dir
* @return {!goog.html.SafeHtml}
* @private
*/
goog.html.SafeHtml.prototype.initSecurityPrivateDoNotAccessOrElse_ = function(
html, dir) {
this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ = html;
this.dir_ = dir;
return this;
};
/**
* Like create() but does not restrict which tags can be constructed.
*
* @param {string} tagName Tag name. Set or validated by caller.
* @param {?Object<string, ?goog.html.SafeHtml.AttributeValue>=} opt_attributes
* @param {(!goog.html.SafeHtml.TextOrHtml_|
* !Array<!goog.html.SafeHtml.TextOrHtml_>)=} opt_content
* @return {!goog.html.SafeHtml}
* @throws {Error} If invalid or unsafe attribute name or value is provided.
* @throws {goog.asserts.AssertionError} If content for void tag is provided.
* @package
*/
goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse = function(
tagName, opt_attributes, opt_content) {
var dir = null;
var result = '<' + tagName;
result += goog.html.SafeHtml.stringifyAttributes(tagName, opt_attributes);
var content = opt_content;
if (!goog.isDefAndNotNull(content)) {
content = [];
} else if (!goog.isArray(content)) {
content = [content];
}
if (goog.dom.tags.isVoidTag(tagName.toLowerCase())) {
goog.asserts.assert(
!content.length, 'Void tag <' + tagName + '> does not allow content.');
result += '>';
} else {
var html = goog.html.SafeHtml.concat(content);
result += '>' + goog.html.SafeHtml.unwrap(html) + '</' + tagName + '>';
dir = html.getDirection();
}
var dirAttribute = opt_attributes && opt_attributes['dir'];
if (dirAttribute) {
if (/^(ltr|rtl|auto)$/i.test(dirAttribute)) {
// If the tag has the "dir" attribute specified then its direction is
// neutral because it can be safely used in any context.
dir = goog.i18n.bidi.Dir.NEUTRAL;
} else {
dir = null;
}
}
return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
result, dir);
};
/**
* Creates a string with attributes to insert after tagName.
* @param {string} tagName
* @param {?Object<string, ?goog.html.SafeHtml.AttributeValue>=} opt_attributes
* @return {string} Returns an empty string if there are no attributes, returns
* a string starting with a space otherwise.
* @throws {Error} If attribute value is unsafe for the given tag and attribute.
* @package
*/
goog.html.SafeHtml.stringifyAttributes = function(tagName, opt_attributes) {
var result = '';
if (opt_attributes) {
for (var name in opt_attributes) {
if (!goog.html.SafeHtml.VALID_NAMES_IN_TAG_.test(name)) {
throw Error('Invalid attribute name "' + name + '".');
}
var value = opt_attributes[name];
if (!goog.isDefAndNotNull(value)) {
continue;
}
result +=
' ' + goog.html.SafeHtml.getAttrNameAndValue_(tagName, name, value);
}
}
return result;
};
/**
* @param {!Object<string, string>} fixedAttributes
* @param {!Object<string, string>} defaultAttributes
* @param {?Object<string, ?goog.html.SafeHtml.AttributeValue>=} opt_attributes
* Optional attributes passed to create*().
* @return {!Object<string, ?goog.html.SafeHtml.AttributeValue>}
* @throws {Error} If opt_attributes contains an attribute with the same name
* as an attribute in fixedAttributes.
* @package
*/
goog.html.SafeHtml.combineAttributes = function(
fixedAttributes, defaultAttributes, opt_attributes) {
var combinedAttributes = {};
var name;
for (name in fixedAttributes) {
goog.asserts.assert(name.toLowerCase() == name, 'Must be lower case');
combinedAttributes[name] = fixedAttributes[name];
}
for (name in defaultAttributes) {
goog.asserts.assert(name.toLowerCase() == name, 'Must be lower case');
combinedAttributes[name] = defaultAttributes[name];
}
for (name in opt_attributes) {
var nameLower = name.toLowerCase();
if (nameLower in fixedAttributes) {
throw Error(
'Cannot override "' + nameLower + '" attribute, got "' + name +
'" with value "' + opt_attributes[name] + '"');
}
if (nameLower in defaultAttributes) {
delete combinedAttributes[nameLower];
}
combinedAttributes[name] = opt_attributes[name];
}
return combinedAttributes;
};
/**
* A SafeHtml instance corresponding to the HTML doctype: "<!DOCTYPE html>".
* @const {!goog.html.SafeHtml}
*/
goog.html.SafeHtml.DOCTYPE_HTML =
goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
'<!DOCTYPE html>', goog.i18n.bidi.Dir.NEUTRAL);
/**
* A SafeHtml instance corresponding to the empty string.
* @const {!goog.html.SafeHtml}
*/
goog.html.SafeHtml.EMPTY =
goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
'', goog.i18n.bidi.Dir.NEUTRAL);
/**
* A SafeHtml instance corresponding to the <br> tag.
* @const {!goog.html.SafeHtml}
*/
goog.html.SafeHtml.BR =
goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
'<br>', goog.i18n.bidi.Dir.NEUTRAL);

View File

@ -0,0 +1,234 @@
// Copyright 2014 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview The SafeScript type and its builders.
*
* TODO(xtof): Link to document stating type contract.
*/
goog.provide('goog.html.SafeScript');
goog.require('goog.asserts');
goog.require('goog.string.Const');
goog.require('goog.string.TypedString');
/**
* A string-like object which represents JavaScript code and that carries the
* security type contract that its value, as a string, will not cause execution
* of unconstrained attacker controlled code (XSS) when evaluated as JavaScript
* in a browser.
*
* Instances of this type must be created via the factory method
* {@code goog.html.SafeScript.fromConstant} and not by invoking its
* constructor. The constructor intentionally takes no parameters and the type
* is immutable; hence only a default instance corresponding to the empty string
* can be obtained via constructor invocation.
*
* A SafeScript's string representation can safely be interpolated as the
* content of a script element within HTML. The SafeScript string should not be
* escaped before interpolation.
*
* Note that the SafeScript might contain text that is attacker-controlled but
* that text should have been interpolated with appropriate escaping,
* sanitization and/or validation into the right location in the script, such
* that it is highly constrained in its effect (for example, it had to match a
* set of whitelisted words).
*
* A SafeScript can be constructed via security-reviewed unchecked
* conversions. In this case producers of SafeScript must ensure themselves that
* the SafeScript does not contain unsafe script. Note in particular that
* {@code &lt;} is dangerous, even when inside JavaScript strings, and so should
* always be forbidden or JavaScript escaped in user controlled input. For
* example, if {@code &lt;/script&gt;&lt;script&gt;evil&lt;/script&gt;"} were
* interpolated inside a JavaScript string, it would break out of the context
* of the original script element and {@code evil} would execute. Also note
* that within an HTML script (raw text) element, HTML character references,
* such as "&lt;" are not allowed. See
* http://www.w3.org/TR/html5/scripting-1.html#restrictions-for-contents-of-script-elements.
*
* @see goog.html.SafeScript#fromConstant
* @constructor
* @final
* @struct
* @implements {goog.string.TypedString}
*/
goog.html.SafeScript = function() {
/**
* The contained value of this SafeScript. The field has a purposely
* ugly name to make (non-compiled) code that attempts to directly access this
* field stand out.
* @private {string}
*/
this.privateDoNotAccessOrElseSafeScriptWrappedValue_ = '';
/**
* A type marker used to implement additional run-time type checking.
* @see goog.html.SafeScript#unwrap
* @const
* @private
*/
this.SAFE_SCRIPT_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ =
goog.html.SafeScript.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_;
};
/**
* @override
* @const
*/
goog.html.SafeScript.prototype.implementsGoogStringTypedString = true;
/**
* Type marker for the SafeScript type, used to implement additional
* run-time type checking.
* @const {!Object}
* @private
*/
goog.html.SafeScript.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {};
/**
* Creates a SafeScript object from a compile-time constant string.
*
* @param {!goog.string.Const} script A compile-time-constant string from which
* to create a SafeScript.
* @return {!goog.html.SafeScript} A SafeScript object initialized to
* {@code script}.
*/
goog.html.SafeScript.fromConstant = function(script) {
var scriptString = goog.string.Const.unwrap(script);
if (scriptString.length === 0) {
return goog.html.SafeScript.EMPTY;
}
return goog.html.SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse(
scriptString);
};
/**
* Returns this SafeScript's value as a string.
*
* IMPORTANT: In code where it is security relevant that an object's type is
* indeed {@code SafeScript}, use {@code goog.html.SafeScript.unwrap} instead of
* this method. If in doubt, assume that it's security relevant. In particular,
* note that goog.html functions which return a goog.html type do not guarantee
* the returned instance is of the right type. For example:
*
* <pre>
* var fakeSafeHtml = new String('fake');
* fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype;
* var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml);
* // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by
* // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml
* // instanceof goog.html.SafeHtml.
* </pre>
*
* @see goog.html.SafeScript#unwrap
* @override
*/
goog.html.SafeScript.prototype.getTypedStringValue = function() {
return this.privateDoNotAccessOrElseSafeScriptWrappedValue_;
};
if (goog.DEBUG) {
/**
* Returns a debug string-representation of this value.
*
* To obtain the actual string value wrapped in a SafeScript, use
* {@code goog.html.SafeScript.unwrap}.
*
* @see goog.html.SafeScript#unwrap
* @override
*/
goog.html.SafeScript.prototype.toString = function() {
return 'SafeScript{' +
this.privateDoNotAccessOrElseSafeScriptWrappedValue_ + '}';
};
}
/**
* Performs a runtime check that the provided object is indeed a
* SafeScript object, and returns its value.
*
* @param {!goog.html.SafeScript} safeScript The object to extract from.
* @return {string} The safeScript object's contained string, unless
* the run-time type check fails. In that case, {@code unwrap} returns an
* innocuous string, or, if assertions are enabled, throws
* {@code goog.asserts.AssertionError}.
*/
goog.html.SafeScript.unwrap = function(safeScript) {
// Perform additional Run-time type-checking to ensure that
// safeScript is indeed an instance of the expected type. This
// provides some additional protection against security bugs due to
// application code that disables type checks.
// Specifically, the following checks are performed:
// 1. The object is an instance of the expected type.
// 2. The object is not an instance of a subclass.
// 3. The object carries a type marker for the expected type. "Faking" an
// object requires a reference to the type marker, which has names intended
// to stand out in code reviews.
if (safeScript instanceof goog.html.SafeScript &&
safeScript.constructor === goog.html.SafeScript &&
safeScript.SAFE_SCRIPT_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ ===
goog.html.SafeScript.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) {
return safeScript.privateDoNotAccessOrElseSafeScriptWrappedValue_;
} else {
goog.asserts.fail('expected object of type SafeScript, got \'' +
safeScript + '\' of type ' + goog.typeOf(safeScript));
return 'type_error:SafeScript';
}
};
/**
* Package-internal utility method to create SafeScript instances.
*
* @param {string} script The string to initialize the SafeScript object with.
* @return {!goog.html.SafeScript} The initialized SafeScript object.
* @package
*/
goog.html.SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse =
function(script) {
return new goog.html.SafeScript().initSecurityPrivateDoNotAccessOrElse_(
script);
};
/**
* Called from createSafeScriptSecurityPrivateDoNotAccessOrElse(). This
* method exists only so that the compiler can dead code eliminate static
* fields (like EMPTY) when they're not accessed.
* @param {string} script
* @return {!goog.html.SafeScript}
* @private
*/
goog.html.SafeScript.prototype.initSecurityPrivateDoNotAccessOrElse_ = function(
script) {
this.privateDoNotAccessOrElseSafeScriptWrappedValue_ = script;
return this;
};
/**
* A SafeScript instance corresponding to the empty string.
* @const {!goog.html.SafeScript}
*/
goog.html.SafeScript.EMPTY =
goog.html.SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse('');

View File

@ -0,0 +1,449 @@
// Copyright 2014 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview The SafeStyle type and its builders.
*
* TODO(xtof): Link to document stating type contract.
*/
goog.provide('goog.html.SafeStyle');
goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.string');
goog.require('goog.string.Const');
goog.require('goog.string.TypedString');
/**
* A string-like object which represents a sequence of CSS declarations
* ({@code propertyName1: propertyvalue1; propertyName2: propertyValue2; ...})
* and that carries the security type contract that its value, as a string,
* will not cause untrusted script execution (XSS) when evaluated as CSS in a
* browser.
*
* Instances of this type must be created via the factory methods
* ({@code goog.html.SafeStyle.create} or
* {@code goog.html.SafeStyle.fromConstant}) and not by invoking its
* constructor. The constructor intentionally takes no parameters and the type
* is immutable; hence only a default instance corresponding to the empty string
* can be obtained via constructor invocation.
*
* A SafeStyle's string representation ({@link #getTypedStringValue()}) can
* safely:
* <ul>
* <li>Be interpolated as the entire content of a *quoted* HTML style
* attribute, or before already existing properties. The SafeStyle string
* *must be HTML-attribute-escaped* (where " and ' are escaped) before
* interpolation.
* <li>Be interpolated as the entire content of a {}-wrapped block within a
* stylesheet, or before already existing properties. The SafeStyle string
* should not be escaped before interpolation. SafeStyle's contract also
* guarantees that the string will not be able to introduce new properties
* or elide existing ones.
* <li>Be assigned to the style property of a DOM node. The SafeStyle string
* should not be escaped before being assigned to the property.
* </ul>
*
* A SafeStyle may never contain literal angle brackets. Otherwise, it could
* be unsafe to place a SafeStyle into a &lt;style&gt; tag (where it can't
* be HTML escaped). For example, if the SafeStyle containing
* "{@code font: 'foo &lt;style/&gt;&lt;script&gt;evil&lt;/script&gt;'}" were
* interpolated within a &lt;style&gt; tag, this would then break out of the
* style context into HTML.
*
* A SafeStyle may contain literal single or double quotes, and as such the
* entire style string must be escaped when used in a style attribute (if
* this were not the case, the string could contain a matching quote that
* would escape from the style attribute).
*
* Values of this type must be composable, i.e. for any two values
* {@code style1} and {@code style2} of this type,
* {@code goog.html.SafeStyle.unwrap(style1) +
* goog.html.SafeStyle.unwrap(style2)} must itself be a value that satisfies
* the SafeStyle type constraint. This requirement implies that for any value
* {@code style} of this type, {@code goog.html.SafeStyle.unwrap(style)} must
* not end in a "property value" or "property name" context. For example,
* a value of {@code background:url("} or {@code font-} would not satisfy the
* SafeStyle contract. This is because concatenating such strings with a
* second value that itself does not contain unsafe CSS can result in an
* overall string that does. For example, if {@code javascript:evil())"} is
* appended to {@code background:url("}, the resulting string may result in
* the execution of a malicious script.
*
* TODO(user): Consider whether we should implement UTF-8 interchange
* validity checks and blacklisting of newlines (including Unicode ones) and
* other whitespace characters (\t, \f). Document here if so and also update
* SafeStyle.fromConstant().
*
* The following example values comply with this type's contract:
* <ul>
* <li><pre>width: 1em;</pre>
* <li><pre>height:1em;</pre>
* <li><pre>width: 1em;height: 1em;</pre>
* <li><pre>background:url('http://url');</pre>
* </ul>
* In addition, the empty string is safe for use in a CSS attribute.
*
* The following example values do NOT comply with this type's contract:
* <ul>
* <li><pre>background: red</pre> (missing a trailing semi-colon)
* <li><pre>background:</pre> (missing a value and a trailing semi-colon)
* <li><pre>1em</pre> (missing an attribute name, which provides context for
* the value)
* </ul>
*
* @see goog.html.SafeStyle#create
* @see goog.html.SafeStyle#fromConstant
* @see http://www.w3.org/TR/css3-syntax/
* @constructor
* @final
* @struct
* @implements {goog.string.TypedString}
*/
goog.html.SafeStyle = function() {
/**
* The contained value of this SafeStyle. The field has a purposely
* ugly name to make (non-compiled) code that attempts to directly access this
* field stand out.
* @private {string}
*/
this.privateDoNotAccessOrElseSafeStyleWrappedValue_ = '';
/**
* A type marker used to implement additional run-time type checking.
* @see goog.html.SafeStyle#unwrap
* @const
* @private
*/
this.SAFE_STYLE_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ =
goog.html.SafeStyle.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_;
};
/**
* @override
* @const
*/
goog.html.SafeStyle.prototype.implementsGoogStringTypedString = true;
/**
* Type marker for the SafeStyle type, used to implement additional
* run-time type checking.
* @const {!Object}
* @private
*/
goog.html.SafeStyle.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {};
/**
* Creates a SafeStyle object from a compile-time constant string.
*
* {@code style} should be in the format
* {@code name: value; [name: value; ...]} and must not have any < or >
* characters in it. This is so that SafeStyle's contract is preserved,
* allowing the SafeStyle to correctly be interpreted as a sequence of CSS
* declarations and without affecting the syntactic structure of any
* surrounding CSS and HTML.
*
* This method performs basic sanity checks on the format of {@code style}
* but does not constrain the format of {@code name} and {@code value}, except
* for disallowing tag characters.
*
* @param {!goog.string.Const} style A compile-time-constant string from which
* to create a SafeStyle.
* @return {!goog.html.SafeStyle} A SafeStyle object initialized to
* {@code style}.
*/
goog.html.SafeStyle.fromConstant = function(style) {
var styleString = goog.string.Const.unwrap(style);
if (styleString.length === 0) {
return goog.html.SafeStyle.EMPTY;
}
goog.html.SafeStyle.checkStyle_(styleString);
goog.asserts.assert(
goog.string.endsWith(styleString, ';'),
'Last character of style string is not \';\': ' + styleString);
goog.asserts.assert(
goog.string.contains(styleString, ':'),
'Style string must contain at least one \':\', to ' +
'specify a "name: value" pair: ' + styleString);
return goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse(
styleString);
};
/**
* Checks if the style definition is valid.
* @param {string} style
* @private
*/
goog.html.SafeStyle.checkStyle_ = function(style) {
goog.asserts.assert(
!/[<>]/.test(style), 'Forbidden characters in style string: ' + style);
};
/**
* Returns this SafeStyle's value as a string.
*
* IMPORTANT: In code where it is security relevant that an object's type is
* indeed {@code SafeStyle}, use {@code goog.html.SafeStyle.unwrap} instead of
* this method. If in doubt, assume that it's security relevant. In particular,
* note that goog.html functions which return a goog.html type do not guarantee
* the returned instance is of the right type. For example:
*
* <pre>
* var fakeSafeHtml = new String('fake');
* fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype;
* var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml);
* // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by
* // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml
* // instanceof goog.html.SafeHtml.
* </pre>
*
* @see goog.html.SafeStyle#unwrap
* @override
*/
goog.html.SafeStyle.prototype.getTypedStringValue = function() {
return this.privateDoNotAccessOrElseSafeStyleWrappedValue_;
};
if (goog.DEBUG) {
/**
* Returns a debug string-representation of this value.
*
* To obtain the actual string value wrapped in a SafeStyle, use
* {@code goog.html.SafeStyle.unwrap}.
*
* @see goog.html.SafeStyle#unwrap
* @override
*/
goog.html.SafeStyle.prototype.toString = function() {
return 'SafeStyle{' + this.privateDoNotAccessOrElseSafeStyleWrappedValue_ +
'}';
};
}
/**
* Performs a runtime check that the provided object is indeed a
* SafeStyle object, and returns its value.
*
* @param {!goog.html.SafeStyle} safeStyle The object to extract from.
* @return {string} The safeStyle object's contained string, unless
* the run-time type check fails. In that case, {@code unwrap} returns an
* innocuous string, or, if assertions are enabled, throws
* {@code goog.asserts.AssertionError}.
*/
goog.html.SafeStyle.unwrap = function(safeStyle) {
// Perform additional Run-time type-checking to ensure that
// safeStyle is indeed an instance of the expected type. This
// provides some additional protection against security bugs due to
// application code that disables type checks.
// Specifically, the following checks are performed:
// 1. The object is an instance of the expected type.
// 2. The object is not an instance of a subclass.
// 3. The object carries a type marker for the expected type. "Faking" an
// object requires a reference to the type marker, which has names intended
// to stand out in code reviews.
if (safeStyle instanceof goog.html.SafeStyle &&
safeStyle.constructor === goog.html.SafeStyle &&
safeStyle.SAFE_STYLE_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ ===
goog.html.SafeStyle.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) {
return safeStyle.privateDoNotAccessOrElseSafeStyleWrappedValue_;
} else {
goog.asserts.fail('expected object of type SafeStyle, got \'' +
safeStyle + '\' of type ' + goog.typeOf(safeStyle));
return 'type_error:SafeStyle';
}
};
/**
* Package-internal utility method to create SafeStyle instances.
*
* @param {string} style The string to initialize the SafeStyle object with.
* @return {!goog.html.SafeStyle} The initialized SafeStyle object.
* @package
*/
goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse = function(
style) {
return new goog.html.SafeStyle().initSecurityPrivateDoNotAccessOrElse_(style);
};
/**
* Called from createSafeStyleSecurityPrivateDoNotAccessOrElse(). This
* method exists only so that the compiler can dead code eliminate static
* fields (like EMPTY) when they're not accessed.
* @param {string} style
* @return {!goog.html.SafeStyle}
* @private
*/
goog.html.SafeStyle.prototype.initSecurityPrivateDoNotAccessOrElse_ = function(
style) {
this.privateDoNotAccessOrElseSafeStyleWrappedValue_ = style;
return this;
};
/**
* A SafeStyle instance corresponding to the empty string.
* @const {!goog.html.SafeStyle}
*/
goog.html.SafeStyle.EMPTY =
goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse('');
/**
* The innocuous string generated by goog.html.SafeUrl.create when passed
* an unsafe value.
* @const {string}
*/
goog.html.SafeStyle.INNOCUOUS_STRING = 'zClosurez';
/**
* Mapping of property names to their values.
* @typedef {!Object<string, goog.string.Const|string>}
*/
goog.html.SafeStyle.PropertyMap;
/**
* Creates a new SafeStyle object from the properties specified in the map.
* @param {goog.html.SafeStyle.PropertyMap} map Mapping of property names to
* their values, for example {'margin': '1px'}. Names must consist of
* [-_a-zA-Z0-9]. Values might be strings consisting of
* [-,.'"%_!# a-zA-Z0-9], where " and ' must be properly balanced.
* Other values must be wrapped in goog.string.Const. Null value causes
* skipping the property.
* @return {!goog.html.SafeStyle}
* @throws {Error} If invalid name is provided.
* @throws {goog.asserts.AssertionError} If invalid value is provided. With
* disabled assertions, invalid value is replaced by
* goog.html.SafeStyle.INNOCUOUS_STRING.
*/
goog.html.SafeStyle.create = function(map) {
var style = '';
for (var name in map) {
if (!/^[-_a-zA-Z0-9]+$/.test(name)) {
throw Error('Name allows only [-_a-zA-Z0-9], got: ' + name);
}
var value = map[name];
if (value == null) {
continue;
}
if (value instanceof goog.string.Const) {
value = goog.string.Const.unwrap(value);
// These characters can be used to change context and we don't want that
// even with const values.
goog.asserts.assert(!/[{;}]/.test(value), 'Value does not allow [{;}].');
} else if (!goog.html.SafeStyle.VALUE_RE_.test(value)) {
goog.asserts.fail(
'String value allows only [-,."\'%_!# a-zA-Z0-9], rgb() and ' +
'rgba(), got: ' + value);
value = goog.html.SafeStyle.INNOCUOUS_STRING;
} else if (!goog.html.SafeStyle.hasBalancedQuotes_(value)) {
goog.asserts.fail('String value requires balanced quotes, got: ' + value);
value = goog.html.SafeStyle.INNOCUOUS_STRING;
}
style += name + ':' + value + ';';
}
if (!style) {
return goog.html.SafeStyle.EMPTY;
}
goog.html.SafeStyle.checkStyle_(style);
return goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse(
style);
};
/**
* Checks that quotes (" and ') are properly balanced inside a string. Assumes
* that neither escape (\) nor any other character that could result in
* breaking out of a string parsing context are allowed;
* see http://www.w3.org/TR/css3-syntax/#string-token-diagram.
* @param {string} value Untrusted CSS property value.
* @return {boolean} True if property value is safe with respect to quote
* balancedness.
* @private
*/
goog.html.SafeStyle.hasBalancedQuotes_ = function(value) {
var outsideSingle = true;
var outsideDouble = true;
for (var i = 0; i < value.length; i++) {
var c = value.charAt(i);
if (c == "'" && outsideDouble) {
outsideSingle = !outsideSingle;
} else if (c == '"' && outsideSingle) {
outsideDouble = !outsideDouble;
}
}
return outsideSingle && outsideDouble;
};
// Keep in sync with the error string in create().
/**
* Regular expression for safe values.
*
* Quotes (" and ') are allowed, but a check must be done elsewhere to ensure
* they're balanced.
*
* ',' allows multiple values to be assigned to the same property
* (e.g. background-attachment or font-family) and hence could allow
* multiple values to get injected, but that should pose no risk of XSS.
*
* The rgb() and rgba() expression checks only for XSS safety, not for CSS
* validity.
* @const {!RegExp}
* @private
*/
goog.html.SafeStyle.VALUE_RE_ =
/^([-,."'%_!# a-zA-Z0-9]+|(?:rgb|hsl)a?\([0-9.%, ]+\))$/;
/**
* Creates a new SafeStyle object by concatenating the values.
* @param {...(!goog.html.SafeStyle|!Array<!goog.html.SafeStyle>)} var_args
* SafeStyles to concatenate.
* @return {!goog.html.SafeStyle}
*/
goog.html.SafeStyle.concat = function(var_args) {
var style = '';
/**
* @param {!goog.html.SafeStyle|!Array<!goog.html.SafeStyle>} argument
*/
var addArgument = function(argument) {
if (goog.isArray(argument)) {
goog.array.forEach(argument, addArgument);
} else {
style += goog.html.SafeStyle.unwrap(argument);
}
};
goog.array.forEach(arguments, addArgument);
if (!style) {
return goog.html.SafeStyle.EMPTY;
}
return goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse(
style);
};

View File

@ -0,0 +1,278 @@
// Copyright 2014 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview The SafeStyleSheet type and its builders.
*
* TODO(xtof): Link to document stating type contract.
*/
goog.provide('goog.html.SafeStyleSheet');
goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.string');
goog.require('goog.string.Const');
goog.require('goog.string.TypedString');
/**
* A string-like object which represents a CSS style sheet and that carries the
* security type contract that its value, as a string, will not cause untrusted
* script execution (XSS) when evaluated as CSS in a browser.
*
* Instances of this type must be created via the factory method
* {@code goog.html.SafeStyleSheet.fromConstant} and not by invoking its
* constructor. The constructor intentionally takes no parameters and the type
* is immutable; hence only a default instance corresponding to the empty string
* can be obtained via constructor invocation.
*
* A SafeStyleSheet's string representation can safely be interpolated as the
* content of a style element within HTML. The SafeStyleSheet string should
* not be escaped before interpolation.
*
* Values of this type must be composable, i.e. for any two values
* {@code styleSheet1} and {@code styleSheet2} of this type,
* {@code goog.html.SafeStyleSheet.unwrap(styleSheet1) +
* goog.html.SafeStyleSheet.unwrap(styleSheet2)} must itself be a value that
* satisfies the SafeStyleSheet type constraint. This requirement implies that
* for any value {@code styleSheet} of this type,
* {@code goog.html.SafeStyleSheet.unwrap(styleSheet1)} must end in
* "beginning of rule" context.
* A SafeStyleSheet can be constructed via security-reviewed unchecked
* conversions. In this case producers of SafeStyleSheet must ensure themselves
* that the SafeStyleSheet does not contain unsafe script. Note in particular
* that {@code &lt;} is dangerous, even when inside CSS strings, and so should
* always be forbidden or CSS-escaped in user controlled input. For example, if
* {@code &lt;/style&gt;&lt;script&gt;evil&lt;/script&gt;"} were interpolated
* inside a CSS string, it would break out of the context of the original
* style element and {@code evil} would execute. Also note that within an HTML
* style (raw text) element, HTML character references, such as
* {@code &amp;lt;}, are not allowed. See
*
http://www.w3.org/TR/html5/scripting-1.html#restrictions-for-contents-of-script-elements
* (similar considerations apply to the style element).
*
* @see goog.html.SafeStyleSheet#fromConstant
* @constructor
* @final
* @struct
* @implements {goog.string.TypedString}
*/
goog.html.SafeStyleSheet = function() {
/**
* The contained value of this SafeStyleSheet. The field has a purposely
* ugly name to make (non-compiled) code that attempts to directly access this
* field stand out.
* @private {string}
*/
this.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_ = '';
/**
* A type marker used to implement additional run-time type checking.
* @see goog.html.SafeStyleSheet#unwrap
* @const
* @private
*/
this.SAFE_STYLE_SHEET_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ =
goog.html.SafeStyleSheet.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_;
};
/**
* @override
* @const
*/
goog.html.SafeStyleSheet.prototype.implementsGoogStringTypedString = true;
/**
* Type marker for the SafeStyleSheet type, used to implement additional
* run-time type checking.
* @const {!Object}
* @private
*/
goog.html.SafeStyleSheet.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {};
/**
* Creates a new SafeStyleSheet object by concatenating values.
* @param {...(!goog.html.SafeStyleSheet|!Array<!goog.html.SafeStyleSheet>)}
* var_args Values to concatenate.
* @return {!goog.html.SafeStyleSheet}
*/
goog.html.SafeStyleSheet.concat = function(var_args) {
var result = '';
/**
* @param {!goog.html.SafeStyleSheet|!Array<!goog.html.SafeStyleSheet>}
* argument
*/
var addArgument = function(argument) {
if (goog.isArray(argument)) {
goog.array.forEach(argument, addArgument);
} else {
result += goog.html.SafeStyleSheet.unwrap(argument);
}
};
goog.array.forEach(arguments, addArgument);
return goog.html.SafeStyleSheet
.createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(result);
};
/**
* Creates a SafeStyleSheet object from a compile-time constant string.
*
* {@code styleSheet} must not have any &lt; characters in it, so that
* the syntactic structure of the surrounding HTML is not affected.
*
* @param {!goog.string.Const} styleSheet A compile-time-constant string from
* which to create a SafeStyleSheet.
* @return {!goog.html.SafeStyleSheet} A SafeStyleSheet object initialized to
* {@code styleSheet}.
*/
goog.html.SafeStyleSheet.fromConstant = function(styleSheet) {
var styleSheetString = goog.string.Const.unwrap(styleSheet);
if (styleSheetString.length === 0) {
return goog.html.SafeStyleSheet.EMPTY;
}
// > is a valid character in CSS selectors and there's no strict need to
// block it if we already block <.
goog.asserts.assert(
!goog.string.contains(styleSheetString, '<'),
"Forbidden '<' character in style sheet string: " + styleSheetString);
return goog.html.SafeStyleSheet
.createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(styleSheetString);
};
/**
* Returns this SafeStyleSheet's value as a string.
*
* IMPORTANT: In code where it is security relevant that an object's type is
* indeed {@code SafeStyleSheet}, use {@code goog.html.SafeStyleSheet.unwrap}
* instead of this method. If in doubt, assume that it's security relevant. In
* particular, note that goog.html functions which return a goog.html type do
* not guarantee the returned instance is of the right type. For example:
*
* <pre>
* var fakeSafeHtml = new String('fake');
* fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype;
* var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml);
* // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by
* // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml
* // instanceof goog.html.SafeHtml.
* </pre>
*
* @see goog.html.SafeStyleSheet#unwrap
* @override
*/
goog.html.SafeStyleSheet.prototype.getTypedStringValue = function() {
return this.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_;
};
if (goog.DEBUG) {
/**
* Returns a debug string-representation of this value.
*
* To obtain the actual string value wrapped in a SafeStyleSheet, use
* {@code goog.html.SafeStyleSheet.unwrap}.
*
* @see goog.html.SafeStyleSheet#unwrap
* @override
*/
goog.html.SafeStyleSheet.prototype.toString = function() {
return 'SafeStyleSheet{' +
this.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_ + '}';
};
}
/**
* Performs a runtime check that the provided object is indeed a
* SafeStyleSheet object, and returns its value.
*
* @param {!goog.html.SafeStyleSheet} safeStyleSheet The object to extract from.
* @return {string} The safeStyleSheet object's contained string, unless
* the run-time type check fails. In that case, {@code unwrap} returns an
* innocuous string, or, if assertions are enabled, throws
* {@code goog.asserts.AssertionError}.
*/
goog.html.SafeStyleSheet.unwrap = function(safeStyleSheet) {
// Perform additional Run-time type-checking to ensure that
// safeStyleSheet is indeed an instance of the expected type. This
// provides some additional protection against security bugs due to
// application code that disables type checks.
// Specifically, the following checks are performed:
// 1. The object is an instance of the expected type.
// 2. The object is not an instance of a subclass.
// 3. The object carries a type marker for the expected type. "Faking" an
// object requires a reference to the type marker, which has names intended
// to stand out in code reviews.
if (safeStyleSheet instanceof goog.html.SafeStyleSheet &&
safeStyleSheet.constructor === goog.html.SafeStyleSheet &&
safeStyleSheet
.SAFE_STYLE_SHEET_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ ===
goog.html.SafeStyleSheet.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) {
return safeStyleSheet.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_;
} else {
goog.asserts.fail('expected object of type SafeStyleSheet, got \'' +
safeStyleSheet + '\' of type ' + goog.typeOf(safeStyleSheet));
return 'type_error:SafeStyleSheet';
}
};
/**
* Package-internal utility method to create SafeStyleSheet instances.
*
* @param {string} styleSheet The string to initialize the SafeStyleSheet
* object with.
* @return {!goog.html.SafeStyleSheet} The initialized SafeStyleSheet object.
* @package
*/
goog.html.SafeStyleSheet.createSafeStyleSheetSecurityPrivateDoNotAccessOrElse =
function(styleSheet) {
return new goog.html.SafeStyleSheet().initSecurityPrivateDoNotAccessOrElse_(
styleSheet);
};
/**
* Called from createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(). This
* method exists only so that the compiler can dead code eliminate static
* fields (like EMPTY) when they're not accessed.
* @param {string} styleSheet
* @return {!goog.html.SafeStyleSheet}
* @private
*/
goog.html.SafeStyleSheet.prototype.initSecurityPrivateDoNotAccessOrElse_ =
function(styleSheet) {
this.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_ = styleSheet;
return this;
};
/**
* A SafeStyleSheet instance corresponding to the empty string.
* @const {!goog.html.SafeStyleSheet}
*/
goog.html.SafeStyleSheet.EMPTY =
goog.html.SafeStyleSheet
.createSafeStyleSheetSecurityPrivateDoNotAccessOrElse('');

View File

@ -0,0 +1,412 @@
// Copyright 2013 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview The SafeUrl type and its builders.
*
* TODO(xtof): Link to document stating type contract.
*/
goog.provide('goog.html.SafeUrl');
goog.require('goog.asserts');
goog.require('goog.fs.url');
goog.require('goog.i18n.bidi.Dir');
goog.require('goog.i18n.bidi.DirectionalString');
goog.require('goog.string');
goog.require('goog.string.Const');
goog.require('goog.string.TypedString');
/**
* A string that is safe to use in URL context in DOM APIs and HTML documents.
*
* A SafeUrl is a string-like object that carries the security type contract
* that its value as a string will not cause untrusted script execution
* when evaluated as a hyperlink URL in a browser.
*
* Values of this type are guaranteed to be safe to use in URL/hyperlink
* contexts, such as, assignment to URL-valued DOM properties, or
* interpolation into a HTML template in URL context (e.g., inside a href
* attribute), in the sense that the use will not result in a
* Cross-Site-Scripting vulnerability.
*
* Note that, as documented in {@code goog.html.SafeUrl.unwrap}, this type's
* contract does not guarantee that instances are safe to interpolate into HTML
* without appropriate escaping.
*
* Note also that this type's contract does not imply any guarantees regarding
* the resource the URL refers to. In particular, SafeUrls are <b>not</b>
* safe to use in a context where the referred-to resource is interpreted as
* trusted code, e.g., as the src of a script tag.
*
* Instances of this type must be created via the factory methods
* ({@code goog.html.SafeUrl.fromConstant}, {@code goog.html.SafeUrl.sanitize}),
* etc and not by invoking its constructor. The constructor intentionally
* takes no parameters and the type is immutable; hence only a default instance
* corresponding to the empty string can be obtained via constructor invocation.
*
* @see goog.html.SafeUrl#fromConstant
* @see goog.html.SafeUrl#from
* @see goog.html.SafeUrl#sanitize
* @constructor
* @final
* @struct
* @implements {goog.i18n.bidi.DirectionalString}
* @implements {goog.string.TypedString}
*/
goog.html.SafeUrl = function() {
/**
* The contained value of this SafeUrl. The field has a purposely ugly
* name to make (non-compiled) code that attempts to directly access this
* field stand out.
* @private {string}
*/
this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ = '';
/**
* A type marker used to implement additional run-time type checking.
* @see goog.html.SafeUrl#unwrap
* @const
* @private
*/
this.SAFE_URL_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ =
goog.html.SafeUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_;
};
/**
* The innocuous string generated by goog.html.SafeUrl.sanitize when passed
* an unsafe URL.
*
* about:invalid is registered in
* http://www.w3.org/TR/css3-values/#about-invalid.
* http://tools.ietf.org/html/rfc6694#section-2.2.1 permits about URLs to
* contain a fragment, which is not to be considered when determining if an
* about URL is well-known.
*
* Using about:invalid seems preferable to using a fixed data URL, since
* browsers might choose to not report CSP violations on it, as legitimate
* CSS function calls to attr() can result in this URL being produced. It is
* also a standard URL which matches exactly the semantics we need:
* "The about:invalid URI references a non-existent document with a generic
* error condition. It can be used when a URI is necessary, but the default
* value shouldn't be resolveable as any type of document".
*
* @const {string}
*/
goog.html.SafeUrl.INNOCUOUS_STRING = 'about:invalid#zClosurez';
/**
* @override
* @const
*/
goog.html.SafeUrl.prototype.implementsGoogStringTypedString = true;
/**
* Returns this SafeUrl's value a string.
*
* IMPORTANT: In code where it is security relevant that an object's type is
* indeed {@code SafeUrl}, use {@code goog.html.SafeUrl.unwrap} instead of this
* method. If in doubt, assume that it's security relevant. In particular, note
* that goog.html functions which return a goog.html type do not guarantee that
* the returned instance is of the right type. For example:
*
* <pre>
* var fakeSafeHtml = new String('fake');
* fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype;
* var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml);
* // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by
* // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml instanceof
* // goog.html.SafeHtml.
* </pre>
*
* IMPORTANT: The guarantees of the SafeUrl type contract only extend to the
* behavior of browsers when interpreting URLs. Values of SafeUrl objects MUST
* be appropriately escaped before embedding in a HTML document. Note that the
* required escaping is context-sensitive (e.g. a different escaping is
* required for embedding a URL in a style property within a style
* attribute, as opposed to embedding in a href attribute).
*
* @see goog.html.SafeUrl#unwrap
* @override
*/
goog.html.SafeUrl.prototype.getTypedStringValue = function() {
return this.privateDoNotAccessOrElseSafeHtmlWrappedValue_;
};
/**
* @override
* @const
*/
goog.html.SafeUrl.prototype.implementsGoogI18nBidiDirectionalString = true;
/**
* Returns this URLs directionality, which is always {@code LTR}.
* @override
*/
goog.html.SafeUrl.prototype.getDirection = function() {
return goog.i18n.bidi.Dir.LTR;
};
if (goog.DEBUG) {
/**
* Returns a debug string-representation of this value.
*
* To obtain the actual string value wrapped in a SafeUrl, use
* {@code goog.html.SafeUrl.unwrap}.
*
* @see goog.html.SafeUrl#unwrap
* @override
*/
goog.html.SafeUrl.prototype.toString = function() {
return 'SafeUrl{' + this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ +
'}';
};
}
/**
* Performs a runtime check that the provided object is indeed a SafeUrl
* object, and returns its value.
*
* IMPORTANT: The guarantees of the SafeUrl type contract only extend to the
* behavior of browsers when interpreting URLs. Values of SafeUrl objects MUST
* be appropriately escaped before embedding in a HTML document. Note that the
* required escaping is context-sensitive (e.g. a different escaping is
* required for embedding a URL in a style property within a style
* attribute, as opposed to embedding in a href attribute).
*
* @param {!goog.html.SafeUrl} safeUrl The object to extract from.
* @return {string} The SafeUrl object's contained string, unless the run-time
* type check fails. In that case, {@code unwrap} returns an innocuous
* string, or, if assertions are enabled, throws
* {@code goog.asserts.AssertionError}.
*/
goog.html.SafeUrl.unwrap = function(safeUrl) {
// Perform additional Run-time type-checking to ensure that safeUrl is indeed
// an instance of the expected type. This provides some additional protection
// against security bugs due to application code that disables type checks.
// Specifically, the following checks are performed:
// 1. The object is an instance of the expected type.
// 2. The object is not an instance of a subclass.
// 3. The object carries a type marker for the expected type. "Faking" an
// object requires a reference to the type marker, which has names intended
// to stand out in code reviews.
if (safeUrl instanceof goog.html.SafeUrl &&
safeUrl.constructor === goog.html.SafeUrl &&
safeUrl.SAFE_URL_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ ===
goog.html.SafeUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) {
return safeUrl.privateDoNotAccessOrElseSafeHtmlWrappedValue_;
} else {
goog.asserts.fail('expected object of type SafeUrl, got \'' +
safeUrl + '\' of type ' + goog.typeOf(safeUrl));
return 'type_error:SafeUrl';
}
};
/**
* Creates a SafeUrl object from a compile-time constant string.
*
* Compile-time constant strings are inherently program-controlled and hence
* trusted.
*
* @param {!goog.string.Const} url A compile-time-constant string from which to
* create a SafeUrl.
* @return {!goog.html.SafeUrl} A SafeUrl object initialized to {@code url}.
*/
goog.html.SafeUrl.fromConstant = function(url) {
return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(
goog.string.Const.unwrap(url));
};
/**
* A pattern that matches Blob or data types that can have SafeUrls created
* from URL.createObjectURL(blob) or via a data: URI. Only matches image and
* video types, currently.
* @const
* @private
*/
goog.html.SAFE_MIME_TYPE_PATTERN_ =
/^(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm))$/i;
/**
* Creates a SafeUrl wrapping a blob URL for the given {@code blob}.
*
* The blob URL is created with {@code URL.createObjectURL}. If the MIME type
* for {@code blob} is not of a known safe image or video MIME type, then the
* SafeUrl will wrap {@link #INNOCUOUS_STRING}.
*
* @see http://www.w3.org/TR/FileAPI/#url
* @param {!Blob} blob
* @return {!goog.html.SafeUrl} The blob URL, or an innocuous string wrapped
* as a SafeUrl.
*/
goog.html.SafeUrl.fromBlob = function(blob) {
var url = goog.html.SAFE_MIME_TYPE_PATTERN_.test(blob.type) ?
goog.fs.url.createObjectUrl(blob) :
goog.html.SafeUrl.INNOCUOUS_STRING;
return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(url);
};
/**
* Matches a base-64 data URL, with the first match group being the MIME type.
* @const
* @private
*/
goog.html.DATA_URL_PATTERN_ = /^data:([^;,]*);base64,[a-z0-9+\/]+=*$/i;
/**
* Creates a SafeUrl wrapping a data: URL, after validating it matches a
* known-safe image or video MIME type.
*
* @param {string} dataUrl A valid base64 data URL with one of the whitelisted
* image or video MIME types.
* @return {!goog.html.SafeUrl} A matching safe URL, or {@link INNOCUOUS_STRING}
* wrapped as a SafeUrl if it does not pass.
*/
goog.html.SafeUrl.fromDataUrl = function(dataUrl) {
// There's a slight risk here that a browser sniffs the content type if it
// doesn't know the MIME type and executes HTML within the data: URL. For this
// to cause XSS it would also have to execute the HTML in the same origin
// of the page with the link. It seems unlikely that both of these will
// happen, particularly in not really old IEs.
var match = dataUrl.match(goog.html.DATA_URL_PATTERN_);
var valid = match && goog.html.SAFE_MIME_TYPE_PATTERN_.test(match[1]);
return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(
valid ? dataUrl : goog.html.SafeUrl.INNOCUOUS_STRING);
};
/**
* Creates a SafeUrl wrapping a tel: URL.
*
* @param {string} telUrl A tel URL.
* @return {!goog.html.SafeUrl} A matching safe URL, or {@link INNOCUOUS_STRING}
* wrapped as a SafeUrl if it does not pass.
*/
goog.html.SafeUrl.fromTelUrl = function(telUrl) {
// There's a risk that a tel: URL could immediately place a call once
// clicked, without requiring user confirmation. For that reason it is
// handled in this separate function.
if (!goog.string.caseInsensitiveStartsWith(telUrl, 'tel:')) {
telUrl = goog.html.SafeUrl.INNOCUOUS_STRING;
}
return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(
telUrl);
};
/**
* A pattern that recognizes a commonly useful subset of URLs that satisfy
* the SafeUrl contract.
*
* This regular expression matches a subset of URLs that will not cause script
* execution if used in URL context within a HTML document. Specifically, this
* regular expression matches if (comment from here on and regex copied from
* Soy's EscapingConventions):
* (1) Either a protocol in a whitelist (http, https, mailto or ftp).
* (2) or no protocol. A protocol must be followed by a colon. The below
* allows that by allowing colons only after one of the characters [/?#].
* A colon after a hash (#) must be in the fragment.
* Otherwise, a colon after a (?) must be in a query.
* Otherwise, a colon after a single solidus (/) must be in a path.
* Otherwise, a colon after a double solidus (//) must be in the authority
* (before port).
*
* The pattern disallows &, used in HTML entity declarations before
* one of the characters in [/?#]. This disallows HTML entities used in the
* protocol name, which should never happen, e.g. "h&#116;tp" for "http".
* It also disallows HTML entities in the first path part of a relative path,
* e.g. "foo&lt;bar/baz". Our existing escaping functions should not produce
* that. More importantly, it disallows masking of a colon,
* e.g. "javascript&#58;...".
*
* @private
* @const {!RegExp}
*/
goog.html.SAFE_URL_PATTERN_ =
/^(?:(?:https?|mailto|ftp):|[^&:/?#]*(?:[/?#]|$))/i;
/**
* Creates a SafeUrl object from {@code url}. If {@code url} is a
* goog.html.SafeUrl then it is simply returned. Otherwise the input string is
* validated to match a pattern of commonly used safe URLs.
*
* {@code url} may be a URL with the http, https, mailto or ftp scheme,
* or a relative URL (i.e., a URL without a scheme; specifically, a
* scheme-relative, absolute-path-relative, or path-relative URL).
*
* @see http://url.spec.whatwg.org/#concept-relative-url
* @param {string|!goog.string.TypedString} url The URL to validate.
* @return {!goog.html.SafeUrl} The validated URL, wrapped as a SafeUrl.
*/
goog.html.SafeUrl.sanitize = function(url) {
if (url instanceof goog.html.SafeUrl) {
return url;
} else if (url.implementsGoogStringTypedString) {
url = url.getTypedStringValue();
} else {
url = String(url);
}
if (!goog.html.SAFE_URL_PATTERN_.test(url)) {
url = goog.html.SafeUrl.INNOCUOUS_STRING;
}
return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(url);
};
/**
* Type marker for the SafeUrl type, used to implement additional run-time
* type checking.
* @const {!Object}
* @private
*/
goog.html.SafeUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {};
/**
* Package-internal utility method to create SafeUrl instances.
*
* @param {string} url The string to initialize the SafeUrl object with.
* @return {!goog.html.SafeUrl} The initialized SafeUrl object.
* @package
*/
goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse = function(
url) {
var safeUrl = new goog.html.SafeUrl();
safeUrl.privateDoNotAccessOrElseSafeHtmlWrappedValue_ = url;
return safeUrl;
};
/**
* A SafeUrl corresponding to the special about:blank url.
* @const {!goog.html.SafeUrl}
*/
goog.html.SafeUrl.ABOUT_BLANK =
goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(
'about:blank');

View File

@ -0,0 +1,244 @@
// Copyright 2013 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview The TrustedResourceUrl type and its builders.
*
* TODO(xtof): Link to document stating type contract.
*/
goog.provide('goog.html.TrustedResourceUrl');
goog.require('goog.asserts');
goog.require('goog.i18n.bidi.Dir');
goog.require('goog.i18n.bidi.DirectionalString');
goog.require('goog.string.Const');
goog.require('goog.string.TypedString');
/**
* A URL which is under application control and from which script, CSS, and
* other resources that represent executable code, can be fetched.
*
* Given that the URL can only be constructed from strings under application
* control and is used to load resources, bugs resulting in a malformed URL
* should not have a security impact and are likely to be easily detectable
* during testing. Given the wide number of non-RFC compliant URLs in use,
* stricter validation could prevent some applications from being able to use
* this type.
*
* Instances of this type must be created via the factory method,
* ({@code goog.html.TrustedResourceUrl.fromConstant}), and not by invoking its
* constructor. The constructor intentionally takes no parameters and the type
* is immutable; hence only a default instance corresponding to the empty
* string can be obtained via constructor invocation.
*
* @see goog.html.TrustedResourceUrl#fromConstant
* @constructor
* @final
* @struct
* @implements {goog.i18n.bidi.DirectionalString}
* @implements {goog.string.TypedString}
*/
goog.html.TrustedResourceUrl = function() {
/**
* The contained value of this TrustedResourceUrl. The field has a purposely
* ugly name to make (non-compiled) code that attempts to directly access this
* field stand out.
* @private {string}
*/
this.privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_ = '';
/**
* A type marker used to implement additional run-time type checking.
* @see goog.html.TrustedResourceUrl#unwrap
* @const
* @private
*/
this.TRUSTED_RESOURCE_URL_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ =
goog.html.TrustedResourceUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_;
};
/**
* @override
* @const
*/
goog.html.TrustedResourceUrl.prototype.implementsGoogStringTypedString = true;
/**
* Returns this TrustedResourceUrl's value as a string.
*
* IMPORTANT: In code where it is security relevant that an object's type is
* indeed {@code TrustedResourceUrl}, use
* {@code goog.html.TrustedResourceUrl.unwrap} instead of this method. If in
* doubt, assume that it's security relevant. In particular, note that
* goog.html functions which return a goog.html type do not guarantee that
* the returned instance is of the right type. For example:
*
* <pre>
* var fakeSafeHtml = new String('fake');
* fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype;
* var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml);
* // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by
* // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml instanceof
* // goog.html.SafeHtml.
* </pre>
*
* @see goog.html.TrustedResourceUrl#unwrap
* @override
*/
goog.html.TrustedResourceUrl.prototype.getTypedStringValue = function() {
return this.privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_;
};
/**
* @override
* @const
*/
goog.html.TrustedResourceUrl.prototype.implementsGoogI18nBidiDirectionalString =
true;
/**
* Returns this URLs directionality, which is always {@code LTR}.
* @override
*/
goog.html.TrustedResourceUrl.prototype.getDirection = function() {
return goog.i18n.bidi.Dir.LTR;
};
if (goog.DEBUG) {
/**
* Returns a debug string-representation of this value.
*
* To obtain the actual string value wrapped in a TrustedResourceUrl, use
* {@code goog.html.TrustedResourceUrl.unwrap}.
*
* @see goog.html.TrustedResourceUrl#unwrap
* @override
*/
goog.html.TrustedResourceUrl.prototype.toString = function() {
return 'TrustedResourceUrl{' +
this.privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_ + '}';
};
}
/**
* Performs a runtime check that the provided object is indeed a
* TrustedResourceUrl object, and returns its value.
*
* @param {!goog.html.TrustedResourceUrl} trustedResourceUrl The object to
* extract from.
* @return {string} The trustedResourceUrl object's contained string, unless
* the run-time type check fails. In that case, {@code unwrap} returns an
* innocuous string, or, if assertions are enabled, throws
* {@code goog.asserts.AssertionError}.
*/
goog.html.TrustedResourceUrl.unwrap = function(trustedResourceUrl) {
// Perform additional Run-time type-checking to ensure that
// trustedResourceUrl is indeed an instance of the expected type. This
// provides some additional protection against security bugs due to
// application code that disables type checks.
// Specifically, the following checks are performed:
// 1. The object is an instance of the expected type.
// 2. The object is not an instance of a subclass.
// 3. The object carries a type marker for the expected type. "Faking" an
// object requires a reference to the type marker, which has names intended
// to stand out in code reviews.
if (trustedResourceUrl instanceof goog.html.TrustedResourceUrl &&
trustedResourceUrl.constructor === goog.html.TrustedResourceUrl &&
trustedResourceUrl
.TRUSTED_RESOURCE_URL_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ ===
goog.html.TrustedResourceUrl
.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) {
return trustedResourceUrl
.privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_;
} else {
goog.asserts.fail('expected object of type TrustedResourceUrl, got \'' +
trustedResourceUrl + '\' of type ' + goog.typeOf(trustedResourceUrl));
return 'type_error:TrustedResourceUrl';
}
};
/**
* Creates a TrustedResourceUrl object from a compile-time constant string.
*
* Compile-time constant strings are inherently program-controlled and hence
* trusted.
*
* @param {!goog.string.Const} url A compile-time-constant string from which to
* create a TrustedResourceUrl.
* @return {!goog.html.TrustedResourceUrl} A TrustedResourceUrl object
* initialized to {@code url}.
*/
goog.html.TrustedResourceUrl.fromConstant = function(url) {
return goog.html.TrustedResourceUrl
.createTrustedResourceUrlSecurityPrivateDoNotAccessOrElse(
goog.string.Const.unwrap(url));
};
/**
* Creates a TrustedResourceUrl object from a compile-time constant strings.
*
* Compile-time constant strings are inherently program-controlled and hence
* trusted.
*
* @param {!Array<!goog.string.Const>} parts Compile-time-constant strings from
* which to create a TrustedResourceUrl.
* @return {!goog.html.TrustedResourceUrl} A TrustedResourceUrl object
* initialized to concatenation of {@code parts}.
*/
goog.html.TrustedResourceUrl.fromConstants = function(parts) {
var unwrapped = '';
for (var i = 0; i < parts.length; i++) {
unwrapped += goog.string.Const.unwrap(parts[i]);
}
return goog.html.TrustedResourceUrl
.createTrustedResourceUrlSecurityPrivateDoNotAccessOrElse(unwrapped);
};
/**
* Type marker for the TrustedResourceUrl type, used to implement additional
* run-time type checking.
* @const {!Object}
* @private
*/
goog.html.TrustedResourceUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {};
/**
* Package-internal utility method to create TrustedResourceUrl instances.
*
* @param {string} url The string to initialize the TrustedResourceUrl object
* with.
* @return {!goog.html.TrustedResourceUrl} The initialized TrustedResourceUrl
* object.
* @package
*/
goog.html.TrustedResourceUrl
.createTrustedResourceUrlSecurityPrivateDoNotAccessOrElse = function(url) {
var trustedResourceUrl = new goog.html.TrustedResourceUrl();
trustedResourceUrl.privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_ =
url;
return trustedResourceUrl;
};

View File

@ -0,0 +1,232 @@
// Copyright 2013 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Unchecked conversions to create values of goog.html types from
* plain strings. Use of these functions could potentially result in instances
* of goog.html types that violate their type contracts, and hence result in
* security vulnerabilties.
*
* Therefore, all uses of the methods herein must be carefully security
* reviewed. Avoid use of the methods in this file whenever possible; instead
* prefer to create instances of goog.html types using inherently safe builders
* or template systems.
*
*
*
* @visibility {//closure/goog/html:approved_for_unchecked_conversion}
* @visibility {//closure/goog/bin/sizetests:__pkg__}
*/
goog.provide('goog.html.uncheckedconversions');
goog.require('goog.asserts');
goog.require('goog.html.SafeHtml');
goog.require('goog.html.SafeScript');
goog.require('goog.html.SafeStyle');
goog.require('goog.html.SafeStyleSheet');
goog.require('goog.html.SafeUrl');
goog.require('goog.html.TrustedResourceUrl');
goog.require('goog.string');
goog.require('goog.string.Const');
/**
* Performs an "unchecked conversion" to SafeHtml from a plain string that is
* known to satisfy the SafeHtml type contract.
*
* IMPORTANT: Uses of this method must be carefully security-reviewed to ensure
* that the value of {@code html} satisfies the SafeHtml type contract in all
* possible program states.
*
*
* @param {!goog.string.Const} justification A constant string explaining why
* this use of this method is safe. May include a security review ticket
* number.
* @param {string} html A string that is claimed to adhere to the SafeHtml
* contract.
* @param {?goog.i18n.bidi.Dir=} opt_dir The optional directionality of the
* SafeHtml to be constructed. A null or undefined value signifies an
* unknown directionality.
* @return {!goog.html.SafeHtml} The value of html, wrapped in a SafeHtml
* object.
* @suppress {visibility} For access to SafeHtml.create... Note that this
* use is appropriate since this method is intended to be "package private"
* within goog.html. DO NOT call SafeHtml.create... from outside this
* package; use appropriate wrappers instead.
*/
goog.html.uncheckedconversions.safeHtmlFromStringKnownToSatisfyTypeContract =
function(justification, html, opt_dir) {
// unwrap() called inside an assert so that justification can be optimized
// away in production code.
goog.asserts.assertString(
goog.string.Const.unwrap(justification), 'must provide justification');
goog.asserts.assert(
!goog.string.isEmptyOrWhitespace(goog.string.Const.unwrap(justification)),
'must provide non-empty justification');
return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
html, opt_dir || null);
};
/**
* Performs an "unchecked conversion" to SafeScript from a plain string that is
* known to satisfy the SafeScript type contract.
*
* IMPORTANT: Uses of this method must be carefully security-reviewed to ensure
* that the value of {@code script} satisfies the SafeScript type contract in
* all possible program states.
*
*
* @param {!goog.string.Const} justification A constant string explaining why
* this use of this method is safe. May include a security review ticket
* number.
* @param {string} script The string to wrap as a SafeScript.
* @return {!goog.html.SafeScript} The value of {@code script}, wrapped in a
* SafeScript object.
*/
goog.html.uncheckedconversions.safeScriptFromStringKnownToSatisfyTypeContract =
function(justification, script) {
// unwrap() called inside an assert so that justification can be optimized
// away in production code.
goog.asserts.assertString(
goog.string.Const.unwrap(justification), 'must provide justification');
goog.asserts.assert(
!goog.string.isEmpty(goog.string.Const.unwrap(justification)),
'must provide non-empty justification');
return goog.html.SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse(
script);
};
/**
* Performs an "unchecked conversion" to SafeStyle from a plain string that is
* known to satisfy the SafeStyle type contract.
*
* IMPORTANT: Uses of this method must be carefully security-reviewed to ensure
* that the value of {@code style} satisfies the SafeUrl type contract in all
* possible program states.
*
*
* @param {!goog.string.Const} justification A constant string explaining why
* this use of this method is safe. May include a security review ticket
* number.
* @param {string} style The string to wrap as a SafeStyle.
* @return {!goog.html.SafeStyle} The value of {@code style}, wrapped in a
* SafeStyle object.
*/
goog.html.uncheckedconversions.safeStyleFromStringKnownToSatisfyTypeContract =
function(justification, style) {
// unwrap() called inside an assert so that justification can be optimized
// away in production code.
goog.asserts.assertString(
goog.string.Const.unwrap(justification), 'must provide justification');
goog.asserts.assert(
!goog.string.isEmptyOrWhitespace(goog.string.Const.unwrap(justification)),
'must provide non-empty justification');
return goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse(
style);
};
/**
* Performs an "unchecked conversion" to SafeStyleSheet from a plain string
* that is known to satisfy the SafeStyleSheet type contract.
*
* IMPORTANT: Uses of this method must be carefully security-reviewed to ensure
* that the value of {@code styleSheet} satisfies the SafeUrl type contract in
* all possible program states.
*
*
* @param {!goog.string.Const} justification A constant string explaining why
* this use of this method is safe. May include a security review ticket
* number.
* @param {string} styleSheet The string to wrap as a SafeStyleSheet.
* @return {!goog.html.SafeStyleSheet} The value of {@code styleSheet}, wrapped
* in a SafeStyleSheet object.
*/
goog.html.uncheckedconversions
.safeStyleSheetFromStringKnownToSatisfyTypeContract = function(
justification, styleSheet) {
// unwrap() called inside an assert so that justification can be optimized
// away in production code.
goog.asserts.assertString(
goog.string.Const.unwrap(justification), 'must provide justification');
goog.asserts.assert(
!goog.string.isEmptyOrWhitespace(goog.string.Const.unwrap(justification)),
'must provide non-empty justification');
return goog.html.SafeStyleSheet
.createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(styleSheet);
};
/**
* Performs an "unchecked conversion" to SafeUrl from a plain string that is
* known to satisfy the SafeUrl type contract.
*
* IMPORTANT: Uses of this method must be carefully security-reviewed to ensure
* that the value of {@code url} satisfies the SafeUrl type contract in all
* possible program states.
*
*
* @param {!goog.string.Const} justification A constant string explaining why
* this use of this method is safe. May include a security review ticket
* number.
* @param {string} url The string to wrap as a SafeUrl.
* @return {!goog.html.SafeUrl} The value of {@code url}, wrapped in a SafeUrl
* object.
*/
goog.html.uncheckedconversions.safeUrlFromStringKnownToSatisfyTypeContract =
function(justification, url) {
// unwrap() called inside an assert so that justification can be optimized
// away in production code.
goog.asserts.assertString(
goog.string.Const.unwrap(justification), 'must provide justification');
goog.asserts.assert(
!goog.string.isEmptyOrWhitespace(goog.string.Const.unwrap(justification)),
'must provide non-empty justification');
return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(url);
};
/**
* Performs an "unchecked conversion" to TrustedResourceUrl from a plain string
* that is known to satisfy the TrustedResourceUrl type contract.
*
* IMPORTANT: Uses of this method must be carefully security-reviewed to ensure
* that the value of {@code url} satisfies the TrustedResourceUrl type contract
* in all possible program states.
*
*
* @param {!goog.string.Const} justification A constant string explaining why
* this use of this method is safe. May include a security review ticket
* number.
* @param {string} url The string to wrap as a TrustedResourceUrl.
* @return {!goog.html.TrustedResourceUrl} The value of {@code url}, wrapped in
* a TrustedResourceUrl object.
*/
goog.html.uncheckedconversions
.trustedResourceUrlFromStringKnownToSatisfyTypeContract = function(
justification, url) {
// unwrap() called inside an assert so that justification can be optimized
// away in production code.
goog.asserts.assertString(
goog.string.Const.unwrap(justification), 'must provide justification');
goog.asserts.assert(
!goog.string.isEmptyOrWhitespace(goog.string.Const.unwrap(justification)),
'must provide non-empty justification');
return goog.html.TrustedResourceUrl
.createTrustedResourceUrlSecurityPrivateDoNotAccessOrElse(url);
};

876
viz/out/goog/i18n/bidi.js Normal file
View File

@ -0,0 +1,876 @@
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Utility functions for supporting Bidi issues.
*/
/**
* Namespace for bidi supporting functions.
*/
goog.provide('goog.i18n.bidi');
goog.provide('goog.i18n.bidi.Dir');
goog.provide('goog.i18n.bidi.DirectionalString');
goog.provide('goog.i18n.bidi.Format');
/**
* @define {boolean} FORCE_RTL forces the {@link goog.i18n.bidi.IS_RTL} constant
* to say that the current locale is a RTL locale. This should only be used
* if you want to override the default behavior for deciding whether the
* current locale is RTL or not.
*
* {@see goog.i18n.bidi.IS_RTL}
*/
goog.define('goog.i18n.bidi.FORCE_RTL', false);
/**
* Constant that defines whether or not the current locale is a RTL locale.
* If {@link goog.i18n.bidi.FORCE_RTL} is not true, this constant will default
* to check that {@link goog.LOCALE} is one of a few major RTL locales.
*
* <p>This is designed to be a maximally efficient compile-time constant. For
* example, for the default goog.LOCALE, compiling
* "if (goog.i18n.bidi.IS_RTL) alert('rtl') else {}" should produce no code. It
* is this design consideration that limits the implementation to only
* supporting a few major RTL locales, as opposed to the broader repertoire of
* something like goog.i18n.bidi.isRtlLanguage.
*
* <p>Since this constant refers to the directionality of the locale, it is up
* to the caller to determine if this constant should also be used for the
* direction of the UI.
*
* {@see goog.LOCALE}
*
* @type {boolean}
*
* TODO(user): write a test that checks that this is a compile-time constant.
*/
goog.i18n.bidi.IS_RTL = goog.i18n.bidi.FORCE_RTL ||
((goog.LOCALE.substring(0, 2).toLowerCase() == 'ar' ||
goog.LOCALE.substring(0, 2).toLowerCase() == 'fa' ||
goog.LOCALE.substring(0, 2).toLowerCase() == 'he' ||
goog.LOCALE.substring(0, 2).toLowerCase() == 'iw' ||
goog.LOCALE.substring(0, 2).toLowerCase() == 'ps' ||
goog.LOCALE.substring(0, 2).toLowerCase() == 'sd' ||
goog.LOCALE.substring(0, 2).toLowerCase() == 'ug' ||
goog.LOCALE.substring(0, 2).toLowerCase() == 'ur' ||
goog.LOCALE.substring(0, 2).toLowerCase() == 'yi') &&
(goog.LOCALE.length == 2 || goog.LOCALE.substring(2, 3) == '-' ||
goog.LOCALE.substring(2, 3) == '_')) ||
(goog.LOCALE.length >= 3 &&
goog.LOCALE.substring(0, 3).toLowerCase() == 'ckb' &&
(goog.LOCALE.length == 3 || goog.LOCALE.substring(3, 4) == '-' ||
goog.LOCALE.substring(3, 4) == '_'));
/**
* Unicode formatting characters and directionality string constants.
* @enum {string}
*/
goog.i18n.bidi.Format = {
/** Unicode "Left-To-Right Embedding" (LRE) character. */
LRE: '\u202A',
/** Unicode "Right-To-Left Embedding" (RLE) character. */
RLE: '\u202B',
/** Unicode "Pop Directional Formatting" (PDF) character. */
PDF: '\u202C',
/** Unicode "Left-To-Right Mark" (LRM) character. */
LRM: '\u200E',
/** Unicode "Right-To-Left Mark" (RLM) character. */
RLM: '\u200F'
};
/**
* Directionality enum.
* @enum {number}
*/
goog.i18n.bidi.Dir = {
/**
* Left-to-right.
*/
LTR: 1,
/**
* Right-to-left.
*/
RTL: -1,
/**
* Neither left-to-right nor right-to-left.
*/
NEUTRAL: 0
};
/**
* 'right' string constant.
* @type {string}
*/
goog.i18n.bidi.RIGHT = 'right';
/**
* 'left' string constant.
* @type {string}
*/
goog.i18n.bidi.LEFT = 'left';
/**
* 'left' if locale is RTL, 'right' if not.
* @type {string}
*/
goog.i18n.bidi.I18N_RIGHT =
goog.i18n.bidi.IS_RTL ? goog.i18n.bidi.LEFT : goog.i18n.bidi.RIGHT;
/**
* 'right' if locale is RTL, 'left' if not.
* @type {string}
*/
goog.i18n.bidi.I18N_LEFT =
goog.i18n.bidi.IS_RTL ? goog.i18n.bidi.RIGHT : goog.i18n.bidi.LEFT;
/**
* Convert a directionality given in various formats to a goog.i18n.bidi.Dir
* constant. Useful for interaction with different standards of directionality
* representation.
*
* @param {goog.i18n.bidi.Dir|number|boolean|null} givenDir Directionality given
* in one of the following formats:
* 1. A goog.i18n.bidi.Dir constant.
* 2. A number (positive = LTR, negative = RTL, 0 = neutral).
* 3. A boolean (true = RTL, false = LTR).
* 4. A null for unknown directionality.
* @param {boolean=} opt_noNeutral Whether a givenDir of zero or
* goog.i18n.bidi.Dir.NEUTRAL should be treated as null, i.e. unknown, in
* order to preserve legacy behavior.
* @return {?goog.i18n.bidi.Dir} A goog.i18n.bidi.Dir constant matching the
* given directionality. If given null, returns null (i.e. unknown).
*/
goog.i18n.bidi.toDir = function(givenDir, opt_noNeutral) {
if (typeof givenDir == 'number') {
// This includes the non-null goog.i18n.bidi.Dir case.
return givenDir > 0 ? goog.i18n.bidi.Dir.LTR : givenDir < 0 ?
goog.i18n.bidi.Dir.RTL :
opt_noNeutral ? null : goog.i18n.bidi.Dir.NEUTRAL;
} else if (givenDir == null) {
return null;
} else {
// Must be typeof givenDir == 'boolean'.
return givenDir ? goog.i18n.bidi.Dir.RTL : goog.i18n.bidi.Dir.LTR;
}
};
/**
* A practical pattern to identify strong LTR characters. This pattern is not
* theoretically correct according to the Unicode standard. It is simplified for
* performance and small code size.
* @type {string}
* @private
*/
goog.i18n.bidi.ltrChars_ =
'A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02B8\u0300-\u0590\u0800-\u1FFF' +
'\u200E\u2C00-\uFB1C\uFE00-\uFE6F\uFEFD-\uFFFF';
/**
* A practical pattern to identify strong RTL character. This pattern is not
* theoretically correct according to the Unicode standard. It is simplified
* for performance and small code size.
* @type {string}
* @private
*/
goog.i18n.bidi.rtlChars_ =
'\u0591-\u06EF\u06FA-\u07FF\u200F\uFB1D-\uFDFF\uFE70-\uFEFC';
/**
* Simplified regular expression for an HTML tag (opening or closing) or an HTML
* escape. We might want to skip over such expressions when estimating the text
* directionality.
* @type {RegExp}
* @private
*/
goog.i18n.bidi.htmlSkipReg_ = /<[^>]*>|&[^;]+;/g;
/**
* Returns the input text with spaces instead of HTML tags or HTML escapes, if
* opt_isStripNeeded is true. Else returns the input as is.
* Useful for text directionality estimation.
* Note: the function should not be used in other contexts; it is not 100%
* correct, but rather a good-enough implementation for directionality
* estimation purposes.
* @param {string} str The given string.
* @param {boolean=} opt_isStripNeeded Whether to perform the stripping.
* Default: false (to retain consistency with calling functions).
* @return {string} The given string cleaned of HTML tags / escapes.
* @private
*/
goog.i18n.bidi.stripHtmlIfNeeded_ = function(str, opt_isStripNeeded) {
return opt_isStripNeeded ? str.replace(goog.i18n.bidi.htmlSkipReg_, '') : str;
};
/**
* Regular expression to check for RTL characters.
* @type {RegExp}
* @private
*/
goog.i18n.bidi.rtlCharReg_ = new RegExp('[' + goog.i18n.bidi.rtlChars_ + ']');
/**
* Regular expression to check for LTR characters.
* @type {RegExp}
* @private
*/
goog.i18n.bidi.ltrCharReg_ = new RegExp('[' + goog.i18n.bidi.ltrChars_ + ']');
/**
* Test whether the given string has any RTL characters in it.
* @param {string} str The given string that need to be tested.
* @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
* Default: false.
* @return {boolean} Whether the string contains RTL characters.
*/
goog.i18n.bidi.hasAnyRtl = function(str, opt_isHtml) {
return goog.i18n.bidi.rtlCharReg_.test(
goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml));
};
/**
* Test whether the given string has any RTL characters in it.
* @param {string} str The given string that need to be tested.
* @return {boolean} Whether the string contains RTL characters.
* @deprecated Use hasAnyRtl.
*/
goog.i18n.bidi.hasRtlChar = goog.i18n.bidi.hasAnyRtl;
/**
* Test whether the given string has any LTR characters in it.
* @param {string} str The given string that need to be tested.
* @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
* Default: false.
* @return {boolean} Whether the string contains LTR characters.
*/
goog.i18n.bidi.hasAnyLtr = function(str, opt_isHtml) {
return goog.i18n.bidi.ltrCharReg_.test(
goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml));
};
/**
* Regular expression pattern to check if the first character in the string
* is LTR.
* @type {RegExp}
* @private
*/
goog.i18n.bidi.ltrRe_ = new RegExp('^[' + goog.i18n.bidi.ltrChars_ + ']');
/**
* Regular expression pattern to check if the first character in the string
* is RTL.
* @type {RegExp}
* @private
*/
goog.i18n.bidi.rtlRe_ = new RegExp('^[' + goog.i18n.bidi.rtlChars_ + ']');
/**
* Check if the first character in the string is RTL or not.
* @param {string} str The given string that need to be tested.
* @return {boolean} Whether the first character in str is an RTL char.
*/
goog.i18n.bidi.isRtlChar = function(str) {
return goog.i18n.bidi.rtlRe_.test(str);
};
/**
* Check if the first character in the string is LTR or not.
* @param {string} str The given string that need to be tested.
* @return {boolean} Whether the first character in str is an LTR char.
*/
goog.i18n.bidi.isLtrChar = function(str) {
return goog.i18n.bidi.ltrRe_.test(str);
};
/**
* Check if the first character in the string is neutral or not.
* @param {string} str The given string that need to be tested.
* @return {boolean} Whether the first character in str is a neutral char.
*/
goog.i18n.bidi.isNeutralChar = function(str) {
return !goog.i18n.bidi.isLtrChar(str) && !goog.i18n.bidi.isRtlChar(str);
};
/**
* Regular expressions to check if a piece of text is of LTR directionality
* on first character with strong directionality.
* @type {RegExp}
* @private
*/
goog.i18n.bidi.ltrDirCheckRe_ = new RegExp(
'^[^' + goog.i18n.bidi.rtlChars_ + ']*[' + goog.i18n.bidi.ltrChars_ + ']');
/**
* Regular expressions to check if a piece of text is of RTL directionality
* on first character with strong directionality.
* @type {RegExp}
* @private
*/
goog.i18n.bidi.rtlDirCheckRe_ = new RegExp(
'^[^' + goog.i18n.bidi.ltrChars_ + ']*[' + goog.i18n.bidi.rtlChars_ + ']');
/**
* Check whether the first strongly directional character (if any) is RTL.
* @param {string} str String being checked.
* @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
* Default: false.
* @return {boolean} Whether RTL directionality is detected using the first
* strongly-directional character method.
*/
goog.i18n.bidi.startsWithRtl = function(str, opt_isHtml) {
return goog.i18n.bidi.rtlDirCheckRe_.test(
goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml));
};
/**
* Check whether the first strongly directional character (if any) is RTL.
* @param {string} str String being checked.
* @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
* Default: false.
* @return {boolean} Whether RTL directionality is detected using the first
* strongly-directional character method.
* @deprecated Use startsWithRtl.
*/
goog.i18n.bidi.isRtlText = goog.i18n.bidi.startsWithRtl;
/**
* Check whether the first strongly directional character (if any) is LTR.
* @param {string} str String being checked.
* @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
* Default: false.
* @return {boolean} Whether LTR directionality is detected using the first
* strongly-directional character method.
*/
goog.i18n.bidi.startsWithLtr = function(str, opt_isHtml) {
return goog.i18n.bidi.ltrDirCheckRe_.test(
goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml));
};
/**
* Check whether the first strongly directional character (if any) is LTR.
* @param {string} str String being checked.
* @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
* Default: false.
* @return {boolean} Whether LTR directionality is detected using the first
* strongly-directional character method.
* @deprecated Use startsWithLtr.
*/
goog.i18n.bidi.isLtrText = goog.i18n.bidi.startsWithLtr;
/**
* Regular expression to check if a string looks like something that must
* always be LTR even in RTL text, e.g. a URL. When estimating the
* directionality of text containing these, we treat these as weakly LTR,
* like numbers.
* @type {RegExp}
* @private
*/
goog.i18n.bidi.isRequiredLtrRe_ = /^http:\/\/.*/;
/**
* Check whether the input string either contains no strongly directional
* characters or looks like a url.
* @param {string} str String being checked.
* @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
* Default: false.
* @return {boolean} Whether neutral directionality is detected.
*/
goog.i18n.bidi.isNeutralText = function(str, opt_isHtml) {
str = goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml);
return goog.i18n.bidi.isRequiredLtrRe_.test(str) ||
!goog.i18n.bidi.hasAnyLtr(str) && !goog.i18n.bidi.hasAnyRtl(str);
};
/**
* Regular expressions to check if the last strongly-directional character in a
* piece of text is LTR.
* @type {RegExp}
* @private
*/
goog.i18n.bidi.ltrExitDirCheckRe_ = new RegExp(
'[' + goog.i18n.bidi.ltrChars_ + '][^' + goog.i18n.bidi.rtlChars_ + ']*$');
/**
* Regular expressions to check if the last strongly-directional character in a
* piece of text is RTL.
* @type {RegExp}
* @private
*/
goog.i18n.bidi.rtlExitDirCheckRe_ = new RegExp(
'[' + goog.i18n.bidi.rtlChars_ + '][^' + goog.i18n.bidi.ltrChars_ + ']*$');
/**
* Check if the exit directionality a piece of text is LTR, i.e. if the last
* strongly-directional character in the string is LTR.
* @param {string} str String being checked.
* @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
* Default: false.
* @return {boolean} Whether LTR exit directionality was detected.
*/
goog.i18n.bidi.endsWithLtr = function(str, opt_isHtml) {
return goog.i18n.bidi.ltrExitDirCheckRe_.test(
goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml));
};
/**
* Check if the exit directionality a piece of text is LTR, i.e. if the last
* strongly-directional character in the string is LTR.
* @param {string} str String being checked.
* @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
* Default: false.
* @return {boolean} Whether LTR exit directionality was detected.
* @deprecated Use endsWithLtr.
*/
goog.i18n.bidi.isLtrExitText = goog.i18n.bidi.endsWithLtr;
/**
* Check if the exit directionality a piece of text is RTL, i.e. if the last
* strongly-directional character in the string is RTL.
* @param {string} str String being checked.
* @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
* Default: false.
* @return {boolean} Whether RTL exit directionality was detected.
*/
goog.i18n.bidi.endsWithRtl = function(str, opt_isHtml) {
return goog.i18n.bidi.rtlExitDirCheckRe_.test(
goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml));
};
/**
* Check if the exit directionality a piece of text is RTL, i.e. if the last
* strongly-directional character in the string is RTL.
* @param {string} str String being checked.
* @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
* Default: false.
* @return {boolean} Whether RTL exit directionality was detected.
* @deprecated Use endsWithRtl.
*/
goog.i18n.bidi.isRtlExitText = goog.i18n.bidi.endsWithRtl;
/**
* A regular expression for matching right-to-left language codes.
* See {@link #isRtlLanguage} for the design.
* @type {RegExp}
* @private
*/
goog.i18n.bidi.rtlLocalesRe_ = new RegExp(
'^(ar|ckb|dv|he|iw|fa|nqo|ps|sd|ug|ur|yi|' +
'.*[-_](Arab|Hebr|Thaa|Nkoo|Tfng))' +
'(?!.*[-_](Latn|Cyrl)($|-|_))($|-|_)',
'i');
/**
* Check if a BCP 47 / III language code indicates an RTL language, i.e. either:
* - a language code explicitly specifying one of the right-to-left scripts,
* e.g. "az-Arab", or<p>
* - a language code specifying one of the languages normally written in a
* right-to-left script, e.g. "fa" (Farsi), except ones explicitly specifying
* Latin or Cyrillic script (which are the usual LTR alternatives).<p>
* The list of right-to-left scripts appears in the 100-199 range in
* http://www.unicode.org/iso15924/iso15924-num.html, of which Arabic and
* Hebrew are by far the most widely used. We also recognize Thaana, N'Ko, and
* Tifinagh, which also have significant modern usage. The rest (Syriac,
* Samaritan, Mandaic, etc.) seem to have extremely limited or no modern usage
* and are not recognized to save on code size.
* The languages usually written in a right-to-left script are taken as those
* with Suppress-Script: Hebr|Arab|Thaa|Nkoo|Tfng in
* http://www.iana.org/assignments/language-subtag-registry,
* as well as Central (or Sorani) Kurdish (ckb), Sindhi (sd) and Uyghur (ug).
* Other subtags of the language code, e.g. regions like EG (Egypt), are
* ignored.
* @param {string} lang BCP 47 (a.k.a III) language code.
* @return {boolean} Whether the language code is an RTL language.
*/
goog.i18n.bidi.isRtlLanguage = function(lang) {
return goog.i18n.bidi.rtlLocalesRe_.test(lang);
};
/**
* Regular expression for bracket guard replacement in text.
* @type {RegExp}
* @private
*/
goog.i18n.bidi.bracketGuardTextRe_ =
/(\(.*?\)+)|(\[.*?\]+)|(\{.*?\}+)|(<.*?>+)/g;
/**
* Apply bracket guard using LRM and RLM. This is to address the problem of
* messy bracket display frequently happens in RTL layout.
* This function works for plain text, not for HTML. In HTML, the opening
* bracket might be in a different context than the closing bracket (such as
* an attribute value).
* @param {string} s The string that need to be processed.
* @param {boolean=} opt_isRtlContext specifies default direction (usually
* direction of the UI).
* @return {string} The processed string, with all bracket guarded.
*/
goog.i18n.bidi.guardBracketInText = function(s, opt_isRtlContext) {
var useRtl = opt_isRtlContext === undefined ? goog.i18n.bidi.hasAnyRtl(s) :
opt_isRtlContext;
var mark = useRtl ? goog.i18n.bidi.Format.RLM : goog.i18n.bidi.Format.LRM;
return s.replace(goog.i18n.bidi.bracketGuardTextRe_, mark + '$&' + mark);
};
/**
* Enforce the html snippet in RTL directionality regardless overall context.
* If the html piece was enclosed by tag, dir will be applied to existing
* tag, otherwise a span tag will be added as wrapper. For this reason, if
* html snippet start with with tag, this tag must enclose the whole piece. If
* the tag already has a dir specified, this new one will override existing
* one in behavior (tested on FF and IE).
* @param {string} html The string that need to be processed.
* @return {string} The processed string, with directionality enforced to RTL.
*/
goog.i18n.bidi.enforceRtlInHtml = function(html) {
if (html.charAt(0) == '<') {
return html.replace(/<\w+/, '$& dir=rtl');
}
// '\n' is important for FF so that it won't incorrectly merge span groups
return '\n<span dir=rtl>' + html + '</span>';
};
/**
* Enforce RTL on both end of the given text piece using unicode BiDi formatting
* characters RLE and PDF.
* @param {string} text The piece of text that need to be wrapped.
* @return {string} The wrapped string after process.
*/
goog.i18n.bidi.enforceRtlInText = function(text) {
return goog.i18n.bidi.Format.RLE + text + goog.i18n.bidi.Format.PDF;
};
/**
* Enforce the html snippet in RTL directionality regardless overall context.
* If the html piece was enclosed by tag, dir will be applied to existing
* tag, otherwise a span tag will be added as wrapper. For this reason, if
* html snippet start with with tag, this tag must enclose the whole piece. If
* the tag already has a dir specified, this new one will override existing
* one in behavior (tested on FF and IE).
* @param {string} html The string that need to be processed.
* @return {string} The processed string, with directionality enforced to RTL.
*/
goog.i18n.bidi.enforceLtrInHtml = function(html) {
if (html.charAt(0) == '<') {
return html.replace(/<\w+/, '$& dir=ltr');
}
// '\n' is important for FF so that it won't incorrectly merge span groups
return '\n<span dir=ltr>' + html + '</span>';
};
/**
* Enforce LTR on both end of the given text piece using unicode BiDi formatting
* characters LRE and PDF.
* @param {string} text The piece of text that need to be wrapped.
* @return {string} The wrapped string after process.
*/
goog.i18n.bidi.enforceLtrInText = function(text) {
return goog.i18n.bidi.Format.LRE + text + goog.i18n.bidi.Format.PDF;
};
/**
* Regular expression to find dimensions such as "padding: .3 0.4ex 5px 6;"
* @type {RegExp}
* @private
*/
goog.i18n.bidi.dimensionsRe_ =
/:\s*([.\d][.\w]*)\s+([.\d][.\w]*)\s+([.\d][.\w]*)\s+([.\d][.\w]*)/g;
/**
* Regular expression for left.
* @type {RegExp}
* @private
*/
goog.i18n.bidi.leftRe_ = /left/gi;
/**
* Regular expression for right.
* @type {RegExp}
* @private
*/
goog.i18n.bidi.rightRe_ = /right/gi;
/**
* Placeholder regular expression for swapping.
* @type {RegExp}
* @private
*/
goog.i18n.bidi.tempRe_ = /%%%%/g;
/**
* Swap location parameters and 'left'/'right' in CSS specification. The
* processed string will be suited for RTL layout. Though this function can
* cover most cases, there are always exceptions. It is suggested to put
* those exceptions in separate group of CSS string.
* @param {string} cssStr CSS spefication string.
* @return {string} Processed CSS specification string.
*/
goog.i18n.bidi.mirrorCSS = function(cssStr) {
return cssStr
.
// reverse dimensions
replace(goog.i18n.bidi.dimensionsRe_, ':$1 $4 $3 $2')
.replace(goog.i18n.bidi.leftRe_, '%%%%')
. // swap left and right
replace(goog.i18n.bidi.rightRe_, goog.i18n.bidi.LEFT)
.replace(goog.i18n.bidi.tempRe_, goog.i18n.bidi.RIGHT);
};
/**
* Regular expression for hebrew double quote substitution, finding quote
* directly after hebrew characters.
* @type {RegExp}
* @private
*/
goog.i18n.bidi.doubleQuoteSubstituteRe_ = /([\u0591-\u05f2])"/g;
/**
* Regular expression for hebrew single quote substitution, finding quote
* directly after hebrew characters.
* @type {RegExp}
* @private
*/
goog.i18n.bidi.singleQuoteSubstituteRe_ = /([\u0591-\u05f2])'/g;
/**
* Replace the double and single quote directly after a Hebrew character with
* GERESH and GERSHAYIM. In such case, most likely that's user intention.
* @param {string} str String that need to be processed.
* @return {string} Processed string with double/single quote replaced.
*/
goog.i18n.bidi.normalizeHebrewQuote = function(str) {
return str.replace(goog.i18n.bidi.doubleQuoteSubstituteRe_, '$1\u05f4')
.replace(goog.i18n.bidi.singleQuoteSubstituteRe_, '$1\u05f3');
};
/**
* Regular expression to split a string into "words" for directionality
* estimation based on relative word counts.
* @type {RegExp}
* @private
*/
goog.i18n.bidi.wordSeparatorRe_ = /\s+/;
/**
* Regular expression to check if a string contains any numerals. Used to
* differentiate between completely neutral strings and those containing
* numbers, which are weakly LTR.
*
* Native Arabic digits (\u0660 - \u0669) are not included because although they
* do flow left-to-right inside a number, this is the case even if the overall
* directionality is RTL, and a mathematical expression using these digits is
* supposed to flow right-to-left overall, including unary plus and minus
* appearing to the right of a number, and this does depend on the overall
* directionality being RTL. The digits used in Farsi (\u06F0 - \u06F9), on the
* other hand, are included, since Farsi math (including unary plus and minus)
* does flow left-to-right.
*
* @type {RegExp}
* @private
*/
goog.i18n.bidi.hasNumeralsRe_ = /[\d\u06f0-\u06f9]/;
/**
* This constant controls threshold of RTL directionality.
* @type {number}
* @private
*/
goog.i18n.bidi.rtlDetectionThreshold_ = 0.40;
/**
* Estimates the directionality of a string based on relative word counts.
* If the number of RTL words is above a certain percentage of the total number
* of strongly directional words, returns RTL.
* Otherwise, if any words are strongly or weakly LTR, returns LTR.
* Otherwise, returns UNKNOWN, which is used to mean "neutral".
* Numbers are counted as weakly LTR.
* @param {string} str The string to be checked.
* @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
* Default: false.
* @return {goog.i18n.bidi.Dir} Estimated overall directionality of {@code str}.
*/
goog.i18n.bidi.estimateDirection = function(str, opt_isHtml) {
var rtlCount = 0;
var totalCount = 0;
var hasWeaklyLtr = false;
var tokens = goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml)
.split(goog.i18n.bidi.wordSeparatorRe_);
for (var i = 0; i < tokens.length; i++) {
var token = tokens[i];
if (goog.i18n.bidi.startsWithRtl(token)) {
rtlCount++;
totalCount++;
} else if (goog.i18n.bidi.isRequiredLtrRe_.test(token)) {
hasWeaklyLtr = true;
} else if (goog.i18n.bidi.hasAnyLtr(token)) {
totalCount++;
} else if (goog.i18n.bidi.hasNumeralsRe_.test(token)) {
hasWeaklyLtr = true;
}
}
return totalCount == 0 ?
(hasWeaklyLtr ? goog.i18n.bidi.Dir.LTR : goog.i18n.bidi.Dir.NEUTRAL) :
(rtlCount / totalCount > goog.i18n.bidi.rtlDetectionThreshold_ ?
goog.i18n.bidi.Dir.RTL :
goog.i18n.bidi.Dir.LTR);
};
/**
* Check the directionality of a piece of text, return true if the piece of
* text should be laid out in RTL direction.
* @param {string} str The piece of text that need to be detected.
* @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
* Default: false.
* @return {boolean} Whether this piece of text should be laid out in RTL.
*/
goog.i18n.bidi.detectRtlDirectionality = function(str, opt_isHtml) {
return goog.i18n.bidi.estimateDirection(str, opt_isHtml) ==
goog.i18n.bidi.Dir.RTL;
};
/**
* Sets text input element's directionality and text alignment based on a
* given directionality. Does nothing if the given directionality is unknown or
* neutral.
* @param {Element} element Input field element to set directionality to.
* @param {goog.i18n.bidi.Dir|number|boolean|null} dir Desired directionality,
* given in one of the following formats:
* 1. A goog.i18n.bidi.Dir constant.
* 2. A number (positive = LRT, negative = RTL, 0 = neutral).
* 3. A boolean (true = RTL, false = LTR).
* 4. A null for unknown directionality.
*/
goog.i18n.bidi.setElementDirAndAlign = function(element, dir) {
if (element) {
dir = goog.i18n.bidi.toDir(dir);
if (dir) {
element.style.textAlign = dir == goog.i18n.bidi.Dir.RTL ?
goog.i18n.bidi.RIGHT :
goog.i18n.bidi.LEFT;
element.dir = dir == goog.i18n.bidi.Dir.RTL ? 'rtl' : 'ltr';
}
}
};
/**
* Sets element dir based on estimated directionality of the given text.
* @param {!Element} element
* @param {string} text
*/
goog.i18n.bidi.setElementDirByTextDirectionality = function(element, text) {
switch (goog.i18n.bidi.estimateDirection(text)) {
case (goog.i18n.bidi.Dir.LTR):
element.dir = 'ltr';
break;
case (goog.i18n.bidi.Dir.RTL):
element.dir = 'rtl';
break;
default:
// Default for no direction, inherit from document.
element.removeAttribute('dir');
}
};
/**
* Strings that have an (optional) known direction.
*
* Implementations of this interface are string-like objects that carry an
* attached direction, if known.
* @interface
*/
goog.i18n.bidi.DirectionalString = function() {};
/**
* Interface marker of the DirectionalString interface.
*
* This property can be used to determine at runtime whether or not an object
* implements this interface. All implementations of this interface set this
* property to {@code true}.
* @type {boolean}
*/
goog.i18n.bidi.DirectionalString.prototype
.implementsGoogI18nBidiDirectionalString;
/**
* Retrieves this object's known direction (if any).
* @return {?goog.i18n.bidi.Dir} The known direction. Null if unknown.
*/
goog.i18n.bidi.DirectionalString.prototype.getDirection;

View File

@ -0,0 +1,338 @@
// Copyright 2013 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Closure user agent detection (Browser).
* @see <a href="http://www.useragentstring.com/">User agent strings</a>
* For more information on rendering engine, platform, or device see the other
* sub-namespaces in goog.labs.userAgent, goog.labs.userAgent.platform,
* goog.labs.userAgent.device respectively.)
*
* @author martone@google.com (Andy Martone)
*/
goog.provide('goog.labs.userAgent.browser');
goog.require('goog.array');
goog.require('goog.labs.userAgent.util');
goog.require('goog.object');
goog.require('goog.string');
// TODO(nnaze): Refactor to remove excessive exclusion logic in matching
// functions.
/**
* @return {boolean} Whether the user's browser is Opera. Note: Chromium
* based Opera (Opera 15+) is detected as Chrome to avoid unnecessary
* special casing.
* @private
*/
goog.labs.userAgent.browser.matchOpera_ = function() {
return goog.labs.userAgent.util.matchUserAgent('Opera');
};
/**
* @return {boolean} Whether the user's browser is IE.
* @private
*/
goog.labs.userAgent.browser.matchIE_ = function() {
return goog.labs.userAgent.util.matchUserAgent('Trident') ||
goog.labs.userAgent.util.matchUserAgent('MSIE');
};
/**
* @return {boolean} Whether the user's browser is Edge.
* @private
*/
goog.labs.userAgent.browser.matchEdge_ = function() {
return goog.labs.userAgent.util.matchUserAgent('Edge');
};
/**
* @return {boolean} Whether the user's browser is Firefox.
* @private
*/
goog.labs.userAgent.browser.matchFirefox_ = function() {
return goog.labs.userAgent.util.matchUserAgent('Firefox');
};
/**
* @return {boolean} Whether the user's browser is Safari.
* @private
*/
goog.labs.userAgent.browser.matchSafari_ = function() {
return goog.labs.userAgent.util.matchUserAgent('Safari') &&
!(goog.labs.userAgent.browser.matchChrome_() ||
goog.labs.userAgent.browser.matchCoast_() ||
goog.labs.userAgent.browser.matchOpera_() ||
goog.labs.userAgent.browser.matchEdge_() ||
goog.labs.userAgent.browser.isSilk() ||
goog.labs.userAgent.util.matchUserAgent('Android'));
};
/**
* @return {boolean} Whether the user's browser is Coast (Opera's Webkit-based
* iOS browser).
* @private
*/
goog.labs.userAgent.browser.matchCoast_ = function() {
return goog.labs.userAgent.util.matchUserAgent('Coast');
};
/**
* @return {boolean} Whether the user's browser is iOS Webview.
* @private
*/
goog.labs.userAgent.browser.matchIosWebview_ = function() {
// iOS Webview does not show up as Chrome or Safari. Also check for Opera's
// WebKit-based iOS browser, Coast.
return (goog.labs.userAgent.util.matchUserAgent('iPad') ||
goog.labs.userAgent.util.matchUserAgent('iPhone')) &&
!goog.labs.userAgent.browser.matchSafari_() &&
!goog.labs.userAgent.browser.matchChrome_() &&
!goog.labs.userAgent.browser.matchCoast_() &&
goog.labs.userAgent.util.matchUserAgent('AppleWebKit');
};
/**
* @return {boolean} Whether the user's browser is Chrome.
* @private
*/
goog.labs.userAgent.browser.matchChrome_ = function() {
return (goog.labs.userAgent.util.matchUserAgent('Chrome') ||
goog.labs.userAgent.util.matchUserAgent('CriOS')) &&
!goog.labs.userAgent.browser.matchEdge_();
};
/**
* @return {boolean} Whether the user's browser is the Android browser.
* @private
*/
goog.labs.userAgent.browser.matchAndroidBrowser_ = function() {
// Android can appear in the user agent string for Chrome on Android.
// This is not the Android standalone browser if it does.
return goog.labs.userAgent.util.matchUserAgent('Android') &&
!(goog.labs.userAgent.browser.isChrome() ||
goog.labs.userAgent.browser.isFirefox() ||
goog.labs.userAgent.browser.isOpera() ||
goog.labs.userAgent.browser.isSilk());
};
/**
* @return {boolean} Whether the user's browser is Opera.
*/
goog.labs.userAgent.browser.isOpera = goog.labs.userAgent.browser.matchOpera_;
/**
* @return {boolean} Whether the user's browser is IE.
*/
goog.labs.userAgent.browser.isIE = goog.labs.userAgent.browser.matchIE_;
/**
* @return {boolean} Whether the user's browser is Edge.
*/
goog.labs.userAgent.browser.isEdge = goog.labs.userAgent.browser.matchEdge_;
/**
* @return {boolean} Whether the user's browser is Firefox.
*/
goog.labs.userAgent.browser.isFirefox =
goog.labs.userAgent.browser.matchFirefox_;
/**
* @return {boolean} Whether the user's browser is Safari.
*/
goog.labs.userAgent.browser.isSafari = goog.labs.userAgent.browser.matchSafari_;
/**
* @return {boolean} Whether the user's browser is Coast (Opera's Webkit-based
* iOS browser).
*/
goog.labs.userAgent.browser.isCoast = goog.labs.userAgent.browser.matchCoast_;
/**
* @return {boolean} Whether the user's browser is iOS Webview.
*/
goog.labs.userAgent.browser.isIosWebview =
goog.labs.userAgent.browser.matchIosWebview_;
/**
* @return {boolean} Whether the user's browser is Chrome.
*/
goog.labs.userAgent.browser.isChrome = goog.labs.userAgent.browser.matchChrome_;
/**
* @return {boolean} Whether the user's browser is the Android browser.
*/
goog.labs.userAgent.browser.isAndroidBrowser =
goog.labs.userAgent.browser.matchAndroidBrowser_;
/**
* For more information, see:
* http://docs.aws.amazon.com/silk/latest/developerguide/user-agent.html
* @return {boolean} Whether the user's browser is Silk.
*/
goog.labs.userAgent.browser.isSilk = function() {
return goog.labs.userAgent.util.matchUserAgent('Silk');
};
/**
* @return {string} The browser version or empty string if version cannot be
* determined. Note that for Internet Explorer, this returns the version of
* the browser, not the version of the rendering engine. (IE 8 in
* compatibility mode will return 8.0 rather than 7.0. To determine the
* rendering engine version, look at document.documentMode instead. See
* http://msdn.microsoft.com/en-us/library/cc196988(v=vs.85).aspx for more
* details.)
*/
goog.labs.userAgent.browser.getVersion = function() {
var userAgentString = goog.labs.userAgent.util.getUserAgent();
// Special case IE since IE's version is inside the parenthesis and
// without the '/'.
if (goog.labs.userAgent.browser.isIE()) {
return goog.labs.userAgent.browser.getIEVersion_(userAgentString);
}
var versionTuples =
goog.labs.userAgent.util.extractVersionTuples(userAgentString);
// Construct a map for easy lookup.
var versionMap = {};
goog.array.forEach(versionTuples, function(tuple) {
// Note that the tuple is of length three, but we only care about the
// first two.
var key = tuple[0];
var value = tuple[1];
versionMap[key] = value;
});
var versionMapHasKey = goog.partial(goog.object.containsKey, versionMap);
// Gives the value with the first key it finds, otherwise empty string.
function lookUpValueWithKeys(keys) {
var key = goog.array.find(keys, versionMapHasKey);
return versionMap[key] || '';
}
// Check Opera before Chrome since Opera 15+ has "Chrome" in the string.
// See
// http://my.opera.com/ODIN/blog/2013/07/15/opera-user-agent-strings-opera-15-and-beyond
if (goog.labs.userAgent.browser.isOpera()) {
// Opera 10 has Version/10.0 but Opera/9.8, so look for "Version" first.
// Opera uses 'OPR' for more recent UAs.
return lookUpValueWithKeys(['Version', 'Opera']);
}
// Check Edge before Chrome since it has Chrome in the string.
if (goog.labs.userAgent.browser.isEdge()) {
return lookUpValueWithKeys(['Edge']);
}
if (goog.labs.userAgent.browser.isChrome()) {
return lookUpValueWithKeys(['Chrome', 'CriOS']);
}
// Usually products browser versions are in the third tuple after "Mozilla"
// and the engine.
var tuple = versionTuples[2];
return tuple && tuple[1] || '';
};
/**
* @param {string|number} version The version to check.
* @return {boolean} Whether the browser version is higher or the same as the
* given version.
*/
goog.labs.userAgent.browser.isVersionOrHigher = function(version) {
return goog.string.compareVersions(
goog.labs.userAgent.browser.getVersion(), version) >= 0;
};
/**
* Determines IE version. More information:
* http://msdn.microsoft.com/en-us/library/ie/bg182625(v=vs.85).aspx#uaString
* http://msdn.microsoft.com/en-us/library/hh869301(v=vs.85).aspx
* http://blogs.msdn.com/b/ie/archive/2010/03/23/introducing-ie9-s-user-agent-string.aspx
* http://blogs.msdn.com/b/ie/archive/2009/01/09/the-internet-explorer-8-user-agent-string-updated-edition.aspx
*
* @param {string} userAgent the User-Agent.
* @return {string}
* @private
*/
goog.labs.userAgent.browser.getIEVersion_ = function(userAgent) {
// IE11 may identify itself as MSIE 9.0 or MSIE 10.0 due to an IE 11 upgrade
// bug. Example UA:
// Mozilla/5.0 (MSIE 9.0; Windows NT 6.1; WOW64; Trident/7.0; rv:11.0)
// like Gecko.
// See http://www.whatismybrowser.com/developers/unknown-user-agent-fragments.
var rv = /rv: *([\d\.]*)/.exec(userAgent);
if (rv && rv[1]) {
return rv[1];
}
var version = '';
var msie = /MSIE +([\d\.]+)/.exec(userAgent);
if (msie && msie[1]) {
// IE in compatibility mode usually identifies itself as MSIE 7.0; in this
// case, use the Trident version to determine the version of IE. For more
// details, see the links above.
var tridentVersion = /Trident\/(\d.\d)/.exec(userAgent);
if (msie[1] == '7.0') {
if (tridentVersion && tridentVersion[1]) {
switch (tridentVersion[1]) {
case '4.0':
version = '8.0';
break;
case '5.0':
version = '9.0';
break;
case '6.0':
version = '10.0';
break;
case '7.0':
version = '11.0';
break;
}
} else {
version = '7.0';
}
} else {
version = msie[1];
}
}
return version;
};

View File

@ -0,0 +1,156 @@
// Copyright 2013 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Closure user agent detection.
* @see http://en.wikipedia.org/wiki/User_agent
* For more information on browser brand, platform, or device see the other
* sub-namespaces in goog.labs.userAgent (browser, platform, and device).
*
*/
goog.provide('goog.labs.userAgent.engine');
goog.require('goog.array');
goog.require('goog.labs.userAgent.util');
goog.require('goog.string');
/**
* @return {boolean} Whether the rendering engine is Presto.
*/
goog.labs.userAgent.engine.isPresto = function() {
return goog.labs.userAgent.util.matchUserAgent('Presto');
};
/**
* @return {boolean} Whether the rendering engine is Trident.
*/
goog.labs.userAgent.engine.isTrident = function() {
// IE only started including the Trident token in IE8.
return goog.labs.userAgent.util.matchUserAgent('Trident') ||
goog.labs.userAgent.util.matchUserAgent('MSIE');
};
/**
* @return {boolean} Whether the rendering engine is Edge.
*/
goog.labs.userAgent.engine.isEdge = function() {
return goog.labs.userAgent.util.matchUserAgent('Edge');
};
/**
* @return {boolean} Whether the rendering engine is WebKit.
*/
goog.labs.userAgent.engine.isWebKit = function() {
return goog.labs.userAgent.util.matchUserAgentIgnoreCase('WebKit') &&
!goog.labs.userAgent.engine.isEdge();
};
/**
* @return {boolean} Whether the rendering engine is Gecko.
*/
goog.labs.userAgent.engine.isGecko = function() {
return goog.labs.userAgent.util.matchUserAgent('Gecko') &&
!goog.labs.userAgent.engine.isWebKit() &&
!goog.labs.userAgent.engine.isTrident() &&
!goog.labs.userAgent.engine.isEdge();
};
/**
* @return {string} The rendering engine's version or empty string if version
* can't be determined.
*/
goog.labs.userAgent.engine.getVersion = function() {
var userAgentString = goog.labs.userAgent.util.getUserAgent();
if (userAgentString) {
var tuples = goog.labs.userAgent.util.extractVersionTuples(userAgentString);
var engineTuple = goog.labs.userAgent.engine.getEngineTuple_(tuples);
if (engineTuple) {
// In Gecko, the version string is either in the browser info or the
// Firefox version. See Gecko user agent string reference:
// http://goo.gl/mULqa
if (engineTuple[0] == 'Gecko') {
return goog.labs.userAgent.engine.getVersionForKey_(tuples, 'Firefox');
}
return engineTuple[1];
}
// MSIE has only one version identifier, and the Trident version is
// specified in the parenthetical. IE Edge is covered in the engine tuple
// detection.
var browserTuple = tuples[0];
var info;
if (browserTuple && (info = browserTuple[2])) {
var match = /Trident\/([^\s;]+)/.exec(info);
if (match) {
return match[1];
}
}
}
return '';
};
/**
* @param {!Array<!Array<string>>} tuples Extracted version tuples.
* @return {!Array<string>|undefined} The engine tuple or undefined if not
* found.
* @private
*/
goog.labs.userAgent.engine.getEngineTuple_ = function(tuples) {
if (!goog.labs.userAgent.engine.isEdge()) {
return tuples[1];
}
for (var i = 0; i < tuples.length; i++) {
var tuple = tuples[i];
if (tuple[0] == 'Edge') {
return tuple;
}
}
};
/**
* @param {string|number} version The version to check.
* @return {boolean} Whether the rendering engine version is higher or the same
* as the given version.
*/
goog.labs.userAgent.engine.isVersionOrHigher = function(version) {
return goog.string.compareVersions(
goog.labs.userAgent.engine.getVersion(), version) >= 0;
};
/**
* @param {!Array<!Array<string>>} tuples Version tuples.
* @param {string} key The key to look for.
* @return {string} The version string of the given key, if present.
* Otherwise, the empty string.
* @private
*/
goog.labs.userAgent.engine.getVersionForKey_ = function(tuples, key) {
// TODO(nnaze): Move to util if useful elsewhere.
var pair = goog.array.find(tuples, function(pair) { return key == pair[0]; });
return pair && pair[1] || '';
};

View File

@ -0,0 +1,160 @@
// Copyright 2013 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Closure user agent platform detection.
* @see <a href="http://www.useragentstring.com/">User agent strings</a>
* For more information on browser brand, rendering engine, or device see the
* other sub-namespaces in goog.labs.userAgent (browser, engine, and device
* respectively).
*
*/
goog.provide('goog.labs.userAgent.platform');
goog.require('goog.labs.userAgent.util');
goog.require('goog.string');
/**
* @return {boolean} Whether the platform is Android.
*/
goog.labs.userAgent.platform.isAndroid = function() {
return goog.labs.userAgent.util.matchUserAgent('Android');
};
/**
* @return {boolean} Whether the platform is iPod.
*/
goog.labs.userAgent.platform.isIpod = function() {
return goog.labs.userAgent.util.matchUserAgent('iPod');
};
/**
* @return {boolean} Whether the platform is iPhone.
*/
goog.labs.userAgent.platform.isIphone = function() {
return goog.labs.userAgent.util.matchUserAgent('iPhone') &&
!goog.labs.userAgent.util.matchUserAgent('iPod') &&
!goog.labs.userAgent.util.matchUserAgent('iPad');
};
/**
* @return {boolean} Whether the platform is iPad.
*/
goog.labs.userAgent.platform.isIpad = function() {
return goog.labs.userAgent.util.matchUserAgent('iPad');
};
/**
* @return {boolean} Whether the platform is iOS.
*/
goog.labs.userAgent.platform.isIos = function() {
return goog.labs.userAgent.platform.isIphone() ||
goog.labs.userAgent.platform.isIpad() ||
goog.labs.userAgent.platform.isIpod();
};
/**
* @return {boolean} Whether the platform is Mac.
*/
goog.labs.userAgent.platform.isMacintosh = function() {
return goog.labs.userAgent.util.matchUserAgent('Macintosh');
};
/**
* Note: ChromeOS is not considered to be Linux as it does not report itself
* as Linux in the user agent string.
* @return {boolean} Whether the platform is Linux.
*/
goog.labs.userAgent.platform.isLinux = function() {
return goog.labs.userAgent.util.matchUserAgent('Linux');
};
/**
* @return {boolean} Whether the platform is Windows.
*/
goog.labs.userAgent.platform.isWindows = function() {
return goog.labs.userAgent.util.matchUserAgent('Windows');
};
/**
* @return {boolean} Whether the platform is ChromeOS.
*/
goog.labs.userAgent.platform.isChromeOS = function() {
return goog.labs.userAgent.util.matchUserAgent('CrOS');
};
/**
* The version of the platform. We only determine the version for Windows,
* Mac, and Chrome OS. It doesn't make much sense on Linux. For Windows, we only
* look at the NT version. Non-NT-based versions (e.g. 95, 98, etc.) are given
* version 0.0.
*
* @return {string} The platform version or empty string if version cannot be
* determined.
*/
goog.labs.userAgent.platform.getVersion = function() {
var userAgentString = goog.labs.userAgent.util.getUserAgent();
var version = '', re;
if (goog.labs.userAgent.platform.isWindows()) {
re = /Windows (?:NT|Phone) ([0-9.]+)/;
var match = re.exec(userAgentString);
if (match) {
version = match[1];
} else {
version = '0.0';
}
} else if (goog.labs.userAgent.platform.isIos()) {
re = /(?:iPhone|iPod|iPad|CPU)\s+OS\s+(\S+)/;
var match = re.exec(userAgentString);
// Report the version as x.y.z and not x_y_z
version = match && match[1].replace(/_/g, '.');
} else if (goog.labs.userAgent.platform.isMacintosh()) {
re = /Mac OS X ([0-9_.]+)/;
var match = re.exec(userAgentString);
// Note: some old versions of Camino do not report an OSX version.
// Default to 10.
version = match ? match[1].replace(/_/g, '.') : '10';
} else if (goog.labs.userAgent.platform.isAndroid()) {
re = /Android\s+([^\);]+)(\)|;)/;
var match = re.exec(userAgentString);
version = match && match[1];
} else if (goog.labs.userAgent.platform.isChromeOS()) {
re = /(?:CrOS\s+(?:i686|x86_64)\s+([0-9.]+))/;
var match = re.exec(userAgentString);
version = match && match[1];
}
return version || '';
};
/**
* @param {string|number} version The version to check.
* @return {boolean} Whether the browser version is higher or the same as the
* given version.
*/
goog.labs.userAgent.platform.isVersionOrHigher = function(version) {
return goog.string.compareVersions(
goog.labs.userAgent.platform.getVersion(), version) >= 0;
};

View File

@ -0,0 +1,147 @@
// Copyright 2013 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Utilities used by goog.labs.userAgent tools. These functions
* should not be used outside of goog.labs.userAgent.*.
*
*
* @author nnaze@google.com (Nathan Naze)
*/
goog.provide('goog.labs.userAgent.util');
goog.require('goog.string');
/**
* Gets the native userAgent string from navigator if it exists.
* If navigator or navigator.userAgent string is missing, returns an empty
* string.
* @return {string}
* @private
*/
goog.labs.userAgent.util.getNativeUserAgentString_ = function() {
var navigator = goog.labs.userAgent.util.getNavigator_();
if (navigator) {
var userAgent = navigator.userAgent;
if (userAgent) {
return userAgent;
}
}
return '';
};
/**
* Getter for the native navigator.
* This is a separate function so it can be stubbed out in testing.
* @return {Navigator}
* @private
*/
goog.labs.userAgent.util.getNavigator_ = function() {
return goog.global.navigator;
};
/**
* A possible override for applications which wish to not check
* navigator.userAgent but use a specified value for detection instead.
* @private {string}
*/
goog.labs.userAgent.util.userAgent_ =
goog.labs.userAgent.util.getNativeUserAgentString_();
/**
* Applications may override browser detection on the built in
* navigator.userAgent object by setting this string. Set to null to use the
* browser object instead.
* @param {?string=} opt_userAgent The User-Agent override.
*/
goog.labs.userAgent.util.setUserAgent = function(opt_userAgent) {
goog.labs.userAgent.util.userAgent_ =
opt_userAgent || goog.labs.userAgent.util.getNativeUserAgentString_();
};
/**
* @return {string} The user agent string.
*/
goog.labs.userAgent.util.getUserAgent = function() {
return goog.labs.userAgent.util.userAgent_;
};
/**
* @param {string} str
* @return {boolean} Whether the user agent contains the given string, ignoring
* case.
*/
goog.labs.userAgent.util.matchUserAgent = function(str) {
var userAgent = goog.labs.userAgent.util.getUserAgent();
return goog.string.contains(userAgent, str);
};
/**
* @param {string} str
* @return {boolean} Whether the user agent contains the given string.
*/
goog.labs.userAgent.util.matchUserAgentIgnoreCase = function(str) {
var userAgent = goog.labs.userAgent.util.getUserAgent();
return goog.string.caseInsensitiveContains(userAgent, str);
};
/**
* Parses the user agent into tuples for each section.
* @param {string} userAgent
* @return {!Array<!Array<string>>} Tuples of key, version, and the contents
* of the parenthetical.
*/
goog.labs.userAgent.util.extractVersionTuples = function(userAgent) {
// Matches each section of a user agent string.
// Example UA:
// Mozilla/5.0 (iPad; U; CPU OS 3_2_1 like Mac OS X; en-us)
// AppleWebKit/531.21.10 (KHTML, like Gecko) Mobile/7B405
// This has three version tuples: Mozilla, AppleWebKit, and Mobile.
var versionRegExp = new RegExp(
// Key. Note that a key may have a space.
// (i.e. 'Mobile Safari' in 'Mobile Safari/5.0')
'(\\w[\\w ]+)' +
'/' + // slash
'([^\\s]+)' + // version (i.e. '5.0b')
'\\s*' + // whitespace
'(?:\\((.*?)\\))?', // parenthetical info. parentheses not matched.
'g');
var data = [];
var match;
// Iterate and collect the version tuples. Each iteration will be the
// next regex match.
while (match = versionRegExp.exec(userAgent)) {
data.push([
match[1], // key
match[2], // value
// || undefined as this is not undefined in IE7 and IE8
match[3] || undefined // info
]);
}
return data;
};

View File

@ -0,0 +1,268 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview A utility class for representing two-dimensional positions.
*/
goog.provide('goog.math.Coordinate');
goog.require('goog.math');
/**
* Class for representing coordinates and positions.
* @param {number=} opt_x Left, defaults to 0.
* @param {number=} opt_y Top, defaults to 0.
* @struct
* @constructor
*/
goog.math.Coordinate = function(opt_x, opt_y) {
/**
* X-value
* @type {number}
*/
this.x = goog.isDef(opt_x) ? opt_x : 0;
/**
* Y-value
* @type {number}
*/
this.y = goog.isDef(opt_y) ? opt_y : 0;
};
/**
* Returns a new copy of the coordinate.
* @return {!goog.math.Coordinate} A clone of this coordinate.
*/
goog.math.Coordinate.prototype.clone = function() {
return new goog.math.Coordinate(this.x, this.y);
};
if (goog.DEBUG) {
/**
* Returns a nice string representing the coordinate.
* @return {string} In the form (50, 73).
* @override
*/
goog.math.Coordinate.prototype.toString = function() {
return '(' + this.x + ', ' + this.y + ')';
};
}
/**
* Compares coordinates for equality.
* @param {goog.math.Coordinate} a A Coordinate.
* @param {goog.math.Coordinate} b A Coordinate.
* @return {boolean} True iff the coordinates are equal, or if both are null.
*/
goog.math.Coordinate.equals = function(a, b) {
if (a == b) {
return true;
}
if (!a || !b) {
return false;
}
return a.x == b.x && a.y == b.y;
};
/**
* Returns the distance between two coordinates.
* @param {!goog.math.Coordinate} a A Coordinate.
* @param {!goog.math.Coordinate} b A Coordinate.
* @return {number} The distance between {@code a} and {@code b}.
*/
goog.math.Coordinate.distance = function(a, b) {
var dx = a.x - b.x;
var dy = a.y - b.y;
return Math.sqrt(dx * dx + dy * dy);
};
/**
* Returns the magnitude of a coordinate.
* @param {!goog.math.Coordinate} a A Coordinate.
* @return {number} The distance between the origin and {@code a}.
*/
goog.math.Coordinate.magnitude = function(a) {
return Math.sqrt(a.x * a.x + a.y * a.y);
};
/**
* Returns the angle from the origin to a coordinate.
* @param {!goog.math.Coordinate} a A Coordinate.
* @return {number} The angle, in degrees, clockwise from the positive X
* axis to {@code a}.
*/
goog.math.Coordinate.azimuth = function(a) {
return goog.math.angle(0, 0, a.x, a.y);
};
/**
* Returns the squared distance between two coordinates. Squared distances can
* be used for comparisons when the actual value is not required.
*
* Performance note: eliminating the square root is an optimization often used
* in lower-level languages, but the speed difference is not nearly as
* pronounced in JavaScript (only a few percent.)
*
* @param {!goog.math.Coordinate} a A Coordinate.
* @param {!goog.math.Coordinate} b A Coordinate.
* @return {number} The squared distance between {@code a} and {@code b}.
*/
goog.math.Coordinate.squaredDistance = function(a, b) {
var dx = a.x - b.x;
var dy = a.y - b.y;
return dx * dx + dy * dy;
};
/**
* Returns the difference between two coordinates as a new
* goog.math.Coordinate.
* @param {!goog.math.Coordinate} a A Coordinate.
* @param {!goog.math.Coordinate} b A Coordinate.
* @return {!goog.math.Coordinate} A Coordinate representing the difference
* between {@code a} and {@code b}.
*/
goog.math.Coordinate.difference = function(a, b) {
return new goog.math.Coordinate(a.x - b.x, a.y - b.y);
};
/**
* Returns the sum of two coordinates as a new goog.math.Coordinate.
* @param {!goog.math.Coordinate} a A Coordinate.
* @param {!goog.math.Coordinate} b A Coordinate.
* @return {!goog.math.Coordinate} A Coordinate representing the sum of the two
* coordinates.
*/
goog.math.Coordinate.sum = function(a, b) {
return new goog.math.Coordinate(a.x + b.x, a.y + b.y);
};
/**
* Rounds the x and y fields to the next larger integer values.
* @return {!goog.math.Coordinate} This coordinate with ceil'd fields.
*/
goog.math.Coordinate.prototype.ceil = function() {
this.x = Math.ceil(this.x);
this.y = Math.ceil(this.y);
return this;
};
/**
* Rounds the x and y fields to the next smaller integer values.
* @return {!goog.math.Coordinate} This coordinate with floored fields.
*/
goog.math.Coordinate.prototype.floor = function() {
this.x = Math.floor(this.x);
this.y = Math.floor(this.y);
return this;
};
/**
* Rounds the x and y fields to the nearest integer values.
* @return {!goog.math.Coordinate} This coordinate with rounded fields.
*/
goog.math.Coordinate.prototype.round = function() {
this.x = Math.round(this.x);
this.y = Math.round(this.y);
return this;
};
/**
* Translates this box by the given offsets. If a {@code goog.math.Coordinate}
* is given, then the x and y values are translated by the coordinate's x and y.
* Otherwise, x and y are translated by {@code tx} and {@code opt_ty}
* respectively.
* @param {number|goog.math.Coordinate} tx The value to translate x by or the
* the coordinate to translate this coordinate by.
* @param {number=} opt_ty The value to translate y by.
* @return {!goog.math.Coordinate} This coordinate after translating.
*/
goog.math.Coordinate.prototype.translate = function(tx, opt_ty) {
if (tx instanceof goog.math.Coordinate) {
this.x += tx.x;
this.y += tx.y;
} else {
this.x += Number(tx);
if (goog.isNumber(opt_ty)) {
this.y += opt_ty;
}
}
return this;
};
/**
* Scales this coordinate by the given scale factors. The x and y values are
* scaled by {@code sx} and {@code opt_sy} respectively. If {@code opt_sy}
* is not given, then {@code sx} is used for both x and y.
* @param {number} sx The scale factor to use for the x dimension.
* @param {number=} opt_sy The scale factor to use for the y dimension.
* @return {!goog.math.Coordinate} This coordinate after scaling.
*/
goog.math.Coordinate.prototype.scale = function(sx, opt_sy) {
var sy = goog.isNumber(opt_sy) ? opt_sy : sx;
this.x *= sx;
this.y *= sy;
return this;
};
/**
* Rotates this coordinate clockwise about the origin (or, optionally, the given
* center) by the given angle, in radians.
* @param {number} radians The angle by which to rotate this coordinate
* clockwise about the given center, in radians.
* @param {!goog.math.Coordinate=} opt_center The center of rotation. Defaults
* to (0, 0) if not given.
*/
goog.math.Coordinate.prototype.rotateRadians = function(radians, opt_center) {
var center = opt_center || new goog.math.Coordinate(0, 0);
var x = this.x;
var y = this.y;
var cos = Math.cos(radians);
var sin = Math.sin(radians);
this.x = (x - center.x) * cos - (y - center.y) * sin + center.x;
this.y = (x - center.x) * sin + (y - center.y) * cos + center.y;
};
/**
* Rotates this coordinate clockwise about the origin (or, optionally, the given
* center) by the given angle, in degrees.
* @param {number} degrees The angle by which to rotate this coordinate
* clockwise about the given center, in degrees.
* @param {!goog.math.Coordinate=} opt_center The center of rotation. Defaults
* to (0, 0) if not given.
*/
goog.math.Coordinate.prototype.rotateDegrees = function(degrees, opt_center) {
this.rotateRadians(goog.math.toRadians(degrees), opt_center);
};

View File

@ -0,0 +1,807 @@
// Copyright 2009 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Defines an Integer class for representing (potentially)
* infinite length two's-complement integer values.
*
* For the specific case of 64-bit integers, use goog.math.Long, which is more
* efficient.
*
*/
goog.provide('goog.math.Integer');
/**
* Constructs a two's-complement integer an array containing bits of the
* integer in 32-bit (signed) pieces, given in little-endian order (i.e.,
* lowest-order bits in the first piece), and the sign of -1 or 0.
*
* See the from* functions below for other convenient ways of constructing
* Integers.
*
* The internal representation of an integer is an array of 32-bit signed
* pieces, along with a sign (0 or -1) that indicates the contents of all the
* other 32-bit pieces out to infinity. We use 32-bit pieces because these are
* the size of integers on which Javascript performs bit-operations. For
* operations like addition and multiplication, we split each number into 16-bit
* pieces, which can easily be multiplied within Javascript's floating-point
* representation without overflow or change in sign.
*
* @struct
* @constructor
* @param {Array<number>} bits Array containing the bits of the number.
* @param {number} sign The sign of the number: -1 for negative and 0 positive.
* @final
*/
goog.math.Integer = function(bits, sign) {
/**
* @type {!Array<number>}
* @private
*/
this.bits_ = [];
/**
* @type {number}
* @private
*/
this.sign_ = sign;
// Copy the 32-bit signed integer values passed in. We prune out those at the
// top that equal the sign since they are redundant.
var top = true;
for (var i = bits.length - 1; i >= 0; i--) {
var val = bits[i] | 0;
if (!top || val != sign) {
this.bits_[i] = val;
top = false;
}
}
};
// NOTE: Common constant values ZERO, ONE, NEG_ONE, etc. are defined below the
// from* methods on which they depend.
/**
* A cache of the Integer representations of small integer values.
* @type {!Object}
* @private
*/
goog.math.Integer.IntCache_ = {};
/**
* Returns an Integer representing the given (32-bit) integer value.
* @param {number} value A 32-bit integer value.
* @return {!goog.math.Integer} The corresponding Integer value.
*/
goog.math.Integer.fromInt = function(value) {
if (-128 <= value && value < 128) {
var cachedObj = goog.math.Integer.IntCache_[value];
if (cachedObj) {
return cachedObj;
}
}
var obj = new goog.math.Integer([value | 0], value < 0 ? -1 : 0);
if (-128 <= value && value < 128) {
goog.math.Integer.IntCache_[value] = obj;
}
return obj;
};
/**
* Returns an Integer representing the given value, provided that it is a finite
* number. Otherwise, zero is returned.
* @param {number} value The value in question.
* @return {!goog.math.Integer} The corresponding Integer value.
*/
goog.math.Integer.fromNumber = function(value) {
if (isNaN(value) || !isFinite(value)) {
return goog.math.Integer.ZERO;
} else if (value < 0) {
return goog.math.Integer.fromNumber(-value).negate();
} else {
var bits = [];
var pow = 1;
for (var i = 0; value >= pow; i++) {
bits[i] = (value / pow) | 0;
pow *= goog.math.Integer.TWO_PWR_32_DBL_;
}
return new goog.math.Integer(bits, 0);
}
};
/**
* Returns a Integer representing the value that comes by concatenating the
* given entries, each is assumed to be 32 signed bits, given in little-endian
* order (lowest order bits in the lowest index), and sign-extending the highest
* order 32-bit value.
* @param {Array<number>} bits The bits of the number, in 32-bit signed pieces,
* in little-endian order.
* @return {!goog.math.Integer} The corresponding Integer value.
*/
goog.math.Integer.fromBits = function(bits) {
var high = bits[bits.length - 1];
return new goog.math.Integer(bits, high & (1 << 31) ? -1 : 0);
};
/**
* Returns an Integer representation of the given string, written using the
* given radix.
* @param {string} str The textual representation of the Integer.
* @param {number=} opt_radix The radix in which the text is written.
* @return {!goog.math.Integer} The corresponding Integer value.
*/
goog.math.Integer.fromString = function(str, opt_radix) {
if (str.length == 0) {
throw Error('number format error: empty string');
}
var radix = opt_radix || 10;
if (radix < 2 || 36 < radix) {
throw Error('radix out of range: ' + radix);
}
if (str.charAt(0) == '-') {
return goog.math.Integer.fromString(str.substring(1), radix).negate();
} else if (str.indexOf('-') >= 0) {
throw Error('number format error: interior "-" character');
}
// Do several (8) digits each time through the loop, so as to
// minimize the calls to the very expensive emulated div.
var radixToPower = goog.math.Integer.fromNumber(Math.pow(radix, 8));
var result = goog.math.Integer.ZERO;
for (var i = 0; i < str.length; i += 8) {
var size = Math.min(8, str.length - i);
var value = parseInt(str.substring(i, i + size), radix);
if (size < 8) {
var power = goog.math.Integer.fromNumber(Math.pow(radix, size));
result = result.multiply(power).add(goog.math.Integer.fromNumber(value));
} else {
result = result.multiply(radixToPower);
result = result.add(goog.math.Integer.fromNumber(value));
}
}
return result;
};
/**
* A number used repeatedly in calculations. This must appear before the first
* call to the from* functions below.
* @type {number}
* @private
*/
goog.math.Integer.TWO_PWR_32_DBL_ = (1 << 16) * (1 << 16);
/** @type {!goog.math.Integer} */
goog.math.Integer.ZERO = goog.math.Integer.fromInt(0);
/** @type {!goog.math.Integer} */
goog.math.Integer.ONE = goog.math.Integer.fromInt(1);
/**
* @type {!goog.math.Integer}
* @private
*/
goog.math.Integer.TWO_PWR_24_ = goog.math.Integer.fromInt(1 << 24);
/**
* Returns the value, assuming it is a 32-bit integer.
* @return {number} The corresponding int value.
*/
goog.math.Integer.prototype.toInt = function() {
return this.bits_.length > 0 ? this.bits_[0] : this.sign_;
};
/** @return {number} The closest floating-point representation to this value. */
goog.math.Integer.prototype.toNumber = function() {
if (this.isNegative()) {
return -this.negate().toNumber();
} else {
var val = 0;
var pow = 1;
for (var i = 0; i < this.bits_.length; i++) {
val += this.getBitsUnsigned(i) * pow;
pow *= goog.math.Integer.TWO_PWR_32_DBL_;
}
return val;
}
};
/**
* @param {number=} opt_radix The radix in which the text should be written.
* @return {string} The textual representation of this value.
* @override
*/
goog.math.Integer.prototype.toString = function(opt_radix) {
var radix = opt_radix || 10;
if (radix < 2 || 36 < radix) {
throw Error('radix out of range: ' + radix);
}
if (this.isZero()) {
return '0';
} else if (this.isNegative()) {
return '-' + this.negate().toString(radix);
}
// Do several (6) digits each time through the loop, so as to
// minimize the calls to the very expensive emulated div.
var radixToPower = goog.math.Integer.fromNumber(Math.pow(radix, 6));
var rem = this;
var result = '';
while (true) {
var remDiv = rem.divide(radixToPower);
// The right shifting fixes negative values in the case when
// intval >= 2^31; for more details see
// https://github.com/google/closure-library/pull/498
var intval = rem.subtract(remDiv.multiply(radixToPower)).toInt() >>> 0;
var digits = intval.toString(radix);
rem = remDiv;
if (rem.isZero()) {
return digits + result;
} else {
while (digits.length < 6) {
digits = '0' + digits;
}
result = '' + digits + result;
}
}
};
/**
* Returns the index-th 32-bit (signed) piece of the Integer according to
* little-endian order (i.e., index 0 contains the smallest bits).
* @param {number} index The index in question.
* @return {number} The requested 32-bits as a signed number.
*/
goog.math.Integer.prototype.getBits = function(index) {
if (index < 0) {
return 0; // Allowing this simplifies bit shifting operations below...
} else if (index < this.bits_.length) {
return this.bits_[index];
} else {
return this.sign_;
}
};
/**
* Returns the index-th 32-bit piece as an unsigned number.
* @param {number} index The index in question.
* @return {number} The requested 32-bits as an unsigned number.
*/
goog.math.Integer.prototype.getBitsUnsigned = function(index) {
var val = this.getBits(index);
return val >= 0 ? val : goog.math.Integer.TWO_PWR_32_DBL_ + val;
};
/** @return {number} The sign bit of this number, -1 or 0. */
goog.math.Integer.prototype.getSign = function() {
return this.sign_;
};
/** @return {boolean} Whether this value is zero. */
goog.math.Integer.prototype.isZero = function() {
if (this.sign_ != 0) {
return false;
}
for (var i = 0; i < this.bits_.length; i++) {
if (this.bits_[i] != 0) {
return false;
}
}
return true;
};
/** @return {boolean} Whether this value is negative. */
goog.math.Integer.prototype.isNegative = function() {
return this.sign_ == -1;
};
/** @return {boolean} Whether this value is odd. */
goog.math.Integer.prototype.isOdd = function() {
return (this.bits_.length == 0) && (this.sign_ == -1) ||
(this.bits_.length > 0) && ((this.bits_[0] & 1) != 0);
};
/**
* @param {goog.math.Integer} other Integer to compare against.
* @return {boolean} Whether this Integer equals the other.
*/
goog.math.Integer.prototype.equals = function(other) {
if (this.sign_ != other.sign_) {
return false;
}
var len = Math.max(this.bits_.length, other.bits_.length);
for (var i = 0; i < len; i++) {
if (this.getBits(i) != other.getBits(i)) {
return false;
}
}
return true;
};
/**
* @param {goog.math.Integer} other Integer to compare against.
* @return {boolean} Whether this Integer does not equal the other.
*/
goog.math.Integer.prototype.notEquals = function(other) {
return !this.equals(other);
};
/**
* @param {goog.math.Integer} other Integer to compare against.
* @return {boolean} Whether this Integer is greater than the other.
*/
goog.math.Integer.prototype.greaterThan = function(other) {
return this.compare(other) > 0;
};
/**
* @param {goog.math.Integer} other Integer to compare against.
* @return {boolean} Whether this Integer is greater than or equal to the other.
*/
goog.math.Integer.prototype.greaterThanOrEqual = function(other) {
return this.compare(other) >= 0;
};
/**
* @param {goog.math.Integer} other Integer to compare against.
* @return {boolean} Whether this Integer is less than the other.
*/
goog.math.Integer.prototype.lessThan = function(other) {
return this.compare(other) < 0;
};
/**
* @param {goog.math.Integer} other Integer to compare against.
* @return {boolean} Whether this Integer is less than or equal to the other.
*/
goog.math.Integer.prototype.lessThanOrEqual = function(other) {
return this.compare(other) <= 0;
};
/**
* Compares this Integer with the given one.
* @param {goog.math.Integer} other Integer to compare against.
* @return {number} 0 if they are the same, 1 if the this is greater, and -1
* if the given one is greater.
*/
goog.math.Integer.prototype.compare = function(other) {
var diff = this.subtract(other);
if (diff.isNegative()) {
return -1;
} else if (diff.isZero()) {
return 0;
} else {
return +1;
}
};
/**
* Returns an integer with only the first numBits bits of this value, sign
* extended from the final bit.
* @param {number} numBits The number of bits by which to shift.
* @return {!goog.math.Integer} The shorted integer value.
*/
goog.math.Integer.prototype.shorten = function(numBits) {
var arr_index = (numBits - 1) >> 5;
var bit_index = (numBits - 1) % 32;
var bits = [];
for (var i = 0; i < arr_index; i++) {
bits[i] = this.getBits(i);
}
var sigBits = bit_index == 31 ? 0xFFFFFFFF : (1 << (bit_index + 1)) - 1;
var val = this.getBits(arr_index) & sigBits;
if (val & (1 << bit_index)) {
val |= 0xFFFFFFFF - sigBits;
bits[arr_index] = val;
return new goog.math.Integer(bits, -1);
} else {
bits[arr_index] = val;
return new goog.math.Integer(bits, 0);
}
};
/** @return {!goog.math.Integer} The negation of this value. */
goog.math.Integer.prototype.negate = function() {
return this.not().add(goog.math.Integer.ONE);
};
/**
* Returns the sum of this and the given Integer.
* @param {goog.math.Integer} other The Integer to add to this.
* @return {!goog.math.Integer} The Integer result.
*/
goog.math.Integer.prototype.add = function(other) {
var len = Math.max(this.bits_.length, other.bits_.length);
var arr = [];
var carry = 0;
for (var i = 0; i <= len; i++) {
var a1 = this.getBits(i) >>> 16;
var a0 = this.getBits(i) & 0xFFFF;
var b1 = other.getBits(i) >>> 16;
var b0 = other.getBits(i) & 0xFFFF;
var c0 = carry + a0 + b0;
var c1 = (c0 >>> 16) + a1 + b1;
carry = c1 >>> 16;
c0 &= 0xFFFF;
c1 &= 0xFFFF;
arr[i] = (c1 << 16) | c0;
}
return goog.math.Integer.fromBits(arr);
};
/**
* Returns the difference of this and the given Integer.
* @param {goog.math.Integer} other The Integer to subtract from this.
* @return {!goog.math.Integer} The Integer result.
*/
goog.math.Integer.prototype.subtract = function(other) {
return this.add(other.negate());
};
/**
* Returns the product of this and the given Integer.
* @param {goog.math.Integer} other The Integer to multiply against this.
* @return {!goog.math.Integer} The product of this and the other.
*/
goog.math.Integer.prototype.multiply = function(other) {
if (this.isZero()) {
return goog.math.Integer.ZERO;
} else if (other.isZero()) {
return goog.math.Integer.ZERO;
}
if (this.isNegative()) {
if (other.isNegative()) {
return this.negate().multiply(other.negate());
} else {
return this.negate().multiply(other).negate();
}
} else if (other.isNegative()) {
return this.multiply(other.negate()).negate();
}
// If both numbers are small, use float multiplication
if (this.lessThan(goog.math.Integer.TWO_PWR_24_) &&
other.lessThan(goog.math.Integer.TWO_PWR_24_)) {
return goog.math.Integer.fromNumber(this.toNumber() * other.toNumber());
}
// Fill in an array of 16-bit products.
var len = this.bits_.length + other.bits_.length;
var arr = [];
for (var i = 0; i < 2 * len; i++) {
arr[i] = 0;
}
for (var i = 0; i < this.bits_.length; i++) {
for (var j = 0; j < other.bits_.length; j++) {
var a1 = this.getBits(i) >>> 16;
var a0 = this.getBits(i) & 0xFFFF;
var b1 = other.getBits(j) >>> 16;
var b0 = other.getBits(j) & 0xFFFF;
arr[2 * i + 2 * j] += a0 * b0;
goog.math.Integer.carry16_(arr, 2 * i + 2 * j);
arr[2 * i + 2 * j + 1] += a1 * b0;
goog.math.Integer.carry16_(arr, 2 * i + 2 * j + 1);
arr[2 * i + 2 * j + 1] += a0 * b1;
goog.math.Integer.carry16_(arr, 2 * i + 2 * j + 1);
arr[2 * i + 2 * j + 2] += a1 * b1;
goog.math.Integer.carry16_(arr, 2 * i + 2 * j + 2);
}
}
// Combine the 16-bit values into 32-bit values.
for (var i = 0; i < len; i++) {
arr[i] = (arr[2 * i + 1] << 16) | arr[2 * i];
}
for (var i = len; i < 2 * len; i++) {
arr[i] = 0;
}
return new goog.math.Integer(arr, 0);
};
/**
* Carries any overflow from the given index into later entries.
* @param {Array<number>} bits Array of 16-bit values in little-endian order.
* @param {number} index The index in question.
* @private
*/
goog.math.Integer.carry16_ = function(bits, index) {
while ((bits[index] & 0xFFFF) != bits[index]) {
bits[index + 1] += bits[index] >>> 16;
bits[index] &= 0xFFFF;
}
};
/**
* Returns "this" Integer divided by the given one. Both "this" and the given
* Integer MUST be positive.
*
* This method is only needed for very large numbers (>10^308),
* for which the original division algorithm gets into an infinite
* loop (see https://github.com/google/closure-library/issues/500).
*
* The algorithm has some possible performance enhancements (or
* could be rewritten entirely), it's just an initial solution for
* the issue linked above.
*
* @param {!goog.math.Integer} other The Integer to divide "this" by.
* @return {!goog.math.Integer} "this" value divided by the given one.
* @private
*/
goog.math.Integer.prototype.slowDivide_ = function(other) {
if (this.isNegative() || other.isNegative()) {
throw Error('slowDivide_ only works with positive integers.');
}
var twoPower = goog.math.Integer.ONE;
var multiple = other;
// First we have to figure out what the highest bit of the result
// is, so we increase "twoPower" and "multiple" until "multiple"
// exceeds "this".
while (multiple.lessThanOrEqual(this)) {
twoPower = twoPower.shiftLeft(1);
multiple = multiple.shiftLeft(1);
}
// Rewind by one power of two, giving us the highest bit of the
// result.
var res = twoPower.shiftRight(1);
var total = multiple.shiftRight(1);
// Now we starting decreasing "multiple" and "twoPower" to find the
// rest of the bits of the result.
var total2;
multiple = multiple.shiftRight(2);
twoPower = twoPower.shiftRight(2);
while (!multiple.isZero()) {
// whenever we can add "multiple" to the total and not exceed
// "this", that means we've found a 1 bit. Else we've found a 0
// and don't need to add to the result.
total2 = total.add(multiple);
if (total2.lessThanOrEqual(this)) {
res = res.add(twoPower);
total = total2;
}
multiple = multiple.shiftRight(1);
twoPower = twoPower.shiftRight(1);
}
return res;
};
/**
* Returns this Integer divided by the given one.
* @param {!goog.math.Integer} other The Integer to divide this by.
* @return {!goog.math.Integer} This value divided by the given one.
*/
goog.math.Integer.prototype.divide = function(other) {
if (other.isZero()) {
throw Error('division by zero');
} else if (this.isZero()) {
return goog.math.Integer.ZERO;
}
if (this.isNegative()) {
if (other.isNegative()) {
return this.negate().divide(other.negate());
} else {
return this.negate().divide(other).negate();
}
} else if (other.isNegative()) {
return this.divide(other.negate()).negate();
}
// Have to degrade to slowDivide for Very Large Numbers, because
// they're out of range for the floating-point approximation
// technique used below.
if (this.bits_.length > 30) {
return this.slowDivide_(other);
}
// Repeat the following until the remainder is less than other: find a
// floating-point that approximates remainder / other *from below*, add this
// into the result, and subtract it from the remainder. It is critical that
// the approximate value is less than or equal to the real value so that the
// remainder never becomes negative.
var res = goog.math.Integer.ZERO;
var rem = this;
while (rem.greaterThanOrEqual(other)) {
// Approximate the result of division. This may be a little greater or
// smaller than the actual value.
var approx = Math.max(1, Math.floor(rem.toNumber() / other.toNumber()));
// We will tweak the approximate result by changing it in the 48-th digit or
// the smallest non-fractional digit, whichever is larger.
var log2 = Math.ceil(Math.log(approx) / Math.LN2);
var delta = (log2 <= 48) ? 1 : Math.pow(2, log2 - 48);
// Decrease the approximation until it is smaller than the remainder. Note
// that if it is too large, the product overflows and is negative.
var approxRes = goog.math.Integer.fromNumber(approx);
var approxRem = approxRes.multiply(other);
while (approxRem.isNegative() || approxRem.greaterThan(rem)) {
approx -= delta;
approxRes = goog.math.Integer.fromNumber(approx);
approxRem = approxRes.multiply(other);
}
// We know the answer can't be zero... and actually, zero would cause
// infinite recursion since we would make no progress.
if (approxRes.isZero()) {
approxRes = goog.math.Integer.ONE;
}
res = res.add(approxRes);
rem = rem.subtract(approxRem);
}
return res;
};
/**
* Returns this Integer modulo the given one.
* @param {!goog.math.Integer} other The Integer by which to mod.
* @return {!goog.math.Integer} This value modulo the given one.
*/
goog.math.Integer.prototype.modulo = function(other) {
return this.subtract(this.divide(other).multiply(other));
};
/** @return {!goog.math.Integer} The bitwise-NOT of this value. */
goog.math.Integer.prototype.not = function() {
var len = this.bits_.length;
var arr = [];
for (var i = 0; i < len; i++) {
arr[i] = ~this.bits_[i];
}
return new goog.math.Integer(arr, ~this.sign_);
};
/**
* Returns the bitwise-AND of this Integer and the given one.
* @param {goog.math.Integer} other The Integer to AND with this.
* @return {!goog.math.Integer} The bitwise-AND of this and the other.
*/
goog.math.Integer.prototype.and = function(other) {
var len = Math.max(this.bits_.length, other.bits_.length);
var arr = [];
for (var i = 0; i < len; i++) {
arr[i] = this.getBits(i) & other.getBits(i);
}
return new goog.math.Integer(arr, this.sign_ & other.sign_);
};
/**
* Returns the bitwise-OR of this Integer and the given one.
* @param {goog.math.Integer} other The Integer to OR with this.
* @return {!goog.math.Integer} The bitwise-OR of this and the other.
*/
goog.math.Integer.prototype.or = function(other) {
var len = Math.max(this.bits_.length, other.bits_.length);
var arr = [];
for (var i = 0; i < len; i++) {
arr[i] = this.getBits(i) | other.getBits(i);
}
return new goog.math.Integer(arr, this.sign_ | other.sign_);
};
/**
* Returns the bitwise-XOR of this Integer and the given one.
* @param {goog.math.Integer} other The Integer to XOR with this.
* @return {!goog.math.Integer} The bitwise-XOR of this and the other.
*/
goog.math.Integer.prototype.xor = function(other) {
var len = Math.max(this.bits_.length, other.bits_.length);
var arr = [];
for (var i = 0; i < len; i++) {
arr[i] = this.getBits(i) ^ other.getBits(i);
}
return new goog.math.Integer(arr, this.sign_ ^ other.sign_);
};
/**
* Returns this value with bits shifted to the left by the given amount.
* @param {number} numBits The number of bits by which to shift.
* @return {!goog.math.Integer} This shifted to the left by the given amount.
*/
goog.math.Integer.prototype.shiftLeft = function(numBits) {
var arr_delta = numBits >> 5;
var bit_delta = numBits % 32;
var len = this.bits_.length + arr_delta + (bit_delta > 0 ? 1 : 0);
var arr = [];
for (var i = 0; i < len; i++) {
if (bit_delta > 0) {
arr[i] = (this.getBits(i - arr_delta) << bit_delta) |
(this.getBits(i - arr_delta - 1) >>> (32 - bit_delta));
} else {
arr[i] = this.getBits(i - arr_delta);
}
}
return new goog.math.Integer(arr, this.sign_);
};
/**
* Returns this value with bits shifted to the right by the given amount.
* @param {number} numBits The number of bits by which to shift.
* @return {!goog.math.Integer} This shifted to the right by the given amount.
*/
goog.math.Integer.prototype.shiftRight = function(numBits) {
var arr_delta = numBits >> 5;
var bit_delta = numBits % 32;
var len = this.bits_.length - arr_delta;
var arr = [];
for (var i = 0; i < len; i++) {
if (bit_delta > 0) {
arr[i] = (this.getBits(i + arr_delta) >>> bit_delta) |
(this.getBits(i + arr_delta + 1) << (32 - bit_delta));
} else {
arr[i] = this.getBits(i + arr_delta);
}
}
return new goog.math.Integer(arr, this.sign_);
};

843
viz/out/goog/math/long.js Normal file
View File

@ -0,0 +1,843 @@
// Copyright 2009 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Defines a Long class for representing a 64-bit two's-complement
* integer value, which faithfully simulates the behavior of a Java "long". This
* implementation is derived from LongLib in GWT.
*
*/
goog.provide('goog.math.Long');
goog.require('goog.reflect');
/**
* Constructs a 64-bit two's-complement integer, given its low and high 32-bit
* values as *signed* integers. See the from* functions below for more
* convenient ways of constructing Longs.
*
* The internal representation of a long is the two given signed, 32-bit values.
* We use 32-bit pieces because these are the size of integers on which
* Javascript performs bit-operations. For operations like addition and
* multiplication, we split each number into 16-bit pieces, which can easily be
* multiplied within Javascript's floating-point representation without overflow
* or change in sign.
*
* In the algorithms below, we frequently reduce the negative case to the
* positive case by negating the input(s) and then post-processing the result.
* Note that we must ALWAYS check specially whether those values are MIN_VALUE
* (-2^63) because -MIN_VALUE == MIN_VALUE (since 2^63 cannot be represented as
* a positive number, it overflows back into a negative). Not handling this
* case would often result in infinite recursion.
*
* @param {number} low The low (signed) 32 bits of the long.
* @param {number} high The high (signed) 32 bits of the long.
* @struct
* @constructor
* @final
*/
goog.math.Long = function(low, high) {
/**
* @type {number}
* @private
*/
this.low_ = low | 0; // force into 32 signed bits.
/**
* @type {number}
* @private
*/
this.high_ = high | 0; // force into 32 signed bits.
};
// NOTE: Common constant values ZERO, ONE, NEG_ONE, etc. are defined below the
// from* methods on which they depend.
/**
* A cache of the Long representations of small integer values.
* @type {!Object<number, !goog.math.Long>}
* @private
*/
goog.math.Long.IntCache_ = {};
/**
* A cache of the Long representations of common values.
* @type {!Object<goog.math.Long.ValueCacheId_, !goog.math.Long>}
* @private
*/
goog.math.Long.valueCache_ = {};
/**
* Returns a Long representing the given (32-bit) integer value.
* @param {number} value The 32-bit integer in question.
* @return {!goog.math.Long} The corresponding Long value.
*/
goog.math.Long.fromInt = function(value) {
if (-128 <= value && value < 128) {
return goog.reflect.cache(goog.math.Long.IntCache_, value, function(val) {
return new goog.math.Long(val | 0, val < 0 ? -1 : 0);
});
} else {
return new goog.math.Long(value | 0, value < 0 ? -1 : 0);
}
};
/**
* Returns a Long representing the given value.
* NaN will be returned as zero. Infinity is converted to max value and
* -Infinity to min value.
* @param {number} value The number in question.
* @return {!goog.math.Long} The corresponding Long value.
*/
goog.math.Long.fromNumber = function(value) {
if (isNaN(value)) {
return goog.math.Long.getZero();
} else if (value <= -goog.math.Long.TWO_PWR_63_DBL_) {
return goog.math.Long.getMinValue();
} else if (value + 1 >= goog.math.Long.TWO_PWR_63_DBL_) {
return goog.math.Long.getMaxValue();
} else if (value < 0) {
return goog.math.Long.fromNumber(-value).negate();
} else {
return new goog.math.Long(
(value % goog.math.Long.TWO_PWR_32_DBL_) | 0,
(value / goog.math.Long.TWO_PWR_32_DBL_) | 0);
}
};
/**
* Returns a Long representing the 64-bit integer that comes by concatenating
* the given high and low bits. Each is assumed to use 32 bits.
* @param {number} lowBits The low 32-bits.
* @param {number} highBits The high 32-bits.
* @return {!goog.math.Long} The corresponding Long value.
*/
goog.math.Long.fromBits = function(lowBits, highBits) {
return new goog.math.Long(lowBits, highBits);
};
/**
* Returns a Long representation of the given string, written using the given
* radix.
* @param {string} str The textual representation of the Long.
* @param {number=} opt_radix The radix in which the text is written.
* @return {!goog.math.Long} The corresponding Long value.
*/
goog.math.Long.fromString = function(str, opt_radix) {
if (str.length == 0) {
throw Error('number format error: empty string');
}
var radix = opt_radix || 10;
if (radix < 2 || 36 < radix) {
throw Error('radix out of range: ' + radix);
}
if (str.charAt(0) == '-') {
return goog.math.Long.fromString(str.substring(1), radix).negate();
} else if (str.indexOf('-') >= 0) {
throw Error('number format error: interior "-" character: ' + str);
}
// Do several (8) digits each time through the loop, so as to
// minimize the calls to the very expensive emulated div.
var radixToPower = goog.math.Long.fromNumber(Math.pow(radix, 8));
var result = goog.math.Long.getZero();
for (var i = 0; i < str.length; i += 8) {
var size = Math.min(8, str.length - i);
var value = parseInt(str.substring(i, i + size), radix);
if (size < 8) {
var power = goog.math.Long.fromNumber(Math.pow(radix, size));
result = result.multiply(power).add(goog.math.Long.fromNumber(value));
} else {
result = result.multiply(radixToPower);
result = result.add(goog.math.Long.fromNumber(value));
}
}
return result;
};
// NOTE: the compiler should inline these constant values below and then remove
// these variables, so there should be no runtime penalty for these.
/**
* Number used repeated below in calculations. This must appear before the
* first call to any from* function below.
* @type {number}
* @private
*/
goog.math.Long.TWO_PWR_16_DBL_ = 1 << 16;
/**
* @type {number}
* @private
*/
goog.math.Long.TWO_PWR_32_DBL_ =
goog.math.Long.TWO_PWR_16_DBL_ * goog.math.Long.TWO_PWR_16_DBL_;
/**
* @type {number}
* @private
*/
goog.math.Long.TWO_PWR_64_DBL_ =
goog.math.Long.TWO_PWR_32_DBL_ * goog.math.Long.TWO_PWR_32_DBL_;
/**
* @type {number}
* @private
*/
goog.math.Long.TWO_PWR_63_DBL_ = goog.math.Long.TWO_PWR_64_DBL_ / 2;
/**
* @return {!goog.math.Long}
* @public
*/
goog.math.Long.getZero = function() {
return goog.reflect.cache(
goog.math.Long.valueCache_, goog.math.Long.ValueCacheId_.ZERO,
function() { return goog.math.Long.fromInt(0); });
};
/**
* @return {!goog.math.Long}
* @public
*/
goog.math.Long.getOne = function() {
return goog.reflect.cache(
goog.math.Long.valueCache_, goog.math.Long.ValueCacheId_.ONE,
function() { return goog.math.Long.fromInt(1); });
};
/**
* @return {!goog.math.Long}
* @public
*/
goog.math.Long.getNegOne = function() {
return goog.reflect.cache(
goog.math.Long.valueCache_, goog.math.Long.ValueCacheId_.NEG_ONE,
function() { return goog.math.Long.fromInt(-1); });
};
/**
* @return {!goog.math.Long}
* @public
*/
goog.math.Long.getMaxValue = function() {
return goog.reflect.cache(
goog.math.Long.valueCache_, goog.math.Long.ValueCacheId_.MAX_VALUE,
function() {
return goog.math.Long.fromBits(0xFFFFFFFF | 0, 0x7FFFFFFF | 0);
});
};
/**
* @return {!goog.math.Long}
* @public
*/
goog.math.Long.getMinValue = function() {
return goog.reflect.cache(
goog.math.Long.valueCache_, goog.math.Long.ValueCacheId_.MIN_VALUE,
function() { return goog.math.Long.fromBits(0, 0x80000000 | 0); });
};
/**
* @return {!goog.math.Long}
* @public
*/
goog.math.Long.getTwoPwr24 = function() {
return goog.reflect.cache(
goog.math.Long.valueCache_, goog.math.Long.ValueCacheId_.TWO_PWR_24,
function() { return goog.math.Long.fromInt(1 << 24); });
};
/** @return {number} The value, assuming it is a 32-bit integer. */
goog.math.Long.prototype.toInt = function() {
return this.low_;
};
/** @return {number} The closest floating-point representation to this value. */
goog.math.Long.prototype.toNumber = function() {
return this.high_ * goog.math.Long.TWO_PWR_32_DBL_ +
this.getLowBitsUnsigned();
};
/**
* @param {number=} opt_radix The radix in which the text should be written.
* @return {string} The textual representation of this value.
* @override
*/
goog.math.Long.prototype.toString = function(opt_radix) {
var radix = opt_radix || 10;
if (radix < 2 || 36 < radix) {
throw Error('radix out of range: ' + radix);
}
if (this.isZero()) {
return '0';
}
if (this.isNegative()) {
if (this.equals(goog.math.Long.getMinValue())) {
// We need to change the Long value before it can be negated, so we remove
// the bottom-most digit in this base and then recurse to do the rest.
var radixLong = goog.math.Long.fromNumber(radix);
var div = this.div(radixLong);
var rem = div.multiply(radixLong).subtract(this);
return div.toString(radix) + rem.toInt().toString(radix);
} else {
return '-' + this.negate().toString(radix);
}
}
// Do several (6) digits each time through the loop, so as to
// minimize the calls to the very expensive emulated div.
var radixToPower = goog.math.Long.fromNumber(Math.pow(radix, 6));
var rem = this;
var result = '';
while (true) {
var remDiv = rem.div(radixToPower);
// The right shifting fixes negative values in the case when
// intval >= 2^31; for more details see
// https://github.com/google/closure-library/pull/498
var intval = rem.subtract(remDiv.multiply(radixToPower)).toInt() >>> 0;
var digits = intval.toString(radix);
rem = remDiv;
if (rem.isZero()) {
return digits + result;
} else {
while (digits.length < 6) {
digits = '0' + digits;
}
result = '' + digits + result;
}
}
};
/** @return {number} The high 32-bits as a signed value. */
goog.math.Long.prototype.getHighBits = function() {
return this.high_;
};
/** @return {number} The low 32-bits as a signed value. */
goog.math.Long.prototype.getLowBits = function() {
return this.low_;
};
/** @return {number} The low 32-bits as an unsigned value. */
goog.math.Long.prototype.getLowBitsUnsigned = function() {
return (this.low_ >= 0) ? this.low_ :
goog.math.Long.TWO_PWR_32_DBL_ + this.low_;
};
/**
* @return {number} Returns the number of bits needed to represent the absolute
* value of this Long.
*/
goog.math.Long.prototype.getNumBitsAbs = function() {
if (this.isNegative()) {
if (this.equals(goog.math.Long.getMinValue())) {
return 64;
} else {
return this.negate().getNumBitsAbs();
}
} else {
var val = this.high_ != 0 ? this.high_ : this.low_;
for (var bit = 31; bit > 0; bit--) {
if ((val & (1 << bit)) != 0) {
break;
}
}
return this.high_ != 0 ? bit + 33 : bit + 1;
}
};
/** @return {boolean} Whether this value is zero. */
goog.math.Long.prototype.isZero = function() {
return this.high_ == 0 && this.low_ == 0;
};
/** @return {boolean} Whether this value is negative. */
goog.math.Long.prototype.isNegative = function() {
return this.high_ < 0;
};
/** @return {boolean} Whether this value is odd. */
goog.math.Long.prototype.isOdd = function() {
return (this.low_ & 1) == 1;
};
/**
* @param {goog.math.Long} other Long to compare against.
* @return {boolean} Whether this Long equals the other.
*/
goog.math.Long.prototype.equals = function(other) {
return (this.high_ == other.high_) && (this.low_ == other.low_);
};
/**
* @param {goog.math.Long} other Long to compare against.
* @return {boolean} Whether this Long does not equal the other.
*/
goog.math.Long.prototype.notEquals = function(other) {
return (this.high_ != other.high_) || (this.low_ != other.low_);
};
/**
* @param {goog.math.Long} other Long to compare against.
* @return {boolean} Whether this Long is less than the other.
*/
goog.math.Long.prototype.lessThan = function(other) {
return this.compare(other) < 0;
};
/**
* @param {goog.math.Long} other Long to compare against.
* @return {boolean} Whether this Long is less than or equal to the other.
*/
goog.math.Long.prototype.lessThanOrEqual = function(other) {
return this.compare(other) <= 0;
};
/**
* @param {goog.math.Long} other Long to compare against.
* @return {boolean} Whether this Long is greater than the other.
*/
goog.math.Long.prototype.greaterThan = function(other) {
return this.compare(other) > 0;
};
/**
* @param {goog.math.Long} other Long to compare against.
* @return {boolean} Whether this Long is greater than or equal to the other.
*/
goog.math.Long.prototype.greaterThanOrEqual = function(other) {
return this.compare(other) >= 0;
};
/**
* Compares this Long with the given one.
* @param {goog.math.Long} other Long to compare against.
* @return {number} 0 if they are the same, 1 if the this is greater, and -1
* if the given one is greater.
*/
goog.math.Long.prototype.compare = function(other) {
if (this.equals(other)) {
return 0;
}
var thisNeg = this.isNegative();
var otherNeg = other.isNegative();
if (thisNeg && !otherNeg) {
return -1;
}
if (!thisNeg && otherNeg) {
return 1;
}
// at this point, the signs are the same, so subtraction will not overflow
if (this.subtract(other).isNegative()) {
return -1;
} else {
return 1;
}
};
/** @return {!goog.math.Long} The negation of this value. */
goog.math.Long.prototype.negate = function() {
if (this.equals(goog.math.Long.getMinValue())) {
return goog.math.Long.getMinValue();
} else {
return this.not().add(goog.math.Long.getOne());
}
};
/**
* Returns the sum of this and the given Long.
* @param {goog.math.Long} other Long to add to this one.
* @return {!goog.math.Long} The sum of this and the given Long.
*/
goog.math.Long.prototype.add = function(other) {
// Divide each number into 4 chunks of 16 bits, and then sum the chunks.
var a48 = this.high_ >>> 16;
var a32 = this.high_ & 0xFFFF;
var a16 = this.low_ >>> 16;
var a00 = this.low_ & 0xFFFF;
var b48 = other.high_ >>> 16;
var b32 = other.high_ & 0xFFFF;
var b16 = other.low_ >>> 16;
var b00 = other.low_ & 0xFFFF;
var c48 = 0, c32 = 0, c16 = 0, c00 = 0;
c00 += a00 + b00;
c16 += c00 >>> 16;
c00 &= 0xFFFF;
c16 += a16 + b16;
c32 += c16 >>> 16;
c16 &= 0xFFFF;
c32 += a32 + b32;
c48 += c32 >>> 16;
c32 &= 0xFFFF;
c48 += a48 + b48;
c48 &= 0xFFFF;
return goog.math.Long.fromBits((c16 << 16) | c00, (c48 << 16) | c32);
};
/**
* Returns the difference of this and the given Long.
* @param {goog.math.Long} other Long to subtract from this.
* @return {!goog.math.Long} The difference of this and the given Long.
*/
goog.math.Long.prototype.subtract = function(other) {
return this.add(other.negate());
};
/**
* Returns the product of this and the given long.
* @param {goog.math.Long} other Long to multiply with this.
* @return {!goog.math.Long} The product of this and the other.
*/
goog.math.Long.prototype.multiply = function(other) {
if (this.isZero()) {
return goog.math.Long.getZero();
} else if (other.isZero()) {
return goog.math.Long.getZero();
}
if (this.equals(goog.math.Long.getMinValue())) {
return other.isOdd() ? goog.math.Long.getMinValue() :
goog.math.Long.getZero();
} else if (other.equals(goog.math.Long.getMinValue())) {
return this.isOdd() ? goog.math.Long.getMinValue() :
goog.math.Long.getZero();
}
if (this.isNegative()) {
if (other.isNegative()) {
return this.negate().multiply(other.negate());
} else {
return this.negate().multiply(other).negate();
}
} else if (other.isNegative()) {
return this.multiply(other.negate()).negate();
}
// If both longs are small, use float multiplication
if (this.lessThan(goog.math.Long.getTwoPwr24()) &&
other.lessThan(goog.math.Long.getTwoPwr24())) {
return goog.math.Long.fromNumber(this.toNumber() * other.toNumber());
}
// Divide each long into 4 chunks of 16 bits, and then add up 4x4 products.
// We can skip products that would overflow.
var a48 = this.high_ >>> 16;
var a32 = this.high_ & 0xFFFF;
var a16 = this.low_ >>> 16;
var a00 = this.low_ & 0xFFFF;
var b48 = other.high_ >>> 16;
var b32 = other.high_ & 0xFFFF;
var b16 = other.low_ >>> 16;
var b00 = other.low_ & 0xFFFF;
var c48 = 0, c32 = 0, c16 = 0, c00 = 0;
c00 += a00 * b00;
c16 += c00 >>> 16;
c00 &= 0xFFFF;
c16 += a16 * b00;
c32 += c16 >>> 16;
c16 &= 0xFFFF;
c16 += a00 * b16;
c32 += c16 >>> 16;
c16 &= 0xFFFF;
c32 += a32 * b00;
c48 += c32 >>> 16;
c32 &= 0xFFFF;
c32 += a16 * b16;
c48 += c32 >>> 16;
c32 &= 0xFFFF;
c32 += a00 * b32;
c48 += c32 >>> 16;
c32 &= 0xFFFF;
c48 += a48 * b00 + a32 * b16 + a16 * b32 + a00 * b48;
c48 &= 0xFFFF;
return goog.math.Long.fromBits((c16 << 16) | c00, (c48 << 16) | c32);
};
/**
* Returns this Long divided by the given one.
* @param {goog.math.Long} other Long by which to divide.
* @return {!goog.math.Long} This Long divided by the given one.
*/
goog.math.Long.prototype.div = function(other) {
if (other.isZero()) {
throw Error('division by zero');
} else if (this.isZero()) {
return goog.math.Long.getZero();
}
if (this.equals(goog.math.Long.getMinValue())) {
if (other.equals(goog.math.Long.getOne()) ||
other.equals(goog.math.Long.getNegOne())) {
return goog.math.Long.getMinValue(); // recall -MIN_VALUE == MIN_VALUE
} else if (other.equals(goog.math.Long.getMinValue())) {
return goog.math.Long.getOne();
} else {
// At this point, we have |other| >= 2, so |this/other| < |MIN_VALUE|.
var halfThis = this.shiftRight(1);
var approx = halfThis.div(other).shiftLeft(1);
if (approx.equals(goog.math.Long.getZero())) {
return other.isNegative() ? goog.math.Long.getOne() :
goog.math.Long.getNegOne();
} else {
var rem = this.subtract(other.multiply(approx));
var result = approx.add(rem.div(other));
return result;
}
}
} else if (other.equals(goog.math.Long.getMinValue())) {
return goog.math.Long.getZero();
}
if (this.isNegative()) {
if (other.isNegative()) {
return this.negate().div(other.negate());
} else {
return this.negate().div(other).negate();
}
} else if (other.isNegative()) {
return this.div(other.negate()).negate();
}
// Repeat the following until the remainder is less than other: find a
// floating-point that approximates remainder / other *from below*, add this
// into the result, and subtract it from the remainder. It is critical that
// the approximate value is less than or equal to the real value so that the
// remainder never becomes negative.
var res = goog.math.Long.getZero();
var rem = this;
while (rem.greaterThanOrEqual(other)) {
// Approximate the result of division. This may be a little greater or
// smaller than the actual value.
var approx = Math.max(1, Math.floor(rem.toNumber() / other.toNumber()));
// We will tweak the approximate result by changing it in the 48-th digit or
// the smallest non-fractional digit, whichever is larger.
var log2 = Math.ceil(Math.log(approx) / Math.LN2);
var delta = (log2 <= 48) ? 1 : Math.pow(2, log2 - 48);
// Decrease the approximation until it is smaller than the remainder. Note
// that if it is too large, the product overflows and is negative.
var approxRes = goog.math.Long.fromNumber(approx);
var approxRem = approxRes.multiply(other);
while (approxRem.isNegative() || approxRem.greaterThan(rem)) {
approx -= delta;
approxRes = goog.math.Long.fromNumber(approx);
approxRem = approxRes.multiply(other);
}
// We know the answer can't be zero... and actually, zero would cause
// infinite recursion since we would make no progress.
if (approxRes.isZero()) {
approxRes = goog.math.Long.getOne();
}
res = res.add(approxRes);
rem = rem.subtract(approxRem);
}
return res;
};
/**
* Returns this Long modulo the given one.
* @param {goog.math.Long} other Long by which to mod.
* @return {!goog.math.Long} This Long modulo the given one.
*/
goog.math.Long.prototype.modulo = function(other) {
return this.subtract(this.div(other).multiply(other));
};
/** @return {!goog.math.Long} The bitwise-NOT of this value. */
goog.math.Long.prototype.not = function() {
return goog.math.Long.fromBits(~this.low_, ~this.high_);
};
/**
* Returns the bitwise-AND of this Long and the given one.
* @param {goog.math.Long} other The Long with which to AND.
* @return {!goog.math.Long} The bitwise-AND of this and the other.
*/
goog.math.Long.prototype.and = function(other) {
return goog.math.Long.fromBits(
this.low_ & other.low_, this.high_ & other.high_);
};
/**
* Returns the bitwise-OR of this Long and the given one.
* @param {goog.math.Long} other The Long with which to OR.
* @return {!goog.math.Long} The bitwise-OR of this and the other.
*/
goog.math.Long.prototype.or = function(other) {
return goog.math.Long.fromBits(
this.low_ | other.low_, this.high_ | other.high_);
};
/**
* Returns the bitwise-XOR of this Long and the given one.
* @param {goog.math.Long} other The Long with which to XOR.
* @return {!goog.math.Long} The bitwise-XOR of this and the other.
*/
goog.math.Long.prototype.xor = function(other) {
return goog.math.Long.fromBits(
this.low_ ^ other.low_, this.high_ ^ other.high_);
};
/**
* Returns this Long with bits shifted to the left by the given amount.
* @param {number} numBits The number of bits by which to shift.
* @return {!goog.math.Long} This shifted to the left by the given amount.
*/
goog.math.Long.prototype.shiftLeft = function(numBits) {
numBits &= 63;
if (numBits == 0) {
return this;
} else {
var low = this.low_;
if (numBits < 32) {
var high = this.high_;
return goog.math.Long.fromBits(
low << numBits, (high << numBits) | (low >>> (32 - numBits)));
} else {
return goog.math.Long.fromBits(0, low << (numBits - 32));
}
}
};
/**
* Returns this Long with bits shifted to the right by the given amount.
* The new leading bits match the current sign bit.
* @param {number} numBits The number of bits by which to shift.
* @return {!goog.math.Long} This shifted to the right by the given amount.
*/
goog.math.Long.prototype.shiftRight = function(numBits) {
numBits &= 63;
if (numBits == 0) {
return this;
} else {
var high = this.high_;
if (numBits < 32) {
var low = this.low_;
return goog.math.Long.fromBits(
(low >>> numBits) | (high << (32 - numBits)), high >> numBits);
} else {
return goog.math.Long.fromBits(
high >> (numBits - 32), high >= 0 ? 0 : -1);
}
}
};
/**
* Returns this Long with bits shifted to the right by the given amount, with
* zeros placed into the new leading bits.
* @param {number} numBits The number of bits by which to shift.
* @return {!goog.math.Long} This shifted to the right by the given amount, with
* zeros placed into the new leading bits.
*/
goog.math.Long.prototype.shiftRightUnsigned = function(numBits) {
numBits &= 63;
if (numBits == 0) {
return this;
} else {
var high = this.high_;
if (numBits < 32) {
var low = this.low_;
return goog.math.Long.fromBits(
(low >>> numBits) | (high << (32 - numBits)), high >>> numBits);
} else if (numBits == 32) {
return goog.math.Long.fromBits(high, 0);
} else {
return goog.math.Long.fromBits(high >>> (numBits - 32), 0);
}
}
};
/**
* @enum {number} Ids of commonly requested Long instances.
* @private
*/
goog.math.Long.ValueCacheId_ = {
MAX_VALUE: 1,
MIN_VALUE: 2,
ZERO: 3,
ONE: 4,
NEG_ONE: 5,
TWO_PWR_24: 6
};

447
viz/out/goog/math/math.js Normal file
View File

@ -0,0 +1,447 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Additional mathematical functions.
*/
goog.provide('goog.math');
goog.require('goog.array');
goog.require('goog.asserts');
/**
* Returns a random integer greater than or equal to 0 and less than {@code a}.
* @param {number} a The upper bound for the random integer (exclusive).
* @return {number} A random integer N such that 0 <= N < a.
*/
goog.math.randomInt = function(a) {
return Math.floor(Math.random() * a);
};
/**
* Returns a random number greater than or equal to {@code a} and less than
* {@code b}.
* @param {number} a The lower bound for the random number (inclusive).
* @param {number} b The upper bound for the random number (exclusive).
* @return {number} A random number N such that a <= N < b.
*/
goog.math.uniformRandom = function(a, b) {
return a + Math.random() * (b - a);
};
/**
* Takes a number and clamps it to within the provided bounds.
* @param {number} value The input number.
* @param {number} min The minimum value to return.
* @param {number} max The maximum value to return.
* @return {number} The input number if it is within bounds, or the nearest
* number within the bounds.
*/
goog.math.clamp = function(value, min, max) {
return Math.min(Math.max(value, min), max);
};
/**
* The % operator in JavaScript returns the remainder of a / b, but differs from
* some other languages in that the result will have the same sign as the
* dividend. For example, -1 % 8 == -1, whereas in some other languages
* (such as Python) the result would be 7. This function emulates the more
* correct modulo behavior, which is useful for certain applications such as
* calculating an offset index in a circular list.
*
* @param {number} a The dividend.
* @param {number} b The divisor.
* @return {number} a % b where the result is between 0 and b (either 0 <= x < b
* or b < x <= 0, depending on the sign of b).
*/
goog.math.modulo = function(a, b) {
var r = a % b;
// If r and b differ in sign, add b to wrap the result to the correct sign.
return (r * b < 0) ? r + b : r;
};
/**
* Performs linear interpolation between values a and b. Returns the value
* between a and b proportional to x (when x is between 0 and 1. When x is
* outside this range, the return value is a linear extrapolation).
* @param {number} a A number.
* @param {number} b A number.
* @param {number} x The proportion between a and b.
* @return {number} The interpolated value between a and b.
*/
goog.math.lerp = function(a, b, x) {
return a + x * (b - a);
};
/**
* Tests whether the two values are equal to each other, within a certain
* tolerance to adjust for floating point errors.
* @param {number} a A number.
* @param {number} b A number.
* @param {number=} opt_tolerance Optional tolerance range. Defaults
* to 0.000001. If specified, should be greater than 0.
* @return {boolean} Whether {@code a} and {@code b} are nearly equal.
*/
goog.math.nearlyEquals = function(a, b, opt_tolerance) {
return Math.abs(a - b) <= (opt_tolerance || 0.000001);
};
// TODO(user): Rename to normalizeAngle, retaining old name as deprecated
// alias.
/**
* Normalizes an angle to be in range [0-360). Angles outside this range will
* be normalized to be the equivalent angle with that range.
* @param {number} angle Angle in degrees.
* @return {number} Standardized angle.
*/
goog.math.standardAngle = function(angle) {
return goog.math.modulo(angle, 360);
};
/**
* Normalizes an angle to be in range [0-2*PI). Angles outside this range will
* be normalized to be the equivalent angle with that range.
* @param {number} angle Angle in radians.
* @return {number} Standardized angle.
*/
goog.math.standardAngleInRadians = function(angle) {
return goog.math.modulo(angle, 2 * Math.PI);
};
/**
* Converts degrees to radians.
* @param {number} angleDegrees Angle in degrees.
* @return {number} Angle in radians.
*/
goog.math.toRadians = function(angleDegrees) {
return angleDegrees * Math.PI / 180;
};
/**
* Converts radians to degrees.
* @param {number} angleRadians Angle in radians.
* @return {number} Angle in degrees.
*/
goog.math.toDegrees = function(angleRadians) {
return angleRadians * 180 / Math.PI;
};
/**
* For a given angle and radius, finds the X portion of the offset.
* @param {number} degrees Angle in degrees (zero points in +X direction).
* @param {number} radius Radius.
* @return {number} The x-distance for the angle and radius.
*/
goog.math.angleDx = function(degrees, radius) {
return radius * Math.cos(goog.math.toRadians(degrees));
};
/**
* For a given angle and radius, finds the Y portion of the offset.
* @param {number} degrees Angle in degrees (zero points in +X direction).
* @param {number} radius Radius.
* @return {number} The y-distance for the angle and radius.
*/
goog.math.angleDy = function(degrees, radius) {
return radius * Math.sin(goog.math.toRadians(degrees));
};
/**
* Computes the angle between two points (x1,y1) and (x2,y2).
* Angle zero points in the +X direction, 90 degrees points in the +Y
* direction (down) and from there we grow clockwise towards 360 degrees.
* @param {number} x1 x of first point.
* @param {number} y1 y of first point.
* @param {number} x2 x of second point.
* @param {number} y2 y of second point.
* @return {number} Standardized angle in degrees of the vector from
* x1,y1 to x2,y2.
*/
goog.math.angle = function(x1, y1, x2, y2) {
return goog.math.standardAngle(
goog.math.toDegrees(Math.atan2(y2 - y1, x2 - x1)));
};
/**
* Computes the difference between startAngle and endAngle (angles in degrees).
* @param {number} startAngle Start angle in degrees.
* @param {number} endAngle End angle in degrees.
* @return {number} The number of degrees that when added to
* startAngle will result in endAngle. Positive numbers mean that the
* direction is clockwise. Negative numbers indicate a counter-clockwise
* direction.
* The shortest route (clockwise vs counter-clockwise) between the angles
* is used.
* When the difference is 180 degrees, the function returns 180 (not -180)
* angleDifference(30, 40) is 10, and angleDifference(40, 30) is -10.
* angleDifference(350, 10) is 20, and angleDifference(10, 350) is -20.
*/
goog.math.angleDifference = function(startAngle, endAngle) {
var d =
goog.math.standardAngle(endAngle) - goog.math.standardAngle(startAngle);
if (d > 180) {
d = d - 360;
} else if (d <= -180) {
d = 360 + d;
}
return d;
};
/**
* Returns the sign of a number as per the "sign" or "signum" function.
* @param {number} x The number to take the sign of.
* @return {number} -1 when negative, 1 when positive, 0 when 0. Preserves
* signed zeros and NaN.
*/
goog.math.sign = Math.sign || function(x) {
if (x > 0) {
return 1;
}
if (x < 0) {
return -1;
}
return x; // Preserves signed zeros and NaN.
};
/**
* JavaScript implementation of Longest Common Subsequence problem.
* http://en.wikipedia.org/wiki/Longest_common_subsequence
*
* Returns the longest possible array that is subarray of both of given arrays.
*
* @param {IArrayLike<S>} array1 First array of objects.
* @param {IArrayLike<T>} array2 Second array of objects.
* @param {Function=} opt_compareFn Function that acts as a custom comparator
* for the array ojects. Function should return true if objects are equal,
* otherwise false.
* @param {Function=} opt_collectorFn Function used to decide what to return
* as a result subsequence. It accepts 2 arguments: index of common element
* in the first array and index in the second. The default function returns
* element from the first array.
* @return {!Array<S|T>} A list of objects that are common to both arrays
* such that there is no common subsequence with size greater than the
* length of the list.
* @template S,T
*/
goog.math.longestCommonSubsequence = function(
array1, array2, opt_compareFn, opt_collectorFn) {
var compare = opt_compareFn || function(a, b) { return a == b; };
var collect = opt_collectorFn || function(i1, i2) { return array1[i1]; };
var length1 = array1.length;
var length2 = array2.length;
var arr = [];
for (var i = 0; i < length1 + 1; i++) {
arr[i] = [];
arr[i][0] = 0;
}
for (var j = 0; j < length2 + 1; j++) {
arr[0][j] = 0;
}
for (i = 1; i <= length1; i++) {
for (j = 1; j <= length2; j++) {
if (compare(array1[i - 1], array2[j - 1])) {
arr[i][j] = arr[i - 1][j - 1] + 1;
} else {
arr[i][j] = Math.max(arr[i - 1][j], arr[i][j - 1]);
}
}
}
// Backtracking
var result = [];
var i = length1, j = length2;
while (i > 0 && j > 0) {
if (compare(array1[i - 1], array2[j - 1])) {
result.unshift(collect(i - 1, j - 1));
i--;
j--;
} else {
if (arr[i - 1][j] > arr[i][j - 1]) {
i--;
} else {
j--;
}
}
}
return result;
};
/**
* Returns the sum of the arguments.
* @param {...number} var_args Numbers to add.
* @return {number} The sum of the arguments (0 if no arguments were provided,
* {@code NaN} if any of the arguments is not a valid number).
*/
goog.math.sum = function(var_args) {
return /** @type {number} */ (
goog.array.reduce(
arguments, function(sum, value) { return sum + value; }, 0));
};
/**
* Returns the arithmetic mean of the arguments.
* @param {...number} var_args Numbers to average.
* @return {number} The average of the arguments ({@code NaN} if no arguments
* were provided or any of the arguments is not a valid number).
*/
goog.math.average = function(var_args) {
return goog.math.sum.apply(null, arguments) / arguments.length;
};
/**
* Returns the unbiased sample variance of the arguments. For a definition,
* see e.g. http://en.wikipedia.org/wiki/Variance
* @param {...number} var_args Number samples to analyze.
* @return {number} The unbiased sample variance of the arguments (0 if fewer
* than two samples were provided, or {@code NaN} if any of the samples is
* not a valid number).
*/
goog.math.sampleVariance = function(var_args) {
var sampleSize = arguments.length;
if (sampleSize < 2) {
return 0;
}
var mean = goog.math.average.apply(null, arguments);
var variance =
goog.math.sum.apply(null, goog.array.map(arguments, function(val) {
return Math.pow(val - mean, 2);
})) / (sampleSize - 1);
return variance;
};
/**
* Returns the sample standard deviation of the arguments. For a definition of
* sample standard deviation, see e.g.
* http://en.wikipedia.org/wiki/Standard_deviation
* @param {...number} var_args Number samples to analyze.
* @return {number} The sample standard deviation of the arguments (0 if fewer
* than two samples were provided, or {@code NaN} if any of the samples is
* not a valid number).
*/
goog.math.standardDeviation = function(var_args) {
return Math.sqrt(goog.math.sampleVariance.apply(null, arguments));
};
/**
* Returns whether the supplied number represents an integer, i.e. that is has
* no fractional component. No range-checking is performed on the number.
* @param {number} num The number to test.
* @return {boolean} Whether {@code num} is an integer.
*/
goog.math.isInt = function(num) {
return isFinite(num) && num % 1 == 0;
};
/**
* Returns whether the supplied number is finite and not NaN.
* @param {number} num The number to test.
* @return {boolean} Whether {@code num} is a finite number.
*/
goog.math.isFiniteNumber = function(num) {
return isFinite(num) && !isNaN(num);
};
/**
* @param {number} num The number to test.
* @return {boolean} Whether it is negative zero.
*/
goog.math.isNegativeZero = function(num) {
return num == 0 && 1 / num < 0;
};
/**
* Returns the precise value of floor(log10(num)).
* Simpler implementations didn't work because of floating point rounding
* errors. For example
* <ul>
* <li>Math.floor(Math.log(num) / Math.LN10) is off by one for num == 1e+3.
* <li>Math.floor(Math.log(num) * Math.LOG10E) is off by one for num == 1e+15.
* <li>Math.floor(Math.log10(num)) is off by one for num == 1e+15 - 1.
* </ul>
* @param {number} num A floating point number.
* @return {number} Its logarithm to base 10 rounded down to the nearest
* integer if num > 0. -Infinity if num == 0. NaN if num < 0.
*/
goog.math.log10Floor = function(num) {
if (num > 0) {
var x = Math.round(Math.log(num) * Math.LOG10E);
return x - (parseFloat('1e' + x) > num ? 1 : 0);
}
return num == 0 ? -Infinity : NaN;
};
/**
* A tweaked variant of {@code Math.floor} which tolerates if the passed number
* is infinitesimally smaller than the closest integer. It often happens with
* the results of floating point calculations because of the finite precision
* of the intermediate results. For example {@code Math.floor(Math.log(1000) /
* Math.LN10) == 2}, not 3 as one would expect.
* @param {number} num A number.
* @param {number=} opt_epsilon An infinitesimally small positive number, the
* rounding error to tolerate.
* @return {number} The largest integer less than or equal to {@code num}.
*/
goog.math.safeFloor = function(num, opt_epsilon) {
goog.asserts.assert(!goog.isDef(opt_epsilon) || opt_epsilon > 0);
return Math.floor(num + (opt_epsilon || 2e-15));
};
/**
* A tweaked variant of {@code Math.ceil}. See {@code goog.math.safeFloor} for
* details.
* @param {number} num A number.
* @param {number=} opt_epsilon An infinitesimally small positive number, the
* rounding error to tolerate.
* @return {number} The smallest integer greater than or equal to {@code num}.
*/
goog.math.safeCeil = function(num, opt_epsilon) {
goog.asserts.assert(!goog.isDef(opt_epsilon) || opt_epsilon > 0);
return Math.ceil(num - (opt_epsilon || 2e-15));
};

227
viz/out/goog/math/size.js Normal file
View File

@ -0,0 +1,227 @@
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview A utility class for representing two-dimensional sizes.
* @author brenneman@google.com (Shawn Brenneman)
*/
goog.provide('goog.math.Size');
/**
* Class for representing sizes consisting of a width and height. Undefined
* width and height support is deprecated and results in compiler warning.
* @param {number} width Width.
* @param {number} height Height.
* @struct
* @constructor
*/
goog.math.Size = function(width, height) {
/**
* Width
* @type {number}
*/
this.width = width;
/**
* Height
* @type {number}
*/
this.height = height;
};
/**
* Compares sizes for equality.
* @param {goog.math.Size} a A Size.
* @param {goog.math.Size} b A Size.
* @return {boolean} True iff the sizes have equal widths and equal
* heights, or if both are null.
*/
goog.math.Size.equals = function(a, b) {
if (a == b) {
return true;
}
if (!a || !b) {
return false;
}
return a.width == b.width && a.height == b.height;
};
/**
* @return {!goog.math.Size} A new copy of the Size.
*/
goog.math.Size.prototype.clone = function() {
return new goog.math.Size(this.width, this.height);
};
if (goog.DEBUG) {
/**
* Returns a nice string representing size.
* @return {string} In the form (50 x 73).
* @override
*/
goog.math.Size.prototype.toString = function() {
return '(' + this.width + ' x ' + this.height + ')';
};
}
/**
* @return {number} The longer of the two dimensions in the size.
*/
goog.math.Size.prototype.getLongest = function() {
return Math.max(this.width, this.height);
};
/**
* @return {number} The shorter of the two dimensions in the size.
*/
goog.math.Size.prototype.getShortest = function() {
return Math.min(this.width, this.height);
};
/**
* @return {number} The area of the size (width * height).
*/
goog.math.Size.prototype.area = function() {
return this.width * this.height;
};
/**
* @return {number} The perimeter of the size (width + height) * 2.
*/
goog.math.Size.prototype.perimeter = function() {
return (this.width + this.height) * 2;
};
/**
* @return {number} The ratio of the size's width to its height.
*/
goog.math.Size.prototype.aspectRatio = function() {
return this.width / this.height;
};
/**
* @return {boolean} True if the size has zero area, false if both dimensions
* are non-zero numbers.
*/
goog.math.Size.prototype.isEmpty = function() {
return !this.area();
};
/**
* Clamps the width and height parameters upward to integer values.
* @return {!goog.math.Size} This size with ceil'd components.
*/
goog.math.Size.prototype.ceil = function() {
this.width = Math.ceil(this.width);
this.height = Math.ceil(this.height);
return this;
};
/**
* @param {!goog.math.Size} target The target size.
* @return {boolean} True if this Size is the same size or smaller than the
* target size in both dimensions.
*/
goog.math.Size.prototype.fitsInside = function(target) {
return this.width <= target.width && this.height <= target.height;
};
/**
* Clamps the width and height parameters downward to integer values.
* @return {!goog.math.Size} This size with floored components.
*/
goog.math.Size.prototype.floor = function() {
this.width = Math.floor(this.width);
this.height = Math.floor(this.height);
return this;
};
/**
* Rounds the width and height parameters to integer values.
* @return {!goog.math.Size} This size with rounded components.
*/
goog.math.Size.prototype.round = function() {
this.width = Math.round(this.width);
this.height = Math.round(this.height);
return this;
};
/**
* Scales this size by the given scale factors. The width and height are scaled
* by {@code sx} and {@code opt_sy} respectively. If {@code opt_sy} is not
* given, then {@code sx} is used for both the width and height.
* @param {number} sx The scale factor to use for the width.
* @param {number=} opt_sy The scale factor to use for the height.
* @return {!goog.math.Size} This Size object after scaling.
*/
goog.math.Size.prototype.scale = function(sx, opt_sy) {
var sy = goog.isNumber(opt_sy) ? opt_sy : sx;
this.width *= sx;
this.height *= sy;
return this;
};
/**
* Uniformly scales the size to perfectly cover the dimensions of a given size.
* If the size is already larger than the target, it will be scaled down to the
* minimum size at which it still covers the entire target. The original aspect
* ratio will be preserved.
*
* This function assumes that both Sizes contain strictly positive dimensions.
* @param {!goog.math.Size} target The target size.
* @return {!goog.math.Size} This Size object, after optional scaling.
*/
goog.math.Size.prototype.scaleToCover = function(target) {
var s = this.aspectRatio() <= target.aspectRatio() ?
target.width / this.width :
target.height / this.height;
return this.scale(s);
};
/**
* Uniformly scales the size to fit inside the dimensions of a given size. The
* original aspect ratio will be preserved.
*
* This function assumes that both Sizes contain strictly positive dimensions.
* @param {!goog.math.Size} target The target size.
* @return {!goog.math.Size} This Size object, after optional scaling.
*/
goog.math.Size.prototype.scaleToFit = function(target) {
var s = this.aspectRatio() > target.aspectRatio() ?
target.width / this.width :
target.height / this.height;
return this.scale(s);
};

View File

@ -0,0 +1,705 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Utilities for manipulating objects/maps/hashes.
* @author arv@google.com (Erik Arvidsson)
*/
goog.provide('goog.object');
/**
* Whether two values are not observably distinguishable. This
* correctly detects that 0 is not the same as -0 and two NaNs are
* practically equivalent.
*
* The implementation is as suggested by harmony:egal proposal.
*
* @param {*} v The first value to compare.
* @param {*} v2 The second value to compare.
* @return {boolean} Whether two values are not observably distinguishable.
* @see http://wiki.ecmascript.org/doku.php?id=harmony:egal
*/
goog.object.is = function(v, v2) {
if (v === v2) {
// 0 === -0, but they are not identical.
// We need the cast because the compiler requires that v2 is a
// number (although 1/v2 works with non-number). We cast to ? to
// stop the compiler from type-checking this statement.
return v !== 0 || 1 / v === 1 / /** @type {?} */ (v2);
}
// NaN is non-reflexive: NaN !== NaN, although they are identical.
return v !== v && v2 !== v2;
};
/**
* Calls a function for each element in an object/map/hash.
*
* @param {Object<K,V>} obj The object over which to iterate.
* @param {function(this:T,V,?,Object<K,V>):?} f The function to call
* for every element. This function takes 3 arguments (the value, the
* key and the object) and the return value is ignored.
* @param {T=} opt_obj This is used as the 'this' object within f.
* @template T,K,V
*/
goog.object.forEach = function(obj, f, opt_obj) {
for (var key in obj) {
f.call(/** @type {?} */ (opt_obj), obj[key], key, obj);
}
};
/**
* Calls a function for each element in an object/map/hash. If that call returns
* true, adds the element to a new object.
*
* @param {Object<K,V>} obj The object over which to iterate.
* @param {function(this:T,V,?,Object<K,V>):boolean} f The function to call
* for every element. This
* function takes 3 arguments (the value, the key and the object)
* and should return a boolean. If the return value is true the
* element is added to the result object. If it is false the
* element is not included.
* @param {T=} opt_obj This is used as the 'this' object within f.
* @return {!Object<K,V>} a new object in which only elements that passed the
* test are present.
* @template T,K,V
*/
goog.object.filter = function(obj, f, opt_obj) {
var res = {};
for (var key in obj) {
if (f.call(/** @type {?} */ (opt_obj), obj[key], key, obj)) {
res[key] = obj[key];
}
}
return res;
};
/**
* For every element in an object/map/hash calls a function and inserts the
* result into a new object.
*
* @param {Object<K,V>} obj The object over which to iterate.
* @param {function(this:T,V,?,Object<K,V>):R} f The function to call
* for every element. This function
* takes 3 arguments (the value, the key and the object)
* and should return something. The result will be inserted
* into a new object.
* @param {T=} opt_obj This is used as the 'this' object within f.
* @return {!Object<K,R>} a new object with the results from f.
* @template T,K,V,R
*/
goog.object.map = function(obj, f, opt_obj) {
var res = {};
for (var key in obj) {
res[key] = f.call(/** @type {?} */ (opt_obj), obj[key], key, obj);
}
return res;
};
/**
* Calls a function for each element in an object/map/hash. If any
* call returns true, returns true (without checking the rest). If
* all calls return false, returns false.
*
* @param {Object<K,V>} obj The object to check.
* @param {function(this:T,V,?,Object<K,V>):boolean} f The function to
* call for every element. This function
* takes 3 arguments (the value, the key and the object) and should
* return a boolean.
* @param {T=} opt_obj This is used as the 'this' object within f.
* @return {boolean} true if any element passes the test.
* @template T,K,V
*/
goog.object.some = function(obj, f, opt_obj) {
for (var key in obj) {
if (f.call(/** @type {?} */ (opt_obj), obj[key], key, obj)) {
return true;
}
}
return false;
};
/**
* Calls a function for each element in an object/map/hash. If
* all calls return true, returns true. If any call returns false, returns
* false at this point and does not continue to check the remaining elements.
*
* @param {Object<K,V>} obj The object to check.
* @param {?function(this:T,V,?,Object<K,V>):boolean} f The function to
* call for every element. This function
* takes 3 arguments (the value, the key and the object) and should
* return a boolean.
* @param {T=} opt_obj This is used as the 'this' object within f.
* @return {boolean} false if any element fails the test.
* @template T,K,V
*/
goog.object.every = function(obj, f, opt_obj) {
for (var key in obj) {
if (!f.call(/** @type {?} */ (opt_obj), obj[key], key, obj)) {
return false;
}
}
return true;
};
/**
* Returns the number of key-value pairs in the object map.
*
* @param {Object} obj The object for which to get the number of key-value
* pairs.
* @return {number} The number of key-value pairs in the object map.
*/
goog.object.getCount = function(obj) {
var rv = 0;
for (var key in obj) {
rv++;
}
return rv;
};
/**
* Returns one key from the object map, if any exists.
* For map literals the returned key will be the first one in most of the
* browsers (a know exception is Konqueror).
*
* @param {Object} obj The object to pick a key from.
* @return {string|undefined} The key or undefined if the object is empty.
*/
goog.object.getAnyKey = function(obj) {
for (var key in obj) {
return key;
}
};
/**
* Returns one value from the object map, if any exists.
* For map literals the returned value will be the first one in most of the
* browsers (a know exception is Konqueror).
*
* @param {Object<K,V>} obj The object to pick a value from.
* @return {V|undefined} The value or undefined if the object is empty.
* @template K,V
*/
goog.object.getAnyValue = function(obj) {
for (var key in obj) {
return obj[key];
}
};
/**
* Whether the object/hash/map contains the given object as a value.
* An alias for goog.object.containsValue(obj, val).
*
* @param {Object<K,V>} obj The object in which to look for val.
* @param {V} val The object for which to check.
* @return {boolean} true if val is present.
* @template K,V
*/
goog.object.contains = function(obj, val) {
return goog.object.containsValue(obj, val);
};
/**
* Returns the values of the object/map/hash.
*
* @param {Object<K,V>} obj The object from which to get the values.
* @return {!Array<V>} The values in the object/map/hash.
* @template K,V
*/
goog.object.getValues = function(obj) {
var res = [];
var i = 0;
for (var key in obj) {
res[i++] = obj[key];
}
return res;
};
/**
* Returns the keys of the object/map/hash.
*
* @param {Object} obj The object from which to get the keys.
* @return {!Array<string>} Array of property keys.
*/
goog.object.getKeys = function(obj) {
var res = [];
var i = 0;
for (var key in obj) {
res[i++] = key;
}
return res;
};
/**
* Get a value from an object multiple levels deep. This is useful for
* pulling values from deeply nested objects, such as JSON responses.
* Example usage: getValueByKeys(jsonObj, 'foo', 'entries', 3)
*
* @param {!Object} obj An object to get the value from. Can be array-like.
* @param {...(string|number|!IArrayLike<number|string>)}
* var_args A number of keys
* (as strings, or numbers, for array-like objects). Can also be
* specified as a single array of keys.
* @return {*} The resulting value. If, at any point, the value for a key
* is undefined, returns undefined.
*/
goog.object.getValueByKeys = function(obj, var_args) {
var isArrayLike = goog.isArrayLike(var_args);
var keys = isArrayLike ? var_args : arguments;
// Start with the 2nd parameter for the variable parameters syntax.
for (var i = isArrayLike ? 0 : 1; i < keys.length; i++) {
obj = obj[keys[i]];
if (!goog.isDef(obj)) {
break;
}
}
return obj;
};
/**
* Whether the object/map/hash contains the given key.
*
* @param {Object} obj The object in which to look for key.
* @param {?} key The key for which to check.
* @return {boolean} true If the map contains the key.
*/
goog.object.containsKey = function(obj, key) {
return obj !== null && key in obj;
};
/**
* Whether the object/map/hash contains the given value. This is O(n).
*
* @param {Object<K,V>} obj The object in which to look for val.
* @param {V} val The value for which to check.
* @return {boolean} true If the map contains the value.
* @template K,V
*/
goog.object.containsValue = function(obj, val) {
for (var key in obj) {
if (obj[key] == val) {
return true;
}
}
return false;
};
/**
* Searches an object for an element that satisfies the given condition and
* returns its key.
* @param {Object<K,V>} obj The object to search in.
* @param {function(this:T,V,string,Object<K,V>):boolean} f The
* function to call for every element. Takes 3 arguments (the value,
* the key and the object) and should return a boolean.
* @param {T=} opt_this An optional "this" context for the function.
* @return {string|undefined} The key of an element for which the function
* returns true or undefined if no such element is found.
* @template T,K,V
*/
goog.object.findKey = function(obj, f, opt_this) {
for (var key in obj) {
if (f.call(/** @type {?} */ (opt_this), obj[key], key, obj)) {
return key;
}
}
return undefined;
};
/**
* Searches an object for an element that satisfies the given condition and
* returns its value.
* @param {Object<K,V>} obj The object to search in.
* @param {function(this:T,V,string,Object<K,V>):boolean} f The function
* to call for every element. Takes 3 arguments (the value, the key
* and the object) and should return a boolean.
* @param {T=} opt_this An optional "this" context for the function.
* @return {V} The value of an element for which the function returns true or
* undefined if no such element is found.
* @template T,K,V
*/
goog.object.findValue = function(obj, f, opt_this) {
var key = goog.object.findKey(obj, f, opt_this);
return key && obj[key];
};
/**
* Whether the object/map/hash is empty.
*
* @param {Object} obj The object to test.
* @return {boolean} true if obj is empty.
*/
goog.object.isEmpty = function(obj) {
for (var key in obj) {
return false;
}
return true;
};
/**
* Removes all key value pairs from the object/map/hash.
*
* @param {Object} obj The object to clear.
*/
goog.object.clear = function(obj) {
for (var i in obj) {
delete obj[i];
}
};
/**
* Removes a key-value pair based on the key.
*
* @param {Object} obj The object from which to remove the key.
* @param {?} key The key to remove.
* @return {boolean} Whether an element was removed.
*/
goog.object.remove = function(obj, key) {
var rv;
if (rv = key in /** @type {!Object} */ (obj)) {
delete obj[key];
}
return rv;
};
/**
* Adds a key-value pair to the object. Throws an exception if the key is
* already in use. Use set if you want to change an existing pair.
*
* @param {Object<K,V>} obj The object to which to add the key-value pair.
* @param {string} key The key to add.
* @param {V} val The value to add.
* @template K,V
*/
goog.object.add = function(obj, key, val) {
if (obj !== null && key in obj) {
throw Error('The object already contains the key "' + key + '"');
}
goog.object.set(obj, key, val);
};
/**
* Returns the value for the given key.
*
* @param {Object<K,V>} obj The object from which to get the value.
* @param {string} key The key for which to get the value.
* @param {R=} opt_val The value to return if no item is found for the given
* key (default is undefined).
* @return {V|R|undefined} The value for the given key.
* @template K,V,R
*/
goog.object.get = function(obj, key, opt_val) {
if (obj !== null && key in obj) {
return obj[key];
}
return opt_val;
};
/**
* Adds a key-value pair to the object/map/hash.
*
* @param {Object<K,V>} obj The object to which to add the key-value pair.
* @param {string} key The key to add.
* @param {V} value The value to add.
* @template K,V
*/
goog.object.set = function(obj, key, value) {
obj[key] = value;
};
/**
* Adds a key-value pair to the object/map/hash if it doesn't exist yet.
*
* @param {Object<K,V>} obj The object to which to add the key-value pair.
* @param {string} key The key to add.
* @param {V} value The value to add if the key wasn't present.
* @return {V} The value of the entry at the end of the function.
* @template K,V
*/
goog.object.setIfUndefined = function(obj, key, value) {
return key in /** @type {!Object} */ (obj) ? obj[key] : (obj[key] = value);
};
/**
* Sets a key and value to an object if the key is not set. The value will be
* the return value of the given function. If the key already exists, the
* object will not be changed and the function will not be called (the function
* will be lazily evaluated -- only called if necessary).
*
* This function is particularly useful for use with a map used a as a cache.
*
* @param {!Object<K,V>} obj The object to which to add the key-value pair.
* @param {string} key The key to add.
* @param {function():V} f The value to add if the key wasn't present.
* @return {V} The value of the entry at the end of the function.
* @template K,V
*/
goog.object.setWithReturnValueIfNotSet = function(obj, key, f) {
if (key in obj) {
return obj[key];
}
var val = f();
obj[key] = val;
return val;
};
/**
* Compares two objects for equality using === on the values.
*
* @param {!Object<K,V>} a
* @param {!Object<K,V>} b
* @return {boolean}
* @template K,V
*/
goog.object.equals = function(a, b) {
for (var k in a) {
if (!(k in b) || a[k] !== b[k]) {
return false;
}
}
for (var k in b) {
if (!(k in a)) {
return false;
}
}
return true;
};
/**
* Returns a shallow clone of the object.
*
* @param {Object<K,V>} obj Object to clone.
* @return {!Object<K,V>} Clone of the input object.
* @template K,V
*/
goog.object.clone = function(obj) {
// We cannot use the prototype trick because a lot of methods depend on where
// the actual key is set.
var res = {};
for (var key in obj) {
res[key] = obj[key];
}
return res;
// We could also use goog.mixin but I wanted this to be independent from that.
};
/**
* Clones a value. The input may be an Object, Array, or basic type. Objects and
* arrays will be cloned recursively.
*
* WARNINGS:
* <code>goog.object.unsafeClone</code> does not detect reference loops. Objects
* that refer to themselves will cause infinite recursion.
*
* <code>goog.object.unsafeClone</code> is unaware of unique identifiers, and
* copies UIDs created by <code>getUid</code> into cloned results.
*
* @param {*} obj The value to clone.
* @return {*} A clone of the input value.
*/
goog.object.unsafeClone = function(obj) {
var type = goog.typeOf(obj);
if (type == 'object' || type == 'array') {
if (goog.isFunction(obj.clone)) {
return obj.clone();
}
var clone = type == 'array' ? [] : {};
for (var key in obj) {
clone[key] = goog.object.unsafeClone(obj[key]);
}
return clone;
}
return obj;
};
/**
* Returns a new object in which all the keys and values are interchanged
* (keys become values and values become keys). If multiple keys map to the
* same value, the chosen transposed value is implementation-dependent.
*
* @param {Object} obj The object to transpose.
* @return {!Object} The transposed object.
*/
goog.object.transpose = function(obj) {
var transposed = {};
for (var key in obj) {
transposed[obj[key]] = key;
}
return transposed;
};
/**
* The names of the fields that are defined on Object.prototype.
* @type {Array<string>}
* @private
*/
goog.object.PROTOTYPE_FIELDS_ = [
'constructor', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable',
'toLocaleString', 'toString', 'valueOf'
];
/**
* Extends an object with another object.
* This operates 'in-place'; it does not create a new Object.
*
* Example:
* var o = {};
* goog.object.extend(o, {a: 0, b: 1});
* o; // {a: 0, b: 1}
* goog.object.extend(o, {b: 2, c: 3});
* o; // {a: 0, b: 2, c: 3}
*
* @param {Object} target The object to modify. Existing properties will be
* overwritten if they are also present in one of the objects in
* {@code var_args}.
* @param {...Object} var_args The objects from which values will be copied.
*/
goog.object.extend = function(target, var_args) {
var key, source;
for (var i = 1; i < arguments.length; i++) {
source = arguments[i];
for (key in source) {
target[key] = source[key];
}
// For IE the for-in-loop does not contain any properties that are not
// enumerable on the prototype object (for example isPrototypeOf from
// Object.prototype) and it will also not include 'replace' on objects that
// extend String and change 'replace' (not that it is common for anyone to
// extend anything except Object).
for (var j = 0; j < goog.object.PROTOTYPE_FIELDS_.length; j++) {
key = goog.object.PROTOTYPE_FIELDS_[j];
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
};
/**
* Creates a new object built from the key-value pairs provided as arguments.
* @param {...*} var_args If only one argument is provided and it is an array
* then this is used as the arguments, otherwise even arguments are used as
* the property names and odd arguments are used as the property values.
* @return {!Object} The new object.
* @throws {Error} If there are uneven number of arguments or there is only one
* non array argument.
*/
goog.object.create = function(var_args) {
var argLength = arguments.length;
if (argLength == 1 && goog.isArray(arguments[0])) {
return goog.object.create.apply(null, arguments[0]);
}
if (argLength % 2) {
throw Error('Uneven number of arguments');
}
var rv = {};
for (var i = 0; i < argLength; i += 2) {
rv[arguments[i]] = arguments[i + 1];
}
return rv;
};
/**
* Creates a new object where the property names come from the arguments but
* the value is always set to true
* @param {...*} var_args If only one argument is provided and it is an array
* then this is used as the arguments, otherwise the arguments are used
* as the property names.
* @return {!Object} The new object.
*/
goog.object.createSet = function(var_args) {
var argLength = arguments.length;
if (argLength == 1 && goog.isArray(arguments[0])) {
return goog.object.createSet.apply(null, arguments[0]);
}
var rv = {};
for (var i = 0; i < argLength; i++) {
rv[arguments[i]] = true;
}
return rv;
};
/**
* Creates an immutable view of the underlying object, if the browser
* supports immutable objects.
*
* In default mode, writes to this view will fail silently. In strict mode,
* they will throw an error.
*
* @param {!Object<K,V>} obj An object.
* @return {!Object<K,V>} An immutable view of that object, or the
* original object if this browser does not support immutables.
* @template K,V
*/
goog.object.createImmutableView = function(obj) {
var result = obj;
if (Object.isFrozen && !Object.isFrozen(obj)) {
result = Object.create(obj);
Object.freeze(result);
}
return result;
};
/**
* @param {!Object} obj An object.
* @return {boolean} Whether this is an immutable view of the object.
*/
goog.object.isImmutableView = function(obj) {
return !!Object.isFrozen && Object.isFrozen(obj);
};

View File

@ -0,0 +1,138 @@
// Copyright 2009 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Useful compiler idioms.
*
* @author johnlenz@google.com (John Lenz)
*/
goog.provide('goog.reflect');
/**
* Syntax for object literal casts.
* @see http://go/jscompiler-renaming
* @see https://goo.gl/CRs09P
*
* Use this if you have an object literal whose keys need to have the same names
* as the properties of some class even after they are renamed by the compiler.
*
* @param {!Function} type Type to cast to.
* @param {Object} object Object literal to cast.
* @return {Object} The object literal.
*/
goog.reflect.object = function(type, object) {
return object;
};
/**
* Syntax for renaming property strings.
* @see http://go/jscompiler-renaming
* @see https://goo.gl/CRs09P
*
* Use this if you have an need to access a property as a string, but want
* to also have the property renamed by the compiler. In contrast to
* goog.reflect.object, this method takes an instance of an object.
*
* Properties must be simple names (not qualified names).
*
* @param {string} prop Name of the property
* @param {!Object} object Instance of the object whose type will be used
* for renaming
* @return {string} The renamed property.
*/
goog.reflect.objectProperty = function(prop, object) {
return prop;
};
/**
* To assert to the compiler that an operation is needed when it would
* otherwise be stripped. For example:
* <code>
* // Force a layout
* goog.reflect.sinkValue(dialog.offsetHeight);
* </code>
* @param {T} x
* @return {T}
* @template T
*/
goog.reflect.sinkValue = function(x) {
goog.reflect.sinkValue[' '](x);
return x;
};
/**
* The compiler should optimize this function away iff no one ever uses
* goog.reflect.sinkValue.
*/
goog.reflect.sinkValue[' '] = goog.nullFunction;
/**
* Check if a property can be accessed without throwing an exception.
* @param {Object} obj The owner of the property.
* @param {string} prop The property name.
* @return {boolean} Whether the property is accessible. Will also return true
* if obj is null.
*/
goog.reflect.canAccessProperty = function(obj, prop) {
/** @preserveTry */
try {
goog.reflect.sinkValue(obj[prop]);
return true;
} catch (e) {
}
return false;
};
/**
* Retrieves a value from a cache given a key. The compiler provides special
* consideration for this call such that it is generally considered side-effect
* free. However, if the {@code opt_keyFn} or {@code valueFn} have side-effects
* then the entire call is considered to have side-effects.
*
* Conventionally storing the value on the cache would be considered a
* side-effect and preclude unused calls from being pruned, ie. even if
* the value was never used, it would still always be stored in the cache.
*
* Providing a side-effect free {@code valueFn} and {@code opt_keyFn}
* allows unused calls to {@code goog.cache} to be pruned.
*
* @param {!Object<K, V>} cacheObj The object that contains the cached values.
* @param {?} key The key to lookup in the cache. If it is not string or number
* then a {@code opt_keyFn} should be provided. The key is also used as the
* parameter to the {@code valueFn}.
* @param {!function(?):V} valueFn The value provider to use to calculate the
* value to store in the cache. This function should be side-effect free
* to take advantage of the optimization.
* @param {function(?):K=} opt_keyFn The key provider to determine the cache
* map key. This should be used if the given key is not a string or number.
* If not provided then the given key is used. This function should be
* side-effect free to take advantage of the optimization.
* @return {V} The cached or calculated value.
* @template K
* @template V
*/
goog.reflect.cache = function(cacheObj, key, valueFn, opt_keyFn) {
var storedKey = opt_keyFn ? opt_keyFn(key) : key;
if (Object.prototype.hasOwnProperty.call(cacheObj, storedKey)) {
return cacheObj[storedKey];
}
return (cacheObj[storedKey] = valueFn(key));
};

View File

@ -0,0 +1,182 @@
// Copyright 2013 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('goog.string.Const');
goog.require('goog.asserts');
goog.require('goog.string.TypedString');
/**
* Wrapper for compile-time-constant strings.
*
* Const is a wrapper for strings that can only be created from program
* constants (i.e., string literals). This property relies on a custom Closure
* compiler check that {@code goog.string.Const.from} is only invoked on
* compile-time-constant expressions.
*
* Const is useful in APIs whose correct and secure use requires that certain
* arguments are not attacker controlled: Compile-time constants are inherently
* under the control of the application and not under control of external
* attackers, and hence are safe to use in such contexts.
*
* Instances of this type must be created via its factory method
* {@code goog.string.Const.from} and not by invoking its constructor. The
* constructor intentionally takes no parameters and the type is immutable;
* hence only a default instance corresponding to the empty string can be
* obtained via constructor invocation.
*
* @see goog.string.Const#from
* @constructor
* @final
* @struct
* @implements {goog.string.TypedString}
*/
goog.string.Const = function() {
/**
* The wrapped value of this Const object. The field has a purposely ugly
* name to make (non-compiled) code that attempts to directly access this
* field stand out.
* @private {string}
*/
this.stringConstValueWithSecurityContract__googStringSecurityPrivate_ = '';
/**
* A type marker used to implement additional run-time type checking.
* @see goog.string.Const#unwrap
* @const
* @private
*/
this.STRING_CONST_TYPE_MARKER__GOOG_STRING_SECURITY_PRIVATE_ =
goog.string.Const.TYPE_MARKER_;
};
/**
* @override
* @const
*/
goog.string.Const.prototype.implementsGoogStringTypedString = true;
/**
* Returns this Const's value a string.
*
* IMPORTANT: In code where it is security-relevant that an object's type is
* indeed {@code goog.string.Const}, use {@code goog.string.Const.unwrap}
* instead of this method.
*
* @see goog.string.Const#unwrap
* @override
*/
goog.string.Const.prototype.getTypedStringValue = function() {
return this.stringConstValueWithSecurityContract__googStringSecurityPrivate_;
};
/**
* Returns a debug-string representation of this value.
*
* To obtain the actual string value wrapped inside an object of this type,
* use {@code goog.string.Const.unwrap}.
*
* @see goog.string.Const#unwrap
* @override
*/
goog.string.Const.prototype.toString = function() {
return 'Const{' +
this.stringConstValueWithSecurityContract__googStringSecurityPrivate_ +
'}';
};
/**
* Performs a runtime check that the provided object is indeed an instance
* of {@code goog.string.Const}, and returns its value.
* @param {!goog.string.Const} stringConst The object to extract from.
* @return {string} The Const object's contained string, unless the run-time
* type check fails. In that case, {@code unwrap} returns an innocuous
* string, or, if assertions are enabled, throws
* {@code goog.asserts.AssertionError}.
*/
goog.string.Const.unwrap = function(stringConst) {
// Perform additional run-time type-checking to ensure that stringConst is
// indeed an instance of the expected type. This provides some additional
// protection against security bugs due to application code that disables type
// checks.
if (stringConst instanceof goog.string.Const &&
stringConst.constructor === goog.string.Const &&
stringConst.STRING_CONST_TYPE_MARKER__GOOG_STRING_SECURITY_PRIVATE_ ===
goog.string.Const.TYPE_MARKER_) {
return stringConst
.stringConstValueWithSecurityContract__googStringSecurityPrivate_;
} else {
goog.asserts.fail(
'expected object of type Const, got \'' + stringConst + '\'');
return 'type_error:Const';
}
};
/**
* Creates a Const object from a compile-time constant string.
*
* It is illegal to invoke this function on an expression whose
* compile-time-contant value cannot be determined by the Closure compiler.
*
* Correct invocations include,
* <pre>
* var s = goog.string.Const.from('hello');
* var t = goog.string.Const.from('hello' + 'world');
* </pre>
*
* In contrast, the following are illegal:
* <pre>
* var s = goog.string.Const.from(getHello());
* var t = goog.string.Const.from('hello' + world);
* </pre>
*
* TODO(xtof): Compile-time checks that this function is only called
* with compile-time constant expressions.
*
* @param {string} s A constant string from which to create a Const.
* @return {!goog.string.Const} A Const object initialized to stringConst.
*/
goog.string.Const.from = function(s) {
return goog.string.Const.create__googStringSecurityPrivate_(s);
};
/**
* Type marker for the Const type, used to implement additional run-time
* type checking.
* @const {!Object}
* @private
*/
goog.string.Const.TYPE_MARKER_ = {};
/**
* Utility method to create Const instances.
* @param {string} s The string to initialize the Const object with.
* @return {!goog.string.Const} The initialized Const object.
* @private
*/
goog.string.Const.create__googStringSecurityPrivate_ = function(s) {
var stringConst = new goog.string.Const();
stringConst.stringConstValueWithSecurityContract__googStringSecurityPrivate_ =
s;
return stringConst;
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,103 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Utility for fast string concatenation.
*/
goog.provide('goog.string.StringBuffer');
/**
* Utility class to facilitate string concatenation.
*
* @param {*=} opt_a1 Optional first initial item to append.
* @param {...*} var_args Other initial items to
* append, e.g., new goog.string.StringBuffer('foo', 'bar').
* @constructor
*/
goog.string.StringBuffer = function(opt_a1, var_args) {
if (opt_a1 != null) {
this.append.apply(this, arguments);
}
};
/**
* Internal buffer for the string to be concatenated.
* @type {string}
* @private
*/
goog.string.StringBuffer.prototype.buffer_ = '';
/**
* Sets the contents of the string buffer object, replacing what's currently
* there.
*
* @param {*} s String to set.
*/
goog.string.StringBuffer.prototype.set = function(s) {
this.buffer_ = '' + s;
};
/**
* Appends one or more items to the buffer.
*
* Calling this with null, undefined, or empty arguments is an error.
*
* @param {*} a1 Required first string.
* @param {*=} opt_a2 Optional second string.
* @param {...?} var_args Other items to append,
* e.g., sb.append('foo', 'bar', 'baz').
* @return {!goog.string.StringBuffer} This same StringBuffer object.
* @suppress {duplicate}
*/
goog.string.StringBuffer.prototype.append = function(a1, opt_a2, var_args) {
// Use a1 directly to avoid arguments instantiation for single-arg case.
this.buffer_ += String(a1);
if (opt_a2 != null) { // second argument is undefined (null == undefined)
for (var i = 1; i < arguments.length; i++) {
this.buffer_ += arguments[i];
}
}
return this;
};
/**
* Clears the internal buffer.
*/
goog.string.StringBuffer.prototype.clear = function() {
this.buffer_ = '';
};
/**
* @return {number} the length of the current contents of the buffer.
*/
goog.string.StringBuffer.prototype.getLength = function() {
return this.buffer_.length;
};
/**
* @return {string} The concatenated string.
* @override
*/
goog.string.StringBuffer.prototype.toString = function() {
return this.buffer_;
};

View File

@ -0,0 +1,221 @@
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Implementation of sprintf-like, python-%-operator-like,
* .NET-String.Format-like functionality. Uses JS string's replace method to
* extract format specifiers and sends those specifiers to a handler function,
* which then, based on conversion type part of the specifier, calls the
* appropriate function to handle the specific conversion.
* For specific functionality implemented, look at formatRe below, or look
* at the tests.
*/
goog.provide('goog.string.format');
goog.require('goog.string');
/**
* Performs sprintf-like conversion, i.e. puts the values in a template.
* DO NOT use it instead of built-in conversions in simple cases such as
* 'Cost: %.2f' as it would introduce unnecessary latency opposed to
* 'Cost: ' + cost.toFixed(2).
* @param {string} formatString Template string containing % specifiers.
* @param {...string|number} var_args Values formatString is to be filled with.
* @return {string} Formatted string.
*/
goog.string.format = function(formatString, var_args) {
// Convert the arguments to an array (MDC recommended way).
var args = Array.prototype.slice.call(arguments);
// Try to get the template.
var template = args.shift();
if (typeof template == 'undefined') {
throw Error('[goog.string.format] Template required');
}
// This re is used for matching, it also defines what is supported.
var formatRe = /%([0\-\ \+]*)(\d+)?(\.(\d+))?([%sfdiu])/g;
/**
* Chooses which conversion function to call based on type conversion
* specifier.
* @param {string} match Contains the re matched string.
* @param {string} flags Formatting flags.
* @param {string} width Replacement string minimum width.
* @param {string} dotp Matched precision including a dot.
* @param {string} precision Specifies floating point precision.
* @param {string} type Type conversion specifier.
* @param {string} offset Matching location in the original string.
* @param {string} wholeString Has the actualString being searched.
* @return {string} Formatted parameter.
*/
function replacerDemuxer(
match, flags, width, dotp, precision, type, offset, wholeString) {
// The % is too simple and doesn't take an argument.
if (type == '%') {
return '%';
}
// Try to get the actual value from parent function.
var value = args.shift();
// If we didn't get any arguments, fail.
if (typeof value == 'undefined') {
throw Error('[goog.string.format] Not enough arguments');
}
// Patch the value argument to the beginning of our type specific call.
arguments[0] = value;
return goog.string.format.demuxes_[type].apply(null, arguments);
}
return template.replace(formatRe, replacerDemuxer);
};
/**
* Contains various conversion functions (to be filled in later on).
* @private {!Object}
*/
goog.string.format.demuxes_ = {};
/**
* Processes %s conversion specifier.
* @param {string} value Contains the formatRe matched string.
* @param {string} flags Formatting flags.
* @param {string} width Replacement string minimum width.
* @param {string} dotp Matched precision including a dot.
* @param {string} precision Specifies floating point precision.
* @param {string} type Type conversion specifier.
* @param {string} offset Matching location in the original string.
* @param {string} wholeString Has the actualString being searched.
* @return {string} Replacement string.
*/
goog.string.format.demuxes_['s'] = function(
value, flags, width, dotp, precision, type, offset, wholeString) {
var replacement = value;
// If no padding is necessary we're done.
// The check for '' is necessary because Firefox incorrectly provides the
// empty string instead of undefined for non-participating capture groups,
// and isNaN('') == false.
if (isNaN(width) || width == '' || replacement.length >= Number(width)) {
return replacement;
}
// Otherwise we should find out where to put spaces.
if (flags.indexOf('-', 0) > -1) {
replacement = replacement +
goog.string.repeat(' ', Number(width) - replacement.length);
} else {
replacement = goog.string.repeat(' ', Number(width) - replacement.length) +
replacement;
}
return replacement;
};
/**
* Processes %f conversion specifier.
* @param {string} value Contains the formatRe matched string.
* @param {string} flags Formatting flags.
* @param {string} width Replacement string minimum width.
* @param {string} dotp Matched precision including a dot.
* @param {string} precision Specifies floating point precision.
* @param {string} type Type conversion specifier.
* @param {string} offset Matching location in the original string.
* @param {string} wholeString Has the actualString being searched.
* @return {string} Replacement string.
*/
goog.string.format.demuxes_['f'] = function(
value, flags, width, dotp, precision, type, offset, wholeString) {
var replacement = value.toString();
// The check for '' is necessary because Firefox incorrectly provides the
// empty string instead of undefined for non-participating capture groups,
// and isNaN('') == false.
if (!(isNaN(precision) || precision == '')) {
replacement = parseFloat(value).toFixed(precision);
}
// Generates sign string that will be attached to the replacement.
var sign;
if (Number(value) < 0) {
sign = '-';
} else if (flags.indexOf('+') >= 0) {
sign = '+';
} else if (flags.indexOf(' ') >= 0) {
sign = ' ';
} else {
sign = '';
}
if (Number(value) >= 0) {
replacement = sign + replacement;
}
// If no padding is necessary we're done.
if (isNaN(width) || replacement.length >= Number(width)) {
return replacement;
}
// We need a clean signless replacement to start with
replacement = isNaN(precision) ? Math.abs(Number(value)).toString() :
Math.abs(Number(value)).toFixed(precision);
var padCount = Number(width) - replacement.length - sign.length;
// Find out which side to pad, and if it's left side, then which character to
// pad, and set the sign on the left and padding in the middle.
if (flags.indexOf('-', 0) >= 0) {
replacement = sign + replacement + goog.string.repeat(' ', padCount);
} else {
// Decides which character to pad.
var paddingChar = (flags.indexOf('0', 0) >= 0) ? '0' : ' ';
replacement =
sign + goog.string.repeat(paddingChar, padCount) + replacement;
}
return replacement;
};
/**
* Processes %d conversion specifier.
* @param {string} value Contains the formatRe matched string.
* @param {string} flags Formatting flags.
* @param {string} width Replacement string minimum width.
* @param {string} dotp Matched precision including a dot.
* @param {string} precision Specifies floating point precision.
* @param {string} type Type conversion specifier.
* @param {string} offset Matching location in the original string.
* @param {string} wholeString Has the actualString being searched.
* @return {string} Replacement string.
*/
goog.string.format.demuxes_['d'] = function(
value, flags, width, dotp, precision, type, offset, wholeString) {
return goog.string.format.demuxes_['f'](
parseInt(value, 10) /* value */, flags, width, dotp, 0 /* precision */,
type, offset, wholeString);
};
// These are additional aliases, for integer conversion.
goog.string.format.demuxes_['i'] = goog.string.format.demuxes_['d'];
goog.string.format.demuxes_['u'] = goog.string.format.demuxes_['d'];

View File

@ -0,0 +1,48 @@
// Copyright 2013 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('goog.string.TypedString');
/**
* Wrapper for strings that conform to a data type or language.
*
* Implementations of this interface are wrappers for strings, and typically
* associate a type contract with the wrapped string. Concrete implementations
* of this interface may choose to implement additional run-time type checking,
* see for example {@code goog.html.SafeHtml}. If available, client code that
* needs to ensure type membership of an object should use the type's function
* to assert type membership, such as {@code goog.html.SafeHtml.unwrap}.
* @interface
*/
goog.string.TypedString = function() {};
/**
* Interface marker of the TypedString interface.
*
* This property can be used to determine at runtime whether or not an object
* implements this interface. All implementations of this interface set this
* property to {@code true}.
* @type {boolean}
*/
goog.string.TypedString.prototype.implementsGoogStringTypedString;
/**
* Retrieves this wrapped string's value.
* @return {!string} The wrapped string's value.
*/
goog.string.TypedString.prototype.getTypedStringValue;

View File

@ -0,0 +1,568 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Rendering engine detection.
* @see <a href="http://www.useragentstring.com/">User agent strings</a>
* For information on the browser brand (such as Safari versus Chrome), see
* goog.userAgent.product.
* @author arv@google.com (Erik Arvidsson)
* @see ../demos/useragent.html
*/
goog.provide('goog.userAgent');
goog.require('goog.labs.userAgent.browser');
goog.require('goog.labs.userAgent.engine');
goog.require('goog.labs.userAgent.platform');
goog.require('goog.labs.userAgent.util');
goog.require('goog.string');
/**
* @define {boolean} Whether we know at compile-time that the browser is IE.
*/
goog.define('goog.userAgent.ASSUME_IE', false);
/**
* @define {boolean} Whether we know at compile-time that the browser is EDGE.
*/
goog.define('goog.userAgent.ASSUME_EDGE', false);
/**
* @define {boolean} Whether we know at compile-time that the browser is GECKO.
*/
goog.define('goog.userAgent.ASSUME_GECKO', false);
/**
* @define {boolean} Whether we know at compile-time that the browser is WEBKIT.
*/
goog.define('goog.userAgent.ASSUME_WEBKIT', false);
/**
* @define {boolean} Whether we know at compile-time that the browser is a
* mobile device running WebKit e.g. iPhone or Android.
*/
goog.define('goog.userAgent.ASSUME_MOBILE_WEBKIT', false);
/**
* @define {boolean} Whether we know at compile-time that the browser is OPERA.
*/
goog.define('goog.userAgent.ASSUME_OPERA', false);
/**
* @define {boolean} Whether the
* {@code goog.userAgent.isVersionOrHigher}
* function will return true for any version.
*/
goog.define('goog.userAgent.ASSUME_ANY_VERSION', false);
/**
* Whether we know the browser engine at compile-time.
* @type {boolean}
* @private
*/
goog.userAgent.BROWSER_KNOWN_ = goog.userAgent.ASSUME_IE ||
goog.userAgent.ASSUME_EDGE || goog.userAgent.ASSUME_GECKO ||
goog.userAgent.ASSUME_MOBILE_WEBKIT || goog.userAgent.ASSUME_WEBKIT ||
goog.userAgent.ASSUME_OPERA;
/**
* Returns the userAgent string for the current browser.
*
* @return {string} The userAgent string.
*/
goog.userAgent.getUserAgentString = function() {
return goog.labs.userAgent.util.getUserAgent();
};
/**
* TODO(nnaze): Change type to "Navigator" and update compilation targets.
* @return {Object} The native navigator object.
*/
goog.userAgent.getNavigator = function() {
// Need a local navigator reference instead of using the global one,
// to avoid the rare case where they reference different objects.
// (in a WorkerPool, for example).
return goog.global['navigator'] || null;
};
/**
* Whether the user agent is Opera.
* @type {boolean}
*/
goog.userAgent.OPERA = goog.userAgent.BROWSER_KNOWN_ ?
goog.userAgent.ASSUME_OPERA :
goog.labs.userAgent.browser.isOpera();
/**
* Whether the user agent is Internet Explorer.
* @type {boolean}
*/
goog.userAgent.IE = goog.userAgent.BROWSER_KNOWN_ ?
goog.userAgent.ASSUME_IE :
goog.labs.userAgent.browser.isIE();
/**
* Whether the user agent is Microsoft Edge.
* @type {boolean}
*/
goog.userAgent.EDGE = goog.userAgent.BROWSER_KNOWN_ ?
goog.userAgent.ASSUME_EDGE :
goog.labs.userAgent.engine.isEdge();
/**
* Whether the user agent is MS Internet Explorer or MS Edge.
* @type {boolean}
*/
goog.userAgent.EDGE_OR_IE = goog.userAgent.EDGE || goog.userAgent.IE;
/**
* Whether the user agent is Gecko. Gecko is the rendering engine used by
* Mozilla, Firefox, and others.
* @type {boolean}
*/
goog.userAgent.GECKO = goog.userAgent.BROWSER_KNOWN_ ?
goog.userAgent.ASSUME_GECKO :
goog.labs.userAgent.engine.isGecko();
/**
* Whether the user agent is WebKit. WebKit is the rendering engine that
* Safari, Android and others use.
* @type {boolean}
*/
goog.userAgent.WEBKIT = goog.userAgent.BROWSER_KNOWN_ ?
goog.userAgent.ASSUME_WEBKIT || goog.userAgent.ASSUME_MOBILE_WEBKIT :
goog.labs.userAgent.engine.isWebKit();
/**
* Whether the user agent is running on a mobile device.
*
* This is a separate function so that the logic can be tested.
*
* TODO(nnaze): Investigate swapping in goog.labs.userAgent.device.isMobile().
*
* @return {boolean} Whether the user agent is running on a mobile device.
* @private
*/
goog.userAgent.isMobile_ = function() {
return goog.userAgent.WEBKIT &&
goog.labs.userAgent.util.matchUserAgent('Mobile');
};
/**
* Whether the user agent is running on a mobile device.
*
* TODO(nnaze): Consider deprecating MOBILE when labs.userAgent
* is promoted as the gecko/webkit logic is likely inaccurate.
*
* @type {boolean}
*/
goog.userAgent.MOBILE =
goog.userAgent.ASSUME_MOBILE_WEBKIT || goog.userAgent.isMobile_();
/**
* Used while transitioning code to use WEBKIT instead.
* @type {boolean}
* @deprecated Use {@link goog.userAgent.product.SAFARI} instead.
* TODO(nicksantos): Delete this from goog.userAgent.
*/
goog.userAgent.SAFARI = goog.userAgent.WEBKIT;
/**
* @return {string} the platform (operating system) the user agent is running
* on. Default to empty string because navigator.platform may not be defined
* (on Rhino, for example).
* @private
*/
goog.userAgent.determinePlatform_ = function() {
var navigator = goog.userAgent.getNavigator();
return navigator && navigator.platform || '';
};
/**
* The platform (operating system) the user agent is running on. Default to
* empty string because navigator.platform may not be defined (on Rhino, for
* example).
* @type {string}
*/
goog.userAgent.PLATFORM = goog.userAgent.determinePlatform_();
/**
* @define {boolean} Whether the user agent is running on a Macintosh operating
* system.
*/
goog.define('goog.userAgent.ASSUME_MAC', false);
/**
* @define {boolean} Whether the user agent is running on a Windows operating
* system.
*/
goog.define('goog.userAgent.ASSUME_WINDOWS', false);
/**
* @define {boolean} Whether the user agent is running on a Linux operating
* system.
*/
goog.define('goog.userAgent.ASSUME_LINUX', false);
/**
* @define {boolean} Whether the user agent is running on a X11 windowing
* system.
*/
goog.define('goog.userAgent.ASSUME_X11', false);
/**
* @define {boolean} Whether the user agent is running on Android.
*/
goog.define('goog.userAgent.ASSUME_ANDROID', false);
/**
* @define {boolean} Whether the user agent is running on an iPhone.
*/
goog.define('goog.userAgent.ASSUME_IPHONE', false);
/**
* @define {boolean} Whether the user agent is running on an iPad.
*/
goog.define('goog.userAgent.ASSUME_IPAD', false);
/**
* @define {boolean} Whether the user agent is running on an iPod.
*/
goog.define('goog.userAgent.ASSUME_IPOD', false);
/**
* @type {boolean}
* @private
*/
goog.userAgent.PLATFORM_KNOWN_ = goog.userAgent.ASSUME_MAC ||
goog.userAgent.ASSUME_WINDOWS || goog.userAgent.ASSUME_LINUX ||
goog.userAgent.ASSUME_X11 || goog.userAgent.ASSUME_ANDROID ||
goog.userAgent.ASSUME_IPHONE || goog.userAgent.ASSUME_IPAD ||
goog.userAgent.ASSUME_IPOD;
/**
* Whether the user agent is running on a Macintosh operating system.
* @type {boolean}
*/
goog.userAgent.MAC = goog.userAgent.PLATFORM_KNOWN_ ?
goog.userAgent.ASSUME_MAC :
goog.labs.userAgent.platform.isMacintosh();
/**
* Whether the user agent is running on a Windows operating system.
* @type {boolean}
*/
goog.userAgent.WINDOWS = goog.userAgent.PLATFORM_KNOWN_ ?
goog.userAgent.ASSUME_WINDOWS :
goog.labs.userAgent.platform.isWindows();
/**
* Whether the user agent is Linux per the legacy behavior of
* goog.userAgent.LINUX, which considered ChromeOS to also be
* Linux.
* @return {boolean}
* @private
*/
goog.userAgent.isLegacyLinux_ = function() {
return goog.labs.userAgent.platform.isLinux() ||
goog.labs.userAgent.platform.isChromeOS();
};
/**
* Whether the user agent is running on a Linux operating system.
*
* Note that goog.userAgent.LINUX considers ChromeOS to be Linux,
* while goog.labs.userAgent.platform considers ChromeOS and
* Linux to be different OSes.
*
* @type {boolean}
*/
goog.userAgent.LINUX = goog.userAgent.PLATFORM_KNOWN_ ?
goog.userAgent.ASSUME_LINUX :
goog.userAgent.isLegacyLinux_();
/**
* @return {boolean} Whether the user agent is an X11 windowing system.
* @private
*/
goog.userAgent.isX11_ = function() {
var navigator = goog.userAgent.getNavigator();
return !!navigator &&
goog.string.contains(navigator['appVersion'] || '', 'X11');
};
/**
* Whether the user agent is running on a X11 windowing system.
* @type {boolean}
*/
goog.userAgent.X11 = goog.userAgent.PLATFORM_KNOWN_ ?
goog.userAgent.ASSUME_X11 :
goog.userAgent.isX11_();
/**
* Whether the user agent is running on Android.
* @type {boolean}
*/
goog.userAgent.ANDROID = goog.userAgent.PLATFORM_KNOWN_ ?
goog.userAgent.ASSUME_ANDROID :
goog.labs.userAgent.platform.isAndroid();
/**
* Whether the user agent is running on an iPhone.
* @type {boolean}
*/
goog.userAgent.IPHONE = goog.userAgent.PLATFORM_KNOWN_ ?
goog.userAgent.ASSUME_IPHONE :
goog.labs.userAgent.platform.isIphone();
/**
* Whether the user agent is running on an iPad.
* @type {boolean}
*/
goog.userAgent.IPAD = goog.userAgent.PLATFORM_KNOWN_ ?
goog.userAgent.ASSUME_IPAD :
goog.labs.userAgent.platform.isIpad();
/**
* Whether the user agent is running on an iPod.
* @type {boolean}
*/
goog.userAgent.IPOD = goog.userAgent.PLATFORM_KNOWN_ ?
goog.userAgent.ASSUME_IPOD :
goog.labs.userAgent.platform.isIpod();
/**
* @return {string} The string that describes the version number of the user
* agent.
* @private
*/
goog.userAgent.determineVersion_ = function() {
// All browsers have different ways to detect the version and they all have
// different naming schemes.
// version is a string rather than a number because it may contain 'b', 'a',
// and so on.
var version = '';
var arr = goog.userAgent.getVersionRegexResult_();
if (arr) {
version = arr ? arr[1] : '';
}
if (goog.userAgent.IE) {
// IE9 can be in document mode 9 but be reporting an inconsistent user agent
// version. If it is identifying as a version lower than 9 we take the
// documentMode as the version instead. IE8 has similar behavior.
// It is recommended to set the X-UA-Compatible header to ensure that IE9
// uses documentMode 9.
var docMode = goog.userAgent.getDocumentMode_();
if (docMode != null && docMode > parseFloat(version)) {
return String(docMode);
}
}
return version;
};
/**
* @return {?Array|undefined} The version regex matches from parsing the user
* agent string. These regex statements must be executed inline so they can
* be compiled out by the closure compiler with the rest of the useragent
* detection logic when ASSUME_* is specified.
* @private
*/
goog.userAgent.getVersionRegexResult_ = function() {
var userAgent = goog.userAgent.getUserAgentString();
if (goog.userAgent.GECKO) {
return /rv\:([^\);]+)(\)|;)/.exec(userAgent);
}
if (goog.userAgent.EDGE) {
return /Edge\/([\d\.]+)/.exec(userAgent);
}
if (goog.userAgent.IE) {
return /\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/.exec(userAgent);
}
if (goog.userAgent.WEBKIT) {
// WebKit/125.4
return /WebKit\/(\S+)/.exec(userAgent);
}
if (goog.userAgent.OPERA) {
// If none of the above browsers were detected but the browser is Opera, the
// only string that is of interest is 'Version/<number>'.
return /(?:Version)[ \/]?(\S+)/.exec(userAgent);
}
return undefined;
};
/**
* @return {number|undefined} Returns the document mode (for testing).
* @private
*/
goog.userAgent.getDocumentMode_ = function() {
// NOTE(user): goog.userAgent may be used in context where there is no DOM.
var doc = goog.global['document'];
return doc ? doc['documentMode'] : undefined;
};
/**
* The version of the user agent. This is a string because it might contain
* 'b' (as in beta) as well as multiple dots.
* @type {string}
*/
goog.userAgent.VERSION = goog.userAgent.determineVersion_();
/**
* Compares two version numbers.
*
* @param {string} v1 Version of first item.
* @param {string} v2 Version of second item.
*
* @return {number} 1 if first argument is higher
* 0 if arguments are equal
* -1 if second argument is higher.
* @deprecated Use goog.string.compareVersions.
*/
goog.userAgent.compare = function(v1, v2) {
return goog.string.compareVersions(v1, v2);
};
/**
* Cache for {@link goog.userAgent.isVersionOrHigher}.
* Calls to compareVersions are surprisingly expensive and, as a browser's
* version number is unlikely to change during a session, we cache the results.
* @const
* @private
*/
goog.userAgent.isVersionOrHigherCache_ = {};
/**
* Whether the user agent version is higher or the same as the given version.
* NOTE: When checking the version numbers for Firefox and Safari, be sure to
* use the engine's version, not the browser's version number. For example,
* Firefox 3.0 corresponds to Gecko 1.9 and Safari 3.0 to Webkit 522.11.
* Opera and Internet Explorer versions match the product release number.<br>
* @see <a href="http://en.wikipedia.org/wiki/Safari_version_history">
* Webkit</a>
* @see <a href="http://en.wikipedia.org/wiki/Gecko_engine">Gecko</a>
*
* @param {string|number} version The version to check.
* @return {boolean} Whether the user agent version is higher or the same as
* the given version.
*/
goog.userAgent.isVersionOrHigher = function(version) {
return goog.userAgent.ASSUME_ANY_VERSION ||
goog.userAgent.isVersionOrHigherCache_[version] ||
(goog.userAgent.isVersionOrHigherCache_[version] =
goog.string.compareVersions(goog.userAgent.VERSION, version) >= 0);
};
/**
* Deprecated alias to {@code goog.userAgent.isVersionOrHigher}.
* @param {string|number} version The version to check.
* @return {boolean} Whether the user agent version is higher or the same as
* the given version.
* @deprecated Use goog.userAgent.isVersionOrHigher().
*/
goog.userAgent.isVersion = goog.userAgent.isVersionOrHigher;
/**
* Whether the IE effective document mode is higher or the same as the given
* document mode version.
* NOTE: Only for IE, return false for another browser.
*
* @param {number} documentMode The document mode version to check.
* @return {boolean} Whether the IE effective document mode is higher or the
* same as the given version.
*/
goog.userAgent.isDocumentModeOrHigher = function(documentMode) {
return Number(goog.userAgent.DOCUMENT_MODE) >= documentMode;
};
/**
* Deprecated alias to {@code goog.userAgent.isDocumentModeOrHigher}.
* @param {number} version The version to check.
* @return {boolean} Whether the IE effective document mode is higher or the
* same as the given version.
* @deprecated Use goog.userAgent.isDocumentModeOrHigher().
*/
goog.userAgent.isDocumentMode = goog.userAgent.isDocumentModeOrHigher;
/**
* For IE version < 7, documentMode is undefined, so attempt to use the
* CSS1Compat property to see if we are in standards mode. If we are in
* standards mode, treat the browser version as the document mode. Otherwise,
* IE is emulating version 5.
* @type {number|undefined}
* @const
*/
goog.userAgent.DOCUMENT_MODE = (function() {
var doc = goog.global['document'];
var mode = goog.userAgent.getDocumentMode_();
if (!doc || !goog.userAgent.IE) {
return undefined;
}
return mode || (doc['compatMode'] == 'CSS1Compat' ?
parseInt(goog.userAgent.VERSION, 10) :
5);
})();

21796
viz/out/processing.js Normal file

File diff suppressed because it is too large Load Diff

4993
viz/out/quil/core.cljc Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

6643
viz/out/quil/core.js Normal file

File diff suppressed because it is too large Load Diff

1
viz/out/quil/core.js.map Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,144 @@
(ns ^{:doc "Quil middleware."}
quil.middleware
(:require [quil.middlewares.fun-mode :as fun-mode]
#?(:clj [quil.middlewares.pause-on-error :as pause-on-error])
[quil.middlewares.navigation-3d :as navigation-3d]
[quil.middlewares.navigation-2d :as navigation-2d]
))
(defn ^{:requires-bindings false
:category "Middleware"
:subcategory nil
:ns "quil.middleware"
:added "2.1.0"}
fun-mode
"Introduces function mode. Adds 'update' function which takes current
state and returns new state. Makes all other functions (setup, draw,
mouse-click, etc) state-aware. See wiki for more details."
[options]
(fun-mode/fun-mode options))
#?(:clj
(defn ^{:requires-bindings false
:category "Middleware"
:subcategory nil
:ns "quil.middleware"
:added "2.2.0"}
pause-on-error
"Pauses sketch if any of user-provided handlers throws error.
It allows to fix the error on the fly and continue sketch.
May be good alternative to default '500ms pause if exception'
behaviour."
[options]
(pause-on-error/pause-on-error options)))
(defn ^{:requires-bindings false
:category "Middleware"
:subcategory nil
:ns "quil.middleware"
:added "2.2.0"}
navigation-3d
"Enables navigation in 3D space. Similar to how it is done in
shooters: WASD navigation, space is go up, drag mouse to look around.
This middleware requires fun-mode.
Navigation
* Drag mouse to look around. You can change settings to bind
mouse-moved instead of mouse-dragged to look around. See
customization info below.
* Keyboard:
* w - go forward
* s - go backward
* a - strafe left
* d - strafe right
* space - go up
* z - go down, can't bind to ctrl, limitation of Processing
Customization
You can customize this middleware by providing map as
:navigation-3d option in defsketch/sketch. Map can have following
optional keys:
:position - vector of 3 numbers, initial camera position. Default
is the same as in 'camera' function.
:straight - vector of 3 numbers, direction you'll be looking at.
Default is [0 0 -1] (looking down).
:up - vector of 3 numbers, 'up' direction. Default is [0 1 0].
:pixels-in-360 - number, mouse sensitivity. Defines how many pixels
you need to move/drag you mouse to rotate 360 degrees.
The less the number the more sensitive is mouse.
Default is 1000.
:step-size - number, number of pixels you move on each key event (wasd).
Default is 20.
:rotate-on - keyword, either :mouse-dragged or :mouse-moved. Specifies
on which mouse event camera should rotate. Default is
:mouse-dragged.
Accessing position information from sketch
navigation-3d uses fun-mode under the hood so all position-related
information is stored in the state map. It means that you can access in
draw/update/any handler and modify it if you need to. Position
information is a map which is stored under :navigation-3d key in the
state map. Position consists of 3 values: :position, :straight and :up.
See \"Customization\" section above for more details.
Usage example:
(q/defsketch my-sketch
...
:middleware [m/fun-mode m/navigation-3d])
See wiki article for more(?) details:
https://github.com/quil/quil/wiki/Navigation-3D"
[options]
(navigation-3d/navigation-3d options))
(defn ^{:requires-bindings false
:category "Middleware"
:subcategory nil
:ns "quil.middleware"
:added "2.2.6"}
navigation-2d
"Enables navigation over 2D sketch. Drag mouse to change the center of the
sketch and mouse wheel controls zoom. This middleware requires fun-mode.
Customization
You can customize this middleware by providing map as
:navigation-2d option in defsketch/sketch. Map can have following
optional keys:
:position - vector of 2 numbers, x and y - center of the screen.
Default is width/2, height/2.
:zoom - number indicating current zoom level. Default is 1.
Accessing position information from sketch
navigation-2d uses fun-mode under the hood so all position-related
information is stored in the state map. It means that you can access in
draw/update/any handler and modify it if you need to. Position
information is a map which is stored under :navigation-2d key in the
state map. Position consists of 2 values: :position and :zoom.
See \"Customization\" section above for more details.
Usage example:
(q/defsketch my-sketch
...
:middleware [m/fun-mode m/navigation-2d])
"
[options]
(navigation-2d/navigation-2d options))

File diff suppressed because one or more lines are too long

118
viz/out/quil/middleware.js Normal file
View File

@ -0,0 +1,118 @@
// Compiled by ClojureScript 1.9.473 {}
goog.provide('quil.middleware');
goog.require('cljs.core');
goog.require('quil.middlewares.fun_mode');
goog.require('quil.middlewares.navigation_3d');
goog.require('quil.middlewares.navigation_2d');
/**
* Introduces function mode. Adds 'update' function which takes current
* state and returns new state. Makes all other functions (setup, draw,
* mouse-click, etc) state-aware. See wiki for more details.
*/
quil.middleware.fun_mode = (function quil$middleware$fun_mode(options){
return quil.middlewares.fun_mode.fun_mode.call(null,options);
});
/**
* Enables navigation in 3D space. Similar to how it is done in
* shooters: WASD navigation, space is go up, drag mouse to look around.
* This middleware requires fun-mode.
*
*
* Navigation
*
* * Drag mouse to look around. You can change settings to bind
* mouse-moved instead of mouse-dragged to look around. See
* customization info below.
*
* * Keyboard:
* * w - go forward
* * s - go backward
* * a - strafe left
* * d - strafe right
* * space - go up
* * z - go down, can't bind to ctrl, limitation of Processing
*
*
* Customization
*
* You can customize this middleware by providing map as
* :navigation-3d option in defsketch/sketch. Map can have following
* optional keys:
*
* :position - vector of 3 numbers, initial camera position. Default
* is the same as in 'camera' function.
*
* :straight - vector of 3 numbers, direction you'll be looking at.
* Default is [0 0 -1] (looking down).
*
* :up - vector of 3 numbers, 'up' direction. Default is [0 1 0].
*
* :pixels-in-360 - number, mouse sensitivity. Defines how many pixels
* you need to move/drag you mouse to rotate 360 degrees.
* The less the number the more sensitive is mouse.
* Default is 1000.
*
* :step-size - number, number of pixels you move on each key event (wasd).
* Default is 20.
*
* :rotate-on - keyword, either :mouse-dragged or :mouse-moved. Specifies
* on which mouse event camera should rotate. Default is
* :mouse-dragged.
*
*
* Accessing position information from sketch
*
* navigation-3d uses fun-mode under the hood so all position-related
* information is stored in the state map. It means that you can access in
* draw/update/any handler and modify it if you need to. Position
* information is a map which is stored under :navigation-3d key in the
* state map. Position consists of 3 values: :position, :straight and :up.
* See "Customization" section above for more details.
*
* Usage example:
*
* (q/defsketch my-sketch
* ...
* :middleware [m/fun-mode m/navigation-3d])
*
* See wiki article for more(?) details:
* https://github.com/quil/quil/wiki/Navigation-3D
*/
quil.middleware.navigation_3d = (function quil$middleware$navigation_3d(options){
return quil.middlewares.navigation_3d.navigation_3d.call(null,options);
});
/**
* Enables navigation over 2D sketch. Drag mouse to change the center of the
* sketch and mouse wheel controls zoom. This middleware requires fun-mode.
*
* Customization
*
* You can customize this middleware by providing map as
* :navigation-2d option in defsketch/sketch. Map can have following
* optional keys:
*
* :position - vector of 2 numbers, x and y - center of the screen.
* Default is width/2, height/2.
*
* :zoom - number indicating current zoom level. Default is 1.
*
* Accessing position information from sketch
*
* navigation-2d uses fun-mode under the hood so all position-related
* information is stored in the state map. It means that you can access in
* draw/update/any handler and modify it if you need to. Position
* information is a map which is stored under :navigation-2d key in the
* state map. Position consists of 2 values: :position and :zoom.
* See "Customization" section above for more details.
*
* Usage example:
*
* (q/defsketch my-sketch
* ...
* :middleware [m/fun-mode m/navigation-2d])
*/
quil.middleware.navigation_2d = (function quil$middleware$navigation_2d(options){
return quil.middlewares.navigation_2d.navigation_2d.call(null,options);
});
//# sourceMappingURL=middleware.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"\/home\/mediocregopher\/src\/viz\/out\/quil\/middleware.js","sources":["middleware.cljc"],"lineCount":118,"mappings":";AAAA;;;;;AAQA;;;;;2BAAA,3BAKEA,8DAICC;AATH,AAUE,OAACC,6CAAkBD;;AAgBrB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCAAA,hCAKEE,wEAiECF;AAtEH,AAuEE,OAACG,uDAA4BH;;AAE\/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCAAA,hCAKEI,wEA8BCJ;AAnCH,AAoCE,OAACK,uDAA4BL","names":["quil.middleware\/fun-mode","options","quil.middlewares.fun-mode\/fun-mode","quil.middleware\/navigation-3d","quil.middlewares.navigation-3d\/navigation-3d","quil.middleware\/navigation-2d","quil.middlewares.navigation-2d\/navigation-2d"]}

View File

@ -0,0 +1,26 @@
(ns quil.middlewares.deprecated-options)
(def ^:private deprecated
{:decor ["2.0" "Try :features [:present] for similar effect"]
:target ["2.0" "Use :features [:keep-on-top] instead."]
:safe-draw-fn ["2.0" "Use :features [:no-safe-fns] instead."]})
(defn- check-features-vector [features]
(let [features (set features)]
(when (features :no-safe-draw)
(println "Feature :no-safe-draw was renamed to :no-safe-fns in Quil 2.1."
"Use :feature [:no-safe-fns] now."))
(disj features :no-safe-draw)))
(defn deprecated-options
"Checks if options map contains deprected options and removes them.
Prints messages how to fix deprecated functions."
[options]
(let [options (update-in options [:features] check-features-vector)]
(->> (for [[name value] options]
(if-let [[version message] (deprecated name)]
(do (println name "option was removed in Quil" version "." message)
nil)
[name value]))
(remove nil?)
(into {}))))

View File

@ -0,0 +1 @@
{:rename-macros {}, :renames {}, :use-macros {}, :excludes #{}, :name quil.middlewares.deprecated-options, :imports nil, :requires nil, :uses nil, :defs {deprecated {:name quil.middlewares.deprecated-options/deprecated, :file "out/quil/middlewares/deprecated_options.cljc", :line 3, :column 1, :end-line 3, :end-column 26, :private true, :meta {:file "/home/mediocregopher/src/viz/out/quil/middlewares/deprecated_options.cljc", :line 3, :column 16, :end-line 3, :end-column 26, :private true}, :tag cljs.core/IMap}, check-features-vector {:protocol-inline nil, :meta {:file "/home/mediocregopher/src/viz/out/quil/middlewares/deprecated_options.cljc", :line 8, :column 8, :end-line 8, :end-column 29, :private true, :arglists (quote ([features]))}, :private true, :name quil.middlewares.deprecated-options/check-features-vector, :variadic false, :file "out/quil/middlewares/deprecated_options.cljc", :end-column 29, :method-params ([features]), :protocol-impl nil, :arglists-meta (nil nil), :column 1, :line 8, :end-line 8, :max-fixed-arity 1, :fn-var true, :arglists (quote ([features]))}, deprecated-options {:protocol-inline nil, :meta {:file "/home/mediocregopher/src/viz/out/quil/middlewares/deprecated_options.cljc", :line 15, :column 7, :end-line 15, :end-column 25, :arglists (quote ([options])), :doc "Checks if options map contains deprected options and removes them.\n Prints messages how to fix deprecated functions."}, :name quil.middlewares.deprecated-options/deprecated-options, :variadic false, :file "out/quil/middlewares/deprecated_options.cljc", :end-column 25, :method-params ([options]), :protocol-impl nil, :arglists-meta (nil nil), :column 1, :line 15, :end-line 15, :max-fixed-arity 1, :fn-var true, :arglists (quote ([options])), :doc "Checks if options map contains deprected options and removes them.\n Prints messages how to fix deprecated functions."}}, :require-macros nil, :cljs.analyzer/constants {:seen #{:features :decor :safe-draw-fn :no-safe-draw :target}, :order [:decor :target :safe-draw-fn :no-safe-draw :features]}, :doc nil}

View File

@ -0,0 +1,95 @@
// Compiled by ClojureScript 1.9.473 {}
goog.provide('quil.middlewares.deprecated_options');
goog.require('cljs.core');
quil.middlewares.deprecated_options.deprecated = new cljs.core.PersistentArrayMap(null, 3, [new cljs.core.Keyword(null,"decor","decor",-1730969431),new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, ["2.0","Try :features [:present] for similar effect"], null),new cljs.core.Keyword(null,"target","target",253001721),new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, ["2.0","Use :features [:keep-on-top] instead."], null),new cljs.core.Keyword(null,"safe-draw-fn","safe-draw-fn",1454900202),new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, ["2.0","Use :features [:no-safe-fns] instead."], null)], null);
quil.middlewares.deprecated_options.check_features_vector = (function quil$middlewares$deprecated_options$check_features_vector(features){
var features__$1 = cljs.core.set.call(null,features);
if(cljs.core.truth_(features__$1.call(null,new cljs.core.Keyword(null,"no-safe-draw","no-safe-draw",-1157778157)))){
cljs.core.println.call(null,"Feature :no-safe-draw was renamed to :no-safe-fns in Quil 2.1.","Use :feature [:no-safe-fns] now.");
} else {
}
return cljs.core.disj.call(null,features__$1,new cljs.core.Keyword(null,"no-safe-draw","no-safe-draw",-1157778157));
});
/**
* Checks if options map contains deprected options and removes them.
* Prints messages how to fix deprecated functions.
*/
quil.middlewares.deprecated_options.deprecated_options = (function quil$middlewares$deprecated_options$deprecated_options(options){
var options__$1 = cljs.core.update_in.call(null,options,new cljs.core.PersistentVector(null, 1, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"features","features",-1146962336)], null),quil.middlewares.deprecated_options.check_features_vector);
return cljs.core.into.call(null,cljs.core.PersistentArrayMap.EMPTY,cljs.core.remove.call(null,cljs.core.nil_QMARK_,(function (){var iter__7602__auto__ = ((function (options__$1){
return (function quil$middlewares$deprecated_options$deprecated_options_$_iter__8042(s__8043){
return (new cljs.core.LazySeq(null,((function (options__$1){
return (function (){
var s__8043__$1 = s__8043;
while(true){
var temp__4657__auto__ = cljs.core.seq.call(null,s__8043__$1);
if(temp__4657__auto__){
var s__8043__$2 = temp__4657__auto__;
if(cljs.core.chunked_seq_QMARK_.call(null,s__8043__$2)){
var c__7600__auto__ = cljs.core.chunk_first.call(null,s__8043__$2);
var size__7601__auto__ = cljs.core.count.call(null,c__7600__auto__);
var b__8045 = cljs.core.chunk_buffer.call(null,size__7601__auto__);
if((function (){var i__8044 = (0);
while(true){
if((i__8044 < size__7601__auto__)){
var vec__8058 = cljs.core._nth.call(null,c__7600__auto__,i__8044);
var name = cljs.core.nth.call(null,vec__8058,(0),null);
var value = cljs.core.nth.call(null,vec__8058,(1),null);
cljs.core.chunk_append.call(null,b__8045,(function (){var temp__4655__auto__ = quil.middlewares.deprecated_options.deprecated.call(null,name);
if(cljs.core.truth_(temp__4655__auto__)){
var vec__8061 = temp__4655__auto__;
var version = cljs.core.nth.call(null,vec__8061,(0),null);
var message = cljs.core.nth.call(null,vec__8061,(1),null);
cljs.core.println.call(null,name,"option was removed in Quil",version,".",message);
return null;
} else {
return new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, [name,value], null);
}
})());
var G__8070 = (i__8044 + (1));
i__8044 = G__8070;
continue;
} else {
return true;
}
break;
}
})()){
return cljs.core.chunk_cons.call(null,cljs.core.chunk.call(null,b__8045),quil$middlewares$deprecated_options$deprecated_options_$_iter__8042.call(null,cljs.core.chunk_rest.call(null,s__8043__$2)));
} else {
return cljs.core.chunk_cons.call(null,cljs.core.chunk.call(null,b__8045),null);
}
} else {
var vec__8064 = cljs.core.first.call(null,s__8043__$2);
var name = cljs.core.nth.call(null,vec__8064,(0),null);
var value = cljs.core.nth.call(null,vec__8064,(1),null);
return cljs.core.cons.call(null,(function (){var temp__4655__auto__ = quil.middlewares.deprecated_options.deprecated.call(null,name);
if(cljs.core.truth_(temp__4655__auto__)){
var vec__8067 = temp__4655__auto__;
var version = cljs.core.nth.call(null,vec__8067,(0),null);
var message = cljs.core.nth.call(null,vec__8067,(1),null);
cljs.core.println.call(null,name,"option was removed in Quil",version,".",message);
return null;
} else {
return new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, [name,value], null);
}
})(),quil$middlewares$deprecated_options$deprecated_options_$_iter__8042.call(null,cljs.core.rest.call(null,s__8043__$2)));
}
} else {
return null;
}
break;
}
});})(options__$1))
,null,null));
});})(options__$1))
;
return iter__7602__auto__.call(null,options__$1);
})()));
});
//# sourceMappingURL=deprecated_options.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"\/home\/mediocregopher\/src\/viz\/out\/quil\/middlewares\/deprecated_options.js","sources":["deprecated_options.cljc"],"lineCount":95,"mappings":";AAAA;;AAEA,iDAAA,2CAAA,wDAAA,mFAAA,MAAA,sDAAA,wDAAA,mFAAA,MAAA,gDAAA,qEAAA,mFAAA,MAAA,loBAAeA;AAKf,4DAAA,5DAAOC,gIAAuBC;AAA9B,AACE,IAAMA,eAAS,AAACC,wBAAID;AAApB,AACE,oBAAM,uBAAA,vBAACA;AAAP,AACE,4BAAA,iEAAA,7FAACE;;AADH;;AAGA,6CAAA,tCAACC,yBAAKH;;AAEV;;;;yDAAA,zDAAMI,0HAGHC;AAHH,AAIE,IAAMA,cAAQ,sCAAA,mFAAA,zHAACC,8BAAUD,iKAAoBN;AAA7C,mHACO,iBAAAQ,qBAAA,tFAKA,AAAC+B,2BAAOC,9FACR,gCAAA,zBAACC;qFANDhC;AAAA,AAAA,YAAAC,kBAAA,KAAA;;AAAA,AAAA,IAAAD,cAAAA;;AAAA,AAAA,IAAAE,qBAAA,AAAAC,wBAAAH;AAAA,AAAA,GAAAE;AAAA,AAAA,IAAAF,cAAAE;AAAA,AAAA,GAAA,AAAAE,uCAAAJ;AAAA,IAAAK,kBAqwE0C,AAAA4B,gCAAAjC;IArwE1CM,qBAAA,AAAAC,0BAAAF;IAAAG,UAAA,AAAAC,iCAAAH;AAAA,AAAA,GAAA,AAAA,iBAAAI,UAAA;;AAAA,AAAA,GAAA,CAAAA,UAAAJ;AAAA,IAAAK,YAAA,AAAAC,yBAAAP,gBAAAK;WAAA,AAAAG,wBAAAF,UAAA,IAAA,7CAAOY;YAAP,AAAAV,wBAAAF,UAAA,IAAA,9CAAYa;AAAZ,AAAA,AAAA,AAAAV,iCAAAN,QACE,iBAAAiB,qBAA2B,AAACnC,yDAAWiC;AAAvC,AAAA,oBAAAE;AAAA,IAAAC,YAAAD;cAAA,AAAAZ,wBAAAa,UAAA,IAAA,hDAAUE;cAAV,AAAAf,wBAAAa,UAAA,IAAA,hDAAkBG;AAAlB,AACE,AAAI,iCAAA,qCAAA,tEAACnC,4BAAQ6B,kCAAkCK,YAAYC;;AAA3D;;AADF,0FAGGN,KAAKC;;;;AAJV,cAAA,CAAAd,UAAA;;;;AAAA;;;;;AAAA,OAAAK,+BAAA,AAAAC,0BAAAR,SAAA,AAAAS,8EAAA,AAAAC,+BAAAlB;;AAAA,OAAAe,+BAAA,AAAAC,0BAAAR,SAAA;;;AAAA,IAAAW,YAAA,AAAAC,0BAAApB;WAAA,AAAAa,wBAAAM,UAAA,IAAA,7CAAOI;YAAP,AAAAV,wBAAAM,UAAA,IAAA,9CAAYK;AAAZ,AAAA,OAAAH,yBACE,iBAAAI,qBAA2B,AAACnC,yDAAWiC;AAAvC,AAAA,oBAAAE;AAAA,IAAAE,YAAAF;cAAA,AAAAZ,wBAAAc,UAAA,IAAA,hDAAUC;cAAV,AAAAf,wBAAAc,UAAA,IAAA,hDAAkBE;AAAlB,AACE,AAAI,iCAAA,qCAAA,tEAACnC,4BAAQ6B,kCAAkCK,YAAYC;;AAA3D;;AADF,0FAGGN,KAAKC;;KAJV,AAAAP,8EAAA,AAAAK,yBAAAtB;;;AAAA;;;;;CAAA,KAAA;;;AAAA,AAAA,OAAAD,6BAAmBF","names":["quil.middlewares.deprecated-options\/deprecated","quil.middlewares.deprecated-options\/check-features-vector","features","cljs.core\/set","cljs.core\/println","cljs.core\/disj","quil.middlewares.deprecated-options\/deprecated-options","options","cljs.core\/update-in","iter__7602__auto__","s__8043","cljs.core\/LazySeq","temp__4657__auto__","cljs.core\/seq","cljs.core\/chunked-seq?","c__7600__auto__","size__7601__auto__","cljs.core\/count","b__8045","cljs.core\/chunk-buffer","i__8044","vec__8058","cljs.core\/-nth","cljs.core\/nth","cljs.core\/chunk-append","cljs.core\/chunk-cons","cljs.core\/chunk","iter__8042","cljs.core\/chunk-rest","vec__8064","cljs.core\/first","cljs.core\/cons","cljs.core\/rest","name","value","temp__4655__auto__","vec__8061","vec__8067","version","message","cljs.core\/remove","cljs.core\/nil?","cljs.core\/into","cljs.core\/chunk-first"]}

View File

@ -0,0 +1,78 @@
(ns quil.middlewares.fun-mode
(:require [quil.core :as q]))
(defn- wrap-setup [options]
(let [setup (:setup options (fn [] nil))]
(assoc options
:setup #(reset! (q/state-atom) (setup)))))
(defn- wrap-draw-update [options]
(let [draw (:draw options (fn [_]))
update (:update options identity)
quil-draw #(-> (q/state-atom)
(swap! (if (= (q/frame-count) 1)
identity
update))
(draw))]
(-> options
(dissoc :update)
(assoc :draw quil-draw))))
(defn- mouse-event []
{:x (q/mouse-x)
:y (q/mouse-y)})
(defn- mouse-event-full []
{:x (q/mouse-x)
:y (q/mouse-y)
:button (q/mouse-button)})
(defn- key-event []
{:key (q/key-as-keyword)
:key-code (q/key-code)
:raw-key (q/raw-key)
#?@(:clj [:modifiers (q/key-modifiers)])})
(defn- wrap-handler
([options handler-key]
(wrap-handler options handler-key nil))
([options handler-key event-fn]
(if-let [handler (options handler-key)]
(assoc options handler-key
(if event-fn
#(swap! (q/state-atom) handler (event-fn))
#(swap! (q/state-atom) handler)))
options)))
(defn- wrap-handlers [options & handlers]
(reduce (fn [options handler]
(if (keyword? handler)
(wrap-handler options handler)
(apply wrap-handler options handler)))
options handlers))
(defn- wrap-mouse-wheel [options]
(if-let [handler (:mouse-wheel options)]
(assoc options :mouse-wheel
(fn [rotation]
(swap! (q/state-atom) handler rotation)))
options))
(defn fun-mode
"Introduces function mode making all handlers (setup, draw, mouse-click, etc)
state-aware. Adds support for 'update' function."
[options]
(-> options
wrap-setup
wrap-draw-update
(wrap-handlers :focus-gained :focus-lost [:mouse-entered mouse-event]
[:mouse-exited mouse-event] [:mouse-pressed mouse-event-full]
[:mouse-released mouse-event] [:mouse-clicked mouse-event-full]
[:mouse-moved (fn [] {:x (q/mouse-x) :y (q/mouse-y)
:p-x (q/pmouse-x) :p-y (q/pmouse-y)})]
[:mouse-dragged (fn [] {:x (q/mouse-x) :y (q/mouse-y)
:p-x (q/pmouse-x) :p-y (q/pmouse-y)
:button (q/mouse-button)})]
[:key-pressed key-event] :key-released [:key-typed key-event]
:on-close)
wrap-mouse-wheel))

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,155 @@
// Compiled by ClojureScript 1.9.473 {}
goog.provide('quil.middlewares.fun_mode');
goog.require('cljs.core');
goog.require('quil.core');
quil.middlewares.fun_mode.wrap_setup = (function quil$middlewares$fun_mode$wrap_setup(options){
var setup = new cljs.core.Keyword(null,"setup","setup",1987730512).cljs$core$IFn$_invoke$arity$2(options,(function (){
return null;
}));
return cljs.core.assoc.call(null,options,new cljs.core.Keyword(null,"setup","setup",1987730512),((function (setup){
return (function (){
return cljs.core.reset_BANG_.call(null,quil.core.state_atom.call(null),setup.call(null));
});})(setup))
);
});
quil.middlewares.fun_mode.wrap_draw_update = (function quil$middlewares$fun_mode$wrap_draw_update(options){
var draw = new cljs.core.Keyword(null,"draw","draw",1358331674).cljs$core$IFn$_invoke$arity$2(options,(function (_){
return null;
}));
var update = new cljs.core.Keyword(null,"update","update",1045576396).cljs$core$IFn$_invoke$arity$2(options,cljs.core.identity);
var quil_draw = ((function (draw,update){
return (function (){
return draw.call(null,cljs.core.swap_BANG_.call(null,quil.core.state_atom.call(null),((cljs.core._EQ_.call(null,quil.core.frame_count.call(null),(1)))?cljs.core.identity:update)));
});})(draw,update))
;
return cljs.core.assoc.call(null,cljs.core.dissoc.call(null,options,new cljs.core.Keyword(null,"update","update",1045576396)),new cljs.core.Keyword(null,"draw","draw",1358331674),quil_draw);
});
quil.middlewares.fun_mode.mouse_event = (function quil$middlewares$fun_mode$mouse_event(){
return new cljs.core.PersistentArrayMap(null, 2, [new cljs.core.Keyword(null,"x","x",2099068185),quil.core.mouse_x.call(null),new cljs.core.Keyword(null,"y","y",-1757859776),quil.core.mouse_y.call(null)], null);
});
quil.middlewares.fun_mode.mouse_event_full = (function quil$middlewares$fun_mode$mouse_event_full(){
return new cljs.core.PersistentArrayMap(null, 3, [new cljs.core.Keyword(null,"x","x",2099068185),quil.core.mouse_x.call(null),new cljs.core.Keyword(null,"y","y",-1757859776),quil.core.mouse_y.call(null),new cljs.core.Keyword(null,"button","button",1456579943),quil.core.mouse_button.call(null)], null);
});
quil.middlewares.fun_mode.key_event = (function quil$middlewares$fun_mode$key_event(){
return new cljs.core.PersistentArrayMap(null, 3, [new cljs.core.Keyword(null,"key","key",-1516042587),quil.core.key_as_keyword.call(null),new cljs.core.Keyword(null,"key-code","key-code",-1732114304),quil.core.key_code.call(null),new cljs.core.Keyword(null,"raw-key","raw-key",-162482279),quil.core.raw_key.call(null)], null);
});
quil.middlewares.fun_mode.wrap_handler = (function quil$middlewares$fun_mode$wrap_handler(var_args){
var args9435 = [];
var len__7927__auto___9438 = arguments.length;
var i__7928__auto___9439 = (0);
while(true){
if((i__7928__auto___9439 < len__7927__auto___9438)){
args9435.push((arguments[i__7928__auto___9439]));
var G__9440 = (i__7928__auto___9439 + (1));
i__7928__auto___9439 = G__9440;
continue;
} else {
}
break;
}
var G__9437 = args9435.length;
switch (G__9437) {
case 2:
return quil.middlewares.fun_mode.wrap_handler.cljs$core$IFn$_invoke$arity$2((arguments[(0)]),(arguments[(1)]));
break;
case 3:
return quil.middlewares.fun_mode.wrap_handler.cljs$core$IFn$_invoke$arity$3((arguments[(0)]),(arguments[(1)]),(arguments[(2)]));
break;
default:
throw (new Error([cljs.core.str.cljs$core$IFn$_invoke$arity$1("Invalid arity: "),cljs.core.str.cljs$core$IFn$_invoke$arity$1(args9435.length)].join('')));
}
});
quil.middlewares.fun_mode.wrap_handler.cljs$core$IFn$_invoke$arity$2 = (function (options,handler_key){
return quil.middlewares.fun_mode.wrap_handler.call(null,options,handler_key,null);
});
quil.middlewares.fun_mode.wrap_handler.cljs$core$IFn$_invoke$arity$3 = (function (options,handler_key,event_fn){
var temp__4655__auto__ = options.call(null,handler_key);
if(cljs.core.truth_(temp__4655__auto__)){
var handler = temp__4655__auto__;
return cljs.core.assoc.call(null,options,handler_key,(cljs.core.truth_(event_fn)?((function (handler,temp__4655__auto__){
return (function (){
return cljs.core.swap_BANG_.call(null,quil.core.state_atom.call(null),handler,event_fn.call(null));
});})(handler,temp__4655__auto__))
:((function (handler,temp__4655__auto__){
return (function (){
return cljs.core.swap_BANG_.call(null,quil.core.state_atom.call(null),handler);
});})(handler,temp__4655__auto__))
));
} else {
return options;
}
});
quil.middlewares.fun_mode.wrap_handler.cljs$lang$maxFixedArity = 3;
quil.middlewares.fun_mode.wrap_handlers = (function quil$middlewares$fun_mode$wrap_handlers(var_args){
var args__7934__auto__ = [];
var len__7927__auto___9444 = arguments.length;
var i__7928__auto___9445 = (0);
while(true){
if((i__7928__auto___9445 < len__7927__auto___9444)){
args__7934__auto__.push((arguments[i__7928__auto___9445]));
var G__9446 = (i__7928__auto___9445 + (1));
i__7928__auto___9445 = G__9446;
continue;
} else {
}
break;
}
var argseq__7935__auto__ = ((((1) < args__7934__auto__.length))?(new cljs.core.IndexedSeq(args__7934__auto__.slice((1)),(0),null)):null);
return quil.middlewares.fun_mode.wrap_handlers.cljs$core$IFn$_invoke$arity$variadic((arguments[(0)]),argseq__7935__auto__);
});
quil.middlewares.fun_mode.wrap_handlers.cljs$core$IFn$_invoke$arity$variadic = (function (options,handlers){
return cljs.core.reduce.call(null,(function (options__$1,handler){
if((handler instanceof cljs.core.Keyword)){
return quil.middlewares.fun_mode.wrap_handler.call(null,options__$1,handler);
} else {
return cljs.core.apply.call(null,quil.middlewares.fun_mode.wrap_handler,options__$1,handler);
}
}),options,handlers);
});
quil.middlewares.fun_mode.wrap_handlers.cljs$lang$maxFixedArity = (1);
quil.middlewares.fun_mode.wrap_handlers.cljs$lang$applyTo = (function (seq9442){
var G__9443 = cljs.core.first.call(null,seq9442);
var seq9442__$1 = cljs.core.next.call(null,seq9442);
return quil.middlewares.fun_mode.wrap_handlers.cljs$core$IFn$_invoke$arity$variadic(G__9443,seq9442__$1);
});
quil.middlewares.fun_mode.wrap_mouse_wheel = (function quil$middlewares$fun_mode$wrap_mouse_wheel(options){
var temp__4655__auto__ = new cljs.core.Keyword(null,"mouse-wheel","mouse-wheel",1811662439).cljs$core$IFn$_invoke$arity$1(options);
if(cljs.core.truth_(temp__4655__auto__)){
var handler = temp__4655__auto__;
return cljs.core.assoc.call(null,options,new cljs.core.Keyword(null,"mouse-wheel","mouse-wheel",1811662439),((function (handler,temp__4655__auto__){
return (function (rotation){
return cljs.core.swap_BANG_.call(null,quil.core.state_atom.call(null),handler,rotation);
});})(handler,temp__4655__auto__))
);
} else {
return options;
}
});
/**
* Introduces function mode making all handlers (setup, draw, mouse-click, etc)
* state-aware. Adds support for 'update' function.
*/
quil.middlewares.fun_mode.fun_mode = (function quil$middlewares$fun_mode$fun_mode(options){
return quil.middlewares.fun_mode.wrap_mouse_wheel.call(null,quil.middlewares.fun_mode.wrap_handlers.call(null,quil.middlewares.fun_mode.wrap_draw_update.call(null,quil.middlewares.fun_mode.wrap_setup.call(null,options)),new cljs.core.Keyword(null,"focus-gained","focus-gained",-857086384),new cljs.core.Keyword(null,"focus-lost","focus-lost",-554849613),new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"mouse-entered","mouse-entered",811350322),quil.middlewares.fun_mode.mouse_event], null),new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"mouse-exited","mouse-exited",-483205244),quil.middlewares.fun_mode.mouse_event], null),new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"mouse-pressed","mouse-pressed",736955536),quil.middlewares.fun_mode.mouse_event_full], null),new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"mouse-released","mouse-released",-664480061),quil.middlewares.fun_mode.mouse_event], null),new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"mouse-clicked","mouse-clicked",-199339421),quil.middlewares.fun_mode.mouse_event_full], null),new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"mouse-moved","mouse-moved",-1918152310),(function (){
return new cljs.core.PersistentArrayMap(null, 4, [new cljs.core.Keyword(null,"x","x",2099068185),quil.core.mouse_x.call(null),new cljs.core.Keyword(null,"y","y",-1757859776),quil.core.mouse_y.call(null),new cljs.core.Keyword(null,"p-x","p-x",-1721211211),quil.core.pmouse_x.call(null),new cljs.core.Keyword(null,"p-y","p-y",-530704830),quil.core.pmouse_y.call(null)], null);
})], null),new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"mouse-dragged","mouse-dragged",-1220073441),(function (){
return new cljs.core.PersistentArrayMap(null, 5, [new cljs.core.Keyword(null,"x","x",2099068185),quil.core.mouse_x.call(null),new cljs.core.Keyword(null,"y","y",-1757859776),quil.core.mouse_y.call(null),new cljs.core.Keyword(null,"p-x","p-x",-1721211211),quil.core.pmouse_x.call(null),new cljs.core.Keyword(null,"p-y","p-y",-530704830),quil.core.pmouse_y.call(null),new cljs.core.Keyword(null,"button","button",1456579943),quil.core.mouse_button.call(null)], null);
})], null),new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"key-pressed","key-pressed",-757100364),quil.middlewares.fun_mode.key_event], null),new cljs.core.Keyword(null,"key-released","key-released",215919828),new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"key-typed","key-typed",-876037597),quil.middlewares.fun_mode.key_event], null),new cljs.core.Keyword(null,"on-close","on-close",-761178394)));
});
//# sourceMappingURL=fun_mode.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"\/home\/mediocregopher\/src\/viz\/out\/quil\/middlewares\/fun_mode.js","sources":["fun_mode.cljc"],"lineCount":155,"mappings":";AAAA;;;AAGA,uCAAA,vCAAOA,sFAAYC;AAAnB,AACE,IAAMC,QAAM,AAAA,qFAAQD,QAAQ;AAAA,AAAA;;AAA5B,AACE,yCAAA,uDAAA,zFAACE,0BAAMF;;AAAP,AACU,OAACG,gCAAO,AAACC,gCAAc,AAACH;;;;AAEtC,6CAAA,7CAAOI,kGAAkBL;AAAzB,AACE,IAAMM,OAAK,AAAA,mFAAON,QAAQ,WAAKO;AAAL,AAAA;;IACpBC,SAAO,AAAA,uFAASR,QAAQS;gBAD9B,ZAEMC;;AAFN,qDAEqB,AAACN,\/BACD,AAACO,+DAAM,EAAI,0DAAA,1DAACC,yBAAE,AAACC,uCACNJ,mBACAD,1KACT,OAACF;;;AANtB,4DAOMN,3BACA,mCAAA,nCAACc,jCACD,8HAAA,vHAACZ,4KAAYQ;;AAErB,wCAAA,xCAAOK;AAAP,AAAA,kDAAA,4EAAA,7BACM,AAACC,6EACD,AAACC;;AAEP,6CAAA,7CAAOC;AAAP,AAAA,kDAAA,4EAAA,6EAAA,1GACM,AAACF,6EACD,AAACC,sFACI,AAACE;;AAEZ,sCAAA,tCAAOC;AAAP,AAAA,kDAAA,wFAAA,4FAAA,hIACQ,AAACC,kGACI,AAACC,yFACF,AAACC;;AAGb,AAAA,yCAAA,iDAAAC,1FAAOK;AAAP,AAAA,IAAAJ,WAAA;AAAA,AAAA,IAAAC,yBAAA,AAAA;AAAA,AAAA,IAAAC,uBAAA;;AAAA,AAAA,GAAA,CAAAA,uBAAAD;AAAA,AAAA,AAAAD,cAAA,CAAA,UAAAE;;AAAA,cAAA,CAAAA,uBAAA;;;;AAAA;;;;AAAA,IAAAC,UAAA,AAAAH;AAAA,AAAA,QAAAG;KAAA;AAAA,OAAAC,qEAAA,CAAA,UAAA,MAAA,CAAA,UAAA;;;KAAA;AAAA,OAAAA,qEAAA,CAAA,UAAA,MAAA,CAAA,UAAA,MAAA,CAAA,UAAA;;;;AAAA,MAAA,KAAAC,MAAA,6CAAA,+DAAA,AAAAL;;;;;AAAA,AAAA,uEAAA,vEAAOI,kFACH7B,QAAQ+B;AADZ,AAEK,4EAAA,rEAACF,iDAAa7B,QAAQ+B;;;AAF3B,AAAA,uEAAA,vEAAOF,kFAGH7B,QAAQ+B,YAAYC;AAHxB,AAIK,IAAAC,qBAAiB,AAACjC,kBAAQ+B;AAA1B,AAAA,oBAAAE;AAAA,cAAAA,VAASC;AAAT,AACE,OAAChC,0BAAMF,QAAQ+B,YACR,4BAAA,VAAIC;;AAAJ,AACG,OAACrB,+BAAM,AAACP,gCAAc8B,QAAQ,AAACF;;CADlC;;AAAA,AAEG,OAACrB,+BAAM,AAACP,gCAAc8B;;;;AAChClC;;;;AATP,AAAA,iEAAA,jEAAO6B;;AAAP,AAWA,AAAA,0CAAA,kDAAAL,5FAAOc;AAAP,AAAA,IAAAH,qBAAA;AAAA,AAAA,IAAAT,yBAAA,AAAA;AAAA,AAAA,IAAAC,uBAAA;;AAAA,AAAA,GAAA,CAAAA,uBAAAD;AAAA,AAAA,AAAAS,wBAAA,CAAA,UAAAR;;AAAA,cAAA,CAAAA,uBAAA;;;;AAAA;;;;AAAA,IAAAS,uBAAA,EAAA,CAAA,MAAA,AAAAD,4BAAA,AAAA,KAAAE,qBAAA,AAAAF,yBAAA,KAAA,IAAA,OAAA;AAAA,AAAA,OAAAG,6EAAA,CAAA,UAAA,MAAAF;;;AAAA,AAAA,AAAA,AAAAE,+EAAA,WAAsBtC,QAAU2C;AAAhC,AACE,OAACC,2BAAO,WAAK5C,YAAQkC;AAAb,AACE,GAAI,oBAAAW,nBAAUX;AACZ,OAACL,iDAAa7B,YAAQkC;;AACtB,OAACY,0BAAMjB,uCAAa7B,YAAQkC;;GAChClC,QAAQ2C;;;AALlB,AAAA,AAAAL,kEAAA;;AAAA,AAAA,AAAAA,4DAAA,WAAAC;AAAA,AAAA,IAAAC,UAAA,AAAAC,0BAAAF;IAAAA,cAAA,AAAAG,yBAAAH;AAAA,AAAA,OAAAD,6EAAAE,QAAAD;;;AAAA,AAOA,6CAAA,7CAAOQ,kGAAkB\/C;AAAzB,AACE,IAAAiC,qBAAiB,AAAA,iGAAcjC;AAA\/B,AAAA,oBAAAiC;AAAA,cAAAA,VAASC;AAAT,AACE,yCAAA,lCAAChC,0BAAMF,2EACA;kBAAKgD;AAAL,AACE,OAACrC,+BAAM,AAACP,gCAAc8B,QAAQc;;;;AACvChD;;;AAEJ;;;;qCAAA,rCAAMiD,kFAGHjD;AAHH,AAIE,kNAAIA,\/CACAD,rDACAM,lDACA,kDAAA,qDAAA,yDAAA,qEAAA,iEAAA,mFAAA,oHAAA,mFAAA,mHAAA,mFAAA,yHAAA,mFAAA,uHAAA,mFAAA,0HAAA,mFAAA,z2CAACiC,+bAAwDvB,sMAC3BA,uMAA6BG,+MAC3BH,wMAA6BG,0MAChC,l+CAO7B6B;AAP6B,AAAA,kDAAA,4EAAA,6EAAA,kFAAA,5LAAW,AAAC\/B,6EAAc,AAACC,iFACd,AAACiC,iFAAiB,AAACC;WAJ7D,mFAAA,wEAK+B;AAAA,AAAA,kDAAA,4EAAA,6EAAA,kFAAA,iFAAA,7QAAW,AAACnC,6EAAc,AAACC,iFACd,AAACiC,iFAAiB,AAACC,uFAChB,AAAChC;WAPhD,mFAAA,+GAAA,oEAAA,mFAAA,2GAAA,9SAQ6BC,kQAAqCA","names":["quil.middlewares.fun-mode\/wrap-setup","options","setup","cljs.core\/assoc","cljs.core\/reset!","quil.core\/state-atom","quil.middlewares.fun-mode\/wrap-draw-update","draw","_","update","cljs.core\/identity","quil-draw","cljs.core\/swap!","cljs.core\/=","quil.core\/frame-count","cljs.core\/dissoc","quil.middlewares.fun-mode\/mouse-event","quil.core\/mouse-x","quil.core\/mouse-y","quil.middlewares.fun-mode\/mouse-event-full","quil.core\/mouse-button","quil.middlewares.fun-mode\/key-event","quil.core\/key-as-keyword","quil.core\/key-code","quil.core\/raw-key","var_args","args9435","len__7927__auto__","i__7928__auto__","G__9437","quil.middlewares.fun-mode\/wrap-handler","js\/Error","handler-key","event-fn","temp__4655__auto__","handler","args__7934__auto__","argseq__7935__auto__","cljs.core\/IndexedSeq","quil.middlewares.fun-mode\/wrap-handlers","seq9442","G__9443","cljs.core\/first","cljs.core\/next","handlers","cljs.core\/reduce","cljs.core\/Keyword","cljs.core\/apply","quil.middlewares.fun-mode\/wrap-mouse-wheel","rotation","quil.middlewares.fun-mode\/fun-mode","quil.core\/pmouse-x","quil.core\/pmouse-y"]}

View File

@ -0,0 +1,89 @@
(ns quil.middlewares.navigation-2d
(:require [quil.core :as q :include-macros true]))
(def ^:private missing-navigation-key-error
(str "state map is missing :navigation-2d key. "
"Did you accidentally removed it from the state in "
":update or any other handler?"))
(defn- assert-state-has-navigation
"Asserts that state map contains :navigation-2d object."
[state]
(when-not (:navigation-2d state)
(throw #?(:clj (RuntimeException. missing-navigation-key-error)
:cljs (js/Error. missing-navigation-key-error)))))
(defn- default-position
"Default position configuration: zoom is neutral and central point is
width/2, height/2."
[]
{:position [(/ (q/width) 2.0)
(/ (q/height) 2.0)]
:zoom 1})
(defn- setup-2d-nav
"Custom 'setup' function which creates initial position
configuration and puts it to the state map."
[user-setup user-settings]
(let [initial-state (-> user-settings
(select-keys [:position :zoom])
(->> (merge (default-position))))]
(update-in (user-setup) [:navigation-2d]
#(merge initial-state %))))
(defn- mouse-dragged
"Changes center of the sketch depending on the last mouse move. Takes
zoom into account as well."
[state event]
(assert-state-has-navigation state)
(let [dx (- (:p-x event) (:x event))
dy (- (:p-y event) (:y event))
zoom (-> state :navigation-2d :zoom)]
(-> state
(update-in [:navigation-2d :position 0] + (/ dx zoom))
(update-in [:navigation-2d :position 1] + (/ dy zoom)))))
(defn- mouse-wheel
"Changes zoom settings based on scroll."
[state event]
(assert-state-has-navigation state)
(update-in state [:navigation-2d :zoom] * (+ 1 (* -0.1 event))))
(defn- draw
"Calls user draw function with necessary all transformations (position
and zoom) applied."
[user-draw state]
(assert-state-has-navigation state)
(q/push-matrix)
(let [nav-2d (:navigation-2d state)
zoom (:zoom nav-2d)
pos (:position nav-2d)]
(q/scale zoom)
(q/with-translation [(- (/ (q/width) 2 zoom) (first pos))
(- (/ (q/height) 2 zoom) (second pos))]
(user-draw state)))
(q/pop-matrix))
(defn navigation-2d
"Enables navigation over 2D sketch. Dragging mouse will move center of the
skecth and mouse wheel controls zoom."
[options]
(let [; 2d-navigation related user settings
user-settings (:navigation-2d options)
; user-provided handlers which will be overridden
; by 3d-navigation
user-draw (:draw options (fn [state]))
user-mouse-dragged (:mouse-dragged options (fn [state _] state))
user-mouse-wheel (:mouse-wheel options (fn [state _] state))
setup (:setup options (fn [] {}))]
(assoc options
:setup (partial setup-2d-nav setup user-settings)
:draw (partial draw user-draw)
:mouse-dragged (fn [state event]
(user-mouse-dragged (mouse-dragged state event) event))
:mouse-wheel (fn [state event]
(user-mouse-wheel (mouse-wheel state event) event)))))

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,116 @@
// Compiled by ClojureScript 1.9.473 {}
goog.provide('quil.middlewares.navigation_2d');
goog.require('cljs.core');
goog.require('quil.core');
quil.middlewares.navigation_2d.missing_navigation_key_error = [cljs.core.str.cljs$core$IFn$_invoke$arity$1("state map is missing :navigation-2d key. "),cljs.core.str.cljs$core$IFn$_invoke$arity$1("Did you accidentally removed it from the state in "),cljs.core.str.cljs$core$IFn$_invoke$arity$1(":update or any other handler?")].join('');
/**
* Asserts that state map contains :navigation-2d object.
*/
quil.middlewares.navigation_2d.assert_state_has_navigation = (function quil$middlewares$navigation_2d$assert_state_has_navigation(state){
if(cljs.core.truth_(new cljs.core.Keyword(null,"navigation-2d","navigation-2d",-1924168611).cljs$core$IFn$_invoke$arity$1(state))){
return null;
} else {
throw (new Error(quil.middlewares.navigation_2d.missing_navigation_key_error));
}
});
/**
* Default position configuration: zoom is neutral and central point is
* width/2, height/2.
*/
quil.middlewares.navigation_2d.default_position = (function quil$middlewares$navigation_2d$default_position(){
return new cljs.core.PersistentArrayMap(null, 2, [new cljs.core.Keyword(null,"position","position",-2011731912),new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, [(quil.core.width.call(null) / 2.0),(quil.core.height.call(null) / 2.0)], null),new cljs.core.Keyword(null,"zoom","zoom",-1827487038),(1)], null);
});
/**
* Custom 'setup' function which creates initial position
* configuration and puts it to the state map.
*/
quil.middlewares.navigation_2d.setup_2d_nav = (function quil$middlewares$navigation_2d$setup_2d_nav(user_setup,user_settings){
var initial_state = cljs.core.merge.call(null,quil.middlewares.navigation_2d.default_position.call(null),cljs.core.select_keys.call(null,user_settings,new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"position","position",-2011731912),new cljs.core.Keyword(null,"zoom","zoom",-1827487038)], null)));
return cljs.core.update_in.call(null,user_setup.call(null),new cljs.core.PersistentVector(null, 1, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"navigation-2d","navigation-2d",-1924168611)], null),((function (initial_state){
return (function (p1__9428_SHARP_){
return cljs.core.merge.call(null,initial_state,p1__9428_SHARP_);
});})(initial_state))
);
});
/**
* Changes center of the sketch depending on the last mouse move. Takes
* zoom into account as well.
*/
quil.middlewares.navigation_2d.mouse_dragged = (function quil$middlewares$navigation_2d$mouse_dragged(state,event){
quil.middlewares.navigation_2d.assert_state_has_navigation.call(null,state);
var dx = (new cljs.core.Keyword(null,"p-x","p-x",-1721211211).cljs$core$IFn$_invoke$arity$1(event) - new cljs.core.Keyword(null,"x","x",2099068185).cljs$core$IFn$_invoke$arity$1(event));
var dy = (new cljs.core.Keyword(null,"p-y","p-y",-530704830).cljs$core$IFn$_invoke$arity$1(event) - new cljs.core.Keyword(null,"y","y",-1757859776).cljs$core$IFn$_invoke$arity$1(event));
var zoom = new cljs.core.Keyword(null,"zoom","zoom",-1827487038).cljs$core$IFn$_invoke$arity$1(new cljs.core.Keyword(null,"navigation-2d","navigation-2d",-1924168611).cljs$core$IFn$_invoke$arity$1(state));
return cljs.core.update_in.call(null,cljs.core.update_in.call(null,state,new cljs.core.PersistentVector(null, 3, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"navigation-2d","navigation-2d",-1924168611),new cljs.core.Keyword(null,"position","position",-2011731912),(0)], null),cljs.core._PLUS_,(dx / zoom)),new cljs.core.PersistentVector(null, 3, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"navigation-2d","navigation-2d",-1924168611),new cljs.core.Keyword(null,"position","position",-2011731912),(1)], null),cljs.core._PLUS_,(dy / zoom));
});
/**
* Changes zoom settings based on scroll.
*/
quil.middlewares.navigation_2d.mouse_wheel = (function quil$middlewares$navigation_2d$mouse_wheel(state,event){
quil.middlewares.navigation_2d.assert_state_has_navigation.call(null,state);
return cljs.core.update_in.call(null,state,new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"navigation-2d","navigation-2d",-1924168611),new cljs.core.Keyword(null,"zoom","zoom",-1827487038)], null),cljs.core._STAR_,((1) + (-0.1 * event)));
});
/**
* Calls user draw function with necessary all transformations (position
* and zoom) applied.
*/
quil.middlewares.navigation_2d.draw = (function quil$middlewares$navigation_2d$draw(user_draw,state){
quil.middlewares.navigation_2d.assert_state_has_navigation.call(null,state);
quil.core.push_matrix.call(null);
var nav_2d_9429 = new cljs.core.Keyword(null,"navigation-2d","navigation-2d",-1924168611).cljs$core$IFn$_invoke$arity$1(state);
var zoom_9430 = new cljs.core.Keyword(null,"zoom","zoom",-1827487038).cljs$core$IFn$_invoke$arity$1(nav_2d_9429);
var pos_9431 = new cljs.core.Keyword(null,"position","position",-2011731912).cljs$core$IFn$_invoke$arity$1(nav_2d_9429);
quil.core.scale.call(null,zoom_9430);
var tr__9351__auto___9432 = new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, [(((quil.core.width.call(null) / (2)) / zoom_9430) - cljs.core.first.call(null,pos_9431)),(((quil.core.height.call(null) / (2)) / zoom_9430) - cljs.core.second.call(null,pos_9431))], null);
quil.core.push_matrix.call(null);
try{quil.core.translate.call(null,tr__9351__auto___9432);
user_draw.call(null,state);
}finally {quil.core.pop_matrix.call(null);
}
return quil.core.pop_matrix.call(null);
});
/**
* Enables navigation over 2D sketch. Dragging mouse will move center of the
* skecth and mouse wheel controls zoom.
*/
quil.middlewares.navigation_2d.navigation_2d = (function quil$middlewares$navigation_2d$navigation_2d(options){
var user_settings = new cljs.core.Keyword(null,"navigation-2d","navigation-2d",-1924168611).cljs$core$IFn$_invoke$arity$1(options);
var user_draw = new cljs.core.Keyword(null,"draw","draw",1358331674).cljs$core$IFn$_invoke$arity$2(options,((function (user_settings){
return (function (state){
return null;
});})(user_settings))
);
var user_mouse_dragged = new cljs.core.Keyword(null,"mouse-dragged","mouse-dragged",-1220073441).cljs$core$IFn$_invoke$arity$2(options,((function (user_settings,user_draw){
return (function (state,_){
return state;
});})(user_settings,user_draw))
);
var user_mouse_wheel = new cljs.core.Keyword(null,"mouse-wheel","mouse-wheel",1811662439).cljs$core$IFn$_invoke$arity$2(options,((function (user_settings,user_draw,user_mouse_dragged){
return (function (state,_){
return state;
});})(user_settings,user_draw,user_mouse_dragged))
);
var setup = new cljs.core.Keyword(null,"setup","setup",1987730512).cljs$core$IFn$_invoke$arity$2(options,((function (user_settings,user_draw,user_mouse_dragged,user_mouse_wheel){
return (function (){
return cljs.core.PersistentArrayMap.EMPTY;
});})(user_settings,user_draw,user_mouse_dragged,user_mouse_wheel))
);
return cljs.core.assoc.call(null,options,new cljs.core.Keyword(null,"setup","setup",1987730512),cljs.core.partial.call(null,quil.middlewares.navigation_2d.setup_2d_nav,setup,user_settings),new cljs.core.Keyword(null,"draw","draw",1358331674),cljs.core.partial.call(null,quil.middlewares.navigation_2d.draw,user_draw),new cljs.core.Keyword(null,"mouse-dragged","mouse-dragged",-1220073441),((function (user_settings,user_draw,user_mouse_dragged,user_mouse_wheel,setup){
return (function (state,event){
return user_mouse_dragged.call(null,quil.middlewares.navigation_2d.mouse_dragged.call(null,state,event),event);
});})(user_settings,user_draw,user_mouse_dragged,user_mouse_wheel,setup))
,new cljs.core.Keyword(null,"mouse-wheel","mouse-wheel",1811662439),((function (user_settings,user_draw,user_mouse_dragged,user_mouse_wheel,setup){
return (function (state,event){
return user_mouse_wheel.call(null,quil.middlewares.navigation_2d.mouse_wheel.call(null,state,event),event);
});})(user_settings,user_draw,user_mouse_dragged,user_mouse_wheel,setup))
);
});
//# sourceMappingURL=navigation_2d.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"\/home\/mediocregopher\/src\/viz\/out\/quil\/middlewares\/navigation_2d.js","sources":["navigation_2d.cljc"],"lineCount":116,"mappings":";AAAA;;;AAGA,AAAeA,8DACb,6CAAA,yFAAA,kGAAA;AAIF;;;6DAAA,7DAAOC,kIAEJC;AAFH,AAGE,oBAAU,AAAA,sGAAgBA;AAA1B;;AAAA,AACE,MACgB,KAAAC,MAAWH;;;AAE\/B;;;;kDAAA,lDAAOI;AAAP,AAAA,kDAAA,8DAAA,kKAAA,sDAAA,rIAIc,8BAAA,7BAAG,AAACC,kCACJ,+BAAA,9BAAG,AAACC;;AAGlB;;;;8CAAA,9CAAOC,oGAGJC,WAAWC;AAHd,AAIE,IAAMC,qIAAkBD,hCACA,8CAAA,mFAAA,8DAAA,\/LAACE,rFACI,AAACC,0BAAM,AAACR;AAFrC,AAGE,2DAAA,mFAAA,gFAAA,vNAACU,8BAAU,AAACN;kBAAZK;AAAA,AACY,+CAAAA,xCAACD,0BAAMF;;;;AAEvB;;;;+CAAA,\/CAAOK,sGAGJb,MAAMc;AAHT,AAIE,AAACf,qEAA4BC;;AAC7B,IAAMe,KAAG,CAAG,AAAA,kFAAMD,SAAO,AAAA,6EAAIA;IACvBE,KAAG,CAAG,AAAA,iFAAMF,SAAO,AAAA,8EAAIA;IACvBG,OAAK,AAAA,oFAAA,AAAA,sGAAIjB;AAFf,mEAGKA,9BACA,oCAAA,mFAAA,wEAAA,8DAAA,7PAACY,yQAAuCM,iBAAE,CAAGH,KAAGE,rUAChD,4UAAA,mFAAA,wEAAA,8DAAA,9hBAACL,0iBAAuCM,iBAAE,CAAGF,KAAGC;;AAEvD;;;6CAAA,7CAAOE,kGAEJnB,MAAMc;AAFT,AAGE,AAACf,qEAA4BC;;AAC7B,2CAAA,mFAAA,wEAAA,\/LAACY,8BAAUZ,+NAA6BoB,iBAAE,CAAA,MAAK,CAAA,OAAQN;;AAEzD;;;;sCAAA,tCAAOO,oFAGJC,UAAUtB;AAHb,AAIE,AAACD,qEAA4BC;;AAC7B,AAACuB;;AACD,IAAMC,cAAO,AAAA,sGAAgBxB;IACvBiB,YAAK,AAAA,oFAAOO;IACZC,WAAI,AAAA,4FAAWD;AAFrB,AAGE,AAACE,0BAAQT;;AACT,IAAAU,wBAAA,mFAAqB,CAAG,CAAA,8BAAA,7BAAG,AAACxB,oCAAWc,aAAM,AAACa,0BAAML,WAC\/B,CAAG,CAAA,+BAAA,9BAAG,AAACrB,qCAAYa,aAAM,AAACc,2BAAON;AADtD,AAAA,AAAAF;;AAAA,IAAA,AAAA,AAAAK,8BAAAD;;AAEE,AAACL,oBAAUtB;UAFb,AAAA,AAAA6B;;AAGF,OAACA;;AAEH;;;;+CAAA,\/CAAMG,sGAGHC;AAHH,AAIE,IACM1B,gBAAc,AAAA,sGAAgB0B;IAI9BX,YAAU,AAAA,mFAAOW,QAAQ;kBAAKjC;AAAL,AAAA;;;IACzBkC,qBAAmB,AAAA,sGAAgBD,QAAQ;kBAAKjC,MAAMmC;AAAX,AAAcnC;;;IACzDoC,mBAAiB,AAAA,iGAAcH,QAAQ;kBAAKjC,MAAMmC;AAAX,AAAcnC;;;IACrDqC,QAAM,AAAA,qFAAQJ,QAAQ;;AAAA,AAAA;;;AAR5B,AASE,yCAAA,oJAAA,gIAAA,tTAACK,0BAAML,+DAEE,AAACM,4BAAQlC,4CAAagC,MAAM9B,oEAE7B,AAACgC,4BAAQlB,oCAAKC,mFAEL;kBAAKtB,MAAMc;AAAX,AACE,OAACoB,6BAAmB,AAACrB,uDAAcb,MAAMc,OAAOA;;CAPnE,mEAQe;kBAAKd,MAAMc;AAAX,AACE,OAACsB,2BAAiB,AAACjB,qDAAYnB,MAAMc,OAAOA","names":["quil.middlewares.navigation-2d\/missing-navigation-key-error","quil.middlewares.navigation-2d\/assert-state-has-navigation","state","js\/Error","quil.middlewares.navigation-2d\/default-position","quil.core\/width","quil.core\/height","quil.middlewares.navigation-2d\/setup-2d-nav","user-setup","user-settings","initial-state","cljs.core\/select-keys","cljs.core\/merge","p1__9428#","cljs.core\/update-in","quil.middlewares.navigation-2d\/mouse-dragged","event","dx","dy","zoom","cljs.core\/+","quil.middlewares.navigation-2d\/mouse-wheel","cljs.core\/*","quil.middlewares.navigation-2d\/draw","user-draw","quil.core\/push-matrix","nav-2d","pos","quil.core\/scale","tr__9351__auto__","quil.core\/translate","quil.core\/pop-matrix","cljs.core\/first","cljs.core\/second","quil.middlewares.navigation-2d\/navigation-2d","options","user-mouse-dragged","_","user-mouse-wheel","setup","cljs.core\/assoc","cljs.core\/partial"]}

View File

@ -0,0 +1,188 @@
(ns quil.middlewares.navigation-3d
(:require [quil.core :as q]))
(def ^:private missing-navigation-key-error
(str "state map is missing :navigation-3d key. "
"Did you accidentally removed it from the state in "
":update or any other handler?"))
(defn- assert-state-has-navigation
"Asserts that state map contains :navigation-2d object."
[state]
(when-not (:navigation-3d state)
(throw #?(:clj (RuntimeException. missing-navigation-key-error)
:cljs (js/Error. missing-navigation-key-error)))))
(defn- default-position
"Default position configuration. Check default configuration in
'camera' function."
[]
{:position [(/ (q/width) 2.0)
(/ (q/height) 2.0)
(/ (q/height) 2.0 (q/tan (/ (* q/PI 60.0) 360.0)))]
:straight [0 0 -1]
:up [0 1 0]})
(defn- rotate-by-axis-and-angle
"Rotates vector v by angle with axis.
Formula is taken from wiki:
http://en.wikipedia.org/wiki/Rotation_matrix#Rotation_matrix_from_axis_and_angle"
[v axis angle]
(let [[a-x a-y a-z] axis
[x y z] v
cs (q/cos angle)
-cs (- 1 cs)
sn (q/sin angle)
; Matrix is
; [a b c]
; [d e f]
; [g h i]
a (+ cs (* a-x a-x -cs))
b (- (* a-x a-y -cs)
(* a-z sn))
c (+ (* a-x a-z -cs)
(* a-y sn))
d (+ (* a-x a-y -cs)
(* a-z sn))
e (+ cs (* a-y a-y -cs))
f (- (* a-y a-z -cs)
(* a-x sn))
g (- (* a-x a-z -cs)
(* a-y sn))
h (+ (* a-y a-z -cs)
(* a-x sn))
i (+ cs (* a-z a-z -cs))]
[(+ (* a x) (* b y) (* c z))
(+ (* d x) (* e y) (* f z))
(+ (* g x) (* h y) (* i z))]))
(defn- rotate-lr
"Rotates nav-3d configuration left-right. angle positive - rotate right,
negative - left."
[nav-3d angle]
(update-in nav-3d [:straight] rotate-by-axis-and-angle (:up nav-3d) angle))
(defn- cross-product
"Vector cross-product: http://en.wikipedia.org/wiki/Cross_product"
[[u1 u2 u3] [v1 v2 v3]]
[(- (* u2 v3) (* u3 v2))
(- (* u3 v1) (* u1 v3))
(- (* u1 v2) (* u2 v1))])
(defn- v-mult
"Multiply vector v by scalar mult."
[v mult]
(mapv #(* % mult) v))
(defn- v-plus
"Sum of 2 vectors."
[v1 v2]
(mapv + v1 v2))
(defn- v-opposite
"Returns vector opposite to vector v."
[v]
(v-mult v -1))
(defn- v-normalize
"Normalize vector, returning vector
which has same direction but with norm equals to 1."
[v]
(let [norm (->> (map q/sq v)
(apply +)
(q/sqrt))]
(v-mult v (/ norm))))
(defn- rotate-ud
"Rotates nav-3d configuration up-down."
[nav-3d angle]
(let [axis (cross-product (:straight nav-3d) (:up nav-3d))
rotate #(rotate-by-axis-and-angle % axis angle)]
(-> nav-3d
(update-in [:straight] rotate)
(update-in [:up] rotate))))
(defn- rotate
"Mouse handler function which rotates nav-3d configuration.
It uses mouse from event object and pixels-in-360 to calculate
angles to rotate."
[state event pixels-in-360]
(assert-state-has-navigation state)
(if (= 0 (:p-x event) (:p-y event))
state
(let [dx (- (:p-x event) (:x event))
dy (- (:y event) (:p-y event))
angle-lr (q/map-range dx 0 pixels-in-360 0 q/TWO-PI)
angle-ud (q/map-range dy 0 pixels-in-360 0 q/TWO-PI)]
(update-in state [:navigation-3d]
#(-> %
(rotate-lr angle-lr)
(rotate-ud angle-ud))))))
(def ^:private space (keyword " "))
(defn- move
"Keyboard handler function which moves nav-3d configuration.
It uses keyboard key from event object to determing in which
direction to move."
[state event step-size]
(assert-state-has-navigation state)
(let [{:keys [up straight]} (:navigation-3d state)]
(if-let [dir (condp = (:key event)
:w straight
:s (v-opposite straight)
space (v-opposite up)
:z up
:d (cross-product straight up)
:a (cross-product up straight)
nil)]
(update-in state [:navigation-3d :position]
#(v-plus % (v-mult dir step-size)))
state)))
(defn- setup-3d-nav
"Custom 'setup' function which creates initial position
configuration and puts it to the state map."
[user-setup user-settings]
(let [initial-state (-> user-settings
(select-keys [:straight :up :position])
(->> (merge (default-position)))
(update-in [:straight] v-normalize)
(update-in [:up] v-normalize))]
(update-in (user-setup) [:navigation-3d]
#(merge initial-state %))))
(defn navigation-3d
"Enables navigation in 3D space. Similar to how it is done in
shooters: WASD navigation, space is go up, z is go down,
drag mouse to look around."
[options]
(let [; 3d-navigation related user settings
user-settings (:navigation-3d options)
pixels-in-360 (:pixels-in-360 user-settings 1000)
step-size (:step-size user-settings 20)
rotate-on (:rotate-on user-settings :mouse-dragged)
; user-provided handlers which will be overridden
; by 3d-navigation
draw (:draw options (fn [state]))
key-pressed (:key-pressed options (fn [state _] state))
rotate-on-fn (rotate-on options (fn [state _] state))
setup (:setup options (fn [] {}))]
(assoc options
:setup (partial setup-3d-nav setup user-settings)
:draw (fn [state]
(assert-state-has-navigation state)
(let [{[c-x c-y c-z] :straight
[u-x u-y u-z] :up
[p-x p-y p-z] :position} (:navigation-3d state)]
(q/camera p-x p-y p-z (+ p-x c-x) (+ p-y c-y) (+ p-z c-z) u-x u-y u-z))
(draw state))
:key-pressed (fn [state event]
(key-pressed (move state event step-size) event))
rotate-on (fn [state event]
(rotate-on-fn (rotate state event pixels-in-360) event)) )))

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,261 @@
// Compiled by ClojureScript 1.9.473 {}
goog.provide('quil.middlewares.navigation_3d');
goog.require('cljs.core');
goog.require('quil.core');
quil.middlewares.navigation_3d.missing_navigation_key_error = [cljs.core.str.cljs$core$IFn$_invoke$arity$1("state map is missing :navigation-3d key. "),cljs.core.str.cljs$core$IFn$_invoke$arity$1("Did you accidentally removed it from the state in "),cljs.core.str.cljs$core$IFn$_invoke$arity$1(":update or any other handler?")].join('');
/**
* Asserts that state map contains :navigation-2d object.
*/
quil.middlewares.navigation_3d.assert_state_has_navigation = (function quil$middlewares$navigation_3d$assert_state_has_navigation(state){
if(cljs.core.truth_(new cljs.core.Keyword(null,"navigation-3d","navigation-3d",682305301).cljs$core$IFn$_invoke$arity$1(state))){
return null;
} else {
throw (new Error(quil.middlewares.navigation_3d.missing_navigation_key_error));
}
});
/**
* Default position configuration. Check default configuration in
* 'camera' function.
*/
quil.middlewares.navigation_3d.default_position = (function quil$middlewares$navigation_3d$default_position(){
return new cljs.core.PersistentArrayMap(null, 3, [new cljs.core.Keyword(null,"position","position",-2011731912),new cljs.core.PersistentVector(null, 3, 5, cljs.core.PersistentVector.EMPTY_NODE, [(quil.core.width.call(null) / 2.0),(quil.core.height.call(null) / 2.0),((quil.core.height.call(null) / 2.0) / quil.core.tan.call(null,((quil.core.PI * 60.0) / 360.0)))], null),new cljs.core.Keyword(null,"straight","straight",-1252567854),new cljs.core.PersistentVector(null, 3, 5, cljs.core.PersistentVector.EMPTY_NODE, [(0),(0),(-1)], null),new cljs.core.Keyword(null,"up","up",-269712113),new cljs.core.PersistentVector(null, 3, 5, cljs.core.PersistentVector.EMPTY_NODE, [(0),(1),(0)], null)], null);
});
/**
* Rotates vector v by angle with axis.
* Formula is taken from wiki:
* http://en.wikipedia.org/wiki/Rotation_matrix#Rotation_matrix_from_axis_and_angle
*/
quil.middlewares.navigation_3d.rotate_by_axis_and_angle = (function quil$middlewares$navigation_3d$rotate_by_axis_and_angle(v,axis,angle){
var vec__8900 = axis;
var a_x = cljs.core.nth.call(null,vec__8900,(0),null);
var a_y = cljs.core.nth.call(null,vec__8900,(1),null);
var a_z = cljs.core.nth.call(null,vec__8900,(2),null);
var vec__8903 = v;
var x = cljs.core.nth.call(null,vec__8903,(0),null);
var y = cljs.core.nth.call(null,vec__8903,(1),null);
var z = cljs.core.nth.call(null,vec__8903,(2),null);
var cs = quil.core.cos.call(null,angle);
var _cs = ((1) - cs);
var sn = quil.core.sin.call(null,angle);
var a = (cs + ((a_x * a_x) * _cs));
var b = (((a_x * a_y) * _cs) - (a_z * sn));
var c = (((a_x * a_z) * _cs) + (a_y * sn));
var d = (((a_x * a_y) * _cs) + (a_z * sn));
var e = (cs + ((a_y * a_y) * _cs));
var f = (((a_y * a_z) * _cs) - (a_x * sn));
var g = (((a_x * a_z) * _cs) - (a_y * sn));
var h = (((a_y * a_z) * _cs) + (a_x * sn));
var i = (cs + ((a_z * a_z) * _cs));
return new cljs.core.PersistentVector(null, 3, 5, cljs.core.PersistentVector.EMPTY_NODE, [(((a * x) + (b * y)) + (c * z)),(((d * x) + (e * y)) + (f * z)),(((g * x) + (h * y)) + (i * z))], null);
});
/**
* Rotates nav-3d configuration left-right. angle positive - rotate right,
* negative - left.
*/
quil.middlewares.navigation_3d.rotate_lr = (function quil$middlewares$navigation_3d$rotate_lr(nav_3d,angle){
return cljs.core.update_in.call(null,nav_3d,new cljs.core.PersistentVector(null, 1, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"straight","straight",-1252567854)], null),quil.middlewares.navigation_3d.rotate_by_axis_and_angle,new cljs.core.Keyword(null,"up","up",-269712113).cljs$core$IFn$_invoke$arity$1(nav_3d),angle);
});
/**
* Vector cross-product: http://en.wikipedia.org/wiki/Cross_product
*/
quil.middlewares.navigation_3d.cross_product = (function quil$middlewares$navigation_3d$cross_product(p__8906,p__8907){
var vec__8914 = p__8906;
var u1 = cljs.core.nth.call(null,vec__8914,(0),null);
var u2 = cljs.core.nth.call(null,vec__8914,(1),null);
var u3 = cljs.core.nth.call(null,vec__8914,(2),null);
var vec__8917 = p__8907;
var v1 = cljs.core.nth.call(null,vec__8917,(0),null);
var v2 = cljs.core.nth.call(null,vec__8917,(1),null);
var v3 = cljs.core.nth.call(null,vec__8917,(2),null);
return new cljs.core.PersistentVector(null, 3, 5, cljs.core.PersistentVector.EMPTY_NODE, [((u2 * v3) - (u3 * v2)),((u3 * v1) - (u1 * v3)),((u1 * v2) - (u2 * v1))], null);
});
/**
* Multiply vector v by scalar mult.
*/
quil.middlewares.navigation_3d.v_mult = (function quil$middlewares$navigation_3d$v_mult(v,mult){
return cljs.core.mapv.call(null,(function (p1__8920_SHARP_){
return (p1__8920_SHARP_ * mult);
}),v);
});
/**
* Sum of 2 vectors.
*/
quil.middlewares.navigation_3d.v_plus = (function quil$middlewares$navigation_3d$v_plus(v1,v2){
return cljs.core.mapv.call(null,cljs.core._PLUS_,v1,v2);
});
/**
* Returns vector opposite to vector v.
*/
quil.middlewares.navigation_3d.v_opposite = (function quil$middlewares$navigation_3d$v_opposite(v){
return quil.middlewares.navigation_3d.v_mult.call(null,v,(-1));
});
/**
* Normalize vector, returning vector
* which has same direction but with norm equals to 1.
*/
quil.middlewares.navigation_3d.v_normalize = (function quil$middlewares$navigation_3d$v_normalize(v){
var norm = quil.core.sqrt.call(null,cljs.core.apply.call(null,cljs.core._PLUS_,cljs.core.map.call(null,quil.core.sq,v)));
return quil.middlewares.navigation_3d.v_mult.call(null,v,((1) / norm));
});
/**
* Rotates nav-3d configuration up-down.
*/
quil.middlewares.navigation_3d.rotate_ud = (function quil$middlewares$navigation_3d$rotate_ud(nav_3d,angle){
var axis = quil.middlewares.navigation_3d.cross_product.call(null,new cljs.core.Keyword(null,"straight","straight",-1252567854).cljs$core$IFn$_invoke$arity$1(nav_3d),new cljs.core.Keyword(null,"up","up",-269712113).cljs$core$IFn$_invoke$arity$1(nav_3d));
var rotate = ((function (axis){
return (function (p1__8921_SHARP_){
return quil.middlewares.navigation_3d.rotate_by_axis_and_angle.call(null,p1__8921_SHARP_,axis,angle);
});})(axis))
;
return cljs.core.update_in.call(null,cljs.core.update_in.call(null,nav_3d,new cljs.core.PersistentVector(null, 1, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"straight","straight",-1252567854)], null),rotate),new cljs.core.PersistentVector(null, 1, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"up","up",-269712113)], null),rotate);
});
/**
* Mouse handler function which rotates nav-3d configuration.
* It uses mouse from event object and pixels-in-360 to calculate
* angles to rotate.
*/
quil.middlewares.navigation_3d.rotate = (function quil$middlewares$navigation_3d$rotate(state,event,pixels_in_360){
quil.middlewares.navigation_3d.assert_state_has_navigation.call(null,state);
if(cljs.core._EQ_.call(null,(0),new cljs.core.Keyword(null,"p-x","p-x",-1721211211).cljs$core$IFn$_invoke$arity$1(event),new cljs.core.Keyword(null,"p-y","p-y",-530704830).cljs$core$IFn$_invoke$arity$1(event))){
return state;
} else {
var dx = (new cljs.core.Keyword(null,"p-x","p-x",-1721211211).cljs$core$IFn$_invoke$arity$1(event) - new cljs.core.Keyword(null,"x","x",2099068185).cljs$core$IFn$_invoke$arity$1(event));
var dy = (new cljs.core.Keyword(null,"y","y",-1757859776).cljs$core$IFn$_invoke$arity$1(event) - new cljs.core.Keyword(null,"p-y","p-y",-530704830).cljs$core$IFn$_invoke$arity$1(event));
var angle_lr = quil.core.map_range.call(null,dx,(0),pixels_in_360,(0),quil.core.TWO_PI);
var angle_ud = quil.core.map_range.call(null,dy,(0),pixels_in_360,(0),quil.core.TWO_PI);
return cljs.core.update_in.call(null,state,new cljs.core.PersistentVector(null, 1, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"navigation-3d","navigation-3d",682305301)], null),((function (dx,dy,angle_lr,angle_ud){
return (function (p1__8922_SHARP_){
return quil.middlewares.navigation_3d.rotate_ud.call(null,quil.middlewares.navigation_3d.rotate_lr.call(null,p1__8922_SHARP_,angle_lr),angle_ud);
});})(dx,dy,angle_lr,angle_ud))
);
}
});
quil.middlewares.navigation_3d.space = cljs.core.keyword.call(null," ");
/**
* Keyboard handler function which moves nav-3d configuration.
* It uses keyboard key from event object to determing in which
* direction to move.
*/
quil.middlewares.navigation_3d.move = (function quil$middlewares$navigation_3d$move(state,event,step_size){
quil.middlewares.navigation_3d.assert_state_has_navigation.call(null,state);
var map__8929 = new cljs.core.Keyword(null,"navigation-3d","navigation-3d",682305301).cljs$core$IFn$_invoke$arity$1(state);
var map__8929__$1 = ((((!((map__8929 == null)))?((((map__8929.cljs$lang$protocol_mask$partition0$ & (64))) || ((cljs.core.PROTOCOL_SENTINEL === map__8929.cljs$core$ISeq$)))?true:false):false))?cljs.core.apply.call(null,cljs.core.hash_map,map__8929):map__8929);
var up = cljs.core.get.call(null,map__8929__$1,new cljs.core.Keyword(null,"up","up",-269712113));
var straight = cljs.core.get.call(null,map__8929__$1,new cljs.core.Keyword(null,"straight","straight",-1252567854));
var temp__4655__auto__ = (function (){var pred__8931 = cljs.core._EQ_;
var expr__8932 = new cljs.core.Keyword(null,"key","key",-1516042587).cljs$core$IFn$_invoke$arity$1(event);
if(cljs.core.truth_(pred__8931.call(null,new cljs.core.Keyword(null,"w","w",354169001),expr__8932))){
return straight;
} else {
if(cljs.core.truth_(pred__8931.call(null,new cljs.core.Keyword(null,"s","s",1705939918),expr__8932))){
return quil.middlewares.navigation_3d.v_opposite.call(null,straight);
} else {
if(cljs.core.truth_(pred__8931.call(null,quil.middlewares.navigation_3d.space,expr__8932))){
return quil.middlewares.navigation_3d.v_opposite.call(null,up);
} else {
if(cljs.core.truth_(pred__8931.call(null,new cljs.core.Keyword(null,"z","z",-789527183),expr__8932))){
return up;
} else {
if(cljs.core.truth_(pred__8931.call(null,new cljs.core.Keyword(null,"d","d",1972142424),expr__8932))){
return quil.middlewares.navigation_3d.cross_product.call(null,straight,up);
} else {
if(cljs.core.truth_(pred__8931.call(null,new cljs.core.Keyword(null,"a","a",-2123407586),expr__8932))){
return quil.middlewares.navigation_3d.cross_product.call(null,up,straight);
} else {
return null;
}
}
}
}
}
}
})();
if(cljs.core.truth_(temp__4655__auto__)){
var dir = temp__4655__auto__;
return cljs.core.update_in.call(null,state,new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"navigation-3d","navigation-3d",682305301),new cljs.core.Keyword(null,"position","position",-2011731912)], null),((function (dir,temp__4655__auto__,map__8929,map__8929__$1,up,straight){
return (function (p1__8923_SHARP_){
return quil.middlewares.navigation_3d.v_plus.call(null,p1__8923_SHARP_,quil.middlewares.navigation_3d.v_mult.call(null,dir,step_size));
});})(dir,temp__4655__auto__,map__8929,map__8929__$1,up,straight))
);
} else {
return state;
}
});
/**
* Custom 'setup' function which creates initial position
* configuration and puts it to the state map.
*/
quil.middlewares.navigation_3d.setup_3d_nav = (function quil$middlewares$navigation_3d$setup_3d_nav(user_setup,user_settings){
var initial_state = cljs.core.update_in.call(null,cljs.core.update_in.call(null,cljs.core.merge.call(null,quil.middlewares.navigation_3d.default_position.call(null),cljs.core.select_keys.call(null,user_settings,new cljs.core.PersistentVector(null, 3, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"straight","straight",-1252567854),new cljs.core.Keyword(null,"up","up",-269712113),new cljs.core.Keyword(null,"position","position",-2011731912)], null))),new cljs.core.PersistentVector(null, 1, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"straight","straight",-1252567854)], null),quil.middlewares.navigation_3d.v_normalize),new cljs.core.PersistentVector(null, 1, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"up","up",-269712113)], null),quil.middlewares.navigation_3d.v_normalize);
return cljs.core.update_in.call(null,user_setup.call(null),new cljs.core.PersistentVector(null, 1, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"navigation-3d","navigation-3d",682305301)], null),((function (initial_state){
return (function (p1__8934_SHARP_){
return cljs.core.merge.call(null,initial_state,p1__8934_SHARP_);
});})(initial_state))
);
});
/**
* Enables navigation in 3D space. Similar to how it is done in
* shooters: WASD navigation, space is go up, z is go down,
* drag mouse to look around.
*/
quil.middlewares.navigation_3d.navigation_3d = (function quil$middlewares$navigation_3d$navigation_3d(options){
var user_settings = new cljs.core.Keyword(null,"navigation-3d","navigation-3d",682305301).cljs$core$IFn$_invoke$arity$1(options);
var pixels_in_360 = new cljs.core.Keyword(null,"pixels-in-360","pixels-in-360",1789567298).cljs$core$IFn$_invoke$arity$2(user_settings,(1000));
var step_size = new cljs.core.Keyword(null,"step-size","step-size",1545609922).cljs$core$IFn$_invoke$arity$2(user_settings,(20));
var rotate_on = new cljs.core.Keyword(null,"rotate-on","rotate-on",-1282225937).cljs$core$IFn$_invoke$arity$2(user_settings,new cljs.core.Keyword(null,"mouse-dragged","mouse-dragged",-1220073441));
var draw = new cljs.core.Keyword(null,"draw","draw",1358331674).cljs$core$IFn$_invoke$arity$2(options,((function (user_settings,pixels_in_360,step_size,rotate_on){
return (function (state){
return null;
});})(user_settings,pixels_in_360,step_size,rotate_on))
);
var key_pressed = new cljs.core.Keyword(null,"key-pressed","key-pressed",-757100364).cljs$core$IFn$_invoke$arity$2(options,((function (user_settings,pixels_in_360,step_size,rotate_on,draw){
return (function (state,_){
return state;
});})(user_settings,pixels_in_360,step_size,rotate_on,draw))
);
var rotate_on_fn = rotate_on.call(null,options,((function (user_settings,pixels_in_360,step_size,rotate_on,draw,key_pressed){
return (function (state,_){
return state;
});})(user_settings,pixels_in_360,step_size,rotate_on,draw,key_pressed))
);
var setup = new cljs.core.Keyword(null,"setup","setup",1987730512).cljs$core$IFn$_invoke$arity$2(options,((function (user_settings,pixels_in_360,step_size,rotate_on,draw,key_pressed,rotate_on_fn){
return (function (){
return cljs.core.PersistentArrayMap.EMPTY;
});})(user_settings,pixels_in_360,step_size,rotate_on,draw,key_pressed,rotate_on_fn))
);
return cljs.core.assoc.call(null,options,new cljs.core.Keyword(null,"setup","setup",1987730512),cljs.core.partial.call(null,quil.middlewares.navigation_3d.setup_3d_nav,setup,user_settings),new cljs.core.Keyword(null,"draw","draw",1358331674),((function (user_settings,pixels_in_360,step_size,rotate_on,draw,key_pressed,rotate_on_fn,setup){
return (function (state){
quil.middlewares.navigation_3d.assert_state_has_navigation.call(null,state);
var map__8946_8957 = new cljs.core.Keyword(null,"navigation-3d","navigation-3d",682305301).cljs$core$IFn$_invoke$arity$1(state);
var map__8946_8958__$1 = ((((!((map__8946_8957 == null)))?((((map__8946_8957.cljs$lang$protocol_mask$partition0$ & (64))) || ((cljs.core.PROTOCOL_SENTINEL === map__8946_8957.cljs$core$ISeq$)))?true:false):false))?cljs.core.apply.call(null,cljs.core.hash_map,map__8946_8957):map__8946_8957);
var vec__8947_8959 = cljs.core.get.call(null,map__8946_8958__$1,new cljs.core.Keyword(null,"straight","straight",-1252567854));
var c_x_8960 = cljs.core.nth.call(null,vec__8947_8959,(0),null);
var c_y_8961 = cljs.core.nth.call(null,vec__8947_8959,(1),null);
var c_z_8962 = cljs.core.nth.call(null,vec__8947_8959,(2),null);
var vec__8950_8963 = cljs.core.get.call(null,map__8946_8958__$1,new cljs.core.Keyword(null,"up","up",-269712113));
var u_x_8964 = cljs.core.nth.call(null,vec__8950_8963,(0),null);
var u_y_8965 = cljs.core.nth.call(null,vec__8950_8963,(1),null);
var u_z_8966 = cljs.core.nth.call(null,vec__8950_8963,(2),null);
var vec__8953_8967 = cljs.core.get.call(null,map__8946_8958__$1,new cljs.core.Keyword(null,"position","position",-2011731912));
var p_x_8968 = cljs.core.nth.call(null,vec__8953_8967,(0),null);
var p_y_8969 = cljs.core.nth.call(null,vec__8953_8967,(1),null);
var p_z_8970 = cljs.core.nth.call(null,vec__8953_8967,(2),null);
quil.core.camera.call(null,p_x_8968,p_y_8969,p_z_8970,(p_x_8968 + c_x_8960),(p_y_8969 + c_y_8961),(p_z_8970 + c_z_8962),u_x_8964,u_y_8965,u_z_8966);
return draw.call(null,state);
});})(user_settings,pixels_in_360,step_size,rotate_on,draw,key_pressed,rotate_on_fn,setup))
,new cljs.core.Keyword(null,"key-pressed","key-pressed",-757100364),((function (user_settings,pixels_in_360,step_size,rotate_on,draw,key_pressed,rotate_on_fn,setup){
return (function (state,event){
return key_pressed.call(null,quil.middlewares.navigation_3d.move.call(null,state,event,step_size),event);
});})(user_settings,pixels_in_360,step_size,rotate_on,draw,key_pressed,rotate_on_fn,setup))
,rotate_on,((function (user_settings,pixels_in_360,step_size,rotate_on,draw,key_pressed,rotate_on_fn,setup){
return (function (state,event){
return rotate_on_fn.call(null,quil.middlewares.navigation_3d.rotate.call(null,state,event,pixels_in_360),event);
});})(user_settings,pixels_in_360,step_size,rotate_on,draw,key_pressed,rotate_on_fn,setup))
);
});
//# sourceMappingURL=navigation_3d.js.map

File diff suppressed because one or more lines are too long

128
viz/out/quil/sketch.cljs Normal file
View File

@ -0,0 +1,128 @@
(ns quil.sketch
(:require [quil.util :as u :include-macros true]
[quil.middlewares.deprecated-options :as do]
[goog.dom :as dom]
[goog.events :as events]
[goog.events.EventType :as EventType])
(:require-macros [quil.sketch]))
(def ^:dynamic
*applet* nil)
(defn current-applet [] *applet*)
(u/generate-quil-constants :cljs
rendering-modes (:java2d :p2d :p3d :opengl))
(defn resolve-renderer [mode]
(u/resolve-constant-key mode rendering-modes))
(defn size
([width height]
(.size (current-applet) (int width) (int height)))
([width height mode]
(.size (current-applet) (int width) (int height) (u/resolve-constant-key mode rendering-modes))))
(defn- bind-handlers [prc opts]
(doseq [[processing-name quil-name] {:setup :setup
:draw :draw
:keyPressed :key-pressed
:keyReleased :key-released
:keyTyped :key-typed
:mouseClicked :mouse-clicked
:mouseDragged :mouse-dragged
:mouseMoved :mouse-moved
:mousePressed :mouse-pressed
:mouseReleased :mouse-released
:mouseOut :mouse-exited
:mouseOver :mouse-entered
:mouseScrolled :mouse-wheel}]
(when-let [handler (opts quil-name)]
(aset prc (name processing-name)
(fn []
(quil.sketch/with-sketch prc
(handler)))))))
(defn make-sketch [options]
(let [opts (->> (:middleware options [])
(cons do/deprecated-options)
(apply comp)
(#(% options))
(merge {:size [500 300]}))
sketch-size (or (:size opts) [200 200])
renderer (:renderer opts)
features (set (:features opts))
setup (fn []
(->> (if renderer [renderer] [])
(concat sketch-size)
(apply size))
(when (:settings opts) ((:settings opts)))
(when (:setup opts) ((:setup opts))))
mouse-wheel (when (:mouse-wheel opts)
;; -1 need for compability with Clojure version
#((:mouse-wheel opts) (* -1 (.-mouseScroll *applet*))))
opts (assoc opts
:setup setup
:mouse-wheel mouse-wheel)
attach-function (fn [prc]
(bind-handlers prc opts)
(set! (.-quil prc) (atom nil))
(set! (.-target-frame-rate prc) (atom 60)))
sketch (js/Processing.Sketch. attach-function)]
(when (contains? features :global-key-events)
(aset (aget sketch "options") "globalKeyEvents" true))
sketch))
(defn destroy-previous-sketch [host-elem]
(when-let [proc-obj (.-processing-obj host-elem)]
(.exit proc-obj)))
(defn sketch [& opts]
(let [opts-map (apply hash-map opts)
host-elem (dom/getElement (:host opts-map))
renderer (or (:renderer opts-map) :p2d)]
(if host-elem
(do
(if (.-processing-context host-elem)
(when-not (= renderer (.-processing-context host-elem))
(.warn js/console "WARNING: Using different context on one canvas!"))
(set! (.-processing-context host-elem) renderer))
(destroy-previous-sketch host-elem)
(set! (.-processing-obj host-elem)
(js/Processing. host-elem (make-sketch opts-map))))
(.error js/console "ERROR: Cannot create sketch. :host is not specified."))))
(def sketch-init-list (atom (list )))
(defn empty-body? []
(let [child (.-childNodes (.-body js/document))]
; seems hacky, we should come up with better way of
; checking whether body is empty or not
(<= (.-length child) 1)))
(defn add-canvas [canvas-id]
(let [canvas (.createElement js/document "canvas")]
(.setAttribute canvas "id" canvas-id)
(.appendChild (.-body js/document) canvas)))
(defn init-sketches []
(let [add-elem? (empty-body?)]
(doseq [sk @sketch-init-list]
(when add-elem?
(add-canvas (:host-id sk)))
((:fn sk))))
(reset! sketch-init-list []))
(defn add-sketch-to-init-list [sk]
(swap! sketch-init-list conj sk)
; if page already loaded immediately init sketch we just added
(when (= (.-readyState js/document) "complete")
(init-sketches)))
(events/listenOnce js/window EventType/LOAD init-sketches)

File diff suppressed because one or more lines are too long

348
viz/out/quil/sketch.js Normal file
View File

@ -0,0 +1,348 @@
// Compiled by ClojureScript 1.9.473 {}
goog.provide('quil.sketch');
goog.require('cljs.core');
goog.require('quil.util');
goog.require('quil.middlewares.deprecated_options');
goog.require('goog.dom');
goog.require('goog.events');
goog.require('goog.events.EventType');
quil.sketch._STAR_applet_STAR_ = null;
quil.sketch.current_applet = (function quil$sketch$current_applet(){
return quil.sketch._STAR_applet_STAR_;
});
quil.sketch.rendering_modes = new cljs.core.PersistentArrayMap(null, 4, [new cljs.core.Keyword(null,"java2d","java2d",166099237),(Processing.prototype.PConstants["JAVA2D"]),new cljs.core.Keyword(null,"p2d","p2d",-2106175755),(Processing.prototype.PConstants["P2D"]),new cljs.core.Keyword(null,"p3d","p3d",-850380194),(Processing.prototype.PConstants["P3D"]),new cljs.core.Keyword(null,"opengl","opengl",-614998103),(Processing.prototype.PConstants["OPENGL"])], null);
quil.sketch.resolve_renderer = (function quil$sketch$resolve_renderer(mode){
return quil.util.resolve_constant_key.call(null,mode,quil.sketch.rendering_modes);
});
quil.sketch.size = (function quil$sketch$size(var_args){
var args8189 = [];
var len__7927__auto___8192 = arguments.length;
var i__7928__auto___8193 = (0);
while(true){
if((i__7928__auto___8193 < len__7927__auto___8192)){
args8189.push((arguments[i__7928__auto___8193]));
var G__8194 = (i__7928__auto___8193 + (1));
i__7928__auto___8193 = G__8194;
continue;
} else {
}
break;
}
var G__8191 = args8189.length;
switch (G__8191) {
case 2:
return quil.sketch.size.cljs$core$IFn$_invoke$arity$2((arguments[(0)]),(arguments[(1)]));
break;
case 3:
return quil.sketch.size.cljs$core$IFn$_invoke$arity$3((arguments[(0)]),(arguments[(1)]),(arguments[(2)]));
break;
default:
throw (new Error([cljs.core.str.cljs$core$IFn$_invoke$arity$1("Invalid arity: "),cljs.core.str.cljs$core$IFn$_invoke$arity$1(args8189.length)].join('')));
}
});
quil.sketch.size.cljs$core$IFn$_invoke$arity$2 = (function (width,height){
return quil.sketch.current_applet.call(null).size((width | (0)),(height | (0)));
});
quil.sketch.size.cljs$core$IFn$_invoke$arity$3 = (function (width,height,mode){
return quil.sketch.current_applet.call(null).size((width | (0)),(height | (0)),quil.util.resolve_constant_key.call(null,mode,quil.sketch.rendering_modes));
});
quil.sketch.size.cljs$lang$maxFixedArity = 3;
quil.sketch.bind_handlers = (function quil$sketch$bind_handlers(prc,opts){
var seq__8208 = cljs.core.seq.call(null,cljs.core.PersistentHashMap.fromArrays([new cljs.core.Keyword(null,"keyPressed","keyPressed",1791025256),new cljs.core.Keyword(null,"mouseOut","mouseOut",-386669045),new cljs.core.Keyword(null,"mouseScrolled","mouseScrolled",31878252),new cljs.core.Keyword(null,"mouseDragged","mouseDragged",129975181),new cljs.core.Keyword(null,"setup","setup",1987730512),new cljs.core.Keyword(null,"keyReleased","keyReleased",541714964),new cljs.core.Keyword(null,"mouseClicked","mouseClicked",1764302965),new cljs.core.Keyword(null,"mouseReleased","mouseReleased",1116234838),new cljs.core.Keyword(null,"mousePressed","mousePressed",1776186454),new cljs.core.Keyword(null,"mouseMoved","mouseMoved",-1936954058),new cljs.core.Keyword(null,"mouseOver","mouseOver",-1334461930),new cljs.core.Keyword(null,"keyTyped","keyTyped",1437329399),new cljs.core.Keyword(null,"draw","draw",1358331674)],[new cljs.core.Keyword(null,"key-pressed","key-pressed",-757100364),new cljs.core.Keyword(null,"mouse-exited","mouse-exited",-483205244),new cljs.core.Keyword(null,"mouse-wheel","mouse-wheel",1811662439),new cljs.core.Keyword(null,"mouse-dragged","mouse-dragged",-1220073441),new cljs.core.Keyword(null,"setup","setup",1987730512),new cljs.core.Keyword(null,"key-released","key-released",215919828),new cljs.core.Keyword(null,"mouse-clicked","mouse-clicked",-199339421),new cljs.core.Keyword(null,"mouse-released","mouse-released",-664480061),new cljs.core.Keyword(null,"mouse-pressed","mouse-pressed",736955536),new cljs.core.Keyword(null,"mouse-moved","mouse-moved",-1918152310),new cljs.core.Keyword(null,"mouse-entered","mouse-entered",811350322),new cljs.core.Keyword(null,"key-typed","key-typed",-876037597),new cljs.core.Keyword(null,"draw","draw",1358331674)]));
var chunk__8209 = null;
var count__8210 = (0);
var i__8211 = (0);
while(true){
if((i__8211 < count__8210)){
var vec__8212 = cljs.core._nth.call(null,chunk__8209,i__8211);
var processing_name = cljs.core.nth.call(null,vec__8212,(0),null);
var quil_name = cljs.core.nth.call(null,vec__8212,(1),null);
var temp__4657__auto___8220 = opts.call(null,quil_name);
if(cljs.core.truth_(temp__4657__auto___8220)){
var handler_8221 = temp__4657__auto___8220;
(prc[cljs.core.name.call(null,processing_name)] = ((function (seq__8208,chunk__8209,count__8210,i__8211,handler_8221,temp__4657__auto___8220,vec__8212,processing_name,quil_name){
return (function (){
var _STAR_applet_STAR_8215 = quil.sketch._STAR_applet_STAR_;
quil.sketch._STAR_applet_STAR_ = prc;
try{return handler_8221.call(null);
}finally {quil.sketch._STAR_applet_STAR_ = _STAR_applet_STAR_8215;
}});})(seq__8208,chunk__8209,count__8210,i__8211,handler_8221,temp__4657__auto___8220,vec__8212,processing_name,quil_name))
);
} else {
}
var G__8222 = seq__8208;
var G__8223 = chunk__8209;
var G__8224 = count__8210;
var G__8225 = (i__8211 + (1));
seq__8208 = G__8222;
chunk__8209 = G__8223;
count__8210 = G__8224;
i__8211 = G__8225;
continue;
} else {
var temp__4657__auto__ = cljs.core.seq.call(null,seq__8208);
if(temp__4657__auto__){
var seq__8208__$1 = temp__4657__auto__;
if(cljs.core.chunked_seq_QMARK_.call(null,seq__8208__$1)){
var c__7633__auto__ = cljs.core.chunk_first.call(null,seq__8208__$1);
var G__8226 = cljs.core.chunk_rest.call(null,seq__8208__$1);
var G__8227 = c__7633__auto__;
var G__8228 = cljs.core.count.call(null,c__7633__auto__);
var G__8229 = (0);
seq__8208 = G__8226;
chunk__8209 = G__8227;
count__8210 = G__8228;
i__8211 = G__8229;
continue;
} else {
var vec__8216 = cljs.core.first.call(null,seq__8208__$1);
var processing_name = cljs.core.nth.call(null,vec__8216,(0),null);
var quil_name = cljs.core.nth.call(null,vec__8216,(1),null);
var temp__4657__auto___8230__$1 = opts.call(null,quil_name);
if(cljs.core.truth_(temp__4657__auto___8230__$1)){
var handler_8231 = temp__4657__auto___8230__$1;
(prc[cljs.core.name.call(null,processing_name)] = ((function (seq__8208,chunk__8209,count__8210,i__8211,handler_8231,temp__4657__auto___8230__$1,vec__8216,processing_name,quil_name,seq__8208__$1,temp__4657__auto__){
return (function (){
var _STAR_applet_STAR_8219 = quil.sketch._STAR_applet_STAR_;
quil.sketch._STAR_applet_STAR_ = prc;
try{return handler_8231.call(null);
}finally {quil.sketch._STAR_applet_STAR_ = _STAR_applet_STAR_8219;
}});})(seq__8208,chunk__8209,count__8210,i__8211,handler_8231,temp__4657__auto___8230__$1,vec__8216,processing_name,quil_name,seq__8208__$1,temp__4657__auto__))
);
} else {
}
var G__8232 = cljs.core.next.call(null,seq__8208__$1);
var G__8233 = null;
var G__8234 = (0);
var G__8235 = (0);
seq__8208 = G__8232;
chunk__8209 = G__8233;
count__8210 = G__8234;
i__8211 = G__8235;
continue;
}
} else {
return null;
}
}
break;
}
});
quil.sketch.make_sketch = (function quil$sketch$make_sketch(options){
var opts = cljs.core.merge.call(null,new cljs.core.PersistentArrayMap(null, 1, [new cljs.core.Keyword(null,"size","size",1098693007),new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, [(500),(300)], null)], null),(function (p1__8236_SHARP_){
return p1__8236_SHARP_.call(null,options);
}).call(null,cljs.core.apply.call(null,cljs.core.comp,cljs.core.cons.call(null,quil.middlewares.deprecated_options.deprecated_options,new cljs.core.Keyword(null,"middleware","middleware",1462115504).cljs$core$IFn$_invoke$arity$2(options,cljs.core.PersistentVector.EMPTY)))));
var sketch_size = (function (){var or__6814__auto__ = new cljs.core.Keyword(null,"size","size",1098693007).cljs$core$IFn$_invoke$arity$1(opts);
if(cljs.core.truth_(or__6814__auto__)){
return or__6814__auto__;
} else {
return new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, [(200),(200)], null);
}
})();
var renderer = new cljs.core.Keyword(null,"renderer","renderer",336841071).cljs$core$IFn$_invoke$arity$1(opts);
var features = cljs.core.set.call(null,new cljs.core.Keyword(null,"features","features",-1146962336).cljs$core$IFn$_invoke$arity$1(opts));
var setup = ((function (opts,sketch_size,renderer,features){
return (function (){
cljs.core.apply.call(null,quil.sketch.size,cljs.core.concat.call(null,sketch_size,(cljs.core.truth_(renderer)?new cljs.core.PersistentVector(null, 1, 5, cljs.core.PersistentVector.EMPTY_NODE, [renderer], null):cljs.core.PersistentVector.EMPTY)));
if(cljs.core.truth_(new cljs.core.Keyword(null,"settings","settings",1556144875).cljs$core$IFn$_invoke$arity$1(opts))){
new cljs.core.Keyword(null,"settings","settings",1556144875).cljs$core$IFn$_invoke$arity$1(opts).call(null);
} else {
}
if(cljs.core.truth_(new cljs.core.Keyword(null,"setup","setup",1987730512).cljs$core$IFn$_invoke$arity$1(opts))){
return new cljs.core.Keyword(null,"setup","setup",1987730512).cljs$core$IFn$_invoke$arity$1(opts).call(null);
} else {
return null;
}
});})(opts,sketch_size,renderer,features))
;
var mouse_wheel = (cljs.core.truth_(new cljs.core.Keyword(null,"mouse-wheel","mouse-wheel",1811662439).cljs$core$IFn$_invoke$arity$1(opts))?((function (opts,sketch_size,renderer,features,setup){
return (function (){
return new cljs.core.Keyword(null,"mouse-wheel","mouse-wheel",1811662439).cljs$core$IFn$_invoke$arity$1(opts).call(null,((-1) * quil.sketch._STAR_applet_STAR_.mouseScroll));
});})(opts,sketch_size,renderer,features,setup))
:null);
var opts__$1 = cljs.core.assoc.call(null,opts,new cljs.core.Keyword(null,"setup","setup",1987730512),setup,new cljs.core.Keyword(null,"mouse-wheel","mouse-wheel",1811662439),mouse_wheel);
var attach_function = ((function (opts,sketch_size,renderer,features,setup,mouse_wheel,opts__$1){
return (function (prc){
quil.sketch.bind_handlers.call(null,prc,opts__$1);
prc.quil = cljs.core.atom.call(null,null);
return prc.target_frame_rate = cljs.core.atom.call(null,(60));
});})(opts,sketch_size,renderer,features,setup,mouse_wheel,opts__$1))
;
var sketch = (new Processing.Sketch(attach_function));
if(cljs.core.contains_QMARK_.call(null,features,new cljs.core.Keyword(null,"global-key-events","global-key-events",335064944))){
((sketch["options"])["globalKeyEvents"] = true);
} else {
}
return sketch;
});
quil.sketch.destroy_previous_sketch = (function quil$sketch$destroy_previous_sketch(host_elem){
var temp__4657__auto__ = host_elem.processing_obj;
if(cljs.core.truth_(temp__4657__auto__)){
var proc_obj = temp__4657__auto__;
return proc_obj.exit();
} else {
return null;
}
});
quil.sketch.sketch = (function quil$sketch$sketch(var_args){
var args__7934__auto__ = [];
var len__7927__auto___8238 = arguments.length;
var i__7928__auto___8239 = (0);
while(true){
if((i__7928__auto___8239 < len__7927__auto___8238)){
args__7934__auto__.push((arguments[i__7928__auto___8239]));
var G__8240 = (i__7928__auto___8239 + (1));
i__7928__auto___8239 = G__8240;
continue;
} else {
}
break;
}
var argseq__7935__auto__ = ((((0) < args__7934__auto__.length))?(new cljs.core.IndexedSeq(args__7934__auto__.slice((0)),(0),null)):null);
return quil.sketch.sketch.cljs$core$IFn$_invoke$arity$variadic(argseq__7935__auto__);
});
quil.sketch.sketch.cljs$core$IFn$_invoke$arity$variadic = (function (opts){
var opts_map = cljs.core.apply.call(null,cljs.core.hash_map,opts);
var host_elem = goog.dom.getElement(new cljs.core.Keyword(null,"host","host",-1558485167).cljs$core$IFn$_invoke$arity$1(opts_map));
var renderer = (function (){var or__6814__auto__ = new cljs.core.Keyword(null,"renderer","renderer",336841071).cljs$core$IFn$_invoke$arity$1(opts_map);
if(cljs.core.truth_(or__6814__auto__)){
return or__6814__auto__;
} else {
return new cljs.core.Keyword(null,"p2d","p2d",-2106175755);
}
})();
if(cljs.core.truth_(host_elem)){
if(cljs.core.truth_(host_elem.processing_context)){
if(cljs.core._EQ_.call(null,renderer,host_elem.processing_context)){
} else {
console.warn("WARNING: Using different context on one canvas!");
}
} else {
host_elem.processing_context = renderer;
}
quil.sketch.destroy_previous_sketch.call(null,host_elem);
return host_elem.processing_obj = (new Processing(host_elem,quil.sketch.make_sketch.call(null,opts_map)));
} else {
return console.error("ERROR: Cannot create sketch. :host is not specified.");
}
});
quil.sketch.sketch.cljs$lang$maxFixedArity = (0);
quil.sketch.sketch.cljs$lang$applyTo = (function (seq8237){
return quil.sketch.sketch.cljs$core$IFn$_invoke$arity$variadic(cljs.core.seq.call(null,seq8237));
});
quil.sketch.sketch_init_list = cljs.core.atom.call(null,cljs.core.List.EMPTY);
quil.sketch.empty_body_QMARK_ = (function quil$sketch$empty_body_QMARK_(){
var child = document.body.childNodes;
return (child.length <= (1));
});
quil.sketch.add_canvas = (function quil$sketch$add_canvas(canvas_id){
var canvas = document.createElement("canvas");
canvas.setAttribute("id",canvas_id);
return document.body.appendChild(canvas);
});
quil.sketch.init_sketches = (function quil$sketch$init_sketches(){
var add_elem_QMARK__8249 = quil.sketch.empty_body_QMARK_.call(null);
var seq__8245_8250 = cljs.core.seq.call(null,cljs.core.deref.call(null,quil.sketch.sketch_init_list));
var chunk__8246_8251 = null;
var count__8247_8252 = (0);
var i__8248_8253 = (0);
while(true){
if((i__8248_8253 < count__8247_8252)){
var sk_8254 = cljs.core._nth.call(null,chunk__8246_8251,i__8248_8253);
if(cljs.core.truth_(add_elem_QMARK__8249)){
quil.sketch.add_canvas.call(null,new cljs.core.Keyword(null,"host-id","host-id",742376279).cljs$core$IFn$_invoke$arity$1(sk_8254));
} else {
}
new cljs.core.Keyword(null,"fn","fn",-1175266204).cljs$core$IFn$_invoke$arity$1(sk_8254).call(null);
var G__8255 = seq__8245_8250;
var G__8256 = chunk__8246_8251;
var G__8257 = count__8247_8252;
var G__8258 = (i__8248_8253 + (1));
seq__8245_8250 = G__8255;
chunk__8246_8251 = G__8256;
count__8247_8252 = G__8257;
i__8248_8253 = G__8258;
continue;
} else {
var temp__4657__auto___8259 = cljs.core.seq.call(null,seq__8245_8250);
if(temp__4657__auto___8259){
var seq__8245_8260__$1 = temp__4657__auto___8259;
if(cljs.core.chunked_seq_QMARK_.call(null,seq__8245_8260__$1)){
var c__7633__auto___8261 = cljs.core.chunk_first.call(null,seq__8245_8260__$1);
var G__8262 = cljs.core.chunk_rest.call(null,seq__8245_8260__$1);
var G__8263 = c__7633__auto___8261;
var G__8264 = cljs.core.count.call(null,c__7633__auto___8261);
var G__8265 = (0);
seq__8245_8250 = G__8262;
chunk__8246_8251 = G__8263;
count__8247_8252 = G__8264;
i__8248_8253 = G__8265;
continue;
} else {
var sk_8266 = cljs.core.first.call(null,seq__8245_8260__$1);
if(cljs.core.truth_(add_elem_QMARK__8249)){
quil.sketch.add_canvas.call(null,new cljs.core.Keyword(null,"host-id","host-id",742376279).cljs$core$IFn$_invoke$arity$1(sk_8266));
} else {
}
new cljs.core.Keyword(null,"fn","fn",-1175266204).cljs$core$IFn$_invoke$arity$1(sk_8266).call(null);
var G__8267 = cljs.core.next.call(null,seq__8245_8260__$1);
var G__8268 = null;
var G__8269 = (0);
var G__8270 = (0);
seq__8245_8250 = G__8267;
chunk__8246_8251 = G__8268;
count__8247_8252 = G__8269;
i__8248_8253 = G__8270;
continue;
}
} else {
}
}
break;
}
return cljs.core.reset_BANG_.call(null,quil.sketch.sketch_init_list,cljs.core.PersistentVector.EMPTY);
});
quil.sketch.add_sketch_to_init_list = (function quil$sketch$add_sketch_to_init_list(sk){
cljs.core.swap_BANG_.call(null,quil.sketch.sketch_init_list,cljs.core.conj,sk);
if(cljs.core._EQ_.call(null,document.readyState,"complete")){
return quil.sketch.init_sketches.call(null);
} else {
return null;
}
});
goog.events.listenOnce(window,goog.events.EventType.LOAD,quil.sketch.init_sketches);
//# sourceMappingURL=sketch.js.map

File diff suppressed because one or more lines are too long

109
viz/out/quil/util.cljc Normal file
View File

@ -0,0 +1,109 @@
(ns ^{:doc "Utility fns"}
quil.util
(:require [clojure.string :as cstr]))
(defn no-fn
"Function that does nothing."
[])
#?(:clj
(defn callable? [value]
(or (fn? value)
(var? value))))
#?(:clj
(defn absolute-path [path]
(-> (str path)
(java.io.File.)
(.getAbsolutePath))))
#?(:clj
(defn int-like?
[val]
(let [t (type val)]
(or (= java.lang.Long t)
(= java.lang.Integer t)))))
(defn resolve-constant-key
"Returns the val associated with key in mappings or key directly if it
is one of the vals in mappings. Otherwise throws an exception."
[key mappings]
(cond
(get mappings key) (get mappings key)
(some #{key} (vals mappings)) key
:else (throw (#?(:clj Exception.
:cljs js/Error.)
(str "Expecting a keyword, got: " key ". Expected one of: " (vec (sort (keys mappings))))))))
(defn- length-of-longest-key
"Returns the length of the longest key of map m. Assumes m's keys are strings
and returns 0 if map is empty:
(length-of-longest-key {\"foo\" 1 \"barr\" 2 \"bazzz\" 3}) ;=> 5
(length-of-longest-key {}) ;=> 0"
[m]
(or (last (sort (map #(.length %) (keys m))))
0))
(defn- gen-padding
"Generates a padding string starting concatting s with len times pad:
(gen-padding \"\" 5 \"b\") ;=> \"bbbbb\"
May be called without starting string s in which case it defaults to the
empty string and also without pad in which case it defaults to a single space"
([len] (gen-padding "" len " "))
([len pad] (gen-padding "" len pad))
([s len pad]
(if (> len 0)
(gen-padding (str s pad) (dec len) pad)
s)))
(defn print-definition-list
[definitions]
(let [longest-key (length-of-longest-key definitions)]
(dorun
(map (fn [[k v]]
(let [len (.length k)
diff (- longest-key len)
pad (gen-padding diff)]
(println k pad "- " v)))
definitions))))
(defn clj-compilation? []
#?(:clj
(not
(boolean
(when-let [n (find-ns 'cljs.analyzer)]
(when-let [v (ns-resolve n '*cljs-file*)]
@v))))
:cljs false))
(defn prepare-quil-name [const-keyword]
(cstr/replace
(cstr/upper-case (name const-keyword))
#"-" "_"))
(defn prepare-quil-clj-constants [constants]
(into {}
(map
#(vector % (symbol (str "PConstants/" (prepare-quil-name %))))
constants)))
(defn prepare-quil-cljs-constants [constants]
(into {}
(map
#(vector % `(aget js/Processing.prototype.PConstants ~(prepare-quil-name %)))
constants)))
(defn make-quil-constant-map [target const-map-name const-map]
`(def ^{:private true}
~const-map-name
~(if (= target :clj)
(prepare-quil-clj-constants const-map)
(prepare-quil-cljs-constants const-map))))
(defmacro generate-quil-constants [target & opts]
`(do
~@(map
#(make-quil-constant-map target (first %) (second %))
(partition 2 opts))))

File diff suppressed because one or more lines are too long

183
viz/out/quil/util.js Normal file
View File

@ -0,0 +1,183 @@
// Compiled by ClojureScript 1.9.473 {}
goog.provide('quil.util');
goog.require('cljs.core');
goog.require('clojure.string');
/**
* Function that does nothing.
*/
quil.util.no_fn = (function quil$util$no_fn(){
return null;
});
/**
* Returns the val associated with key in mappings or key directly if it
* is one of the vals in mappings. Otherwise throws an exception.
*/
quil.util.resolve_constant_key = (function quil$util$resolve_constant_key(key,mappings){
if(cljs.core.truth_(cljs.core.get.call(null,mappings,key))){
return cljs.core.get.call(null,mappings,key);
} else {
if(cljs.core.truth_(cljs.core.some.call(null,cljs.core.PersistentHashSet.createAsIfByAssoc([key], true),cljs.core.vals.call(null,mappings)))){
return key;
} else {
throw (new Error([cljs.core.str.cljs$core$IFn$_invoke$arity$1("Expecting a keyword, got: "),cljs.core.str.cljs$core$IFn$_invoke$arity$1(key),cljs.core.str.cljs$core$IFn$_invoke$arity$1(". Expected one of: "),cljs.core.str.cljs$core$IFn$_invoke$arity$1(cljs.core.vec.call(null,cljs.core.sort.call(null,cljs.core.keys.call(null,mappings))))].join('')));
}
}
});
/**
* Returns the length of the longest key of map m. Assumes m's keys are strings
* and returns 0 if map is empty:
* (length-of-longest-key {"foo" 1 "barr" 2 "bazzz" 3}) ;=> 5
* (length-of-longest-key {}) ;=> 0
*/
quil.util.length_of_longest_key = (function quil$util$length_of_longest_key(m){
var or__6814__auto__ = cljs.core.last.call(null,cljs.core.sort.call(null,cljs.core.map.call(null,(function (p1__8122_SHARP_){
return p1__8122_SHARP_.length();
}),cljs.core.keys.call(null,m))));
if(cljs.core.truth_(or__6814__auto__)){
return or__6814__auto__;
} else {
return (0);
}
});
/**
* Generates a padding string starting concatting s with len times pad:
* (gen-padding "" 5 "b") ;=> "bbbbb"
* May be called without starting string s in which case it defaults to the
* empty string and also without pad in which case it defaults to a single space
*/
quil.util.gen_padding = (function quil$util$gen_padding(var_args){
var args8123 = [];
var len__7927__auto___8126 = arguments.length;
var i__7928__auto___8127 = (0);
while(true){
if((i__7928__auto___8127 < len__7927__auto___8126)){
args8123.push((arguments[i__7928__auto___8127]));
var G__8128 = (i__7928__auto___8127 + (1));
i__7928__auto___8127 = G__8128;
continue;
} else {
}
break;
}
var G__8125 = args8123.length;
switch (G__8125) {
case 1:
return quil.util.gen_padding.cljs$core$IFn$_invoke$arity$1((arguments[(0)]));
break;
case 2:
return quil.util.gen_padding.cljs$core$IFn$_invoke$arity$2((arguments[(0)]),(arguments[(1)]));
break;
case 3:
return quil.util.gen_padding.cljs$core$IFn$_invoke$arity$3((arguments[(0)]),(arguments[(1)]),(arguments[(2)]));
break;
default:
throw (new Error([cljs.core.str.cljs$core$IFn$_invoke$arity$1("Invalid arity: "),cljs.core.str.cljs$core$IFn$_invoke$arity$1(args8123.length)].join('')));
}
});
quil.util.gen_padding.cljs$core$IFn$_invoke$arity$1 = (function (len){
return quil.util.gen_padding.call(null,"",len," ");
});
quil.util.gen_padding.cljs$core$IFn$_invoke$arity$2 = (function (len,pad){
return quil.util.gen_padding.call(null,"",len,pad);
});
quil.util.gen_padding.cljs$core$IFn$_invoke$arity$3 = (function (s,len,pad){
if((len > (0))){
return quil.util.gen_padding.call(null,[cljs.core.str.cljs$core$IFn$_invoke$arity$1(s),cljs.core.str.cljs$core$IFn$_invoke$arity$1(pad)].join(''),(len - (1)),pad);
} else {
return s;
}
});
quil.util.gen_padding.cljs$lang$maxFixedArity = 3;
quil.util.print_definition_list = (function quil$util$print_definition_list(definitions){
var longest_key = quil.util.length_of_longest_key.call(null,definitions);
return cljs.core.dorun.call(null,cljs.core.map.call(null,((function (longest_key){
return (function (p__8134){
var vec__8135 = p__8134;
var k = cljs.core.nth.call(null,vec__8135,(0),null);
var v = cljs.core.nth.call(null,vec__8135,(1),null);
var len = k.length();
var diff = (longest_key - len);
var pad = quil.util.gen_padding.call(null,diff);
return cljs.core.println.call(null,k,pad,"- ",v);
});})(longest_key))
,definitions));
});
quil.util.clj_compilation_QMARK_ = (function quil$util$clj_compilation_QMARK_(){
return false;
});
quil.util.prepare_quil_name = (function quil$util$prepare_quil_name(const_keyword){
return clojure.string.replace.call(null,clojure.string.upper_case.call(null,cljs.core.name.call(null,const_keyword)),/-/,"_");
});
quil.util.prepare_quil_clj_constants = (function quil$util$prepare_quil_clj_constants(constants){
return cljs.core.into.call(null,cljs.core.PersistentArrayMap.EMPTY,cljs.core.map.call(null,(function (p1__8138_SHARP_){
return (new cljs.core.PersistentVector(null,2,(5),cljs.core.PersistentVector.EMPTY_NODE,[p1__8138_SHARP_,cljs.core.symbol.call(null,[cljs.core.str.cljs$core$IFn$_invoke$arity$1("PConstants/"),cljs.core.str.cljs$core$IFn$_invoke$arity$1(quil.util.prepare_quil_name.call(null,p1__8138_SHARP_))].join(''))],null));
}),constants));
});
quil.util.prepare_quil_cljs_constants = (function quil$util$prepare_quil_cljs_constants(constants){
return cljs.core.into.call(null,cljs.core.PersistentArrayMap.EMPTY,cljs.core.map.call(null,(function (p1__8139_SHARP_){
return (new cljs.core.PersistentVector(null,2,(5),cljs.core.PersistentVector.EMPTY_NODE,[p1__8139_SHARP_,cljs.core.sequence.call(null,cljs.core.seq.call(null,cljs.core.concat.call(null,cljs.core._conj.call(null,cljs.core.List.EMPTY,new cljs.core.Symbol("cljs.core","aget","cljs.core/aget",6345791,null)),cljs.core._conj.call(null,cljs.core.List.EMPTY,new cljs.core.Symbol("js","Processing.prototype.PConstants","js/Processing.prototype.PConstants",2034048972,null)),(function (){var x__7656__auto__ = quil.util.prepare_quil_name.call(null,p1__8139_SHARP_);
return cljs.core._conj.call(null,cljs.core.List.EMPTY,x__7656__auto__);
})())))],null));
}),constants));
});
quil.util.make_quil_constant_map = (function quil$util$make_quil_constant_map(target,const_map_name,const_map){
return cljs.core.sequence.call(null,cljs.core.seq.call(null,cljs.core.concat.call(null,cljs.core._conj.call(null,cljs.core.List.EMPTY,new cljs.core.Symbol(null,"def","def",597100991,null)),(function (){var x__7656__auto__ = const_map_name;
return cljs.core._conj.call(null,cljs.core.List.EMPTY,x__7656__auto__);
})(),(function (){var x__7656__auto__ = ((cljs.core._EQ_.call(null,target,new cljs.core.Keyword(null,"clj","clj",-660495428)))?quil.util.prepare_quil_clj_constants.call(null,const_map):quil.util.prepare_quil_cljs_constants.call(null,const_map));
return cljs.core._conj.call(null,cljs.core.List.EMPTY,x__7656__auto__);
})())));
});
quil.util.generate_quil_constants = (function quil$util$generate_quil_constants(var_args){
var args__7934__auto__ = [];
var len__7927__auto___8145 = arguments.length;
var i__7928__auto___8146 = (0);
while(true){
if((i__7928__auto___8146 < len__7927__auto___8145)){
args__7934__auto__.push((arguments[i__7928__auto___8146]));
var G__8147 = (i__7928__auto___8146 + (1));
i__7928__auto___8146 = G__8147;
continue;
} else {
}
break;
}
var argseq__7935__auto__ = ((((3) < args__7934__auto__.length))?(new cljs.core.IndexedSeq(args__7934__auto__.slice((3)),(0),null)):null);
return quil.util.generate_quil_constants.cljs$core$IFn$_invoke$arity$variadic((arguments[(0)]),(arguments[(1)]),(arguments[(2)]),argseq__7935__auto__);
});
quil.util.generate_quil_constants.cljs$core$IFn$_invoke$arity$variadic = (function (_AMPERSAND_form,_AMPERSAND_env,target,opts){
return cljs.core.sequence.call(null,cljs.core.seq.call(null,cljs.core.concat.call(null,cljs.core._conj.call(null,cljs.core.List.EMPTY,new cljs.core.Symbol(null,"do","do",1686842252,null)),cljs.core.map.call(null,(function (p1__8140_SHARP_){
return quil.util.make_quil_constant_map.call(null,target,cljs.core.first.call(null,p1__8140_SHARP_),cljs.core.second.call(null,p1__8140_SHARP_));
}),cljs.core.partition.call(null,(2),opts)))));
});
quil.util.generate_quil_constants.cljs$lang$maxFixedArity = (3);
quil.util.generate_quil_constants.cljs$lang$applyTo = (function (seq8141){
var G__8142 = cljs.core.first.call(null,seq8141);
var seq8141__$1 = cljs.core.next.call(null,seq8141);
var G__8143 = cljs.core.first.call(null,seq8141__$1);
var seq8141__$2 = cljs.core.next.call(null,seq8141__$1);
var G__8144 = cljs.core.first.call(null,seq8141__$2);
var seq8141__$3 = cljs.core.next.call(null,seq8141__$2);
return quil.util.generate_quil_constants.cljs$core$IFn$_invoke$arity$variadic(G__8142,G__8143,G__8144,seq8141__$3);
});
quil.util.generate_quil_constants.cljs$lang$macro = true;
//# sourceMappingURL=util.js.map

1
viz/out/quil/util.js.map Normal file
View File

@ -0,0 +1 @@
{"version":3,"file":"\/home\/mediocregopher\/src\/viz\/out\/quil\/util.js","sources":["util.cljc"],"lineCount":183,"mappings":";AAAA;;;AAIA;;;kBAAA,lBAAMA;AAAN,AAAA;;AAsBA;;;;iCAAA,jCAAMC,0EAGHC,IAAIC;AAHP,AAIE,oBACE,AAACC,wBAAID,SAASD;AAAgB,OAACE,wBAAID,SAASD;;AAD9C,oBAEE,yBAAA,zBAACG,wEAAOH,YAAK,AAACI,yBAAKH;AAAWD;;AAFhC,AAIgC,MAAO,KAAAK,MAEC,6CAAA,2HAAA,jDAAkCL,oHAA0B,AAACM,wBAAI,AAACC,yBAAK,AAACC,yBAAKP;;;;;AAEvH;;;;;;kCAAA,lCAAOQ,4EAKJC;AALH,AAME,IAAAC,mBAAI,AAACC,yBAAK,AAACL,yBAAK,wBAAA,WAAAM,nCAACC;AAAD,AAAM,OAAAD;GAAY,AAACL,yBAAKE;AAAxC,AAAA,oBAAAC;AAAAA;;AAAA;;;AAGF,AAAA;;;;;;wBAAA,gCAAAI,xDAAOK;AAAP,AAAA,IAAAJ,WAAA;AAAA,AAAA,IAAAC,yBAAA,AAAA;AAAA,AAAA,IAAAC,uBAAA;;AAAA,AAAA,GAAA,CAAAA,uBAAAD;AAAA,AAAA,AAAAD,cAAA,CAAA,UAAAE;;AAAA,cAAA,CAAAA,uBAAA;;;;AAAA;;;;AAAA,IAAAC,UAAA,AAAAH;AAAA,AAAA,QAAAG;KAAA;AAAA,OAAAC,oDAAA,CAAA,UAAA;;;KAAA;AAAA,OAAAA,oDAAA,CAAA,UAAA,MAAA,CAAA,UAAA;;;KAAA;AAAA,OAAAA,oDAAA,CAAA,UAAA,MAAA,CAAA,UAAA,MAAA,CAAA,UAAA;;;;AAAA,MAAA,KAAAf,MAAA,6CAAA,+DAAA,AAAAW;;;;;AAAA,AAAA,sDAAA,tDAAOI,iEAKHC;AALJ,AAKS,uCAAA,OAAA,vCAACD,mCAAeC;;;AALzB,AAAA,sDAAA,tDAAOD,iEAMHC,IAAIC;AANR,AAMa,uCAAA,hCAACF,mCAAeC,IAAIC;;;AANjC,AAAA,sDAAA,tDAAOF,iEAOHG,EAAEF,IAAIC;AAPV,AAQK,GAAI,OAAA,NAAGD;AACL,OAACD,gCAAY,6CAAKG,+CAAED,eAAK,OAAA,NAAKD,WAAKC;;AACnCC;;;;AAVP,AAAA,gDAAA,hDAAOH;;AAAP,AAYA,kCAAA,lCAAMI,4EACHC;AADH,AAEE,IAAMC,cAAY,AAACjB,0CAAsBgB;AAAzC,AACE,OAACE,0BACA,AAACb,wBAAI;kBAAAc;AAAA,AAAA,IAAAC,YAAAD;QAAA,AAAAE,wBAAAD,UAAA,IAAA,1CAAME;QAAN,AAAAD,wBAAAD,UAAA,IAAA,1CAAQG;AAAR,AACE,IAAMX,MAAI,AAASU;IACbE,OAAK,CAAGP,cAAYL;IACpBC,MAAI,AAACF,gCAAYa;AAFvB,AAGE,yCAAA,lCAACC,4BAAQH,EAAET,SAASU;;CACxBP;;AAEV,mCAAA,nCAAMU;AAAN,AAAA;;AAUA,8BAAA,9BAAMC,oEAAmBC;AAAzB,AACE,qHAAA,IAAA,lHAACC,iCACA,AAACC,oCAAgB,AAACC,yBAAKH;;AAG1B,uCAAA,vCAAMI,sFAA4BC;AAAlC,AACE,gCAAA,zBAACC,4DACK,wBAAA,WAAAC,nCAAC9B;AAAD,AACE,YAAA+B,2BAAA,KAAA,EAAA,IAAA,AAAAA,sCAAA,CAAAD,uNAAA,vMAAU,AAACE,2BAAO,6CAAA,2DAAmB,sCAAAF,tCAACR;GACvCM;;AAET,wCAAA,xCAAMK,wFAA6BL;AAAnC,AACE,gCAAA,zBAACC,4DACK,wBAAA,WAAAK,nCAAClC;AAAD,AACE,YAAA+B,2BAAA,KAAA,EAAA,IAAA,AAAAA,sCAAA,CAAAG,gBAAA,AAAAC,6BAAA,AAAAC,wBAAA,AAAAC,2BAAA,AAAAC,+CAAA,wEAAA,AAAAA,+CAAA,mHAAA,iBAAAC,kBAAqD,sCAAAL,tCAACZ,lSAiyE5D,AAAAuC,uHAAA,AAAAA;AAjyEM,AAAA,OAAAvB,+CAAAC,rBAiyEN,AAAAsB;SAjyEM;GACDjC;;AAET,mCAAA,nCAAMY,8EAAwBC,OAAOC,eAAeC;AAApD,AAAA,OAAAR,6BAAA,AAAAC,wBAAA,AAAAC,2BAAA,AAAAC,+CAAA,uDAAA,iBAAAC,kBAEMG,\/GA4xEF,AAAAmB;AA9xEJ,AAAA,OAAAvB,+CAAAC,rBA8xEI,AAAAsB;KA9xEJ,iBAAAtB,kBAGM,EAAI,gCAAA,hCAACK,yBAAEH,4DACL,AAACd,+CAA2BgB,WAC5B,AAACV,gDAA4BU;AALrC,AAAA,OAAAL,+CAAAC,rBA8xEI,AAAAsB;;;AAvxEJ,AAAA,AAAA,oCAAA,4CAAA5D,hFAAU+C;AAAV,AAAA,IAAAH,qBAAA;AAAA,AAAA,IAAA1C,yBAAA,AAAA;AAAA,AAAA,IAAAC,uBAAA;;AAAA,AAAA,GAAA,CAAAA,uBAAAD;AAAA,AAAA,AAAA0C,wBAAA,CAAA,UAAAzC;;AAAA,cAAA,CAAAA,uBAAA;;;;AAAA;;;;AAAA,IAAA0C,uBAAA,EAAA,CAAA,MAAA,AAAAD,4BAAA,AAAA,KAAAE,qBAAA,AAAAF,yBAAA,KAAA,IAAA,OAAA;AAAA,AAAA,OAAAG,uEAAA,CAAA,UAAA,MAAA,CAAA,UAAA,MAAA,CAAA,UAAA,MAAAF;;;AAAA,AAAA,AAAA,AAAAE,yEAAA,WAAAC,gBAAAC,eAAmCT,OAASgB;AAA5C,AAAA,OAAAtB,6BAAA,AAAAC,wBAAA,AAAAC,2BAAA,AAAAC,+CAAA,sDAEO,wBAAA,WAAAoB,nCAAC1D,3EAqxEJ,AAAA6D;AArxEG,AACE,OAACrB,2CAAuBC,OAAO,0BAAAiB,1BAACL,2CAAS,2BAAAK,3BAACC;GAC3C,8BAAA,9BAACC,kCAAYH;;;AAJrB,AAAA,AAAAT,4DAAA;;AAAA,AAAA,AAAAA,sDAAA,WAAAG;AAAA,AAAA,IAAAC,UAAA,AAAAC,0BAAAF;IAAAA,cAAA,AAAAG,yBAAAH;IAAAI,UAAA,AAAAF,0BAAAF;IAAAA,cAAA,AAAAG,yBAAAH;IAAAK,UAAA,AAAAH,0BAAAF;IAAAA,cAAA,AAAAG,yBAAAH;AAAA,AAAA,OAAAH,uEAAAI,QAAAG,QAAAC,QAAAL;;;AAAA;AAAA,AAAA,oDAAA,pDAAUH","names":["quil.util\/no-fn","quil.util\/resolve-constant-key","key","mappings","cljs.core\/get","cljs.core\/some","cljs.core\/vals","js\/Error","cljs.core\/vec","cljs.core\/sort","cljs.core\/keys","quil.util\/length-of-longest-key","m","or__6814__auto__","cljs.core\/last","p1__8122#","cljs.core\/map","var_args","args8123","len__7927__auto__","i__7928__auto__","G__8125","quil.util\/gen-padding","len","pad","s","quil.util\/print-definition-list","definitions","longest-key","cljs.core\/dorun","p__8134","vec__8135","cljs.core\/nth","k","v","diff","cljs.core\/println","quil.util\/clj-compilation?","quil.util\/prepare-quil-name","const-keyword","clojure.string\/replace","clojure.string\/upper-case","cljs.core\/name","quil.util\/prepare-quil-clj-constants","constants","cljs.core\/into","p1__8138#","cljs.core\/PersistentVector","cljs.core\/symbol","quil.util\/prepare-quil-cljs-constants","p1__8139#","cljs.core\/sequence","cljs.core\/seq","cljs.core\/concat","cljs.core\/-conj","x__7656__auto__","quil.util\/make-quil-constant-map","target","const-map-name","const-map","cljs.core\/=","args__7934__auto__","argseq__7935__auto__","cljs.core\/IndexedSeq","quil.util\/generate-quil-constants","&form","&env","seq8141","G__8142","cljs.core\/first","cljs.core\/next","G__8143","G__8144","opts","p1__8140#","cljs.core\/second","cljs.core\/partition","cljs.core\/List"]}

204
viz/out/viz/core.cljs Normal file
View File

@ -0,0 +1,204 @@
(ns viz.core
(:require [quil.core :as q :include-macros true]
[quil.middleware :as m]
[viz.forest :as forest]
[viz.grid :as grid]
[viz.ghost :as ghost]
[goog.string :as gstring]
[goog.string.format]
;[gil.core :as gil]
))
(defn- debug [& args]
(.log js/console (clojure.string/join " " (map str args))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- window-partial [k]
(int (* (aget js/document "documentElement" k) 0.95)))
(def window-size [ (min 1025 (window-partial "clientWidth"))
(int (* (window-partial "clientHeight") 0.75))
])
(def window-half-size (apply vector (map #(float (/ %1 2)) window-size)))
(defn- new-state []
{:frame-rate 15
:exit-wait-frames 40
:tail-length 15
:frame 0
:gif-seconds 0
:grid-width 30 ; from the center
:ghost (-> (ghost/new-ghost grid/euclidean)
(ghost/new-active-node [0 0])
)
})
(defn- curr-second [state]
(float (/ (:frame state) (:frame-rate state))))
(defn- grid-size [state]
(let [h (int (* (window-size 1)
(float (/ (:grid-width state) (window-size 0)))))]
[(:grid-width state) h]))
(defn- positive [n] (if (> 0 n) (- n) n))
(defn- spawn-chance [state]
(let [period-seconds 1
period-frames (* (:frame-rate state) period-seconds)]
(if (zero? (rem (:frame state) period-frames))
1 100)
))
;(let [period-seconds 1
; rad-per-second (float (/ (/ Math/PI 2) period-seconds))
; rad (* rad-per-second (curr-second state))
; chance-raw (positive (q/sin rad))
; ]
; (if (> chance-raw 0.97) 3 50)
; ))
;(defn- mk-poss-fn [state]
; (let [chance (spawn-chance state)]
; (fn [pos adj-poss]
; (if (zero? (rand-int chance))
; adj-poss
; (take 1 (shuffle adj-poss))))
; ))
(defn- mk-poss-fn [state]
(fn [pos adj-poss]
(take 2 (random-sample 0.6 adj-poss))))
(defn setup []
(let [state (new-state)]
(q/frame-rate (:frame-rate state))
state))
(defn- scale [state xy]
(map-indexed #(* %2 (float (/ (window-half-size %1)
((grid-size state) %1)))) xy))
; each bound is a position vector
(defn- in-bounds? [min-bound max-bound pos]
(let [pos-k (keep-indexed #(let [mini (min-bound %1)
maxi (max-bound %1)]
(when (and (>= %2 mini) (<= %2 maxi)) %2)) pos)]
(= (count pos) (count pos-k))))
(defn- quil-bounds [state buffer]
(let [[w h] (apply vector (map #(- % buffer) (grid-size state)))]
[[(- w) (- h)] [w h]]))
(defn- ghost-incr [state]
(assoc state :ghost
(ghost/filter-active-nodes (ghost/incr (:ghost state) (mk-poss-fn state))
#(let [[minb maxb] (quil-bounds state 2)]
(in-bounds? minb maxb (:pos %1))))))
(defn- ghost-expire-roots [state]
(if-not (< (:tail-length state) (:frame state)) state
(update-in state [:ghost] ghost/remove-roots)))
(defn- maybe-exit [state]
(if (empty? (get-in state [:ghost :active-node-ids]))
(if (zero? (:exit-wait-frames state)) (new-state)
(update-in state [:exit-wait-frames] dec))
state))
(defn update-state [state]
(-> state
(ghost-incr)
(ghost-expire-roots)
(update-in [:frame] inc)
(maybe-exit)))
(defn- draw-ellipse [state pos size] ; size is [w h]
(let [scaled-pos (scale state pos)
scaled-size (map int (scale state size))]
(apply q/ellipse (concat scaled-pos scaled-size))))
(defn- in-line? [& nodes]
(apply = (map #(apply map - %1)
(partition 2 1 (map :pos nodes)))))
(defn draw-lines [state forest parent node]
"Draws the lines of all children leading from the node, recursively"
(q/stroke 0xFF000000)
(q/fill 0xFFFFFFFF)
(let [children (map #(forest/get-node forest %) (:child-ids node))]
(if-not parent
(doseq [child children] (draw-lines state forest node child))
(let [in-line-child (some #(if (in-line? parent node %) %) children)
]
(doseq [child children]
(if (and in-line-child (= in-line-child child))
(draw-lines state forest parent child)
(draw-lines state forest node child)))
(when-not in-line-child
(apply q/line (apply concat
(map #(scale state %)
(map :pos (list parent node))))))
))
; we also take the opportunity to draw the leaves
(when (empty? children)
(draw-ellipse state (:pos node) [0.3 0.3]))
))
(defn draw-state [state]
; Clear the sketch by filling it with light-grey color.
(q/background 0xFFFFFFFF)
(q/with-translation [(/ (window-size 0) 2)
(/ (window-size 1) 2)]
(let [lines (forest/lines (get-in state [:ghost :forest]))
leaves (forest/leaves (get-in state [:ghost :forest]))
active (ghost/active-nodes (:ghost state))
roots (forest/roots (get-in state [:ghost :forest]))
]
(q/stroke 0xFF000000)
(doseq [root roots]
(draw-lines state (get-in state [:ghost :forest]) nil root))
(q/stroke 0xFF000000)
(q/fill 0xFF000000)
(doseq [active-node active]
(let [pos (:pos active-node)]
(draw-ellipse state pos [0.35 0.35])
))
))
;(when-not (zero? (:gif-seconds state))
; (let [anim-frames (* (:gif-seconds state) (:frame-rate state))]
; (gil/save-animation "quil.gif" anim-frames 0)
; (when (> (:frame state) anim-frames) (q/exit))))
;(q/text (clojure.string/join
; "\n"
; (list
; (gstring/format "frame:%d" (:frame state))
; (gstring/format "second:%f" (curr-second state))
; (gstring/format "spawn-chance:%d" (spawn-chance state))))
; 30 30)
)
(q/defsketch viz
:title ""
:host "viz"
:size window-size
; setup function called only once, during sketch initialization.
:setup setup
; update-state is called on each iteration before draw-state.
:update update-state
:draw draw-state
:features [:keep-on-top]
; This sketch uses functional-mode middleware.
; Check quil wiki for more info about middlewares and particularly
; fun-mode.
:middleware [m/fun-mode])

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More