summaryrefslogtreecommitdiffstats
path: root/chrome
diff options
context:
space:
mode:
authorrafaelw@chromium.org <rafaelw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-02-02 20:30:32 +0000
committerrafaelw@chromium.org <rafaelw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-02-02 20:30:32 +0000
commit8471424148c07642dee2796673feb2d66be75074 (patch)
tree933001b3e954093fd8b37ea3a52eaef67b7ce980 /chrome
parent43d34dcbf38ed5b68fe94a5782736993dcd84aad (diff)
downloadchromium_src-8471424148c07642dee2796673feb2d66be75074.zip
chromium_src-8471424148c07642dee2796673feb2d66be75074.tar.gz
chromium_src-8471424148c07642dee2796673feb2d66be75074.tar.bz2
Initial cut at irc extension-app example
Review URL: http://codereview.chromium.org/568009 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@37871 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
-rwxr-xr-xchrome/common/extensions/docs/examples/extensions/irc/README.txt29
-rwxr-xr-xchrome/common/extensions/docs/examples/extensions/irc/app/manifest.json10
-rwxr-xr-xchrome/common/extensions/docs/examples/extensions/irc/conf/irc.xml7
-rwxr-xr-xchrome/common/extensions/docs/examples/extensions/irc/conf/jetty.xml197
-rwxr-xr-xchrome/common/extensions/docs/examples/extensions/irc/conf/webdefault.xml404
-rwxr-xr-xchrome/common/extensions/docs/examples/extensions/irc/servlet/WEB-INF/web.xml23
-rwxr-xr-xchrome/common/extensions/docs/examples/extensions/irc/servlet/addChannel.html61
-rwxr-xr-xchrome/common/extensions/docs/examples/extensions/irc/servlet/addServer.html54
-rwxr-xr-xchrome/common/extensions/docs/examples/extensions/irc/servlet/index.html412
-rwxr-xr-xchrome/common/extensions/docs/examples/extensions/irc/servlet/irc.js188
-rwxr-xr-xchrome/common/extensions/docs/examples/extensions/irc/servlet/jstemplate/jsevalcontext.js409
-rwxr-xr-xchrome/common/extensions/docs/examples/extensions/irc/servlet/jstemplate/jstemplate.js1017
-rwxr-xr-xchrome/common/extensions/docs/examples/extensions/irc/servlet/jstemplate/util.js470
-rwxr-xr-xchrome/common/extensions/docs/examples/extensions/irc/servlet/notification.html62
-rwxr-xr-xchrome/common/extensions/docs/examples/extensions/irc/servlet/src/org/chromium/IRCProxyWebSocket.java116
-rwxr-xr-xchrome/common/extensions/docs/examples/extensions/irc/servlet/styles.css161
-rwxr-xr-xchrome/common/extensions/docs/examples/extensions/irc/servlet/util.js19
17 files changed, 3639 insertions, 0 deletions
diff --git a/chrome/common/extensions/docs/examples/extensions/irc/README.txt b/chrome/common/extensions/docs/examples/extensions/irc/README.txt
new file mode 100755
index 0000000..692beb9
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/irc/README.txt
@@ -0,0 +1,29 @@
+This directory contains a simple irc app which is a work in progress.
+
+/app - contains the manifest and any additional resources which are to be
+ packaged in a crx.
+
+/servlet - contains the java servlet which will serve the live resources and
+ also proxy the irc traffic between the client and irc servers
+
+/conf - contains configuration files for running the servlet.
+
+This example depends on WebSockets, so it must be run inside a servlet container
+which supports WebSockets.
+
+The following are instructions for setting up a development jetty server to
+host the servlet.
+
+1) Get the jetty 7.x distribution from eclipse.org. Unpack it anywhere. We'll
+ call that directory JETTY_HOME
+2) Delete the contents of JETTY_HOME/webapps.
+3) Copy /conf/irc.xml to JETTY_HOME/contexts, edit the value of resourceBase in
+ irc.xml to point to the contents of /servlet.
+4) Copy jetty.xml and webdefault.xml to JETTY_HOME/etc
+5) Copy the following jars from JETTY_HOME/lib to /servlet/WEB-INF/lib:
+
+ jetty-client, jetty-continuation, jetty-http, jetty-io, jetty-servlets,
+ jetty-util
+
+6) Compile /servlet/src/org/chromium/IRCProxyWebSocket.java and put the
+ resulting class file in /servlet/WEB-INF/classes
diff --git a/chrome/common/extensions/docs/examples/extensions/irc/app/manifest.json b/chrome/common/extensions/docs/examples/extensions/irc/app/manifest.json
new file mode 100755
index 0000000..ec0d787
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/irc/app/manifest.json
@@ -0,0 +1,10 @@
+{
+ "name": "Chromium IRC App",
+ "version": "0.1",
+ "app": {
+ "launch" : {
+ "url": "http://localhost:8080"
+ },
+ "origins": ["http://localhost:8080"]
+ }
+} \ No newline at end of file
diff --git a/chrome/common/extensions/docs/examples/extensions/irc/conf/irc.xml b/chrome/common/extensions/docs/examples/extensions/irc/conf/irc.xml
new file mode 100755
index 0000000..13441c9
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/irc/conf/irc.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+<Configure class="org.eclipse.jetty.webapp.WebAppContext">
+ <Set name="contextPath">/</Set>
+ <Set name="resourceBase">file:/D:/eclipse/irc-proxy</Set>
+ <Set name="defaultsDescriptor"><SystemProperty name="jetty.home" default="."/>/etc/webdefault.xml</Set>
+</Configure>
diff --git a/chrome/common/extensions/docs/examples/extensions/irc/conf/jetty.xml b/chrome/common/extensions/docs/examples/extensions/irc/conf/jetty.xml
new file mode 100755
index 0000000..5883d61
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/irc/conf/jetty.xml
@@ -0,0 +1,197 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+
+<!-- =============================================================== -->
+<!-- Configure the Jetty Server -->
+<!-- -->
+<!-- Documentation of this file format can be found at: -->
+<!-- http://docs.codehaus.org/display/JETTY/jetty.xml -->
+<!-- -->
+<!-- =============================================================== -->
+
+
+<Configure id="Server" class="org.eclipse.jetty.server.Server">
+
+ <!-- =========================================================== -->
+ <!-- Server Thread Pool -->
+ <!-- =========================================================== -->
+ <Set name="ThreadPool">
+ <!-- Default queued blocking threadpool
+ -->
+ <New class="org.eclipse.jetty.util.thread.QueuedThreadPool">
+ <Set name="minThreads">10</Set>
+ <Set name="maxThreads">200</Set>
+ </New>
+
+ <!-- Optional Java 5 bounded threadpool with job queue
+ <New class="org.eclipse.thread.concurrent.ThreadPool">
+ <Set name="corePoolSize">50</Set>
+ <Set name="maximumPoolSize">50</Set>
+ </New>
+ -->
+ </Set>
+
+
+
+ <!-- =========================================================== -->
+ <!-- Set connectors -->
+ <!-- =========================================================== -->
+
+ <Call name="addConnector">
+ <Arg>
+ <New class="org.eclipse.jetty.server.nio.SelectChannelConnector">
+ <Set name="host"><SystemProperty name="jetty.host" /></Set>
+ <Set name="port"><SystemProperty name="jetty.port" default="8080"/></Set>
+ <Set name="maxIdleTime">300000</Set>
+ <Set name="Acceptors">2</Set>
+ <Set name="statsOn">false</Set>
+ <Set name="confidentialPort">8443</Set>
+ <Set name="lowResourcesConnections">20000</Set>
+ <Set name="lowResourcesMaxIdleTime">5000</Set>
+ </New>
+ </Arg>
+ </Call>
+
+ <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+ <!-- To add a HTTPS SSL connector -->
+ <!-- mixin jetty-ssl.xml: -->
+ <!-- java -jar start.jar etc/jetty.xml etc/jetty-ssl.xml -->
+ <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+ <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+ <!-- To add a HTTP blocking connector -->
+ <!-- mixin jetty-bio.xml: -->
+ <!-- java -jar start.jar etc/jetty.xml etc/jetty-bio.xml -->
+ <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+ <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+ <!-- To allow Jetty to be started from xinetd -->
+ <!-- mixin jetty-xinetd.xml: -->
+ <!-- java -jar start.jar etc/jetty.xml etc/jetty-xinetd.xml -->
+ <!-- -->
+ <!-- See jetty-xinetd.xml for further instructions. -->
+ <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+
+ <!-- =========================================================== -->
+ <!-- Set handler Collection Structure -->
+ <!-- =========================================================== -->
+ <Set name="handler">
+ <New id="Handlers" class="org.eclipse.jetty.server.handler.HandlerCollection">
+ <Set name="handlers">
+ <Array type="org.eclipse.jetty.server.Handler">
+ <Item>
+ <New id="Contexts" class="org.eclipse.jetty.server.handler.ContextHandlerCollection"/>
+ </Item>
+ <Item>
+ <New id="DefaultHandler" class="org.eclipse.jetty.server.handler.DefaultHandler"/>
+ </Item>
+ <Item>
+ <New id="RequestLog" class="org.eclipse.jetty.server.handler.RequestLogHandler"/>
+ </Item>
+ </Array>
+ </Set>
+ </New>
+ </Set>
+
+ <!-- =========================================================== -->
+ <!-- Configure the context deployer -->
+ <!-- A context deployer will deploy contexts described in -->
+ <!-- configuration files discovered in a directory. -->
+ <!-- The configuration directory can be scanned for hot -->
+ <!-- deployments at the configured scanInterval. -->
+ <!-- -->
+ <!-- This deployer is configured to deploy contexts configured -->
+ <!-- in the $JETTY_HOME/contexts directory -->
+ <!-- -->
+ <!-- =========================================================== -->
+ <Call name="addBean">
+ <Arg>
+ <New class="org.eclipse.jetty.deploy.ContextDeployer">
+ <Set name="contexts"><Ref id="Contexts"/></Set>
+ <Set name="configurationDir"><SystemProperty name="jetty.home" default="."/>/contexts</Set>
+ <Set name="scanInterval">5</Set>
+ <Call name="setAttribute">
+ <Arg>org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern</Arg>
+ <Arg>.*/jsp-api-[^/]*\.jar$|.*/jsp-[^/]*\.jar$</Arg>
+ </Call>
+ </New>
+ </Arg>
+ </Call>
+
+ <!-- =========================================================== -->
+ <!-- Configure the webapp deployer. -->
+ <!-- A webapp deployer will deploy standard webapps discovered -->
+ <!-- in a directory at startup, without the need for additional -->
+ <!-- configuration files. It does not support hot deploy or -->
+ <!-- non standard contexts (see ContextDeployer above). -->
+ <!-- -->
+ <!-- This deployer is configured to deploy webapps from the -->
+ <!-- $JETTY_HOME/webapps directory -->
+ <!-- -->
+ <!-- Normally only one type of deployer need be used. -->
+ <!-- -->
+ <!-- =========================================================== -->
+ <Call name="addBean">
+ <Arg>
+ <New class="org.eclipse.jetty.deploy.WebAppDeployer">
+ <Set name="contexts"><Ref id="Contexts"/></Set>
+ <Set name="webAppDir"><SystemProperty name="jetty.home" default="."/>/webapps</Set>
+ <Set name="parentLoaderPriority">false</Set>
+ <Set name="extract">true</Set>
+ <Set name="allowDuplicates">false</Set>
+ <Set name="defaultsDescriptor"><SystemProperty name="jetty.home" default="."/>/etc/webdefault.xml</Set>
+ <Call name="setAttribute">
+ <Arg>org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern</Arg>
+ <Arg>.*/jsp-api-[^/]*\.jar$|.*/jsp-[^/]*\.jar$</Arg>
+ </Call>
+ </New>
+ </Arg>
+ </Call>
+
+ <!-- =========================================================== -->
+ <!-- Configure Authentication Login Service -->
+ <!-- Realms may be configured for the entire server here, or -->
+ <!-- they can be configured for a specific web app in a context -->
+ <!-- configuration (see $(jetty.home)/contexts/test.xml for an -->
+ <!-- example). -->
+ <!-- =========================================================== -->
+ <Call name="addBean">
+ <Arg>
+ <New class="org.eclipse.jetty.security.HashLoginService">
+ <Set name="name">Test Realm</Set>
+ <Set name="config"><SystemProperty name="jetty.home" default="."/>/etc/realm.properties</Set>
+ <Set name="refreshInterval">0</Set>
+ </New>
+ </Arg>
+ </Call>
+
+ <!-- =========================================================== -->
+ <!-- Configure Request Log -->
+ <!-- Request logs may be configured for the entire server here, -->
+ <!-- or they can be configured for a specific web app in a -->
+ <!-- contexts configuration (see $(jetty.home)/contexts/test.xml -->
+ <!-- for an example). -->
+ <!-- =========================================================== -->
+ <Ref id="RequestLog">
+ <Set name="requestLog">
+ <New id="RequestLogImpl" class="org.eclipse.jetty.server.NCSARequestLog">
+ <Set name="filename"><SystemProperty name="jetty.home" default="."/>/logs/yyyy_mm_dd.request.log</Set>
+ <Set name="filenameDateFormat">yyyy_MM_dd</Set>
+ <Set name="retainDays">90</Set>
+ <Set name="append">true</Set>
+ <Set name="extended">false</Set>
+ <Set name="logCookies">false</Set>
+ <Set name="LogTimeZone">GMT</Set>
+ </New>
+ </Set>
+ </Ref>
+
+ <!-- =========================================================== -->
+ <!-- extra options -->
+ <!-- =========================================================== -->
+ <Set name="stopAtShutdown">true</Set>
+ <Set name="sendServerVersion">true</Set>
+ <Set name="sendDateHeader">true</Set>
+ <Set name="gracefulShutdown">1000</Set>
+
+</Configure>
diff --git a/chrome/common/extensions/docs/examples/extensions/irc/conf/webdefault.xml b/chrome/common/extensions/docs/examples/extensions/irc/conf/webdefault.xml
new file mode 100755
index 0000000..b52cadd
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/irc/conf/webdefault.xml
@@ -0,0 +1,404 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+
+<!-- ===================================================================== -->
+<!-- This file contains the default descriptor for web applications. -->
+<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+<!-- The intent of this descriptor is to include jetty specific or common -->
+<!-- configuration for all webapps. If a context has a webdefault.xml -->
+<!-- descriptor, it is applied before the contexts own web.xml file -->
+<!-- -->
+<!-- A context may be assigned a default descriptor by: -->
+<!-- + Calling WebApplicationContext.setDefaultsDescriptor -->
+<!-- + Passed an arg to addWebApplications -->
+<!-- -->
+<!-- This file is used both as the resource within the jetty.jar (which is -->
+<!-- used as the default if no explicit defaults descriptor is set) and it -->
+<!-- is copied to the etc directory of the Jetty distro and explicitly -->
+<!-- by the jetty.xml file. -->
+<!-- -->
+<!-- ===================================================================== -->
+<web-app
+ xmlns="http://java.sun.com/xml/ns/javaee"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+ metadata-complete="true"
+ version="2.5">
+
+ <description>
+ Default web.xml file.
+ This file is applied to a Web application before it's own WEB_INF/web.xml file
+ </description>
+
+
+ <!-- ==================================================================== -->
+ <!-- Context params to control Session Cookies -->
+ <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+ <!-- UNCOMMENT TO ACTIVATE
+ <context-param>
+ <param-name>org.eclipse.jetty.servlet.SessionDomain</param-name>
+ <param-value>127.0.0.1</param-value>
+ </context-param>
+
+ <context-param>
+ <param-name>org.eclipse.jetty.servlet.SessionPath</param-name>
+ <param-value>/</param-value>
+ </context-param>
+
+ <context-param>
+ <param-name>org.eclipse.jetty.servlet.MaxAge</param-name>
+ <param-value>-1</param-value>
+ </context-param>
+ -->
+
+
+ <!-- ==================================================================== -->
+ <!-- The default servlet. -->
+ <!-- This servlet, normally mapped to /, provides the handling for static -->
+ <!-- content, OPTIONS and TRACE methods for the context. -->
+ <!-- The following initParameters are supported: -->
+ <!-- -->
+ <!-- acceptRanges If true, range requests and responses are -->
+ <!-- supported -->
+ <!-- -->
+ <!-- dirAllowed If true, directory listings are returned if no -->
+ <!-- welcome file is found. Else 403 Forbidden. -->
+ <!-- -->
+ <!-- welcomeServlets If true, attempt to dispatch to welcome files -->
+ <!-- that are servlets, if no matching static -->
+ <!-- resources can be found. -->
+ <!-- -->
+ <!-- redirectWelcome If true, redirect welcome file requests -->
+ <!-- else use request dispatcher forwards -->
+ <!-- -->
+ <!-- gzip If set to true, then static content will be served-->
+ <!-- as gzip content encoded if a matching resource is -->
+ <!-- found ending with ".gz" -->
+ <!-- -->
+ <!-- resoureBase Can be set to replace the context resource base -->
+ <!-- -->
+ <!-- relativeResourceBase -->
+ <!-- Set with a pathname relative to the base of the -->
+ <!-- servlet context root. Useful for only serving -->
+ <!-- static content from only specific subdirectories. -->
+ <!-- -->
+ <!-- useFileMappedBuffer -->
+ <!-- If set to true (the default), a memory mapped -->
+ <!-- file buffer will be used to serve static content -->
+ <!-- when using an NIO connector. Setting this value -->
+ <!-- to false means that a direct buffer will be used -->
+ <!-- instead. If you are having trouble with Windows -->
+ <!-- file locking, set this to false. -->
+ <!-- -->
+ <!-- cacheControl If set, all static content will have this value -->
+ <!-- set as the cache-control header. -->
+ <!-- -->
+ <!-- maxCacheSize Maximum size of the static resource cache -->
+ <!-- -->
+ <!-- maxCachedFileSize Maximum size of any single file in the cache -->
+ <!-- -->
+ <!-- maxCachedFiles Maximum number of files in the cache -->
+ <!-- -->
+ <!-- cacheType "nio", "bio" or "both" to determine the type(s) -->
+ <!-- of resource cache. A bio cached buffer may be used-->
+ <!-- by nio but is not as efficient as a nio buffer. -->
+ <!-- An nio cached buffer may not be used by bio. -->
+ <!-- -->
+ <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+ <servlet>
+ <servlet-name>default</servlet-name>
+ <servlet-class>org.eclipse.jetty.servlet.DefaultServlet</servlet-class>
+ <init-param>
+ <param-name>acceptRanges</param-name>
+ <param-value>true</param-value>
+ </init-param>
+ <init-param>
+ <param-name>dirAllowed</param-name>
+ <param-value>true</param-value>
+ </init-param>
+ <init-param>
+ <param-name>welcomeServlets</param-name>
+ <param-value>false</param-value>
+ </init-param>
+ <init-param>
+ <param-name>redirectWelcome</param-name>
+ <param-value>false</param-value>
+ </init-param>
+ <init-param>
+ <param-name>maxCacheSize</param-name>
+ <param-value>256000000</param-value>
+ </init-param>
+ <init-param>
+ <param-name>maxCachedFileSize</param-name>
+ <param-value>10000000</param-value>
+ </init-param>
+ <init-param>
+ <param-name>maxCachedFiles</param-name>
+ <param-value>1000</param-value>
+ </init-param>
+ <init-param>
+ <param-name>cacheType</param-name>
+ <param-value>both</param-value>
+ </init-param>
+ <init-param>
+ <param-name>gzip</param-name>
+ <param-value>true</param-value>
+ </init-param>
+ <init-param>
+ <param-name>useFileMappedBuffer</param-name>
+ <param-value>false</param-value>
+ </init-param>
+ <!--
+ <init-param>
+ <param-name>cacheControl</param-name>
+ <param-value>max-age=3600,public</param-value>
+ </init-param>
+ -->
+ <load-on-startup>0</load-on-startup>
+ </servlet>
+
+ <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
+
+
+ <!-- ==================================================================== -->
+ <!-- JSP Servlet -->
+ <!-- This is the jasper JSP servlet from the jakarta project -->
+ <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+ <!-- The JSP page compiler and execution servlet, which is the mechanism -->
+ <!-- used by Glassfish to support JSP pages. Traditionally, this servlet -->
+ <!-- is mapped to URL patterh "*.jsp". This servlet supports the -->
+ <!-- following initialization parameters (default values are in square -->
+ <!-- brackets): -->
+ <!-- -->
+ <!-- checkInterval If development is false and reloading is true, -->
+ <!-- background compiles are enabled. checkInterval -->
+ <!-- is the time in seconds between checks to see -->
+ <!-- if a JSP page needs to be recompiled. [300] -->
+ <!-- -->
+ <!-- compiler Which compiler Ant should use to compile JSP -->
+ <!-- pages. See the Ant documenation for more -->
+ <!-- information. [javac] -->
+ <!-- -->
+ <!-- classdebuginfo Should the class file be compiled with -->
+ <!-- debugging information? [true] -->
+ <!-- -->
+ <!-- classpath What class path should I use while compiling -->
+ <!-- generated servlets? [Created dynamically -->
+ <!-- based on the current web application] -->
+ <!-- Set to ? to make the container explicitly set -->
+ <!-- this parameter. -->
+ <!-- -->
+ <!-- development Is Jasper used in development mode (will check -->
+ <!-- for JSP modification on every access)? [true] -->
+ <!-- -->
+ <!-- enablePooling Determines whether tag handler pooling is -->
+ <!-- enabled [true] -->
+ <!-- -->
+ <!-- fork Tell Ant to fork compiles of JSP pages so that -->
+ <!-- a separate JVM is used for JSP page compiles -->
+ <!-- from the one Tomcat is running in. [true] -->
+ <!-- -->
+ <!-- ieClassId The class-id value to be sent to Internet -->
+ <!-- Explorer when using <jsp:plugin> tags. -->
+ <!-- [clsid:8AD9C840-044E-11D1-B3E9-00805F499D93] -->
+ <!-- -->
+ <!-- javaEncoding Java file encoding to use for generating java -->
+ <!-- source files. [UTF-8] -->
+ <!-- -->
+ <!-- keepgenerated Should we keep the generated Java source code -->
+ <!-- for each page instead of deleting it? [true] -->
+ <!-- -->
+ <!-- logVerbosityLevel The level of detailed messages to be produced -->
+ <!-- by this servlet. Increasing levels cause the -->
+ <!-- generation of more messages. Valid values are -->
+ <!-- FATAL, ERROR, WARNING, INFORMATION, and DEBUG. -->
+ <!-- [WARNING] -->
+ <!-- -->
+ <!-- mappedfile Should we generate static content with one -->
+ <!-- print statement per input line, to ease -->
+ <!-- debugging? [false] -->
+ <!-- -->
+ <!-- -->
+ <!-- reloading Should Jasper check for modified JSPs? [true] -->
+ <!-- -->
+ <!-- suppressSmap Should the generation of SMAP info for JSR45 -->
+ <!-- debugging be suppressed? [false] -->
+ <!-- -->
+ <!-- dumpSmap Should the SMAP info for JSR45 debugging be -->
+ <!-- dumped to a file? [false] -->
+ <!-- False if suppressSmap is true -->
+ <!-- -->
+ <!-- scratchdir What scratch directory should we use when -->
+ <!-- compiling JSP pages? [default work directory -->
+ <!-- for the current web application] -->
+ <!-- -->
+ <!-- tagpoolMaxSize The maximum tag handler pool size [5] -->
+ <!-- -->
+ <!-- xpoweredBy Determines whether X-Powered-By response -->
+ <!-- header is added by generated servlet [false] -->
+ <!-- -->
+ <!-- If you wish to use Jikes to compile JSP pages: -->
+ <!-- Set the init parameter "compiler" to "jikes". Define -->
+ <!-- the property "-Dbuild.compiler.emacs=true" when starting Jetty -->
+ <!-- to cause Jikes to emit error messages in a format compatible with -->
+ <!-- Jasper. -->
+ <!-- If you get an error reporting that jikes can't use UTF-8 encoding, -->
+ <!-- try setting the init parameter "javaEncoding" to "ISO-8859-1". -->
+ <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+ <servlet id="jsp">
+ <servlet-name>jsp</servlet-name>
+ <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
+ <init-param>
+ <param-name>logVerbosityLevel</param-name>
+ <param-value>DEBUG</param-value>
+ </init-param>
+ <init-param>
+ <param-name>fork</param-name>
+ <param-value>false</param-value>
+ </init-param>
+ <init-param>
+ <param-name>xpoweredBy</param-name>
+ <param-value>false</param-value>
+ </init-param>
+ <!--
+ <init-param>
+ <param-name>classpath</param-name>
+ <param-value>?</param-value>
+ </init-param>
+ -->
+ <load-on-startup>0</load-on-startup>
+ </servlet>
+
+ <servlet-mapping>
+ <servlet-name>jsp</servlet-name>
+ <url-pattern>*.jsp</url-pattern>
+ <url-pattern>*.jspf</url-pattern>
+ <url-pattern>*.jspx</url-pattern>
+ <url-pattern>*.xsp</url-pattern>
+ <url-pattern>*.JSP</url-pattern>
+ <url-pattern>*.JSPF</url-pattern>
+ <url-pattern>*.JSPX</url-pattern>
+ <url-pattern>*.XSP</url-pattern>
+ </servlet-mapping>
+
+ <!-- ==================================================================== -->
+ <!-- Dynamic Servlet Invoker. -->
+ <!-- This servlet invokes anonymous servlets that have not been defined -->
+ <!-- in the web.xml or by other means. The first element of the pathInfo -->
+ <!-- of a request passed to the envoker is treated as a servlet name for -->
+ <!-- an existing servlet, or as a class name of a new servlet. -->
+ <!-- This servlet is normally mapped to /servlet/* -->
+ <!-- This servlet support the following initParams: -->
+ <!-- -->
+ <!-- nonContextServlets If false, the invoker can only load -->
+ <!-- servlets from the contexts classloader. -->
+ <!-- This is false by default and setting this -->
+ <!-- to true may have security implications. -->
+ <!-- -->
+ <!-- verbose If true, log dynamic loads -->
+ <!-- -->
+ <!-- * All other parameters are copied to the -->
+ <!-- each dynamic servlet as init parameters -->
+ <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+ <!-- Uncomment for dynamic invocation
+ <servlet>
+ <servlet-name>invoker</servlet-name>
+ <servlet-class>org.eclipse.jetty.servlet.Invoker</servlet-class>
+ <init-param>
+ <param-name>verbose</param-name>
+ <param-value>false</param-value>
+ </init-param>
+ <init-param>
+ <param-name>nonContextServlets</param-name>
+ <param-value>false</param-value>
+ </init-param>
+ <init-param>
+ <param-name>dynamicParam</param-name>
+ <param-value>anyValue</param-value>
+ </init-param>
+ <load-on-startup>0</load-on-startup>
+ </servlet>
+
+ <servlet-mapping> <servlet-name>invoker</servlet-name> <url-pattern>/servlet/*</url-pattern> </servlet-mapping>
+ -->
+
+
+
+ <!-- ==================================================================== -->
+ <session-config>
+ <session-timeout>30</session-timeout>
+ </session-config>
+
+ <!-- ==================================================================== -->
+ <!-- Default MIME mappings -->
+ <!-- The default MIME mappings are provided by the mime.properties -->
+ <!-- resource in the org.eclipse.jetty.server.jar file. Additional or modified -->
+ <!-- mappings may be specified here -->
+ <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+ <!-- UNCOMMENT TO ACTIVATE
+ <mime-mapping>
+ <extension>mysuffix</extension>
+ <mime-type>mymime/type</mime-type>
+ </mime-mapping>
+ -->
+
+ <!-- ==================================================================== -->
+ <welcome-file-list>
+ <welcome-file>index.html</welcome-file>
+ <welcome-file>index.htm</welcome-file>
+ <welcome-file>index.jsp</welcome-file>
+ </welcome-file-list>
+
+ <!-- ==================================================================== -->
+ <locale-encoding-mapping-list>
+ <locale-encoding-mapping><locale>ar</locale><encoding>ISO-8859-6</encoding></locale-encoding-mapping>
+ <locale-encoding-mapping><locale>be</locale><encoding>ISO-8859-5</encoding></locale-encoding-mapping>
+ <locale-encoding-mapping><locale>bg</locale><encoding>ISO-8859-5</encoding></locale-encoding-mapping>
+ <locale-encoding-mapping><locale>ca</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
+ <locale-encoding-mapping><locale>cs</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
+ <locale-encoding-mapping><locale>da</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
+ <locale-encoding-mapping><locale>de</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
+ <locale-encoding-mapping><locale>el</locale><encoding>ISO-8859-7</encoding></locale-encoding-mapping>
+ <locale-encoding-mapping><locale>en</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
+ <locale-encoding-mapping><locale>es</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
+ <locale-encoding-mapping><locale>et</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
+ <locale-encoding-mapping><locale>fi</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
+ <locale-encoding-mapping><locale>fr</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
+ <locale-encoding-mapping><locale>hr</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
+ <locale-encoding-mapping><locale>hu</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
+ <locale-encoding-mapping><locale>is</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
+ <locale-encoding-mapping><locale>it</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
+ <locale-encoding-mapping><locale>iw</locale><encoding>ISO-8859-8</encoding></locale-encoding-mapping>
+ <locale-encoding-mapping><locale>ja</locale><encoding>Shift_JIS</encoding></locale-encoding-mapping>
+ <locale-encoding-mapping><locale>ko</locale><encoding>EUC-KR</encoding></locale-encoding-mapping>
+ <locale-encoding-mapping><locale>lt</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
+ <locale-encoding-mapping><locale>lv</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
+ <locale-encoding-mapping><locale>mk</locale><encoding>ISO-8859-5</encoding></locale-encoding-mapping>
+ <locale-encoding-mapping><locale>nl</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
+ <locale-encoding-mapping><locale>no</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
+ <locale-encoding-mapping><locale>pl</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
+ <locale-encoding-mapping><locale>pt</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
+ <locale-encoding-mapping><locale>ro</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
+ <locale-encoding-mapping><locale>ru</locale><encoding>ISO-8859-5</encoding></locale-encoding-mapping>
+ <locale-encoding-mapping><locale>sh</locale><encoding>ISO-8859-5</encoding></locale-encoding-mapping>
+ <locale-encoding-mapping><locale>sk</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
+ <locale-encoding-mapping><locale>sl</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
+ <locale-encoding-mapping><locale>sq</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
+ <locale-encoding-mapping><locale>sr</locale><encoding>ISO-8859-5</encoding></locale-encoding-mapping>
+ <locale-encoding-mapping><locale>sv</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
+ <locale-encoding-mapping><locale>tr</locale><encoding>ISO-8859-9</encoding></locale-encoding-mapping>
+ <locale-encoding-mapping><locale>uk</locale><encoding>ISO-8859-5</encoding></locale-encoding-mapping>
+ <locale-encoding-mapping><locale>zh</locale><encoding>GB2312</encoding></locale-encoding-mapping>
+ <locale-encoding-mapping><locale>zh_TW</locale><encoding>Big5</encoding></locale-encoding-mapping>
+ </locale-encoding-mapping-list>
+
+ <security-constraint>
+ <web-resource-collection>
+ <web-resource-name>Disable TRACE</web-resource-name>
+ <url-pattern>/</url-pattern>
+ <http-method>TRACE</http-method>
+ </web-resource-collection>
+ <auth-constraint/>
+ </security-constraint>
+
+</web-app>
+
diff --git a/chrome/common/extensions/docs/examples/extensions/irc/servlet/WEB-INF/web.xml b/chrome/common/extensions/docs/examples/extensions/irc/servlet/WEB-INF/web.xml
new file mode 100755
index 0000000..151b76d
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/irc/servlet/WEB-INF/web.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<web-app
+ xmlns="http://java.sun.com/xml/ns/javaee"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+ version="2.5">
+ <display-name>IRC-Proxy</display-name>
+ <context-param>
+ <param-name>org.eclipse.jetty.server.context.ManagedAttributes</param-name>
+ <param-value>org.eclipse.jetty.servlets.ProxyServlet.Logger,org.eclipse.jetty.servlets.ProxyServlet.ThreadPool,org.eclipse.jetty.servlets.ProxyServlet.HttpClient</param-value>
+ </context-param>
+ <servlet>
+ <servlet-name>irc-proxy</servlet-name>
+ <servlet-class>org.chromium.IRCProxyWebSocket</servlet-class>
+ <load-on-startup>1</load-on-startup>
+ </servlet>
+ <servlet-mapping>
+ <servlet-name>irc-proxy</servlet-name>
+ <url-pattern>/ws/*</url-pattern>
+ </servlet-mapping>
+</web-app>
+
+
diff --git a/chrome/common/extensions/docs/examples/extensions/irc/servlet/addChannel.html b/chrome/common/extensions/docs/examples/extensions/irc/servlet/addChannel.html
new file mode 100755
index 0000000..5007b09
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/irc/servlet/addChannel.html
@@ -0,0 +1,61 @@
+<html>
+ <head>
+ <link rel="stylesheet" type="text/css" href="styles.css">
+ <script src="jstemplate/util.js" type="text/javascript"></script>
+ <script src="jstemplate/jsevalcontext.js" type="text/javascript"></script>
+ <script src="jstemplate/jstemplate.js" type="text/javascript"></script>
+ <script src="util.js" type="text/javascript"></script>
+ <script>
+function addChannel() {
+ try {
+ var servers = JSON.parse(localStorage.servers || "[]");
+ var channelName = $F('channel');
+ var serverName = $F('serverSelect')
+ servers.forEach(function(server) {
+ if (server.name == serverName) {
+ server.channels = server.channels || [];
+ server.channels.forEach(function(channel) {
+ if (channel == channelName) {
+ throw channelName + " is already open";
+ }
+ });
+ server.channels.push(channelName);
+ }
+ });
+
+ localStorage.servers = JSON.stringify(servers);
+ window.opener.syncChannelList();
+ window.opener.joinChannel(serverName, channelName);
+ window.close();
+ } catch (ex) {
+ alert(ex);
+ }
+}
+
+window.onload = function() {
+ var servers = JSON.parse(localStorage.servers || "[]");
+ if (servers.length == 0) {
+ alert("You must first add a server connection");
+ close();
+ }
+
+ jstProcess(new JsEvalContext(servers), $('serverSelect'));
+}
+
+ </script>
+ </head>
+ <body>
+ <div>
+ <select id="serverSelect">
+ <option jsselect="$this" jscontent="name"></option>
+ </select>
+ <input id="channel" type="text" value="#channel">
+ </div>
+ <div>
+ </div>
+ <div>
+ <input type="button" value="Add New Channel"
+ onclick="addChannel();">
+ </div>
+ </body>
+</html> \ No newline at end of file
diff --git a/chrome/common/extensions/docs/examples/extensions/irc/servlet/addServer.html b/chrome/common/extensions/docs/examples/extensions/irc/servlet/addServer.html
new file mode 100755
index 0000000..1fa6b2d
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/irc/servlet/addServer.html
@@ -0,0 +1,54 @@
+<html>
+ <head>
+ <link rel="stylesheet" type="text/css" href="styles.css">
+ <script src="util.js" type="text/javascript"></script>
+ <script>
+function addServer() {
+ try {
+ var servers = JSON.parse(localStorage.servers || "[]");
+ var serverName = $F('serverText');
+
+ servers.forEach(function(server) {
+ if (server.name == serverName) {
+ throw "Connection to " + serverName + " already established";
+ }
+ });
+
+ var portValue = parseInt($F('serverPort'));
+ if (isNaN(portValue)) {
+ throw $F('serverPort') + " is not a valid port value";
+ }
+
+ var nickValue = $F('nick');
+ var newServer = {
+ name: serverName,
+ port: portValue,
+ nick: nickValue,
+ channels: []
+ };
+
+ servers.push(newServer);
+
+ localStorage.servers = JSON.stringify(servers);
+ window.opener.addServerConnection(newServer);
+ close();
+ } catch (ex) {
+ alert(ex);
+ }
+}
+ </script>
+ </head>
+ <body>
+ <div>
+ <input id="serverText" type="text" value="irc.freenode.net">
+ <input id="serverPort" type="text" value="6667">
+ </div>
+ <div>
+ <input id="nick" type="text" value="nick">
+ </div>
+ <div>
+ <input type="button" value="Add New Server"
+ onclick="addServer();">
+ </div>
+ </body>
+</html> \ No newline at end of file
diff --git a/chrome/common/extensions/docs/examples/extensions/irc/servlet/index.html b/chrome/common/extensions/docs/examples/extensions/irc/servlet/index.html
new file mode 100755
index 0000000..f5fff2b
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/irc/servlet/index.html
@@ -0,0 +1,412 @@
+<html>
+ <head>
+ <title>ChromiumIRC</title>
+ <link rel="stylesheet" type="text/css" href="styles.css">
+ <script src="jstemplate/util.js" type="text/javascript"></script>
+ <script src="jstemplate/jsevalcontext.js" type="text/javascript"></script>
+ <script src="jstemplate/jstemplate.js" type="text/javascript"></script>
+ <script src="util.js" type="text/javascript"></script>
+ <script lang="JavaScript" src="irc.js"></script>
+ <script>
+
+var ircConnections = {};
+
+// The server & channel configutation data is stored in localStorage.servers.
+// These are setters and getters for this structure.
+function servers() {
+ return JSON.parse(localStorage.servers || "[]");
+}
+function setServers(servers) {
+ localStorage.servers = JSON.stringify(servers);
+}
+
+// Channel list is a sorted list of "server#channel" strings. This maps to
+// channel slides as represented in the UI.
+function channelList() {
+ var channelList = [];
+ servers().forEach(function(server) {
+ server.channels = server.channels || [];
+ server.channels.forEach(function(channel) {
+ channelList.push(server.name + channel);
+ });
+ });
+
+ channelList.sort();
+ return channelList;
+}
+
+window.onload = function() {
+ // Setup notifications.
+ window.onfocus = function() {
+ windowHasFocus = true;
+ clearNotifications();
+ }
+ window.onblur = function() {
+ windowHasFocus = false;
+ }
+
+ syncChannelList();
+
+ // Setup channel navigation and message entry.
+ function handleBodyKeyDown(event) {
+ switch (event.keyCode) {
+ case 37: // left arrow
+ slideTo(-1);
+ break;
+ case 39: // right arrow
+ slideTo(1);
+ break;
+ }
+ }
+ document.body.addEventListener('keydown', handleBodyKeyDown, false);
+
+ // We don't want left & right arrow inside the text entry to move the channel
+ // slides.
+ $('typingDiv').addEventListener('keydown', function(event) {
+ event.stopPropagation();
+ });
+ $('entryText').addEventListener('keydown', function(event) {
+ if (event.keyCode == 13) { // RETURN key.
+ processEntryMessage();
+ }
+ });
+
+ servers().forEach(addServerConnection);
+};
+
+window.onunload = function() {
+ ircConnections.forEach(function(irc) {
+ irc.disconnect();
+ });
+}
+
+function addServerConnection(server) {
+ var ws = new WebSocket("ws://" + location.host + "/ws");
+ var irc = new IRCConnection(server.name, server.port, server.nick,
+ bind(ws, ws.send), // sendFunc
+ bind(ws, ws.close)); // closeFunc
+ ws.onopen = bind(irc, irc.onOpened);
+ ws.onclose = bind(irc, irc.onClosed);
+ ws.onmessage = function(message) {
+ irc.onMessage(message.data);
+ };
+ irc.onConnect = function(message) {
+ server.channels.forEach(function(channel) {
+ ircConnections[server.name].joinChannel(channel);
+ });
+ };
+ irc.onDisconnect = function(message) {
+ };
+ irc.onText = function(channel, nick, message) {
+ checkForNickReference(server, channel, nick, message);
+ addMessage(server.name, channel, nick, new Date(), message);
+ };
+
+ ircConnections[server.name] = irc;
+}
+
+function joinChannel(serverName, channelName) {
+ ircConnections[serverName].joinChannel(channelName);
+}
+
+function removeChannelListener(channelName) {
+ return function(event) {
+ event.stopPropagation();
+
+ var servers = servers();
+ servers.forEach(function(server) {
+ if (channelName.indexOf(server.name) == 0) {
+ for (var i = 0; server.channels.length; i++) {
+ if (channelName == server.name + server.channels[i]) {
+ ircConnections[server.name].quitChannel(server.channels[i]);
+ server.channels.splice(i, 1);
+ break;
+ }
+ }
+ }
+ });
+
+ setServers(servers);
+ syncChannelList();
+ };
+}
+
+function syncChannelList() {
+ var channels = channelList();
+ var channelSlides = $('channelSlides');
+ var channelSlideProto = $('channelSlideProto');
+
+ var channelIndex = 0;
+ var slideIndex = 0;
+
+ while(channelIndex < channels.length ||
+ channels.length != channelSlides.children.length) {
+ var channel = channels[channelIndex];
+ var slide = channelSlides.children[slideIndex];
+
+ if (slideIndex == channelSlides.children.length ||
+ channel < slideChannel(slide)) {
+ // Add a new slide.
+ var newSlide = channelSlideProto.cloneNode(true);
+ jstProcess(new JsEvalContext({ name: channel }), newSlide);
+ newSlide.setAttribute("id", "channel-" + channel);
+ newSlide.style.display = "";
+ if (slideIndex == channelSlides.children.length) {
+ channelSlides.appendChild(newSlide);
+ } else {
+ channelSlides.insertBefore(newSlide, slide);
+ }
+ newSlide.addEventListener('click', onClickMoveSlide);
+ childNodeWithClass(newSlide, "removeButton")
+ .addEventListener('click', removeChannelListener(channel));
+
+ slide = newSlide;
+ } else if (!channel || channel > slideChannel(slide)) {
+ // Delete a removed slide.
+
+ // If the removed slide is the current slide, we have to pick a new
+ // current slide.
+ if (localStorage.currentSlide == slideChannel(slide)) {
+ if (slide.nextSibling) {
+ localStorage.currentSlide = slideChannel(slide.nextSibling);
+ } else if (channels.length == 0) {
+ localStorage.currentSlide = "";
+ } else if (slideIndex < channelSlides.children.length) {
+ localStorage.currentSlide =
+ slideChannel(channelSlides.children[slideIndex - 1]);
+ }
+ }
+ channelSlides.removeChild(slide);
+ } else {
+ channelIndex++;
+ slideIndex++;
+ }
+
+ slide.setAttribute("slide", "" + slideIndex - 1);
+ }
+
+ slideTo();
+}
+
+function processEntryMessage() {
+ var message = $('entryText').value;
+ $('entryText').value = "";
+
+ if (!localStorage.currentSlide) {
+ alert('No current channel');
+ return;
+ }
+
+ var server;
+ var channel;
+ var nick;
+ servers().forEach(function(s) {
+ if (localStorage.currentSlide.indexOf(s.name) == 0) {
+ server = s.name;
+ nick = s.nick;
+ s.channels.forEach(function(c) {
+ if (localStorage.currentSlide == s.name + c) {
+ channel = c;
+ }
+ });
+ }
+ });
+
+ addMessage(server, channel, nick, new Date(), message);
+ ircConnections[server].sendMessage([channel], message);
+}
+
+function addMessage(server, channel, nick, time, body) {
+ messageLine = childNodeWithClass($('channelSlideProto'), "messageLine");
+ var newMessageLine = messageLine.cloneNode(true);
+
+ jstProcess(new JsEvalContext({
+ 'nick': nick,
+ 'time': time,
+ 'body': body
+ }), newMessageLine);
+ newMessageLine.style.display = "";
+
+ var messageList =
+ childNodeWithClass($("channel-" + server + channel), "messageList");
+ messageList.appendChild(newMessageLine);
+}
+
+function formatTime(time) {
+ return "";
+}
+
+/**
+ * Slide Navigation.
+ */
+
+// Returns the server#channel string value for a given |slide| element.
+function slideChannel(slide) {
+ return childNodeWithClass(slide, "channel").innerText;
+}
+
+// Handler for clicking on the visible portions of the previous & next slides.
+function onClickMoveSlide() {
+ if (localStorage.currentSlide != slideChannel(this)) {
+ localStorage.currentSlide = slideChannel(this);
+ slideTo();
+ }
+}
+
+// Handles navigating between the channel slides. If |slideDelta| is given,
+// it should specify the number of slides to move left (negative value) or right
+// positive value. If |slideDelta| is not provided, It ensures that
+// |localStorage.currentSlide| is navigated to.
+function slideTo(slideDelta) {
+ var slide;
+ var slideNumber;
+
+ if (localStorage.currentSlide) {
+ slide = document.getElementById("channel-" + localStorage.currentSlide);
+ if (slide) {
+ slideNumber = parseInt(slide.getAttribute("slide"));
+ }
+ }
+ if (isNaN(slideNumber) || !slide) {
+ slideNumber = 0;
+ }
+ if (typeof(slideDelta) == "number") {
+ slideNumber += slideDelta;
+ }
+
+ var slides = document.getElementsByClassName("channelSlide");
+ if (slideNumber < 0 || slideNumber == slides.length - 1) {
+ return;
+ }
+
+ for (var i = 0; i < slides.length; i++) {
+ var slide = slides[i];
+ var slideIndex = parseInt(slide.getAttribute("slide")) - slideNumber;
+
+ if (slideIndex <= -2) {
+ slide.className = "channelSlide far-left";
+ }
+ if (slideIndex >= 2) {
+ slide.className = "channelSlide far-right";
+ }
+
+ switch(slideIndex) {
+ case -1:
+ slide.className = "channelSlide left";
+ break;
+ case 0:
+ slide.className = "channelSlide center";
+ localStorage.currentSlide = slideChannel(slide);
+ break;
+ case 1:
+ slide.className = "channelSlide right";
+ break;
+ }
+ }
+
+ clearNotifications();
+}
+
+/**
+ * Notifications
+ */
+var windowHasFocus = false;
+var notifications = {};
+
+function clearNotifications() {
+ for (property in notifications) {
+ notifications[property].cancel();
+ }
+
+ notifications = {};
+}
+
+function checkForNickReference(server, channel, nick, message) {
+ if (windowHasFocus || !message || message.indexOf(server.nick) < 0) {
+ return;
+ }
+
+ // Notifications will be enabled by the app install. Otherwise, don't notity.
+ if (window.webkitNotifications.checkPermission() != 0) {
+ return;
+ }
+
+ // Remove a previous notification from the same channel. Show the newer one.
+ if (notifications[server.name + channel]) {
+ notifications[server.name + channel].cancel();
+ }
+
+ var title = "On " + server.name + channel;
+ var icon = "http://www.google.com/favicon.ico";
+ var text = nick + ": " + message;
+ var url = location.protocol + "//" + location.host + "/notification.html";
+ url += "?title=" + encodeURIComponent(title) +
+ "&content=" + encodeURIComponent(text);
+
+ var n = window.webkitNotifications.createHTMLNotification(url);
+ n.ondisplay = function() {};
+ n.onclose = function() {
+ delete notifications[server.name + channel];
+ };
+ n.show();
+
+ notifications[server.name + channel] = n;
+}
+ </script>
+ </head>
+ <body>
+ <!-- TEMPLATES -->
+ <div id="channelSlideProto" style="display:none" class="channelSlide">
+ <div class="channelControls">
+ <div jscontent="name" class="channel">
+ </div>
+ <div class="removeButton">
+ x
+ </div>
+ </div>
+ <div class="channelSlideContainer">
+ <div class="messageListContainer">
+ <div class="messageList">
+ <div jsselect="messages">
+ <div class="messageLine">
+ <div jscontent="nick" class="messageSender"></div>:
+ <div jscontent="body" class="messageBody"></div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="messageListSpacer">.</div>
+ </div>
+ </div>
+
+ <div id="pageContainer">
+ <div id="headerContainer">
+ <div id="pageControls">
+ <div onclick="window.open('addServer.html');">
+ <div class="addControlLabel">
+ add server
+ </div>
+ <div class="addButton">
+ +
+ </div>
+ </div>
+ <div onclick=" window.open('addChannel.html');">
+ <div class="addControlLabel">
+ add channel
+ </div>
+ <div class="addButton">
+ +
+ </div>
+ </div>
+ </div>
+ </div>
+ <div id="slideContainer">
+ <div id="typingDiv">
+ <input type="text" id="entryText" value="">
+ </div>
+ <div style="" id="channelSlides">
+ </div>
+ </div>
+ </div>
+ </body>
+</html> \ No newline at end of file
diff --git a/chrome/common/extensions/docs/examples/extensions/irc/servlet/irc.js b/chrome/common/extensions/docs/examples/extensions/irc/servlet/irc.js
new file mode 100755
index 0000000..b169952
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/irc/servlet/irc.js
@@ -0,0 +1,188 @@
+/*
+ * IRCConnection is a simple implementation of the IRC protocol. A small
+ * subset of the IRC commands are implemented. To be functional, IRCConnection
+ * needs some mechanism of transport to be hooked up by:
+ * -Passing in |sendFunc| and |closeFunc| which an IRCConnection to use to send
+ * an IRC message command and to close the connection respectively.
+ * -Connecting the in-bound functions |onOpened|, |onMessage|, and |onClosed|,
+ * to the transport so that the IRCConnection can respond to the connection
+ * being opened, a message being received and the connection being closed.
+ */
+
+function NoOp() {};
+function log(message) { console.log(message); };
+
+function IRCConnection(server, port, nick, sendFunc, closeFunc) {
+ this.server = server;
+ this.port = port;
+ this.nick = nick;
+ this.connected = false;
+
+ var that = this;
+
+ /**
+ * Client API
+ */
+ this.onConnect = NoOp;
+ this.onDisconnect = NoOp;
+ this.onText = NoOp;
+ this.onNotice = NoOp;
+ this.onNickReferenced = NoOp;
+
+ this.joinChannel = function(channel) {
+ sendCommand(commands.JOIN, [channel], "");
+ };
+
+ this.sendMessage = function(recipient, message) {
+ sendCommand(commands.PRIVMSG, [recipient], message);
+ };
+
+ this.quitChannel = function(channel) {
+ sendCommand(commands.PART, [channel], "");
+ }
+
+ this.disconnect = function(message) {
+ sendCommand(commands.QUIT, [], message);
+ closeFunc();
+ }
+
+ /**
+ * Transport Interface
+ * Whatever transport is used must provide and connect to the following
+ * in-bound events.
+ */
+ this.onOpened = function() {
+ sendFunc(that.server + ":" + that.port);
+ sendCommand(commands.NICK, [this.nick], "");
+ sendCommand(commands.USER,
+ ["chromium-irc-lib", "chromium-ircproxy", "*"],
+ "indigo");
+ };
+
+ this.onMessage = function(message) {
+ log("<< " + message);
+ if (!message || !message.length) {
+ return;
+ }
+
+ var parsed = parseMessage(message);
+
+ // Respond to PING command.
+ if (parsed.command == commands.PING) {
+ sendCommand(commands.PONG, [], parsed.body);
+ return;
+ }
+
+ // Process PRIVMSG.
+ if (parsed.command == commands.PRIVMSG) {
+ if (parsed.body.charCodeAt(0) == 1) {
+ // Ignore CTCP.
+ return;
+ }
+ that.onText(parsed.parameters[0],
+ parsed.prefix.split("!")[0],
+ parsed.body);
+ return;
+ }
+
+ // TODO: Other IRC commands.
+ var commandCode = parseInt(parsed.command);
+ if (commandCode == NaN) {
+ return;
+ }
+
+ switch(commandCode) {
+ case 001: // Server welcome message.
+ that.connected = true;
+ that.onConnect(parsed.body);
+ break;
+ case 002:
+ case 003:
+ case 004:
+ case 005:
+ if (!that.connected) {
+ that.connected = true;
+ that.onConnect();
+ }
+ break;
+ case 433: // TODO(rafaelw): Nickname in use.
+ throw "NOT IMPLEMENTED";
+ break;
+ default:
+ break;
+ }
+ }
+
+ this.onClosed = function() {
+ that.connected = false;
+ that.onDisconnect();
+ };
+
+ /**
+ * IRC Implementation
+ * What follows in a minimal implementation of the IRC protocol.
+ * Only |commands| are currently implemented.
+ */
+ var commands = {
+ JOIN: "JOIN",
+ NICK: "NICK",
+ NOTICE: "NOTICE",
+ PART: "PART",
+ PING: "PING",
+ PONG: "PONG",
+ PRIVMSG: "PRIVMSG",
+ QUIT: "QUIT",
+ USER: "USER"
+ };
+
+ function parseMessage(message) {
+ var parsed = {};
+ parsed.prefix = "";
+ parsed.command = "";
+ parsed.parameters = [];
+ parsed.body = "";
+
+ // Trim trailing CRLF.
+ var crlfIndex = message.indexOf("\r\n");
+ if(crlfIndex >= 0) {
+ message = message.substring(0, crlfIndex);
+ }
+
+ // If leading character is ':', the message starts with a prefix.
+ if (message.indexOf(':') == 0) {
+ parsed.prefix = message.substring(1, message.indexOf(" "));
+ message = message.substring(parsed.prefix.length + 2);
+
+ // Forward past extra whitespace.
+ while(message.indexOf(" ") == 0) {
+ message = message.substring(1);
+ }
+ }
+
+ // If there is still a ':', then the message has trailing body.
+ var bodyMarker = message.indexOf(':');
+ if (bodyMarker >= 0) {
+ parsed.body = message.substring(bodyMarker + 1);
+ message = message.substring(0, bodyMarker);
+ }
+
+ parsed.parameters = message.split(" ");
+ parsed.command = parsed.parameters.shift(); // First param is the command.
+
+ return parsed;
+ }
+
+ function sendCommand(command, params, message) {
+ var line = command;
+ if (params && params.length > 0) {
+ line += " " + params.join(" ");
+ }
+ if (message && message.length > 0) {
+ line += " :" + message;
+ }
+
+ log(">> " + line);
+ line += "\r\n";
+ sendFunc(line);
+ };
+};
diff --git a/chrome/common/extensions/docs/examples/extensions/irc/servlet/jstemplate/jsevalcontext.js b/chrome/common/extensions/docs/examples/extensions/irc/servlet/jstemplate/jsevalcontext.js
new file mode 100755
index 0000000..0fc00ff7
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/irc/servlet/jstemplate/jsevalcontext.js
@@ -0,0 +1,409 @@
+// Copyright 2006 Google Inc.
+//
+// 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.
+/**
+ * Author: Steffen Meschkat <mesch@google.com>
+ *
+ * @fileoverview This class is used to evaluate expressions in a local
+ * context. Used by JstProcessor.
+ */
+
+
+/**
+ * Names of special variables defined by the jstemplate evaluation
+ * context. These can be used in js expression in jstemplate
+ * attributes.
+ */
+var VAR_index = '$index';
+var VAR_count = '$count';
+var VAR_this = '$this';
+var VAR_context = '$context';
+var VAR_top = '$top';
+
+
+/**
+ * The name of the global variable which holds the value to be returned if
+ * context evaluation results in an error.
+ * Use JsEvalContext.setGlobal(GLOB_default, value) to set this.
+ */
+var GLOB_default = '$default';
+
+
+/**
+ * Un-inlined literals, to avoid object creation in IE6. TODO(mesch):
+ * So far, these are only used here, but we could use them thoughout
+ * the code and thus move them to constants.js.
+ */
+var CHAR_colon = ':';
+var REGEXP_semicolon = /\s*;\s*/;
+
+
+/**
+ * See constructor_()
+ * @param {Object|null} opt_data
+ * @param {Object} opt_parent
+ * @constructor
+ */
+function JsEvalContext(opt_data, opt_parent) {
+ this.constructor_.apply(this, arguments);
+}
+
+/**
+ * Context for processing a jstemplate. The context contains a context
+ * object, whose properties can be referred to in jstemplate
+ * expressions, and it holds the locally defined variables.
+ *
+ * @param {Object|null} opt_data The context object. Null if no context.
+ *
+ * @param {Object} opt_parent The parent context, from which local
+ * variables are inherited. Normally the context object of the parent
+ * context is the object whose property the parent object is. Null for the
+ * context of the root object.
+ */
+JsEvalContext.prototype.constructor_ = function(opt_data, opt_parent) {
+ var me = this;
+
+ /**
+ * The context for variable definitions in which the jstemplate
+ * expressions are evaluated. Other than for the local context,
+ * which replaces the parent context, variable definitions of the
+ * parent are inherited. The special variable $this points to data_.
+ *
+ * If this instance is recycled from the cache, then the property is
+ * already initialized.
+ *
+ * @type {Object}
+ */
+ if (!me.vars_) {
+ me.vars_ = {};
+ }
+ if (opt_parent) {
+ // If there is a parent node, inherit local variables from the
+ // parent.
+ copyProperties(me.vars_, opt_parent.vars_);
+ } else {
+ // If a root node, inherit global symbols. Since every parent
+ // chain has a root with no parent, global variables will be
+ // present in the case above too. This means that globals can be
+ // overridden by locals, as it should be.
+ copyProperties(me.vars_, JsEvalContext.globals_);
+ }
+
+ /**
+ * The current context object is assigned to the special variable
+ * $this so it is possible to use it in expressions.
+ * @type Object
+ */
+ me.vars_[VAR_this] = opt_data;
+
+ /**
+ * The entire context structure is exposed as a variable so it can be
+ * passed to javascript invocations through jseval.
+ */
+ me.vars_[VAR_context] = me;
+
+ /**
+ * The local context of the input data in which the jstemplate
+ * expressions are evaluated. Notice that this is usually an Object,
+ * but it can also be a scalar value (and then still the expression
+ * $this can be used to refer to it). Notice this can even be value,
+ * undefined or null. Hence, we have to protect jsexec() from using
+ * undefined or null, yet we want $this to reflect the true value of
+ * the current context. Thus we assign the original value to $this,
+ * above, but for the expression context we replace null and
+ * undefined by the empty string.
+ *
+ * @type {Object|null}
+ */
+ me.data_ = getDefaultObject(opt_data, STRING_empty);
+
+ if (!opt_parent) {
+ // If this is a top-level context, create a variable reference to the data
+ // to allow for accessing top-level properties of the original context
+ // data from child contexts.
+ me.vars_[VAR_top] = me.data_;
+ }
+};
+
+
+/**
+ * A map of globally defined symbols. Every instance of JsExprContext
+ * inherits them in its vars_.
+ * @type Object
+ */
+JsEvalContext.globals_ = {}
+
+
+/**
+ * Sets a global symbol. It will be available like a variable in every
+ * JsEvalContext instance. This is intended mainly to register
+ * immutable global objects, such as functions, at load time, and not
+ * to add global data at runtime. I.e. the same objections as to
+ * global variables in general apply also here. (Hence the name
+ * "global", and not "global var".)
+ * @param {string} name
+ * @param {Object|null} value
+ */
+JsEvalContext.setGlobal = function(name, value) {
+ JsEvalContext.globals_[name] = value;
+};
+
+
+/**
+ * Set the default value to be returned if context evaluation results in an
+ * error. (This can occur if a non-existent value was requested).
+ */
+JsEvalContext.setGlobal(GLOB_default, null);
+
+
+/**
+ * A cache to reuse JsEvalContext instances. (IE6 perf)
+ *
+ * @type Array.<JsEvalContext>
+ */
+JsEvalContext.recycledInstances_ = [];
+
+
+/**
+ * A factory to create a JsEvalContext instance, possibly reusing
+ * one from recycledInstances_. (IE6 perf)
+ *
+ * @param {Object} opt_data
+ * @param {JsEvalContext} opt_parent
+ * @return {JsEvalContext}
+ */
+JsEvalContext.create = function(opt_data, opt_parent) {
+ if (jsLength(JsEvalContext.recycledInstances_) > 0) {
+ var instance = JsEvalContext.recycledInstances_.pop();
+ JsEvalContext.call(instance, opt_data, opt_parent);
+ return instance;
+ } else {
+ return new JsEvalContext(opt_data, opt_parent);
+ }
+};
+
+
+/**
+ * Recycle a used JsEvalContext instance, so we can avoid creating one
+ * the next time we need one. (IE6 perf)
+ *
+ * @param {JsEvalContext} instance
+ */
+JsEvalContext.recycle = function(instance) {
+ for (var i in instance.vars_) {
+ // NOTE(mesch): We avoid object creation here. (IE6 perf)
+ delete instance.vars_[i];
+ }
+ instance.data_ = null;
+ JsEvalContext.recycledInstances_.push(instance);
+};
+
+
+/**
+ * Executes a function created using jsEvalToFunction() in the context
+ * of vars, data, and template.
+ *
+ * @param {Function} exprFunction A javascript function created from
+ * a jstemplate attribute value.
+ *
+ * @param {Element} template DOM node of the template.
+ *
+ * @return {Object|null} The value of the expression from which
+ * exprFunction was created in the current js expression context and
+ * the context of template.
+ */
+JsEvalContext.prototype.jsexec = function(exprFunction, template) {
+ try {
+ return exprFunction.call(template, this.vars_, this.data_);
+ } catch (e) {
+ log('jsexec EXCEPTION: ' + e + ' at ' + template +
+ ' with ' + exprFunction);
+ return JsEvalContext.globals_[GLOB_default];
+ }
+};
+
+
+/**
+ * Clones the current context for a new context object. The cloned
+ * context has the data object as its context object and the current
+ * context as its parent context. It also sets the $index variable to
+ * the given value. This value usually is the position of the data
+ * object in a list for which a template is instantiated multiply.
+ *
+ * @param {Object} data The new context object.
+ *
+ * @param {number} index Position of the new context when multiply
+ * instantiated. (See implementation of jstSelect().)
+ *
+ * @param {number} count The total number of contexts that were multiply
+ * instantiated. (See implementation of jstSelect().)
+ *
+ * @return {JsEvalContext}
+ */
+JsEvalContext.prototype.clone = function(data, index, count) {
+ var ret = JsEvalContext.create(data, this);
+ ret.setVariable(VAR_index, index);
+ ret.setVariable(VAR_count, count);
+ return ret;
+};
+
+
+/**
+ * Binds a local variable to the given value. If set from jstemplate
+ * jsvalue expressions, variable names must start with $, but in the
+ * API they only have to be valid javascript identifier.
+ *
+ * @param {string} name
+ *
+ * @param {Object?} value
+ */
+JsEvalContext.prototype.setVariable = function(name, value) {
+ this.vars_[name] = value;
+};
+
+
+/**
+ * Returns the value bound to the local variable of the given name, or
+ * undefined if it wasn't set. There is no way to distinguish a
+ * variable that wasn't set from a variable that was set to
+ * undefined. Used mostly for testing.
+ *
+ * @param {string} name
+ *
+ * @return {Object?} value
+ */
+JsEvalContext.prototype.getVariable = function(name) {
+ return this.vars_[name];
+};
+
+
+/**
+ * Evaluates a string expression within the scope of this context
+ * and returns the result.
+ *
+ * @param {string} expr A javascript expression
+ * @param {Element} opt_template An optional node to serve as "this"
+ *
+ * @return {Object?} value
+ */
+JsEvalContext.prototype.evalExpression = function(expr, opt_template) {
+ var exprFunction = jsEvalToFunction(expr);
+ return this.jsexec(exprFunction, opt_template);
+};
+
+
+/**
+ * Uninlined string literals for jsEvalToFunction() (IE6 perf).
+ */
+var STRING_a = 'a_';
+var STRING_b = 'b_';
+var STRING_with = 'with (a_) with (b_) return ';
+
+
+/**
+ * Cache for jsEvalToFunction results.
+ * @type Object
+ */
+JsEvalContext.evalToFunctionCache_ = {};
+
+
+/**
+ * Evaluates the given expression as the body of a function that takes
+ * vars and data as arguments. Since the resulting function depends
+ * only on expr, we cache the result so we save some Function
+ * invocations, and some object creations in IE6.
+ *
+ * @param {string} expr A javascript expression.
+ *
+ * @return {Function} A function that returns the value of expr in the
+ * context of vars and data.
+ */
+function jsEvalToFunction(expr) {
+ if (!JsEvalContext.evalToFunctionCache_[expr]) {
+ try {
+ // NOTE(mesch): The Function constructor is faster than eval().
+ JsEvalContext.evalToFunctionCache_[expr] =
+ new Function(STRING_a, STRING_b, STRING_with + expr);
+ } catch (e) {
+ log('jsEvalToFunction (' + expr + ') EXCEPTION ' + e);
+ }
+ }
+ return JsEvalContext.evalToFunctionCache_[expr];
+}
+
+
+/**
+ * Evaluates the given expression to itself. This is meant to pass
+ * through string attribute values.
+ *
+ * @param {string} expr
+ *
+ * @return {string}
+ */
+function jsEvalToSelf(expr) {
+ return expr;
+}
+
+
+/**
+ * Parses the value of the jsvalues attribute in jstemplates: splits
+ * it up into a map of labels and expressions, and creates functions
+ * from the expressions that are suitable for execution by
+ * JsEvalContext.jsexec(). All that is returned as a flattened array
+ * of pairs of a String and a Function.
+ *
+ * @param {string} expr
+ *
+ * @return {Array}
+ */
+function jsEvalToValues(expr) {
+ // TODO(mesch): It is insufficient to split the values by simply
+ // finding semi-colons, as the semi-colon may be part of a string
+ // constant or escaped.
+ var ret = [];
+ var values = expr.split(REGEXP_semicolon);
+ for (var i = 0, I = jsLength(values); i < I; ++i) {
+ var colon = values[i].indexOf(CHAR_colon);
+ if (colon < 0) {
+ continue;
+ }
+ var label = stringTrim(values[i].substr(0, colon));
+ var value = jsEvalToFunction(values[i].substr(colon + 1));
+ ret.push(label, value);
+ }
+ return ret;
+}
+
+
+/**
+ * Parses the value of the jseval attribute of jstemplates: splits it
+ * up into a list of expressions, and creates functions from the
+ * expressions that are suitable for execution by
+ * JsEvalContext.jsexec(). All that is returned as an Array of
+ * Function.
+ *
+ * @param {string} expr
+ *
+ * @return {Array.<Function>}
+ */
+function jsEvalToExpressions(expr) {
+ var ret = [];
+ var values = expr.split(REGEXP_semicolon);
+ for (var i = 0, I = jsLength(values); i < I; ++i) {
+ if (values[i]) {
+ var value = jsEvalToFunction(values[i]);
+ ret.push(value);
+ }
+ }
+ return ret;
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/irc/servlet/jstemplate/jstemplate.js b/chrome/common/extensions/docs/examples/extensions/irc/servlet/jstemplate/jstemplate.js
new file mode 100755
index 0000000..f5d9f77
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/irc/servlet/jstemplate/jstemplate.js
@@ -0,0 +1,1017 @@
+// Copyright 2006 Google Inc.
+//
+// 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.
+/**
+ * Author: Steffen Meschkat <mesch@google.com>
+ *
+ * @fileoverview A simple formatter to project JavaScript data into
+ * HTML templates. The template is edited in place. I.e. in order to
+ * instantiate a template, clone it from the DOM first, and then
+ * process the cloned template. This allows for updating of templates:
+ * If the templates is processed again, changed values are merely
+ * updated.
+ *
+ * NOTE(mesch): IE DOM doesn't have importNode().
+ *
+ * NOTE(mesch): The property name "length" must not be used in input
+ * data, see comment in jstSelect_().
+ */
+
+
+/**
+ * Names of jstemplate attributes. These attributes are attached to
+ * normal HTML elements and bind expression context data to the HTML
+ * fragment that is used as template.
+ */
+var ATT_select = 'jsselect';
+var ATT_instance = 'jsinstance';
+var ATT_display = 'jsdisplay';
+var ATT_values = 'jsvalues';
+var ATT_vars = 'jsvars';
+var ATT_eval = 'jseval';
+var ATT_transclude = 'transclude';
+var ATT_content = 'jscontent';
+var ATT_skip = 'jsskip';
+
+
+/**
+ * Name of the attribute that caches a reference to the parsed
+ * template processing attribute values on a template node.
+ */
+var ATT_jstcache = 'jstcache';
+
+
+/**
+ * Name of the property that caches the parsed template processing
+ * attribute values on a template node.
+ */
+var PROP_jstcache = '__jstcache';
+
+
+/**
+ * ID of the element that contains dynamically loaded jstemplates.
+ */
+var STRING_jsts = 'jsts';
+
+
+/**
+ * Un-inlined string literals, to avoid object creation in
+ * IE6.
+ */
+var CHAR_asterisk = '*';
+var CHAR_dollar = '$';
+var CHAR_period = '.';
+var CHAR_ampersand = '&';
+var STRING_div = 'div';
+var STRING_id = 'id';
+var STRING_asteriskzero = '*0';
+var STRING_zero = '0';
+
+
+/**
+ * HTML template processor. Data values are bound to HTML templates
+ * using the attributes transclude, jsselect, jsdisplay, jscontent,
+ * jsvalues. The template is modifed in place. The values of those
+ * attributes are JavaScript expressions that are evaluated in the
+ * context of the data object fragment.
+ *
+ * @param {JsEvalContext} context Context created from the input data
+ * object.
+ *
+ * @param {Element} template DOM node of the template. This will be
+ * processed in place. After processing, it will still be a valid
+ * template that, if processed again with the same data, will remain
+ * unchanged.
+ *
+ * @param {boolean} opt_debugging Optional flag to collect debugging
+ * information while processing the template. Only takes effect
+ * in MAPS_DEBUG.
+ */
+function jstProcess(context, template, opt_debugging) {
+ var processor = new JstProcessor;
+ if (MAPS_DEBUG && opt_debugging) {
+ processor.setDebugging(opt_debugging);
+ }
+ JstProcessor.prepareTemplate_(template);
+
+ /**
+ * Caches the document of the template node, so we don't have to
+ * access it through ownerDocument.
+ * @type Document
+ */
+ processor.document_ = ownerDocument(template);
+
+ processor.run_(bindFully(processor, processor.jstProcessOuter_,
+ context, template));
+ if (MAPS_DEBUG && opt_debugging) {
+ log('jstProcess:' + '\n' + processor.getLogs().join('\n'));
+ }
+}
+
+
+/**
+ * Internal class used by jstemplates to maintain context. This is
+ * necessary to process deep templates in Safari which has a
+ * relatively shallow maximum recursion depth of 100.
+ * @class
+ * @constructor
+ */
+function JstProcessor() {
+ if (MAPS_DEBUG) {
+ /**
+ * An array of logging messages. These are collected during processing
+ * and dumped to the console at the end.
+ * @type Array.<string>
+ */
+ this.logs_ = [];
+ }
+}
+
+
+/**
+ * Counter to generate node ids. These ids will be stored in
+ * ATT_jstcache and be used to lookup the preprocessed js attributes
+ * from the jstcache_. The id is stored in an attribute so it
+ * suvives cloneNode() and thus cloned template nodes can share the
+ * same cache entry.
+ * @type number
+ */
+JstProcessor.jstid_ = 0;
+
+
+/**
+ * Map from jstid to processed js attributes.
+ * @type Object
+ */
+JstProcessor.jstcache_ = {};
+
+/**
+ * The neutral cache entry. Used for all nodes that don't have any
+ * jst attributes. We still set the jsid attribute on those nodes so
+ * we can avoid to look again for all the other jst attributes that
+ * aren't there. Remember: not only the processing of the js
+ * attribute values is expensive and we thus want to cache it. The
+ * access to the attributes on the Node in the first place is
+ * expensive too.
+ */
+JstProcessor.jstcache_[0] = {};
+
+
+/**
+ * Map from concatenated attribute string to jstid.
+ * The key is the concatenation of all jst atributes found on a node
+ * formatted as "name1=value1&name2=value2&...", in the order defined by
+ * JST_ATTRIBUTES. The value is the id of the jstcache_ entry that can
+ * be used for this node. This allows the reuse of cache entries in cases
+ * when a cached entry already exists for a given combination of attribute
+ * values. (For example when two different nodes in a template share the same
+ * JST attributes.)
+ * @type Object
+ */
+JstProcessor.jstcacheattributes_ = {};
+
+
+/**
+ * Map for storing temporary attribute values in prepareNode_() so they don't
+ * have to be retrieved twice. (IE6 perf)
+ * @type Object
+ */
+JstProcessor.attributeValues_ = {};
+
+
+/**
+ * A list for storing non-empty attributes found on a node in prepareNode_().
+ * The array is global since it can be reused - this way there is no need to
+ * construct a new array object for each invocation. (IE6 perf)
+ * @type Array
+ */
+JstProcessor.attributeList_ = [];
+
+
+/**
+ * Prepares the template: preprocesses all jstemplate attributes.
+ *
+ * @param {Element} template
+ */
+JstProcessor.prepareTemplate_ = function(template) {
+ if (!template[PROP_jstcache]) {
+ domTraverseElements(template, function(node) {
+ JstProcessor.prepareNode_(node);
+ });
+ }
+};
+
+
+/**
+ * A list of attributes we use to specify jst processing instructions,
+ * and the functions used to parse their values.
+ *
+ * @type Array.<Array>
+ */
+var JST_ATTRIBUTES = [
+ [ ATT_select, jsEvalToFunction ],
+ [ ATT_display, jsEvalToFunction ],
+ [ ATT_values, jsEvalToValues ],
+ [ ATT_vars, jsEvalToValues ],
+ [ ATT_eval, jsEvalToExpressions ],
+ [ ATT_transclude, jsEvalToSelf ],
+ [ ATT_content, jsEvalToFunction ],
+ [ ATT_skip, jsEvalToFunction ]
+];
+
+
+/**
+ * Prepares a single node: preprocesses all template attributes of the
+ * node, and if there are any, assigns a jsid attribute and stores the
+ * preprocessed attributes under the jsid in the jstcache.
+ *
+ * @param {Element} node
+ *
+ * @return {Object} The jstcache entry. The processed jst attributes
+ * are properties of this object. If the node has no jst attributes,
+ * returns an object with no properties (the jscache_[0] entry).
+ */
+JstProcessor.prepareNode_ = function(node) {
+ // If the node already has a cache property, return it.
+ if (node[PROP_jstcache]) {
+ return node[PROP_jstcache];
+ }
+
+ // If it is not found, we always set the PROP_jstcache property on the node.
+ // Accessing the property is faster than executing getAttribute(). If we
+ // don't find the property on a node that was cloned in jstSelect_(), we
+ // will fall back to check for the attribute and set the property
+ // from cache.
+
+ // If the node has an attribute indexing a cache object, set it as a property
+ // and return it.
+ var jstid = domGetAttribute(node, ATT_jstcache);
+ if (jstid != null) {
+ return node[PROP_jstcache] = JstProcessor.jstcache_[jstid];
+ }
+
+ var attributeValues = JstProcessor.attributeValues_;
+ var attributeList = JstProcessor.attributeList_;
+ attributeList.length = 0;
+
+ // Look for interesting attributes.
+ for (var i = 0, I = jsLength(JST_ATTRIBUTES); i < I; ++i) {
+ var name = JST_ATTRIBUTES[i][0];
+ var value = domGetAttribute(node, name);
+ attributeValues[name] = value;
+ if (value != null) {
+ attributeList.push(name + "=" + value);
+ }
+ }
+
+ // If none found, mark this node to prevent further inspection, and return
+ // an empty cache object.
+ if (attributeList.length == 0) {
+ domSetAttribute(node, ATT_jstcache, STRING_zero);
+ return node[PROP_jstcache] = JstProcessor.jstcache_[0];
+ }
+
+ // If we already have a cache object corresponding to these attributes,
+ // annotate the node with it, and return it.
+ var attstring = attributeList.join(CHAR_ampersand);
+ if (jstid = JstProcessor.jstcacheattributes_[attstring]) {
+ domSetAttribute(node, ATT_jstcache, jstid);
+ return node[PROP_jstcache] = JstProcessor.jstcache_[jstid];
+ }
+
+ // Otherwise, build a new cache object.
+ var jstcache = {};
+ for (var i = 0, I = jsLength(JST_ATTRIBUTES); i < I; ++i) {
+ var att = JST_ATTRIBUTES[i];
+ var name = att[0];
+ var parse = att[1];
+ var value = attributeValues[name];
+ if (value != null) {
+ jstcache[name] = parse(value);
+ if (MAPS_DEBUG) {
+ jstcache.jstAttributeValues = jstcache.jstAttributeValues || {};
+ jstcache.jstAttributeValues[name] = value;
+ }
+ }
+ }
+
+ jstid = STRING_empty + ++JstProcessor.jstid_;
+ domSetAttribute(node, ATT_jstcache, jstid);
+ JstProcessor.jstcache_[jstid] = jstcache;
+ JstProcessor.jstcacheattributes_[attstring] = jstid;
+
+ return node[PROP_jstcache] = jstcache;
+};
+
+
+/**
+ * Runs the given function in our state machine.
+ *
+ * It's informative to view the set of all function calls as a tree:
+ * - nodes are states
+ * - edges are state transitions, implemented as calls to the pending
+ * functions in the stack.
+ * - pre-order function calls are downward edges (recursion into call).
+ * - post-order function calls are upward edges (return from call).
+ * - leaves are nodes which do not recurse.
+ * We represent the call tree as an array of array of calls, indexed as
+ * stack[depth][index]. Here [depth] indexes into the call stack, and
+ * [index] indexes into the call queue at that depth. We require a call
+ * queue so that a node may branch to more than one child
+ * (which will be called serially), typically due to a loop structure.
+ *
+ * @param {Function} f The first function to run.
+ */
+JstProcessor.prototype.run_ = function(f) {
+ var me = this;
+
+ /**
+ * A stack of queues of pre-order calls.
+ * The inner arrays (constituent queues) are structured as
+ * [ arg2, arg1, method, arg2, arg1, method, ...]
+ * ie. a flattened array of methods with 2 arguments, in reverse order
+ * for efficient push/pop.
+ *
+ * The outer array is a stack of such queues.
+ *
+ * @type Array.<Array>
+ */
+ var calls = me.calls_ = [];
+
+ /**
+ * The index into the queue for each depth. NOTE: Alternative would
+ * be to maintain the queues in reverse order (popping off of the
+ * end) but the repeated calls to .pop() consumed 90% of this
+ * function's execution time.
+ * @type Array.<number>
+ */
+ var queueIndices = me.queueIndices_ = [];
+
+ /**
+ * A pool of empty arrays. Minimizes object allocation for IE6's benefit.
+ * @type Array.<Array>
+ */
+ var arrayPool = me.arrayPool_ = [];
+
+ f();
+ var queue, queueIndex;
+ var method, arg1, arg2;
+ var temp;
+ while (calls.length) {
+ queue = calls[calls.length - 1];
+ queueIndex = queueIndices[queueIndices.length - 1];
+ if (queueIndex >= queue.length) {
+ me.recycleArray_(calls.pop());
+ queueIndices.pop();
+ continue;
+ }
+
+ // Run the first function in the queue.
+ method = queue[queueIndex++];
+ arg1 = queue[queueIndex++];
+ arg2 = queue[queueIndex++];
+ queueIndices[queueIndices.length - 1] = queueIndex;
+ method.call(me, arg1, arg2);
+ }
+};
+
+
+/**
+ * Pushes one or more functions onto the stack. These will be run in sequence,
+ * interspersed with any recursive calls that they make.
+ *
+ * This method takes ownership of the given array!
+ *
+ * @param {Array} args Array of method calls structured as
+ * [ method, arg1, arg2, method, arg1, arg2, ... ]
+ */
+JstProcessor.prototype.push_ = function(args) {
+ this.calls_.push(args);
+ this.queueIndices_.push(0);
+};
+
+
+/**
+ * Enable/disable debugging.
+ * @param {boolean} debugging New state
+ */
+JstProcessor.prototype.setDebugging = function(debugging) {
+ if (MAPS_DEBUG) {
+ this.debugging_ = debugging;
+ }
+};
+
+
+JstProcessor.prototype.createArray_ = function() {
+ if (this.arrayPool_.length) {
+ return this.arrayPool_.pop();
+ } else {
+ return [];
+ }
+};
+
+
+JstProcessor.prototype.recycleArray_ = function(array) {
+ arrayClear(array);
+ this.arrayPool_.push(array);
+};
+
+/**
+ * Implements internals of jstProcess. This processes the two
+ * attributes transclude and jsselect, which replace or multiply
+ * elements, hence the name "outer". The remainder of the attributes
+ * is processed in jstProcessInner_(), below. That function
+ * jsProcessInner_() only processes attributes that affect an existing
+ * node, but doesn't create or destroy nodes, hence the name
+ * "inner". jstProcessInner_() is called through jstSelect_() if there
+ * is a jsselect attribute (possibly for newly created clones of the
+ * current template node), or directly from here if there is none.
+ *
+ * @param {JsEvalContext} context
+ *
+ * @param {Element} template
+ */
+JstProcessor.prototype.jstProcessOuter_ = function(context, template) {
+ var me = this;
+
+ var jstAttributes = me.jstAttributes_(template);
+ if (MAPS_DEBUG && me.debugging_) {
+ me.logState_('Outer', template, jstAttributes.jstAttributeValues);
+ }
+
+ var transclude = jstAttributes[ATT_transclude];
+ if (transclude) {
+ var tr = jstGetTemplate(transclude);
+ if (tr) {
+ domReplaceChild(tr, template);
+ var call = me.createArray_();
+ call.push(me.jstProcessOuter_, context, tr);
+ me.push_(call);
+ } else {
+ domRemoveNode(template);
+ }
+ return;
+ }
+
+ var select = jstAttributes[ATT_select];
+ if (select) {
+ me.jstSelect_(context, template, select);
+ } else {
+ me.jstProcessInner_(context, template);
+ }
+};
+
+
+/**
+ * Implements internals of jstProcess. This processes all attributes
+ * except transclude and jsselect. It is called either from
+ * jstSelect_() for nodes that have a jsselect attribute so that the
+ * jsselect attribute will not be processed again, or else directly
+ * from jstProcessOuter_(). See the comment on jstProcessOuter_() for
+ * an explanation of the name.
+ *
+ * @param {JsEvalContext} context
+ *
+ * @param {Element} template
+ */
+JstProcessor.prototype.jstProcessInner_ = function(context, template) {
+ var me = this;
+
+ var jstAttributes = me.jstAttributes_(template);
+ if (MAPS_DEBUG && me.debugging_) {
+ me.logState_('Inner', template, jstAttributes.jstAttributeValues);
+ }
+
+ // NOTE(mesch): See NOTE on ATT_content why this is a separate
+ // attribute, and not a special value in ATT_values.
+ var display = jstAttributes[ATT_display];
+ if (display) {
+ var shouldDisplay = context.jsexec(display, template);
+ if (MAPS_DEBUG && me.debugging_) {
+ me.logs_.push(ATT_display + ': ' + shouldDisplay + '<br/>');
+ }
+ if (!shouldDisplay) {
+ displayNone(template);
+ return;
+ }
+ displayDefault(template);
+ }
+
+ // NOTE(mesch): jsvars is evaluated before jsvalues, because it's
+ // more useful to be able to use var values in attribute value
+ // expressions than vice versa.
+ var values = jstAttributes[ATT_vars];
+ if (values) {
+ me.jstVars_(context, template, values);
+ }
+
+ values = jstAttributes[ATT_values];
+ if (values) {
+ me.jstValues_(context, template, values);
+ }
+
+ // Evaluate expressions immediately. Useful for hooking callbacks
+ // into jstemplates.
+ //
+ // NOTE(mesch): Evaluation order is sometimes significant, e.g. when
+ // the expression evaluated in jseval relies on the values set in
+ // jsvalues, so it needs to be evaluated *after*
+ // jsvalues. TODO(mesch): This is quite arbitrary, it would be
+ // better if this would have more necessity to it.
+ var expressions = jstAttributes[ATT_eval];
+ if (expressions) {
+ for (var i = 0, I = jsLength(expressions); i < I; ++i) {
+ context.jsexec(expressions[i], template);
+ }
+ }
+
+ var skip = jstAttributes[ATT_skip];
+ if (skip) {
+ var shouldSkip = context.jsexec(skip, template);
+ if (MAPS_DEBUG && me.debugging_) {
+ me.logs_.push(ATT_skip + ': ' + shouldSkip + '<br/>');
+ }
+ if (shouldSkip) return;
+ }
+
+ // NOTE(mesch): content is a separate attribute, instead of just a
+ // special value mentioned in values, for two reasons: (1) it is
+ // fairly common to have only mapped content, and writing
+ // content="expr" is shorter than writing values="content:expr", and
+ // (2) the presence of content actually terminates traversal, and we
+ // need to check for that. Display is a separate attribute for a
+ // reason similar to the second, in that its presence *may*
+ // terminate traversal.
+ var content = jstAttributes[ATT_content];
+ if (content) {
+ me.jstContent_(context, template, content);
+
+ } else {
+ // Newly generated children should be ignored, so we explicitly
+ // store the children to be processed.
+ var queue = me.createArray_();
+ for (var c = template.firstChild; c; c = c.nextSibling) {
+ if (c.nodeType == DOM_ELEMENT_NODE) {
+ queue.push(me.jstProcessOuter_, context, c);
+ }
+ }
+ if (queue.length) me.push_(queue);
+ }
+};
+
+
+/**
+ * Implements the jsselect attribute: evalutes the value of the
+ * jsselect attribute in the current context, with the current
+ * variable bindings (see JsEvalContext.jseval()). If the value is an
+ * array, the current template node is multiplied once for every
+ * element in the array, with the array element being the context
+ * object. If the array is empty, or the value is undefined, then the
+ * current template node is dropped. If the value is not an array,
+ * then it is just made the context object.
+ *
+ * @param {JsEvalContext} context The current evaluation context.
+ *
+ * @param {Element} template The currently processed node of the template.
+ *
+ * @param {Function} select The javascript expression to evaluate.
+ *
+ * @notypecheck FIXME(hmitchell): See OCL6434950. instance and value need
+ * type checks.
+ */
+JstProcessor.prototype.jstSelect_ = function(context, template, select) {
+ var me = this;
+
+ var value = context.jsexec(select, template);
+
+ // Enable reprocessing: if this template is reprocessed, then only
+ // fill the section instance here. Otherwise do the cardinal
+ // processing of a new template.
+ var instance = domGetAttribute(template, ATT_instance);
+
+ var instanceLast = false;
+ if (instance) {
+ if (instance.charAt(0) == CHAR_asterisk) {
+ instance = parseInt10(instance.substr(1));
+ instanceLast = true;
+ } else {
+ instance = parseInt10(/** @type string */(instance));
+ }
+ }
+
+ // The expression value instanceof Array is occasionally false for
+ // arrays, seen in Firefox. Thus we recognize an array as an object
+ // which is not null that has a length property. Notice that this
+ // also matches input data with a length property, so this property
+ // name should be avoided in input data.
+ var multiple = isArray(value);
+ var count = multiple ? jsLength(value) : 1;
+ var multipleEmpty = (multiple && count == 0);
+
+ if (multiple) {
+ if (multipleEmpty) {
+ // For an empty array, keep the first template instance and mark
+ // it last. Remove all other template instances.
+ if (!instance) {
+ domSetAttribute(template, ATT_instance, STRING_asteriskzero);
+ displayNone(template);
+ } else {
+ domRemoveNode(template);
+ }
+
+ } else {
+ displayDefault(template);
+ // For a non empty array, create as many template instances as
+ // are needed. If the template is first processed, as many
+ // template instances are needed as there are values in the
+ // array. If the template is reprocessed, new template instances
+ // are only needed if there are more array values than template
+ // instances. Those additional instances are created by
+ // replicating the last template instance.
+ //
+ // When the template is first processed, there is no jsinstance
+ // attribute. This is indicated by instance === null, except in
+ // opera it is instance === "". Notice also that the === is
+ // essential, because 0 == "", presumably via type coercion to
+ // boolean.
+ if (instance === null || instance === STRING_empty ||
+ (instanceLast && instance < count - 1)) {
+ // A queue of calls to push.
+ var queue = me.createArray_();
+
+ var instancesStart = instance || 0;
+ var i, I, clone;
+ for (i = instancesStart, I = count - 1; i < I; ++i) {
+ var node = domCloneNode(template);
+ domInsertBefore(node, template);
+
+ jstSetInstance(/** @type Element */(node), value, i);
+ clone = context.clone(value[i], i, count);
+
+ queue.push(me.jstProcessInner_, clone, node,
+ JsEvalContext.recycle, clone, null);
+
+ }
+ // Push the originally present template instance last to keep
+ // the order aligned with the DOM order, because the newly
+ // created template instances are inserted *before* the
+ // original instance.
+ jstSetInstance(template, value, i);
+ clone = context.clone(value[i], i, count);
+ queue.push(me.jstProcessInner_, clone, template,
+ JsEvalContext.recycle, clone, null);
+ me.push_(queue);
+ } else if (instance < count) {
+ var v = value[instance];
+
+ jstSetInstance(template, value, instance);
+ var clone = context.clone(v, instance, count);
+ var queue = me.createArray_();
+ queue.push(me.jstProcessInner_, clone, template,
+ JsEvalContext.recycle, clone, null);
+ me.push_(queue);
+ } else {
+ domRemoveNode(template);
+ }
+ }
+ } else {
+ if (value == null) {
+ displayNone(template);
+ } else {
+ displayDefault(template);
+ var clone = context.clone(value, 0, 1);
+ var queue = me.createArray_();
+ queue.push(me.jstProcessInner_, clone, template,
+ JsEvalContext.recycle, clone, null);
+ me.push_(queue);
+ }
+ }
+};
+
+
+/**
+ * Implements the jsvars attribute: evaluates each of the values and
+ * assigns them to variables in the current context. Similar to
+ * jsvalues, except that all values are treated as vars, independent
+ * of their names.
+ *
+ * @param {JsEvalContext} context Current evaluation context.
+ *
+ * @param {Element} template Currently processed template node.
+ *
+ * @param {Array} values Processed value of the jsvalues attribute: a
+ * flattened array of pairs. The second element in the pair is a
+ * function that can be passed to jsexec() for evaluation in the
+ * current jscontext, and the first element is the variable name that
+ * the value returned by jsexec is assigned to.
+ */
+JstProcessor.prototype.jstVars_ = function(context, template, values) {
+ for (var i = 0, I = jsLength(values); i < I; i += 2) {
+ var label = values[i];
+ var value = context.jsexec(values[i+1], template);
+ context.setVariable(label, value);
+ }
+};
+
+
+/**
+ * Implements the jsvalues attribute: evaluates each of the values and
+ * assigns them to variables in the current context (if the name
+ * starts with '$', javascript properties of the current template node
+ * (if the name starts with '.'), or DOM attributes of the current
+ * template node (otherwise). Since DOM attribute values are always
+ * strings, the value is coerced to string in the latter case,
+ * otherwise it's the uncoerced javascript value.
+ *
+ * @param {JsEvalContext} context Current evaluation context.
+ *
+ * @param {Element} template Currently processed template node.
+ *
+ * @param {Array} values Processed value of the jsvalues attribute: a
+ * flattened array of pairs. The second element in the pair is a
+ * function that can be passed to jsexec() for evaluation in the
+ * current jscontext, and the first element is the label that
+ * determines where the value returned by jsexec is assigned to.
+ */
+JstProcessor.prototype.jstValues_ = function(context, template, values) {
+ for (var i = 0, I = jsLength(values); i < I; i += 2) {
+ var label = values[i];
+ var value = context.jsexec(values[i+1], template);
+
+ if (label.charAt(0) == CHAR_dollar) {
+ // A jsvalues entry whose name starts with $ sets a local
+ // variable.
+ context.setVariable(label, value);
+
+ } else if (label.charAt(0) == CHAR_period) {
+ // A jsvalues entry whose name starts with . sets a property of
+ // the current template node. The name may have further dot
+ // separated components, which are translated into namespace
+ // objects. This specifically allows to set properties on .style
+ // using jsvalues. NOTE(mesch): Setting the style attribute has
+ // no effect in IE and hence should not be done anyway.
+ var nameSpaceLabel = label.substr(1).split(CHAR_period);
+ var nameSpaceObject = template;
+ var nameSpaceDepth = jsLength(nameSpaceLabel);
+ for (var j = 0, J = nameSpaceDepth - 1; j < J; ++j) {
+ var jLabel = nameSpaceLabel[j];
+ if (!nameSpaceObject[jLabel]) {
+ nameSpaceObject[jLabel] = {};
+ }
+ nameSpaceObject = nameSpaceObject[jLabel];
+ }
+ nameSpaceObject[nameSpaceLabel[nameSpaceDepth - 1]] = value;
+
+ } else if (label) {
+ // Any other jsvalues entry sets an attribute of the current
+ // template node.
+ if (typeof value == TYPE_boolean) {
+ // Handle boolean values that are set as attributes specially,
+ // according to the XML/HTML convention.
+ if (value) {
+ domSetAttribute(template, label, label);
+ } else {
+ domRemoveAttribute(template, label);
+ }
+ } else {
+ domSetAttribute(template, label, STRING_empty + value);
+ }
+ }
+ }
+};
+
+
+/**
+ * Implements the jscontent attribute. Evalutes the expression in
+ * jscontent in the current context and with the current variables,
+ * and assigns its string value to the content of the current template
+ * node.
+ *
+ * @param {JsEvalContext} context Current evaluation context.
+ *
+ * @param {Element} template Currently processed template node.
+ *
+ * @param {Function} content Processed value of the jscontent
+ * attribute.
+ */
+JstProcessor.prototype.jstContent_ = function(context, template, content) {
+ // NOTE(mesch): Profiling shows that this method costs significant
+ // time. In jstemplate_perf.html, it's about 50%. I tried to replace
+ // by HTML escaping and assignment to innerHTML, but that was even
+ // slower.
+ var value = STRING_empty + context.jsexec(content, template);
+ // Prevent flicker when refreshing a template and the value doesn't
+ // change.
+ if (template.innerHTML == value) {
+ return;
+ }
+ while (template.firstChild) {
+ domRemoveNode(template.firstChild);
+ }
+ var t = domCreateTextNode(this.document_, value);
+ domAppendChild(template, t);
+};
+
+
+/**
+ * Caches access to and parsing of template processing attributes. If
+ * domGetAttribute() is called every time a template attribute value
+ * is used, it takes more than 10% of the time.
+ *
+ * @param {Element} template A DOM element node of the template.
+ *
+ * @return {Object} A javascript object that has all js template
+ * processing attribute values of the node as properties.
+ */
+JstProcessor.prototype.jstAttributes_ = function(template) {
+ if (template[PROP_jstcache]) {
+ return template[PROP_jstcache];
+ }
+
+ var jstid = domGetAttribute(template, ATT_jstcache);
+ if (jstid) {
+ return template[PROP_jstcache] = JstProcessor.jstcache_[jstid];
+ }
+
+ return JstProcessor.prepareNode_(template);
+};
+
+
+/**
+ * Helps to implement the transclude attribute, and is the initial
+ * call to get hold of a template from its ID.
+ *
+ * If the ID is not present in the DOM, and opt_loadHtmlFn is specified, this
+ * function will call that function and add the result to the DOM, before
+ * returning the template.
+ *
+ * @param {string} name The ID of the HTML element used as template.
+ * @param {Function} opt_loadHtmlFn A function which, when called, will return
+ * HTML that contains an element whose ID is 'name'.
+ *
+ * @return {Element|null} The DOM node of the template. (Only element nodes
+ * can be found by ID, hence it's a Element.)
+ */
+function jstGetTemplate(name, opt_loadHtmlFn) {
+ var doc = document;
+ var section;
+ if (opt_loadHtmlFn) {
+ section = jstLoadTemplateIfNotPresent(doc, name, opt_loadHtmlFn);
+ } else {
+ section = domGetElementById(doc, name);
+ }
+ if (section) {
+ JstProcessor.prepareTemplate_(section);
+ var ret = domCloneElement(section);
+ domRemoveAttribute(ret, STRING_id);
+ return ret;
+ } else {
+ return null;
+ }
+}
+
+/**
+ * This function is the same as 'jstGetTemplate' but, if the template
+ * does not exist, throw an exception.
+ *
+ * @param {string} name The ID of the HTML element used as template.
+ * @param {Function} opt_loadHtmlFn A function which, when called, will return
+ * HTML that contains an element whose ID is 'name'.
+ *
+ * @return {Element} The DOM node of the template. (Only element nodes
+ * can be found by ID, hence it's a Element.)
+ */
+function jstGetTemplateOrDie(name, opt_loadHtmlFn) {
+ var x = jstGetTemplate(name, opt_loadHtmlFn);
+ check(x !== null);
+ return /** @type Element */(x);
+}
+
+
+/**
+ * If an element with id 'name' is not present in the document, call loadHtmlFn
+ * and insert the result into the DOM.
+ *
+ * @param {Document} doc
+ * @param {string} name
+ * @param {Function} loadHtmlFn A function that returns HTML to be inserted
+ * into the DOM.
+ * @param {string} opt_target The id of a DOM object under which to attach the
+ * HTML once it's inserted. An object with this id is created if it does not
+ * exist.
+ * @return {Element} The node whose id is 'name'
+ */
+function jstLoadTemplateIfNotPresent(doc, name, loadHtmlFn, opt_target) {
+ var section = domGetElementById(doc, name);
+ if (section) {
+ return section;
+ }
+ // Load any necessary HTML and try again.
+ jstLoadTemplate_(doc, loadHtmlFn(), opt_target || STRING_jsts);
+ var section = domGetElementById(doc, name);
+ if (!section) {
+ log("Error: jstGetTemplate was provided with opt_loadHtmlFn, " +
+ "but that function did not provide the id '" + name + "'.");
+ }
+ return /** @type Element */(section);
+}
+
+
+/**
+ * Loads the given HTML text into the given document, so that
+ * jstGetTemplate can find it.
+ *
+ * We append it to the element identified by targetId, which is hidden.
+ * If it doesn't exist, it is created.
+ *
+ * @param {Document} doc The document to create the template in.
+ *
+ * @param {string} html HTML text to be inserted into the document.
+ *
+ * @param {string} targetId The id of a DOM object under which to attach the
+ * HTML once it's inserted. An object with this id is created if it does not
+ * exist.
+ */
+function jstLoadTemplate_(doc, html, targetId) {
+ var existing_target = domGetElementById(doc, targetId);
+ var target;
+ if (!existing_target) {
+ target = domCreateElement(doc, STRING_div);
+ target.id = targetId;
+ displayNone(target);
+ positionAbsolute(target);
+ domAppendChild(doc.body, target);
+ } else {
+ target = existing_target;
+ }
+ var div = domCreateElement(doc, STRING_div);
+ target.appendChild(div);
+ div.innerHTML = html;
+}
+
+
+/**
+ * Sets the jsinstance attribute on a node according to its context.
+ *
+ * @param {Element} template The template DOM node to set the instance
+ * attribute on.
+ *
+ * @param {Array} values The current input context, the array of
+ * values of which the template node will render one instance.
+ *
+ * @param {number} index The index of this template node in values.
+ */
+function jstSetInstance(template, values, index) {
+ if (index == jsLength(values) - 1) {
+ domSetAttribute(template, ATT_instance, CHAR_asterisk + index);
+ } else {
+ domSetAttribute(template, ATT_instance, STRING_empty + index);
+ }
+}
+
+
+/**
+ * Log the current state.
+ * @param {string} caller An identifier for the caller of .log_.
+ * @param {Element} template The template node being processed.
+ * @param {Object} jstAttributeValues The jst attributes of the template node.
+ */
+JstProcessor.prototype.logState_ = function(
+ caller, template, jstAttributeValues) {
+ if (MAPS_DEBUG) {
+ var msg = '<table>';
+ msg += '<caption>' + caller + '</caption>';
+ msg += '<tbody>';
+ if (template.id) {
+ msg += '<tr><td>' + 'id:' + '</td><td>' + template.id + '</td></tr>';
+ }
+ if (template.name) {
+ msg += '<tr><td>' + 'name:' + '</td><td>' + template.name + '</td></tr>';
+ }
+ if (jstAttributeValues) {
+ msg += '<tr><td>' + 'attr:' +
+ '</td><td>' + jsToSource(jstAttributeValues) + '</td></tr>';
+ }
+ msg += '</tbody></table><br/>';
+ this.logs_.push(msg);
+ }
+};
+
+
+/**
+ * Retrieve the processing logs.
+ * @return {Array.<string>} The processing logs.
+ */
+JstProcessor.prototype.getLogs = function() {
+ return this.logs_;
+};
diff --git a/chrome/common/extensions/docs/examples/extensions/irc/servlet/jstemplate/util.js b/chrome/common/extensions/docs/examples/extensions/irc/servlet/jstemplate/util.js
new file mode 100755
index 0000000..f6c1f46
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/irc/servlet/jstemplate/util.js
@@ -0,0 +1,470 @@
+// Copyright 2006 Google Inc.
+//
+// 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 Miscellaneous constants and functions referenced in
+ * the main source files.
+ *
+ * @author Steffen Meschkat (mesch@google.com)
+ */
+
+var MAPS_DEBUG = false;
+
+function log(msg) {}
+
+// String literals defined globally and not to be inlined. (IE6 perf)
+/** @const */ var STRING_empty = '';
+
+/** @const */ var CSS_display = 'display';
+/** @const */ var CSS_position = 'position';
+
+// Constants for possible values of the typeof operator.
+var TYPE_boolean = 'boolean';
+var TYPE_number = 'number';
+var TYPE_object = 'object';
+var TYPE_string = 'string';
+var TYPE_function = 'function';
+var TYPE_undefined = 'undefined';
+
+
+/**
+ * Wrapper for the eval() builtin function to evaluate expressions and
+ * obtain their value. It wraps the expression in parentheses such
+ * that object literals are really evaluated to objects. Without the
+ * wrapping, they are evaluated as block, and create syntax
+ * errors. Also protects against other syntax errors in the eval()ed
+ * code and returns null if the eval throws an exception.
+ *
+ * @param {string} expr
+ * @return {Object|null}
+ */
+function jsEval(expr) {
+ try {
+ // NOTE(mesch): An alternative idiom would be:
+ //
+ // eval('(' + expr + ')');
+ //
+ // Note that using the square brackets as below, "" evals to undefined.
+ // The alternative of using parentheses does not work when evaluating
+ // function literals in IE.
+ // e.g. eval("(function() {})") returns undefined, and not a function
+ // object, in IE.
+ return eval('[' + expr + '][0]');
+ } catch (e) {
+ log('EVAL FAILED ' + expr + ': ' + e);
+ return null;
+ }
+}
+
+function jsLength(obj) {
+ return obj.length;
+}
+
+function assert(obj) {}
+
+/**
+ * Copies all properties from second object to the first. Modifies to.
+ *
+ * @param {Object} to The target object.
+ * @param {Object} from The source object.
+ */
+function copyProperties(to, from) {
+ for (var p in from) {
+ to[p] = from[p];
+ }
+}
+
+
+/**
+ * @param {Object|null|undefined} value The possible value to use.
+ * @param {Object} defaultValue The default if the value is not set.
+ * @return {Object} The value, if it is
+ * defined and not null; otherwise the default
+ */
+function getDefaultObject(value, defaultValue) {
+ if (typeof value != TYPE_undefined && value != null) {
+ return /** @type Object */(value);
+ } else {
+ return defaultValue;
+ }
+}
+
+/**
+ * Detect if an object looks like an Array.
+ * Note that instanceof Array is not robust; for example an Array
+ * created in another iframe fails instanceof Array.
+ * @param {Object|null} value Object to interrogate
+ * @return {boolean} Is the object an array?
+ */
+function isArray(value) {
+ return value != null &&
+ typeof value == TYPE_object &&
+ typeof value.length == TYPE_number;
+}
+
+
+/**
+ * Finds a slice of an array.
+ *
+ * @param {Array} array Array to be sliced.
+ * @param {number} start The start of the slice.
+ * @param {number} opt_end The end of the slice (optional).
+ * @return {Array} array The slice of the array from start to end.
+ */
+function arraySlice(array, start, opt_end) {
+ // Use
+ // return Function.prototype.call.apply(Array.prototype.slice, arguments);
+ // instead of the simpler
+ // return Array.prototype.slice.call(array, start, opt_end);
+ // here because of a bug in the FF and IE implementations of
+ // Array.prototype.slice which causes this function to return an empty list
+ // if opt_end is not provided.
+ return Function.prototype.call.apply(Array.prototype.slice, arguments);
+}
+
+
+/**
+ * Jscompiler wrapper for parseInt() with base 10.
+ *
+ * @param {string} s string repersentation of a number.
+ *
+ * @return {number} The integer contained in s, converted on base 10.
+ */
+function parseInt10(s) {
+ return parseInt(s, 10);
+}
+
+
+/**
+ * Clears the array by setting the length property to 0. This usually
+ * works, and if it should turn out not to work everywhere, here would
+ * be the place to implement the browser specific workaround.
+ *
+ * @param {Array} array Array to be cleared.
+ */
+function arrayClear(array) {
+ array.length = 0;
+}
+
+
+/**
+ * Prebinds "this" within the given method to an object, but ignores all
+ * arguments passed to the resulting function.
+ * I.e. var_args are all the arguments that method is invoked with when
+ * invoking the bound function.
+ *
+ * @param {Object|null} object The object that the method call targets.
+ * @param {Function} method The target method.
+ * @return {Function} Method with the target object bound to it and curried by
+ * the provided arguments.
+ */
+function bindFully(object, method, var_args) {
+ var args = arraySlice(arguments, 2);
+ return function() {
+ return method.apply(object, args);
+ }
+}
+
+// Based on <http://www.w3.org/TR/2000/ REC-DOM-Level-2-Core-20001113/
+// core.html#ID-1950641247>.
+var DOM_ELEMENT_NODE = 1;
+var DOM_ATTRIBUTE_NODE = 2;
+var DOM_TEXT_NODE = 3;
+var DOM_CDATA_SECTION_NODE = 4;
+var DOM_ENTITY_REFERENCE_NODE = 5;
+var DOM_ENTITY_NODE = 6;
+var DOM_PROCESSING_INSTRUCTION_NODE = 7;
+var DOM_COMMENT_NODE = 8;
+var DOM_DOCUMENT_NODE = 9;
+var DOM_DOCUMENT_TYPE_NODE = 10;
+var DOM_DOCUMENT_FRAGMENT_NODE = 11;
+var DOM_NOTATION_NODE = 12;
+
+
+
+function domGetElementById(document, id) {
+ return document.getElementById(id);
+}
+
+/**
+ * Creates a new node in the given document
+ *
+ * @param {Document} doc Target document.
+ * @param {string} name Name of new element (i.e. the tag name)..
+ * @return {Element} Newly constructed element.
+ */
+function domCreateElement(doc, name) {
+ return doc.createElement(name);
+}
+
+/**
+ * Traverses the element nodes in the DOM section underneath the given
+ * node and invokes the given callback as a method on every element
+ * node encountered.
+ *
+ * @param {Element} node Parent element of the subtree to traverse.
+ * @param {Function} callback Called on each node in the traversal.
+ */
+function domTraverseElements(node, callback) {
+ var traverser = new DomTraverser(callback);
+ traverser.run(node);
+}
+
+/**
+ * A class to hold state for a dom traversal.
+ * @param {Function} callback Called on each node in the traversal.
+ * @constructor
+ * @class
+ */
+function DomTraverser(callback) {
+ this.callback_ = callback;
+}
+
+/**
+ * Processes the dom tree in breadth-first order.
+ * @param {Element} root The root node of the traversal.
+ */
+DomTraverser.prototype.run = function(root) {
+ var me = this;
+ me.queue_ = [ root ];
+ while (jsLength(me.queue_)) {
+ me.process_(me.queue_.shift());
+ }
+}
+
+/**
+ * Processes a single node.
+ * @param {Element} node The current node of the traversal.
+ */
+DomTraverser.prototype.process_ = function(node) {
+ var me = this;
+
+ me.callback_(node);
+
+ for (var c = node.firstChild; c; c = c.nextSibling) {
+ if (c.nodeType == DOM_ELEMENT_NODE) {
+ me.queue_.push(c);
+ }
+ }
+}
+
+/**
+ * Get an attribute from the DOM. Simple redirect, exists to compress code.
+ *
+ * @param {Element} node Element to interrogate.
+ * @param {string} name Name of parameter to extract.
+ * @return {string|null} Resulting attribute.
+ */
+function domGetAttribute(node, name) {
+ return node.getAttribute(name);
+ // NOTE(mesch): Neither in IE nor in Firefox, HTML DOM attributes
+ // implement namespaces. All items in the attribute collection have
+ // null localName and namespaceURI attribute values. In IE, we even
+ // encounter DIV elements that don't implement the method
+ // getAttributeNS().
+}
+
+
+/**
+ * Set an attribute in the DOM. Simple redirect to compress code.
+ *
+ * @param {Element} node Element to interrogate.
+ * @param {string} name Name of parameter to set.
+ * @param {string|number} value Set attribute to this value.
+ */
+function domSetAttribute(node, name, value) {
+ node.setAttribute(name, value);
+}
+
+/**
+ * Remove an attribute from the DOM. Simple redirect to compress code.
+ *
+ * @param {Element} node Element to interrogate.
+ * @param {string} name Name of parameter to remove.
+ */
+function domRemoveAttribute(node, name) {
+ node.removeAttribute(name);
+}
+
+/**
+ * Clone a node in the DOM.
+ *
+ * @param {Node} node Node to clone.
+ * @return {Node} Cloned node.
+ */
+function domCloneNode(node) {
+ return node.cloneNode(true);
+ // NOTE(mesch): we never so far wanted to use cloneNode(false),
+ // hence the default.
+}
+
+/**
+ * Clone a element in the DOM.
+ *
+ * @param {Element} element Element to clone.
+ * @return {Element} Cloned element.
+ */
+function domCloneElement(element) {
+ return /** @type {Element} */(domCloneNode(element));
+}
+
+/**
+ * Returns the document owner of the given element. In particular,
+ * returns window.document if node is null or the browser does not
+ * support ownerDocument. If the node is a document itself, returns
+ * itself.
+ *
+ * @param {Node|null|undefined} node The node whose ownerDocument is required.
+ * @returns {Document} The owner document or window.document if unsupported.
+ */
+function ownerDocument(node) {
+ if (!node) {
+ return document;
+ } else if (node.nodeType == DOM_DOCUMENT_NODE) {
+ return /** @type Document */(node);
+ } else {
+ return node.ownerDocument || document;
+ }
+}
+
+/**
+ * Creates a new text node in the given document.
+ *
+ * @param {Document} doc Target document.
+ * @param {string} text Text composing new text node.
+ * @return {Text} Newly constructed text node.
+ */
+function domCreateTextNode(doc, text) {
+ return doc.createTextNode(text);
+}
+
+/**
+ * Appends a new child to the specified (parent) node.
+ *
+ * @param {Element} node Parent element.
+ * @param {Node} child Child node to append.
+ * @return {Node} Newly appended node.
+ */
+function domAppendChild(node, child) {
+ return node.appendChild(child);
+}
+
+/**
+ * Sets display to default.
+ *
+ * @param {Element} node The dom element to manipulate.
+ */
+function displayDefault(node) {
+ node.style[CSS_display] = '';
+}
+
+/**
+ * Sets display to none. Doing this as a function saves a few bytes for
+ * the 'style.display' property and the 'none' literal.
+ *
+ * @param {Element} node The dom element to manipulate.
+ */
+function displayNone(node) {
+ node.style[CSS_display] = 'none';
+}
+
+
+/**
+ * Sets position style attribute to absolute.
+ *
+ * @param {Element} node The dom element to manipulate.
+ */
+function positionAbsolute(node) {
+ node.style[CSS_position] = 'absolute';
+}
+
+
+/**
+ * Inserts a new child before a given sibling.
+ *
+ * @param {Node} newChild Node to insert.
+ * @param {Node} oldChild Sibling node.
+ * @return {Node} Reference to new child.
+ */
+function domInsertBefore(newChild, oldChild) {
+ return oldChild.parentNode.insertBefore(newChild, oldChild);
+}
+
+/**
+ * Replaces an old child node with a new child node.
+ *
+ * @param {Node} newChild New child to append.
+ * @param {Node} oldChild Old child to remove.
+ * @return {Node} Replaced node.
+ */
+function domReplaceChild(newChild, oldChild) {
+ return oldChild.parentNode.replaceChild(newChild, oldChild);
+}
+
+/**
+ * Removes a node from the DOM.
+ *
+ * @param {Node} node The node to remove.
+ * @return {Node} The removed node.
+ */
+function domRemoveNode(node) {
+ return domRemoveChild(node.parentNode, node);
+}
+
+/**
+ * Remove a child from the specified (parent) node.
+ *
+ * @param {Element} node Parent element.
+ * @param {Node} child Child node to remove.
+ * @return {Node} Removed node.
+ */
+function domRemoveChild(node, child) {
+ return node.removeChild(child);
+}
+
+
+/**
+ * Trim whitespace from begin and end of string.
+ *
+ * @see testStringTrim();
+ *
+ * @param {string} str Input string.
+ * @return {string} Trimmed string.
+ */
+function stringTrim(str) {
+ return stringTrimRight(stringTrimLeft(str));
+}
+
+/**
+ * Trim whitespace from beginning of string.
+ *
+ * @see testStringTrimLeft();
+ *
+ * @param {string} str Input string.
+ * @return {string} Trimmed string.
+ */
+function stringTrimLeft(str) {
+ return str.replace(/^\s+/, "");
+}
+
+/**
+ * Trim whitespace from end of string.
+ *
+ * @see testStringTrimRight();
+ *
+ * @param {string} str Input string.
+ * @return {string} Trimmed string.
+ */
+function stringTrimRight(str) {
+ return str.replace(/\s+$/, "");
+} \ No newline at end of file
diff --git a/chrome/common/extensions/docs/examples/extensions/irc/servlet/notification.html b/chrome/common/extensions/docs/examples/extensions/irc/servlet/notification.html
new file mode 100755
index 0000000..89b37e03
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/irc/servlet/notification.html
@@ -0,0 +1,62 @@
+<html>
+ <head>
+ <script src="util.js" type="text/javascript"></script>
+ <style>
+body {
+ margin: 0px;
+ padding: 0px;
+ -webkit-user-select: none;
+}
+
+#notification {
+ width: 300px;
+ height: 50px;
+ position: fixed;
+ overflow: hidden;
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-box-align: stretch;
+}
+
+#title {
+ display: -webkit-box;
+ -webkit-box-flex: 0;
+ padding: 2px 4px 2px 4px;
+ background-color: #AAAAAA;
+ color: white;
+ font-family: Verdana, sans-serif;
+ font-size: 10px;
+}
+
+#content {
+ display: -webkit-box;
+ -webkit-box-flex: 1;
+ color: #444444;
+ font-family: "Lucida Console", Monospace;
+ font-size: 12px;
+ padding: 4px;
+ text-overflow: ellipsis;
+}
+ </style>
+ <script>
+window.onload = function() {
+ var argString = location.search.substring(location.search.indexOf("?") + 1);
+ var tokens = argString.split("&");
+ var args = {};
+ tokens.forEach(function(token) {
+ var keyVal = token.split("=");
+ args[keyVal[0]] = decodeURIComponent(keyVal[1]);
+ });
+
+ $('title').innerText = args.title;
+ $('content').innerText = args.content;
+}
+ </script>
+ </head>
+ <body onclick="window.close();">
+ <div id="notification">
+ <div id="title"></div>
+ <div id="content"></div>
+ </div>
+ </body>
+</html> \ No newline at end of file
diff --git a/chrome/common/extensions/docs/examples/extensions/irc/servlet/src/org/chromium/IRCProxyWebSocket.java b/chrome/common/extensions/docs/examples/extensions/irc/servlet/src/org/chromium/IRCProxyWebSocket.java
new file mode 100755
index 0000000..d1de9e5
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/irc/servlet/src/org/chromium/IRCProxyWebSocket.java
@@ -0,0 +1,116 @@
+package org.chromium;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+import javax.net.SocketFactory;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.websocket.WebSocket;
+import org.eclipse.jetty.websocket.WebSocketServlet;
+
+public class IRCProxyWebSocket extends WebSocketServlet {
+
+ private static final long serialVersionUID = 1L;
+
+ private final Set<ChatWebSocket> members_ =
+ new CopyOnWriteArraySet<ChatWebSocket>();
+
+ protected void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException ,IOException {
+ getServletContext().getNamedDispatcher("default").forward(request,response);
+ }
+
+ protected WebSocket doWebSocketConnect(HttpServletRequest request,
+ String protocol) {
+ return new ChatWebSocket();
+ }
+
+ class ChatWebSocket implements WebSocket, Runnable {
+ Outbound outbound_;
+ Socket socket_ = null;
+ OutputStreamWriter out_;
+ BufferedReader in_;
+ Thread thread_;
+ byte frame_ = 0;
+
+ public void onConnect(Outbound outbound) {
+ outbound_= outbound;
+ }
+
+ public void onMessage(byte frame, byte[] data,int offset, int length) {}
+
+ public void onMessage(byte frame, String data) {
+ try {
+ if (socket_ == null) {
+ try {
+ // We assume the client is going to connect and initiate a
+ // connection with the message "server:port".
+ String tokens[] = data.split(":");
+ socket_ = SocketFactory.getDefault().createSocket(tokens[0],
+ Integer.parseInt(tokens[1]));
+ out_ = new OutputStreamWriter(socket_.getOutputStream());
+ InputStreamReader in = new InputStreamReader(
+ socket_.getInputStream());
+ in_ = new BufferedReader(in);
+
+ members_.add(this);
+ thread_ = new Thread(this);
+ thread_.start();
+
+ } catch (UnknownHostException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ } else {
+ System.out.print(">> " + data);
+ out_.write(data);
+ out_.flush();
+ }
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ public void onDisconnect() {
+ try {
+ socket_.close();
+ thread_.stop();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ members_.remove(this);
+ }
+
+ @Override
+ public void run() {
+ while(true) {
+ try {
+ if (in_.ready()) {
+ String line = in_.readLine();
+ System.out.println("<< " + line);
+ outbound_.sendMessage(frame_, line + "\r\n");
+
+ } else {
+ Thread.sleep(100);
+ }
+ } catch (IOException e) {
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+ }
+}
diff --git a/chrome/common/extensions/docs/examples/extensions/irc/servlet/styles.css b/chrome/common/extensions/docs/examples/extensions/irc/servlet/styles.css
new file mode 100755
index 0000000..2964acd
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/irc/servlet/styles.css
@@ -0,0 +1,161 @@
+body {
+ margin: 0;
+ padding: 0;
+}
+
+#pageContainer {
+ display: -webkit-box;
+ position: fixed;
+ -webkit-box-orient: vertical;
+ -webkit-box-align: stretch;
+ height: 100%;
+ width: 100%;
+ }
+
+ #headerContainer {
+ display: -webkit-box;
+ height: 32px;
+ -webkit-box-orient: horizontal;
+ -webkit-box-align: stretch;
+ }
+
+#pageControls {
+ position: absolute;
+ right: 0px;
+ font-family: Verdana, sans-serif;
+ font-size: 16px;
+ color: #aaaaaa;
+ padding: 8px;
+}
+
+#pageControls *, .removeButton, .channel, .messageLine * {
+ display: inline-block;
+}
+
+.addControlLabel {
+ margin-left: 20px;
+}
+
+.addButton {
+ background-color: #aaaaaa;
+ color: white;
+}
+
+#slideContainer {
+ display: -webkit-box;
+ -webkit-box-flex: 1;
+ position: relative;
+}
+
+#channelSlides {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+}
+
+.channelSlide {
+ position: absolute;
+ width: 80%;
+ height: 100%;
+ background: -webkit-gradient(linear, left top, left bottom,
+ from(#aaaaaa), to(white));
+ -webkit-transition: margin 0.25s ease-in-out;
+}
+
+.channelSlide.far-left {
+ margin-left: -160%;
+}
+
+.channelSlide.left {
+ margin-left: -75%;
+}
+
+.channelSlide.center {
+ margin-left: 10%;
+}
+
+.channelSlide.right {
+ margin-left: 95%;
+}
+
+.channelSlide.far-right {
+ margin-left: 180%;
+}
+
+.channelControls {
+ position: absolute;
+ z-index: 1;
+ right: 0px;
+ top:0px;
+ color: white;
+ text-align: right;
+ padding: 8px;
+ font-family: "Verdana", sans-serif;
+ font-size: 20px;
+}
+
+.channelControls .removeButton {
+ background-color: white;
+ color: #999999;
+ padding: 0px 6px 4px 6px;
+ height:
+}
+
+.channelSlideContainer {
+ position: relative;
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-box-align: stretch;
+ height: 100%;
+ width: 100%;
+}
+
+.messageListContainer {
+ overflow: hidden;
+ position: relative;
+ display: -webkit-box;
+ -webkit-box-flex: 1;
+}
+
+.messageListSpacer {
+ display: -webkit-box;
+ -webkit-box-flex: 0;
+ height: 40px;
+ width: 100%;
+}
+
+.messageLine {
+ margin: 6px;
+ color: #999999;
+ font-family: "Lucida Console", Monospace;
+ font-size: 14px;
+}
+
+.messageList {
+ position: absolute;
+ bottom: 0;
+}
+
+#typingDiv {
+ position: fixed;
+ z-index: 4;
+ width: 80%;
+ height: 30px;
+ margin: 10px;
+ margin-left: 10%;
+ bottom: 0px;
+ -webkit-box-shadow: 3px 3px 5px #888;
+}
+
+#entryText {
+ width: 100%;
+ border: 0px;
+ height: 100%;
+ padding-left: 8px;
+ padding-right: 8px;
+ font-family: "Lucida Console", Monospace;
+ color: white;
+ border: 0px;
+ background: #777777;
+}
+
diff --git a/chrome/common/extensions/docs/examples/extensions/irc/servlet/util.js b/chrome/common/extensions/docs/examples/extensions/irc/servlet/util.js
new file mode 100755
index 0000000..ee1d332
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/extensions/irc/servlet/util.js
@@ -0,0 +1,19 @@
+function $(el) {
+ return document.getElementById(el);
+}
+
+function $F(el) {
+ return $(el).value;
+}
+
+function bind(obj, func) {
+ return function() {
+ return func.apply(obj, arguments);
+ };
+}
+
+function childNodeWithClass(node, className) {
+ var expression = ".//*[@class='" + className + "']";
+ return document.evaluate(expression, node,
+ null, XPathResult.ANY_TYPE, null).iterateNext();
+} \ No newline at end of file