Difference between revisions of "User:Becky Pippen/Shared Media LSL Recipes"

From Second Life Wiki
Jump to navigation Jump to search
m
m (→‎Make TinyURLs by script: Added <code> to the URLs and tried to get better indentation)
 
(25 intermediate revisions by 3 users not shown)
Line 8: Line 8:
   one or the other, I think the yellow highlighting is more useful and instructive
   one or the other, I think the yellow highlighting is more useful and instructive
   for this particular page than LSL syntax highlighting.
   for this particular page than LSL syntax highlighting.
  {{mention|Becky_Pippen}} You can see how it's done on https://www.mediawiki.org/wiki/Extension:SyntaxHighlight — very, very easy to follow! But as a courtesy, I have already done most of the 'migration' work on your behalf 😉 — ~~~~
-->
-->


The examples here assume you're comfortable with LSL, and you just need a quick reference for the syntax of handling HTML and JavaScript in LSL for Shared Media scripting. All the examples on this page are ready to copy-n-paste and drop into a prim — no other setup needed. The key syntax in each example is highlighted. There's no error handling in these minimalist examples, so add plenty of your own.
The examples here assume you're comfortable with [[LSL]], and you just need a quick reference for the syntax of handling HTML and JavaScript in LSL for Shared Media scripting. All the examples on this page are ready to copy-n-paste and drop into a prim — no other setup needed. The key syntax in each example is highlighted. There's no error handling in these minimalist examples, so add plenty of your own.


===Display plain text — XyText replacement===
===Display plain text — XyText replacement===
[[Image:MoaP-example_1.png‎|thumb|Plain text]]
[[Image:MoaP-example_1.png‎|thumb|Plain text]]
<tt>
<syntaxhighlight lang="lsl2" line highlight="5-8">
default
default
{
{
    state_entry()
    state_entry()
    {
    {
        <span style="background-color:#F8F8C8">integer face = 4;</span>
        integer face = 4;
        <span style="background-color:#F8F8C8">string message = "Hello World";</span>
        string message = "Hello World";
        <span style="background-color:#F8F8C8">llSetPrimMediaParams(face,</span>
        llSetPrimMediaParams(face,
            <span style="background-color:#F8F8C8">[PRIM_MEDIA_CURRENT_URL, "data:text/html," + message]);</span>
            [PRIM_MEDIA_CURRENT_URL, "data:text/html," + message]);
    }
    }
} </tt>
} </syntaxhighlight>


Comments:
'''Comments:'''
* An avatar must press the Reload button on the browser to see this rendered in-world. A page will be auto-loaded if you include the parameter <tt>PRIM_MEDIA_AUTO_PLAY, TRUE</tt>, and if the avatar has auto-load enabled in preferences.
* An avatar must press the Reload button on the browser to see this rendered in-world. A page will be auto-loaded if you include the parameter <tt>[[PRIM_MEDIA_AUTO_PLAY]], [[TRUE]]</tt>, and if the avatar has auto-load enabled in preferences.
* There's no need to add any %-hex-escaping or to use [[llEscapeURL]](); llSetPrimMediaParams(face, [PRIM_MEDIA_CURRENT_URL, url]) will automatically escape the string for you.
* There's no need to add any %-hex-escaping or to use <tt>[[llEscapeURL]]()</tt>; <tt>[[llSetPrimMediaParams]](face, [&zwj;[[PRIM_MEDIA_CURRENT_URL]], url])</tt> will automatically escape the string for you.
* This works by feeding a data URI to the on-prim browser. Think of it as serving the contents of the page in the URL itself. For more information, see [[Shared_Media_and_data_URI|here]] and [http://en.wikipedia.org/wiki/Data_URI_scheme here].
* This works by feeding a data URI to the on-prim browser. Think of it as serving the contents of the page in the URL itself. For more information, see [[Shared_Media_and_data_URI|here]] and {{Wikipedia|Data_URI_scheme|here}}.
* The in-world browser limits the data URI (or any address) to 1024 bytes after %-hex-escaping and encoded in UTF-8. See below for tricks using external or self-served JavaScript or CSS to leverage the data URI.
* The in-world browser limits the data URI (or any address) to 1024 bytes after %-hex-escaping and encoded in UTF-8. See below for tricks using external or self-served JavaScript or CSS to leverage the data URI.
* If the data URI contains just plain text, the prefix <tt>"data:text/html,"</tt> can be abbreviated <tt>"data:,"</tt>.
* If the data URI contains just plain text, the prefix <tt>"data:text/html,"</tt> can be abbreviated <tt>"data:,"</tt>.


===Force a page reload===
===Force a page reload===
<tt>
[[Image:MoaP-example-refreshpage.gif|thumb|Auto-refresh]]
[[Image:MoaP-example-refreshpage.gif|thumb|Auto-refresh]]
integer face = 4;
<syntaxhighlight lang="lsl2" line highlight="3,28">
string myURL;
integer face = 4;
<span style="background-color:#F8F8C8">integer seq = 0; // sequence number for unique URLs</span>
string myURL;
integer seq = 0; // sequence number for unique URLs
default
 
{
default
    state_entry()
{
    {
    state_entry()
        llRequestURL();
    {
    }
        llRequestURL();
    }
    http_request(key id, string method, string body)
 
    {
    http_request(key id, string method, string body)
        if (method == URL_REQUEST_GRANTED) {
    {
            myURL = body;
        if (method == URL_REQUEST_GRANTED) {
            llSetPrimMediaParams(face,
            myURL = body;
                [PRIM_MEDIA_AUTO_PLAY, TRUE,
            llSetPrimMediaParams(face,
                  PRIM_MEDIA_CURRENT_URL, myURL]);
                [PRIM_MEDIA_AUTO_PLAY, TRUE,
            llSetTimerEvent(5.0);
                PRIM_MEDIA_CURRENT_URL, myURL]);
        } else if (method == "GET") {
            llSetTimerEvent(5.0);
            llHTTPResponse(id, 200, "Sim FPS: " + (string)llGetRegionFPS());
        } else if (method == "GET") {
        }
            llHTTPResponse(id, 200, "Sim FPS: " + (string)llGetRegionFPS());
    }
        }
    }
    timer()
 
    {
    timer()
        llSetPrimMediaParams(face,
    {
            [<span style="background-color:#F8F8C8">PRIM_MEDIA_CURRENT_URL, myURL + "/?r=" + (string)(++seq)</span>]);
        llSetPrimMediaParams(face,
    }
            [PRIM_MEDIA_CURRENT_URL, myURL + "/?r=" + (string)(++seq)]);
} </tt>
    }
Comments:
}</syntaxhighlight>
* The page will reload automatically if <tt>PRIM_MEDIA_AUTO_PLAY</tt> is enabled and if the new <tt>PRIM_MEDIA_CURRENT_URL</tt> is different. This technique appends a short dummy parameter (like <tt>"/?r=12"</tt>) to the end of the URL to make it different each time, forcing a reload. The parameter is otherwise ignored.
'''Comments:'''
* The page will reload automatically if <tt>[[PRIM_MEDIA_AUTO_PLAY]]</tt> is enabled and if the new <tt>[[PRIM_MEDIA_CURRENT_URL]]</tt> is different. This technique appends a short dummy parameter (like <tt>"/?r=12"</tt>) to the end of the URL to make it different each time, forcing a reload. The parameter is otherwise ignored.


===Display text with HTML markup===
===Display text with HTML markup===
[[Image:MoaP-example-markup.png‎‎|thumb|HTML markup]]
[[Image:MoaP-example-markup.png‎‎|thumb|HTML markup]]
<tt>
<syntaxhighlight lang="lsl2" line highlight="6,8">
default
default
{
{
    state_entry()
    state_entry()
    {
    {
        integer face = 4;
        integer face = 4;
        string message = <span style="background-color:#F8F8C8">"&lt;i>Hello&lt;/i>&lt;h2>World!&lt;/h2>"</span>;
        string message = "<i>Hello</i><h2>World</h2>";
        llSetPrimMediaParams(face,
        llSetPrimMediaParams(face,
            [<span style="background-color:#F8F8C8">PRIM_MEDIA_CURRENT_URL, "data:text/html," + message</span>]);
            [PRIM_MEDIA_CURRENT_URL, "data:text/html," + message]);
    }
    }
} </tt>
} </syntaxhighlight>


Comments:
'''Comments:'''
* The text of the data URI will be %-hex-escaped automatically for you, so there's no need to use [[llEscapeURL]](). All you need in the data URI is the prefix <tt>data:text/html,</tt> plus your HTML.
* The text of the data URI will be %-hex-escaped automatically for you, so there's no need to use <tt>[[llEscapeURL]]()</tt>. All you need in the data URI is the prefix <tt>data:text/html,</tt> plus your HTML.


===Change window resolution===
===Change window resolution===
[[Image:MoaP-example-size.png‎|thumb|Resized web window]]
[[Image:MoaP-example-size.png‎|thumb|Resized web window]]
<tt>
<syntaxhighlight lang="lsl2" line highlight="9,10">
default
default
{
{
    state_entry()
    state_entry()
    {
    {
        integer face = 4;
        integer face = 4;
        string message = "Hello World";
        string message = "Hello World";
        llSetPrimMediaParams(face,
        llSetPrimMediaParams(face,
                [PRIM_MEDIA_CURRENT_URL, "data:text/html," + message,
                [PRIM_MEDIA_CURRENT_URL, "data:text/html," + message,
                <span style="background-color:#F8F8C8">PRIM_MEDIA_WIDTH_PIXELS, 128</span>,
                PRIM_MEDIA_WIDTH_PIXELS, 128,
                <span style="background-color:#F8F8C8">PRIM_MEDIA_HEIGHT_PIXELS, 32</span>]);
                PRIM_MEDIA_HEIGHT_PIXELS, 32]);
    }
    }
} </tt>
} </syntaxhighlight>


Comments:
'''Comments:'''
* The browser will add scroll bars if the rendered HTML doesn't fit within the specified PRIM_MEDIA_* size.
* The browser will add scroll bars if the rendered HTML doesn't fit within the specified <tt>PRIM_MEDIA_*</tt> size.


===Display an image — works with animated GIFs too===
===Display an image — works with animated GIFs too===
[[Image:MoaP-example-animgif.gif‎|thumb|Image on a prim]]
[[Image:MoaP-example-animgif.gif‎|thumb|Image on a prim]]
<tt>
<syntaxhighlight lang="lsl2" line highlight="8">
default
default
{
{
    state_entry()
    state_entry()
    {
    {
        integer face = 4;
        integer face = 4;
        string imageURL =
        string imageURL =
          "http://upload.wikimedia.org/wikipedia/commons/7/70/Rotating_earth_(small).gif";
          "https://commons.wikimedia.org/wiki/File:Rotating_earth_(large)_transparent.gif";
        <span style="background-color:#F8F8C8">string dataURI = "data:text/html,&lt;object data='" + imageURL + "'>&lt;/object>";</span>
        string dataURI = "data:text/html,<object data='" + imageURL + "'></object>";
        llSetPrimMediaParams(face,
        llSetPrimMediaParams(face,
            [PRIM_MEDIA_CURRENT_URL, dataURI,
            [PRIM_MEDIA_CURRENT_URL, dataURI,
              PRIM_MEDIA_WIDTH_PIXELS, 256,
            PRIM_MEDIA_WIDTH_PIXELS, 256,
              PRIM_MEDIA_HEIGHT_PIXELS, 256]);
            PRIM_MEDIA_HEIGHT_PIXELS, 256]);
    }
    }
} </tt>
}</syntaxhighlight>


Comments:
'''Comments:'''
* If attempting to display YouTube video full-frame, see [https://blogs.secondlife.com/community/community/tnt/blog/2010/03/16/viewer-2-tip-shared-media-show-youtube-on-a-full-prims-face this discussion].
* If attempting to display YouTube video full-frame, see [https://blogs.secondlife.com/community/community/tnt/blog/2010/03/16/viewer-2-tip-shared-media-show-youtube-on-a-full-prims-face this discussion]{{dead link|2024-04-29 Searched as deep as I could but didn't find it — Gwyn}}.
* For images, you can also use <tt>&lt;image src= ></tt> like this:
* For images, you can also use <syntaxhighlight lang="html" inline><image src= ></syntaxhighlight> like this: <syntaxhighlight lang="lsl2" inline>string dataURI="data:text/html,<img src='" + imageURL + "'>";</syntaxhighlight>
string dataURI="data:text/html,<img src='" + imageURL + "'>";
* Note: The original Wikimedia Commons image given in the code as an example has been deprecated and was replaced by the closest-looking one; if you try this code in-world, it will show up differently than the animated image to the right. — [[User:Gwyneth Llewelyn|Gwyneth Llewelyn]] ([[User talk:Gwyneth Llewelyn|talk]]) 04:51, 29 April 2024 (PDT)


===Display a background image, tiled===
===Display a background image, tiled===
Line 132: Line 134:
====Method #1 — using CSS in a style element in <head>====
====Method #1 — using CSS in a style element in <head>====
[[Image:MoaP-example-bgTiled.png‎|thumb|Tiled background image]]
[[Image:MoaP-example-bgTiled.png‎|thumb|Tiled background image]]
<tt>
<syntaxhighlight lang="lsl2" line highlight="9-10">
default
default
{
{
    state_entry()
    state_entry()
    {
    {
        integer face = 4;
        integer face = 4;
        string imageURL =
        string imageURL =
            "http://www.google.com/intl/en_ALL/images/logo.gif";
            "https://www.google.com/intl/en_ALL/images/logo.gif";
        string dataURI = "data:text/html,&lt;head>"
        string dataURI = "data:text/html,<head>"
            + "<span style="background-color:#F8F8C8">&lt;style type='text/css'>body{background-image:url(\"</span>"
            + "<style type='text/css'>body{background-image:url(\""
            + <span style="background-color:#F8F8C8">imageURL + "\");}&lt;/style></span>&lt;/head>&lt;body>Hello World&lt;/body>";
            + imageURL + "\");}</style></head><body>Hello World</body>";
        llSetPrimMediaParams(face,
        llSetPrimMediaParams(face,
                [PRIM_MEDIA_CURRENT_URL, dataURI]);
                [PRIM_MEDIA_CURRENT_URL, dataURI]);
    }
    }
} </tt>
}</syntaxhighlight>


====Method #2  — using background attribute in &lt;body>====
====Method #2  — using background attribute in <body>====


<tt>
<syntaxhighlight lang="lsl2" line highlight="7-9">
default
default
{
{
    state_entry()
    state_entry()
    {
    {
        integer face = 4;
        integer face = 4;
        string imageURL = "http://www.google.com/intl/en_ALL/images/logo.gif";
        string imageURL = "https://www.google.com/intl/en_ALL/images/logo.gif";
        string dataURI = "<span style="background-color:#F8F8C8">data:text/html,&lt;body background='" + imageURL + "'></span>"
        string dataURI = "data:text/html,<body background='" + imageURL + "'>"
            + "<span style="background-color:#F8F8C8">Hello World</span>"
            + "Hello World"
            + "<span style="background-color:#F8F8C8">&lt;/body></span>";
            + "</body>";
        llSetPrimMediaParams(face,
        llSetPrimMediaParams(face,
                [PRIM_MEDIA_CURRENT_URL, dataURI]);
                [PRIM_MEDIA_CURRENT_URL, dataURI]);
    }
    }
} </tt>
}</syntaxhighlight>


====Method #3 — using style attribute in <body>====
====Method #3 — using style attribute in <body>====


<tt>
<syntaxhighlight lang="lsl2" line highlight="7-9">
default
default
{
{
    state_entry()
    state_entry()
    {
    {
        integer face = 4;
        integer face = 4;
        string imageURL = "http://www.google.com/intl/en_ALL/images/logo.gif";
        string imageURL = "https://www.google.com/intl/en_ALL/images/logo.gif";
        string dataURI = "<span style="background-color:#F8F8C8">data:text/html,&lt;body style='background-image:url(\"" + imageURL + "\")'></span>"
        string dataURI = "data:text/html,<body style='background-image:url(\"" + imageURL + "\")'>"
            + "<span style="background-color:#F8F8C8">Hello World</span>"
            + "Hello World"
            + "<span style="background-color:#F8F8C8">&lt;/body></span>";
            + "</body>";
        llSetPrimMediaParams(face,
        llSetPrimMediaParams(face,
                [PRIM_MEDIA_CURRENT_URL, dataURI]);
                [PRIM_MEDIA_CURRENT_URL, dataURI]);
    }
    }
} </tt>
}</syntaxhighlight>


===Text colors — using &lt;font> element===
===Text colors — using <font> element===
[[Image:MoaP-example-font-color.png‎|thumb|Font color]]
[[Image:MoaP-example-font-color.png‎|thumb|Font color]]
<tt>
<syntaxhighlight lang="lsl2" line highlight="6,8">
default
default
{
{
    state_entry()
    state_entry()
    {
    {
        integer face = 4;
        integer face = 4;
        string message = "<span style="background-color:#F8F8C8">&lt;font color='Red'>Hello World&lt;/font></span>";
        string message = "<font color='Red'>Hello World</font>";
        llSetPrimMediaParams(face,
        llSetPrimMediaParams(face,
                [<span style="background-color:#F8F8C8">PRIM_MEDIA_CURRENT_URL, "data:text/html," + message</span>]);
                [PRIM_MEDIA_CURRENT_URL, "data:text/html," + message]);
    }
    }
} </tt>
}</syntaxhighlight>


Comments:
'''Comments:'''
* You can also specify color and other CSS rules in a style attribute in another element. For example, you can replace message in the example above with:
* You can also specify color and other CSS rules in a style attribute in another element. For example, you can replace message in the example above with:
 
:<syntaxhighlight lang="lsl2" inline>string message = "<h2 style='color:#FF0000'>Hello World</h2>";</syntaxhighlight>
string message = "&lt;h2 style='color:#FF0000'>Hello World&lt;/h2>";


===Link to external CSS file (forms example)===
===Link to external CSS file (forms example)===
[[Image:MoaP-example-externalCSS.png‎|thumb|External CSS file]]
[[Image:MoaP-example-externalCSS.png‎|thumb|External CSS file]]
<tt>
<syntaxhighlight lang="lsl2" line highlight="6-7,10>
default
default
{
{
    state_entry()
    state_entry()
    {
    {
        integer face = 4;
        integer face = 4;
        <span style="background-color:#F8F8C8">string externalCSS =</span>
        string externalCSS =
            <span style="background-color:#F8F8C8">"http://www.google.com/css/modules/buttons/g-button-chocobo.css";</span>
            "https://www.google.com/css/modules/buttons/g-button-chocobo.css";
 
        string dataURI = "data:text/html,&lt;head><span style="background-color:#F8F8C8">&lt;link href='</span>" +
        string dataURI = "data:text/html,<head><link href='</span>" +
                <span style="background-color:#F8F8C8">externalCSS + "' rel='stylesheet' type='text/css' /></span>&lt;/head>" +
                externalCSS + "' rel='stylesheet' type='text/css' /></head>" +
                "&lt;form>&lt;input type='button' value='Click Me' class='g-button' />&lt;/form>";
                "<form><input type='button' value='Click Me' class='g-button' /></form>";
 
        llSetPrimMediaParams(face,
        llSetPrimMediaParams(face,
                [PRIM_MEDIA_CURRENT_URL, dataURI]);
                [PRIM_MEDIA_CURRENT_URL, dataURI]);
    }
    }
} </tt>
}</syntaxhighlight>


Comments:
'''Comments:'''
* This is one way to work around the 1024-byte limitation of data URIs. The external CSS can be as large as the browser permits. Also see the examples below for putting JavaScript in an external file.
* This is one way to work around the 1024-byte limitation of data URIs. The external CSS can be as large as the browser permits. Also see the examples below for putting JavaScript in an external file.
* The tag <tt>&lt;head></tt> and the attribute <tt>type='text/css'</tt> may be omitted to make the data URI a few bytes shorter.
* The tag <syntaxhighlight lang="html" inline><head></syntaxhighlight> and the attribute <syntaxhighlight lang="html" inline>type='text/css'</syntaxhighlight> may be omitted to make the data URI a few bytes shorter.


===Calling external JavaScript functions (JQuery example)===
===Calling external JavaScript functions (JQuery example)===
[[Image:MoaP-example-JQuery.gif‎|thumb|Calling external JQuery]]
[[Image:MoaP-example-JQuery.gif‎|thumb|Calling external JQuery]]
<tt>
<syntaxhighlight lang="lsl2" line highlight="9,12-13">
integer face = 4;
integer face = 4;
string externalJavascript =
string externalJavascript =
    "http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js";
    "https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js";
default
default
{
{
    state_entry()
    state_entry()
    {
    {
        string dataURI = "data:text/html," +
        string dataURI = "data: text/html," +
            "&lt;head><span style="background-color:#F8F8C8">&lt;script src='" + externalJavascript + "'>&lt;/script></span>&lt;/head>" +
            "<head><script src='" + externalJavascript + "'></script></head>" +
            "&lt;button>Toggle&lt;/button>" +
            "<button>Toggle</button>" +
            "&lt;p>Hello&lt;br />World&lt;/p>" +
            "<p>Hello<br />World</p>" +
            "<span style="background-color:#F8F8C8">&lt;script>$('button').click(function () </span>" +
            "<script>$('button').click(function () " +
            "<span style="background-color:#F8F8C8">{$('p').slideToggle('slow');});&lt;/script></span>";
            "{$('p').slideToggle('slow');});</script>";
   
   
        llSetPrimMediaParams(face,
        llSetPrimMediaParams(face,
            [PRIM_MEDIA_CURRENT_URL, dataURI]);
            [PRIM_MEDIA_CURRENT_URL, dataURI]);
    }
    }
} </tt>
}</syntaxhighlight>


Comments:
'''Comments:'''
* This is a technique for working around the 1024-byte limit for data URIs. When you can't fit all the JavaScript you need into one data URI, just move some JavaScript functions into an external file and give the browser a data URI that references the external file with a URL. In this example above, the JavaScript for [http://jquery.com/ JQuery] lives in an external file on Google's servers. You just need enough bytes left over in the data URI to put a JavaScript function call with its parameters inside a <tt>&lt;script></tt> element.
* This is a technique for working around the 1024-byte limit for data URIs. When you can't fit all the JavaScript you need into one data URI, just move some JavaScript functions into an external file and give the browser a data URI that references the external file with a URL. In this example above, the JavaScript for [https://jquery.com/ JQuery] lives in an external file on Google's servers. You just need enough bytes left over in the data URI to put a JavaScript function call with its parameters inside a <syntaxhighlight lang="html" inline><script></syntaxhighlight> element.
* A data URI can be used as a springboard to generate arbitrary complex web pages by making use of these elements:
* A data URI can be used as a springboard to generate arbitrary complex web pages by making use of these elements:
** a reference to external CSS with <tt>&lt;link href= </tt>,  
** a reference to external CSS with <syntaxhighlight lang="html" inline><link href=</syntaxhighlight>,  
** a reference to external JavaScript with <tt>&lt;script src=</tt>, and
** a reference to external JavaScript with <syntaxhighlight lang="html" inline><script src=</syntaxhighlight>, and
** a function call with parameters to the external JavaScript using <tt>&lt;script>someFunction(args);&lt;/script></tt>. (In the JQuery example above, the name of the external function is <tt>"$"</tt>.)
** a function call with parameters to the external JavaScript using <syntaxhighlight lang="html" inline><script>someFunction(args);</script></syntaxhighlight>. (In the JQuery example above, the name of the external function is <code>"$"</code>.)
* See the next two examples for variations of this same technique.
* See the next two examples for variations of this same technique.


===Self-served HTML and JavaScript===
===Self-served HTML and JavaScript===
====Example #1 — using &lt;script src= and .innerHTML= ====
====Example #1 — using <code>script src=</code> and <code>.innerHTML=</code> ====
[[Image:MoaP-example-refresh-clock.gif‎|thumb|Self-served auto-refreshed pages]]
[[Image:MoaP-example-refresh-clock.gif‎|thumb|Self-served auto-refreshed pages]]
<tt>
<syntaxhighlight lang="lsl2" line highlight="5,7,25,31-33,47,50">
integer face = 4;
integer face = 4;
string myURL;
string myURL;
 
// This can be up to 2KBytes after %-hex-escaping:
// This can be up to 2KBytes after %-hex-escaping:
<span style="background-color:#F8F8C8">string servedPage =</span> "
string servedPage = "
function checklength(i){if (i<10) {i='0'+i;} return i;}       
function checklength(i){if (i<10) {i='0'+i;} return i;}       
<span style="background-color:#F8F8C8">function clock()</span>{  
function clock(){  
  var now = new Date();   
var now = new Date();   
  var hours = checklength(now.getHours());   
var hours = checklength(now.getHours());   
  var minutes = checklength(now.getMinutes());   
var minutes = checklength(now.getMinutes());   
  var seconds = checklength(now.getSeconds());   
var seconds = checklength(now.getSeconds());   
  var format = 1; //0=24 hour format, 1=12 hour format   
var format = 1; //0=24 hour format, 1=12 hour format   
  var time;   
var time;   
  if (format == 1) {  
if (format == 1) {  
  if (hours >= 12) {   
  if (hours >= 12) {   
    if (hours ==12 ) { hours = 12;
  if (hours ==12 ) { hours = 12;
    } else { hours = hours-12; }  
  } else { hours = hours-12; }  
    time=hours+':'+minutes+':'+seconds+' PM';   
  time=hours+':'+minutes+':'+seconds+' PM';   
  } else if(hours < 12) {  
  } else if(hours < 12) {  
    if (hours ==0) {hours=12;}   
  if (hours ==0) {hours=12;}   
    time=hours+':'+minutes+':'+seconds+' AM';   
  time=hours+':'+minutes+':'+seconds+' AM';   
  }   
  }   
  }   
}   
  if (format == 0) {time= hours+':'+minutes+':'+seconds;}   
if (format == 0) {time= hours+':'+minutes+':'+seconds;}   
  <span style="background-color:#F8F8C8">document.getElementById('clock').innerHTML=time;</span>
document.getElementById('clock').innerHTML=time;
  setTimeout('clock();', 500);  
setTimeout('clock();', 500);  
} "; // yes, that's one long string.
} "; // yes, that's one long string.
 
displayPage()
displayPage()
{
{
    string dataURI = "data:text/html,<span style="background-color:#F8F8C8">&lt;script src='</span>" +
    string dataURI = "data: text/html,<script src='" +
        <span style="background-color:#F8F8C8">myURL + "'>&lt;/script></span>" +
        myURL + "'></script>" +
        "<span style="background-color:#F8F8C8">&lt;div id='clock'>&lt;script>clock();&lt;/script>&lt;/div></span>";
        "<div id='clock'><script>clock()</script></div>";
    llSetPrimMediaParams(face, [PRIM_MEDIA_CURRENT_URL, dataURI]);
    llSetPrimMediaParams(face, [PRIM_MEDIA_CURRENT_URL, dataURI]);
}
}
 
default
{
    state_entry()
    {
        llRequestURL();
    }
   
   
default
    http_request(key id, string method, string body)
{
    {
    state_entry()
        if (method == URL_REQUEST_GRANTED) {
    {
            myURL = body;
        llRequestURL();
            displayPage();
    }
        } else if (method == "GET") {
            llHTTPResponse(id, 200, servedPage);
    http_request(key id, string method, string body)
        }
    {
    }
        if (method == URL_REQUEST_GRANTED) {
} </syntaxhighlight>
            <span style="background-color:#F8F8C8">myURL = body;</span>
            displayPage();
        } else if (method == "GET") {
            <span style="background-color:#F8F8C8">llHTTPResponse(id, 200, servedPage);</span>
        }
    }
} </tt>


Comments:
'''Comments:'''
* This is a technique for trading the 1024-byte limit of data URIs for the 2048-byte-per-page limit that the script can serve itself. As in the previous example, when you can't fit all the JavaScript into one data URI, just move the JavaScript into an external file and reference the file with <tt>&lt;script src=URL>&lt;/script></tt>. When the URL refers to the script's own HTTP-in URL, then the page served can contain up to 2K bytes, so you get at least that many bytes plus whatever logic you can fit into the rest of the data URI. If you move that JavaScript to an external web server, the size of the external JavaScript file can be as large as the client web browser can render.
* This is a technique for trading the 1024-byte limit of data URIs for the 2048-byte-per-page limit that the script can serve itself. As in the previous example, when you can't fit all the JavaScript into one data URI, just move the JavaScript into an external file and reference the file with <syntaxhighlight lang="HTML" inline><script src=URL></script></syntaxhighlight>. When the URL refers to the script's own HTTP-in URL, then the page served can contain up to 2K bytes, so you get at least that many bytes plus whatever logic you can fit into the rest of the data URI. If you move that JavaScript to an external web server, the size of the external JavaScript file can be as large as the client web browser can render.
* Your script can serve an arbitrary number of different pages through one HTTP-in URL by appending a path or parameter to the URL.
* Your script can serve an arbitrary number of different pages through one HTTP-in URL by appending a path or parameter to the URL.
* This works by assigning a string containing HTML to the <tt>.innerHTML</tt> contents of the <tt>&lt;div></tt> element with id "clock". This achieves something similar to the Ajax-like [http://talirosca.wikidot.com/bootstrapping-html-on-a-prim technique published by Tali Rosca] and mentioned [http://wiki.secondlife.com/wiki/User:Kelly_Linden/lsl_hacks#DHTML_.2F_Javascript_.28Tali_Rosca.29 here]. In those techniques, a string is constructed containing an <tt>&lt;a href=</tt> tag and an <tt>href=</tt> that points to the script's HTTP-in URL. That string then replaces the <tt>&lt;body></tt> element using:
* This works by assigning a string containing HTML to the <code>.innerHTML</code> contents of the <syntaxhighlight lang="HTML" inline><div></syntaxhighlight> element with id "clock". This achieves something similar to the Ajax-like [https://tali.appspot.com/oldhtml/scripting/scripts/bootstrapping.html technique published by Tali Rosca] and mentioned [http://wiki.secondlife.com/wiki/User:Kelly_Linden/lsl_hacks#DHTML_.2F_Javascript_.28Tali_Rosca.29 here]. In those techniques, a string is constructed containing an <syntaxhighlight lang="HTML" inline><a href=</syntaxhighlight> tag and an <syntaxhighlight lang="HTML" inline>href=</syntaxhighlight> that points to the script's HTTP-in URL. That string then replaces the <syntaxhighlight lang="HTML" inline><body></syntaxhighlight> element using:
<tt>document.getElementsByTagName('body')[0].innerHTML = <i>new-content</i></tt>
:<syntaxhighlight lang="JavaScript" inline>document.getElementsByTagName('body')[0].innerHTML = </syntaxhighlight><code><i>new-content</i></code>


====Example #2 — using <body onload= (lag graph example)====
====Example #2 — using <code><body onload=</code> (lag graph example)====
[[Image:MoaP-example-laggraph.gif‎|thumb|Refreshed pages]]
[[Image:MoaP-example-laggraph.gif‎|thumb|Refreshed pages]]
<tt>
<syntaxhighlight lang="lsl2" line highlight="40,41">
  integer face = 4;
  integer face = 4;
  string jsURL; // where to fetch external JavaScript
  string jsURL; // where to fetch external JavaScript
Line 334: Line 335:
     return
     return
         "function bar(widthPct,heightPix) {" +
         "function bar(widthPct,heightPix) {" +
         " document.writeln(\"&lt;hr style='padding:0;margin:0;" +
         " document.writeln(\"<hr style='padding:0;margin:0;" +
         "  margin-top:-1px;text-align:left;align:left;border=0;" +
         "  margin-top:-1px;text-align:left;align:left;border=0;" +
         "  width:\"+widthPct+\"%;height:\"+heightPix+\"px;" +
         "  width:\"+widthPct+\"%;height:\"+heightPix+\"px;" +
         "  background-color:#c22;color:#c22;'>\");}" +
         "  background-color:#c22;color:#c22;'>\");}" +
         " function graphBars(arr){for(var i=0;i&lt;arr.length;++i)" +
         " function graphBars(arr){for(var i=0;i<arr.length;++i)" +
         "  {bar(arr[i],18);}}";
         "  {bar(arr[i],18);}}";
  }
  }
Line 360: Line 361:
         // The dataURI loads external JavaScript functions and calls one with parameters:
         // The dataURI loads external JavaScript functions and calls one with parameters:
   
   
         string dataURI = "data:text/html," +
         string dataURI = "data: text/html," +
             "<span style="background-color:#F8F8C8">&lt;head>&lt;script src='" + jsURL + "'>&lt;/script>&lt;/head></span>" +
             "<head><script src='" + jsURL + "'></script></head></span>" +
             "<span style="background-color:#F8F8C8">&lt;body onload=\"graphBars([" + llList2CSV(numbers) + "]);\">&lt;/body></span>";
             "<body onload=\"graphBars([" + llList2CSV(numbers) + "]);\"></body></span>";
   
   
         llSetPrimMediaParams(face, [PRIM_MEDIA_CURRENT_URL, dataURI, PRIM_MEDIA_AUTO_PLAY, TRUE]);
         llSetPrimMediaParams(face, [PRIM_MEDIA_CURRENT_URL, dataURI, PRIM_MEDIA_AUTO_PLAY, TRUE]);
Line 376: Line 377:
         }
         }
     }
     }
  } </tt>
  } </syntaxhighlight>


Comments:
'''Comments:'''
* Like the previous example, this is another variation of how to structure a data URI to serve as a springboard for arbitrarily large pages. In this example, the data URI first uses a <tt>&lt;script src=...></tt> tag to read a file of external JavaScript functions in the <tt>&lt;head></tt> element, and then the <tt>onload=</tt> attribute in <tt>&lt;body></tt> triggers a call to one of the functions which executes a loop creating a bunch of <tt>&lt;hr></tt> elements that form the bar chart. The size of HTML generated by the JavaScript can be as large as the browser permits.
* Like the previous example, this is another variation of how to structure a data URI to serve as a springboard for arbitrarily large pages. In this example, the data URI first uses a <syntaxhighlight lang="html" inline><script src=...></syntaxhighlight> tag to read a file of external JavaScript functions in the <syntaxhighlight lang="html" inline><head></syntaxhighlight> element, and then the <syntaxhighlight lang="html" inline>onload=</syntaxhighlight> attribute in <syntaxhighlight lang="html" inline><body></syntaxhighlight> triggers a call to one of the functions which executes a loop creating a bunch of <syntaxhighlight lang="html" inline><hr></syntaxhighlight> elements that form the bar chart. The size of HTML generated by the JavaScript can be as large as the browser permits.
* In this example, <tt>jsURL</tt> is set to the script's own HTTP-in URL so that the example can be self-contained, but you can point <tt>jsURL</tt> to an external URL as well. If served by an external server, the JavaScript read in <tt>&lt;head></tt> can be as large as the browser permits.
* In this example, <code>jsURL</code> is set to the script's own HTTP-in URL so that the example can be self-contained, but you can point <code>jsURL</code> to an external URL as well. If served by an external server, the JavaScript read in <syntaxhighlight lang="html" inline><head></syntaxhighlight> can be as large as the browser permits.
* <tt>&lt;head>&lt;/head></tt> can be omitted in the data URI surrounding the <tt>&lt;script src=</tt> tag.
* <syntaxhighlight lang="html" inline><head></head></syntaxhighlight> can be omitted in the data URI surrounding the <syntaxhighlight lang="html" inline><script src=</syntaxhighlight> tag.


===Reverse Ajax Long-polling the HTTP-in server (chat logger example)===
===Reverse Ajax - Long-polling the HTTP-in server, chat logger example===
[[Image:MoaP-example-Long-polling-httpin.gif|thumb|300px|Long-polling HTTP-in]]
[[Image:MoaP-example-Long-polling-httpin.gif|thumb|300px|Long-polling HTTP-in]]
<tt>
<tt>
Line 441: Line 442:
     integer numMessagesQueued = llGetListLength(msgQueue);
     integer numMessagesQueued = llGetListLength(msgQueue);
     integer count = 0;
     integer count = 0;
     while (count &lt; numMessagesQueued) {
    integer done = FALSE;
     while (!done && count &lt; numMessagesQueued) {
         nextMsg = llList2String(msgQueue, count);
         nextMsg = llList2String(msgQueue, count);
         nextMsgSize = llStringLength(nextMsg);
         nextMsgSize = llStringLength(nextMsg);
Line 449: Line 451:
             totalMsgSize += nextMsgSize;
             totalMsgSize += nextMsgSize;
             ++count;
             ++count;
        } else {
            done = TRUE;
         }
         }
     }
     }
Line 618: Line 622:
</tt>
</tt>


Comments:
'''Comments:'''
<ol start="1">
<ol start="1">
<li>This "[http://en.wikipedia.org/wiki/Push_technology server-push]" technique using long-polling gives an LSL script the ability to modify the web page displayed on a prim. The Javascript side always keeps an HTTP GET open to the prim's HTTP-in URL, and the LSL side responds whenever it wants to with a response consisting of strings of Javascript to be executed by the web browser. For example, this LSL code causes an alert box to pop up on the web page:
<li>This "{{Wikipedia|Push technology|server-push}}" technique using long-polling gives an LSL script the ability to modify the web page displayed on a prim. The Javascript side always keeps an HTTP GET open to the prim's HTTP-in URL, and the LSL side responds whenever it wants to with a response consisting of strings of Javascript to be executed by the web browser. For example, this LSL code causes an alert box to pop up on the web page:
: <code>sendMessage( "alert()" );</code></li>
: <syntaxhighlight lang="lsl2" inline>sendMessage( "alert()" );</syntaxhighlight></li>
<li>In the LSL listing above, <span style="background-color:#F8F8C8">the yellow highlighting</span> shows the most important parts of the HTTP long-polling mechanism. The <span style="background-color:#E0FDE0">green highlighting</span> shows the message buffering on top of that. The <span style="background-color:#E8E8FF">blue highlighting</span> is the application code that uses long-polling. To use long-polling for a different application, replace the blue parts. The rest is glue.</li>
<li>In the LSL listing above, <span style="background-color:#F8F8C8">the yellow highlighting</span> shows the most important parts of the HTTP long-polling mechanism. The <span style="background-color:#E0FDE0">green highlighting</span> shows the message buffering on top of that. The <span style="background-color:#E8E8FF">blue highlighting</span> is the application code that uses long-polling. To use long-polling for a different application, replace the blue parts. The rest is glue.<ref>Unfortunately, at the time of editing/cleaning up the WikiText on this fantastic tutorial, I couldn't figure out a neat way of having three separate colours as highlights on the code. I've placed a request on the MediaWiki talk page for <code><syntaxhighlight></code>, but I'm unsure if whatever tricks they suggest will work on the SL Wiki. So, for the time being, this example will remain untouched, as originally written by {{mention|Becky Pippen|p=}} — [[User:Gwyneth Llewelyn|Gwyneth Llewelyn]]</ref></li>
<li>To avoid some subtle WebKit problems when dynamically appending or replacing the first-level child nodes in &lt;head> or &lt;body>, we've made two special &lt;div> elements on the bootstrap HTML page. The first surrounds the script#sc element that we replace for each GET. The second is at the end of &lt;body> as a convenient place for the application to insert new content that would otherwise go in &lt;body>.</li>
<li>To avoid some subtle WebKit<ref>It might be the case that the SL Viewer is now based on the Chromium Embedded Framework instead, so Becky's references might not apply any longer. — [[User:Gwyneth Llewelyn|Gwyneth Llewelyn]]</ref> problems when dynamically appending or replacing the first-level child nodes in <syntaxhighlight lang="html" inline><head></syntaxhighlight> or <syntaxhighlight lang="html" inline><body></syntaxhighlight>, we've made two special <syntaxhighlight lang="html" inline><div></syntaxhighlight> elements on the bootstrap HTML page. The first surrounds the <code>script#sc</code> element that we replace for each GET. The second is at the end of <syntaxhighlight lang="html" inline><body></syntaxhighlight> as a convenient place for the application to insert new content that would otherwise go in <syntaxhighlight lang="html" inline><body></syntaxhighlight>.</li>
<li>To make sure that there is nearly always a valid GET request open, the Javascript side starts a new GET after receiving a response, or when the last GET times out.</li>
<li>To make sure that there is nearly always a valid GET request open, the Javascript side starts a new GET after receiving a response, or just before the last unanswered GET times out.</li>
<li>There are two timeouts hard-coded in the bootstrap data URI:
<li>There are two timeouts hard-coded in the bootstrap data URI:
<ol style="list-style-type:lower-roman"><li>The "20000" is the timeout (20 seconds) for when an open GET expires, and should be set to something around the minimum of the WebKit outgoing GET request timeout, and the SL-LSL incoming GET timeout. This ensures that GETs are nearly continuous. [[LlHTTPResponse|This page]] says the timeout on the LSL side is 25 seconds.</li>
<ol style="list-style-type:lower-roman"><li>The "20000" is the timeout (20 seconds) for when an open GET expires, and should be set to a little less than the minimum of the WebKit outgoing GET request timeout, and the SL-LSL incoming GET timeout. This ensures that GETs are nearly continuous, and the LSL side can always respond to the most recent GET request. [[LlHTTPResponse|This page]] says the timeout on the LSL side is 25 seconds.</li>
<li>The "500" is a throttle that sets an upper limit to how fast the Javascript re-polls HTTP-in to prevent hammering the simulator. It's a half-second delay after receiving a GET response before starting a new GET.</li></ol>
<li>The "500" is a throttle that sets an upper limit to how fast the Javascript re-polls HTTP-in to prevent hammering the simulator. It's a half-second delay after receiving a GET response before starting a new GET.</li></ol>
<li> If there's a little gap between two GETs, or if two overlap a little, the sendMessage() message buffering will compensate by queuing up messages to be sent when the next GET arrives. If there are multiple messages in the queue when a GET arrives, the LSL side will concatenate as many messages as possible and send them together.
<li> If there's a little gap between two GETs, or if two overlap a little, the <syntaxhighlight lang="lsl2" inline>sendMessage()</syntaxhighlight> message buffering will compensate by queuing up messages to be sent when the next GET arrives. If there are multiple messages in the queue when a GET arrives, the LSL side will concatenate as many messages as possible and send them together.
</li>
</li>
<li>On the Javascript side, the GET is not triggered until the <tt>&lt;script></tt> element is attached to its parent node with .appendChild() or .replaceChild().</li>
<li>On the Javascript side, the GET is not triggered until the <syntaxhighlight lang="html" inline><script></syntaxhighlight> element is attached to its parent node with <code>.appendChild()</code> or <code>.replaceChild()</code>.</li>
<li>The bootstrap Javascript above in setDataURI() had to be compressed somewhat to fit into the 1024-byte data URI limit. Here's an expanded listing for reference with more descriptive names:</li>
<li>The bootstrap Javascript above in <syntaxhighlight lang="lsl2" inline>setDataURI()</syntaxhighlight> had to be compressed somewhat to fit into the 1024-byte data URI limit. Here's an expanded listing for reference with more descriptive names:</li>
<tt>
:<syntaxhighlight lang="html">
&lt;!DOCTYPE HTML>
<!DOCTYPE HTML>
&lt;html>
<html>
   &lt;body>
   <body>
     &lt;div>
     <div>
       &lt;script id='script'> &lt;/script>
       <script id='script'> </script>
     &lt;/div>
     </div>
     &lt;script>
     <script>
       var poll=function(){
       var poll=function(){
         var script=document.getElementById('script'),
         var script=document.getElementById('script'),
Line 646: Line 650:
         newScript;
         newScript;
         return {
         return {
           beg:function(){                                      // Initiate a long-poll GET
           beg:function() {                                      // Initiate a long-poll GET
             newScript=document.createElement('script');        // The response will go here
             newScript=document.createElement('script');        // The response will go here
             newScript.onload=poll.end;                        // Call poll.end() when we get a response
             newScript.onload=poll.end;                        // Call poll.end() when we get a response
Line 654: Line 658:
             script=newScript;},
             script=newScript;},
   
   
           end:function(){
           end: function() {
             clearTimeout(timeoutId);
             clearTimeout(timeoutId);
             timeoutId=null;
             timeoutId=null;
Line 662: Line 666:
         };
         };
       }();
       }();
     &lt;/script>
     </script>
     &lt;button id='btn'onclick=poll.beg()>Start&lt;button>
     <button id='btn' onclick=poll.beg()>Start<button>
     &lt;div id='dv'> &lt;/div>
     <div id='dv'> </div>
   &lt;/body>
   </body>
&lt;/html>
</html>
</tt>
</syntaxhighlight>
</ol>
</ol>


===Make TinyURLs by script===
===Make TinyURLs by script===
<tt>
<syntaxhighlight lang="lsl2" line highlight="15,23">
string myTinyURL;
string myTinyURL;
 
default
{
    state_entry()
    {
        llRequestURL();
    }
   
   
default
    http_request(key id, string method, string body)
{
    {
    state_entry()
        if (method == URL_REQUEST_GRANTED) {
    {
            // Send our full URL to tinyurl.com for conversion
        llRequestURL();
            // The answer will come back in http_response()
    }
            llHTTPRequest("https://tinyurl.com/api-create.php?url=" + body, [], "");
        } else if (method == "GET") {
    http_request(key id, string method, string body)
            llHTTPResponse(id, 200, "Hello Real World from the Virtual World");
    {
        }
        if (method == URL_REQUEST_GRANTED) {
    }
            // Send our full URL to tinyurl.com for conversion
 
            // The answer will come back in http_response()
    http_response(key req, integer stat, list met, string body)
            <span style="background-color:#F8F8C8">llHTTPRequest("http://tinyurl.com/api-create.php?url=" + body, [], "");</span>
    {
        } else if (method == "GET") {
        myTinyURL = body;
            llHTTPResponse(id, 200, "Hello Real World from the Virtual World");
        llOwnerSay("My HTTP-in TinyURL is: " + myTinyURL + " , Click Me!");
        }
    }
    }
}</syntaxhighlight>
    http_response(key req, integer stat, list met, string body)
    {
        <span style="background-color:#F8F8C8">myTinyURL = body;</span>
        llOwnerSay("My HTTP-in TinyURL is: " + myTinyURL + " , Click Me!");
    }
} </tt>


Comments:
'''Comments:'''
* Any URL or data URI can be mapped to a TinyURL. The example above reduces an assigned HTTP-in URL, which is a long one such as:
* Any URL or data URI can be mapped to a TinyURL. The example above reduces an assigned HTTP-in URL, which is a long one such as:
<nowiki>http://sim4605.agni.lindenlab.com:12046/cap/2b9f06f7-431e-5b0f-9271-2d03bd15370b</nowiki>
:<code><nowiki>http://sim4605.agni.lindenlab.com:12046/cap/2b9f06f7-431e-5b0f-9271-2d03bd15370b</nowiki></code>
into a TinyURL this size:
into a TinyURL this size:
<nowiki>http://tinyurl.com/y9etul3</nowiki>
:<code><nowiki>https://tinyurl.com/26dsnl2b</nowiki></code>


===Development hints===
===Development hints===
# You can develop your HTML and JavaScript outside of Second Life by putting your JavaScript incantations in data URIs that you feed to any web browser. For the most compatible browsers, use Safari or Google Chrome, or any other that uses the [[QtWebKit|WebKit]] HTML rendering engine. If you can't run Safari or Chrome, try the open source [http://code.google.com/p/arora/ Arora web browser] for simulating Media-on-a-Prim: it's based on straight-up WebKit and has the same handy inspector/debugger found in other WebKit browsers.
# You can develop your HTML and JavaScript outside of Second Life by putting your JavaScript incantations in data URIs that you feed to any web browser. For the most compatible browsers, use Safari or Google Chrome, or any other that uses the [[QtWebKit|WebKit]] HTML rendering engine. If you can't run Safari or Chrome, try the open source [https://github.com/Arora/arora Arora web browser] for simulating Media-on-a-Prim: it's based on straight-up WebKit and has the same handy inspector/debugger found in other WebKit browsers.<ref>Note: Arora seems to be a dead project these days, and I'm personally unsure if LL is still using WebKit or moved to the [https://github.com/chromiumembedded/cef Chromium Embedded Framework] instead. — [[User:Gwyneth Llewelyn|Gwyneth Llewelyn]]</ref>
# If you don't have direct access to a web server while developing this stuff, then consider installing a local copy of [http://www.apache.org/ Apache web server] on your computer. You can restrict it to local access. Then you can refer to files on your own computer with URLs that start with <tt><nowiki>http://localhost/</nowiki>...</tt>, and PHP scripts can simulate what the HTTP-in server would serve. That lets you, for example, edit <tt>myfile.js</tt> on your own computer while working on a data URI that contains <tt>&lt;script src="<nowiki>http://localhost/myfile.js</nowiki>"></tt>. Then when <tt>myfile.js</tt> is working, you can put the debugged code into an LSL script that will serve it through its HTTP-in port. That's easier than debugging all that in-world.
# If you don't have direct access to a web server while developing this stuff, then consider installing a local copy of [https://www.apache.org/ Apache web server] on your computer. You can restrict it to local access. Then you can refer to files on your own computer with URLs that start with <code><nowiki>http://localhost/</nowiki>...</code>, and PHP scripts can simulate what the HTTP-in server would serve. That lets you, for example, edit <code>myfile.js</code> on your own computer while working on a data URI that contains <syntaxhighlight lang="HTML" inline><script src="http://localhost/myfile.js"></syntaxhighlight>. Then when <code>myfile.js</code> is working, you can put the debugged code into an LSL script that will serve it through its HTTP-in port. That's easier than debugging all that in-world.


===References and sources===
===References and sources===
* [[User:Kelly_Linden/lsl_hacks|Kelly Linden's hacks]]
* [[User:Kelly_Linden/lsl_hacks|Kelly Linden's hacks]]
* [http://talirosca.wikidot.com/bootstrapping-html-on-a-prim Tali Rosca's techniques]
* [https://tali.appspot.com/oldhtml/scripting/scripts/bootstrapping.html Tali Rosca's techniques]
* [https://blogs.secondlife.com/message/115303 Notecard Text on a Prim - Demo]
* [https://tali.appspot.com/oldhtml/scripting/scripts/bootstrapping.html Notecard Text on a Prim - Demo]<ref>Replaced with a link to an equivalent text.</ref>
* [[llSetPrimMediaParams]]()
* [[llSetPrimMediaParams]]()
* {{JIRA|SVC-3427}}
* {{JIRA|SVC-3427}}<ref>Internal issue, not accessible to most SL residents.</ref>
* [https://blogs.secondlife.com/message/124144 Ajax in Shared Media #1]
* [https://blogs.secondlife.com/message/124144 Ajax in Shared Media #1]{{dead link|2024-05-01}}
* [https://blogs.secondlife.com/thread/11698 Ajax in Shared Media #2]
* [https://blogs.secondlife.com/thread/11698 Ajax in Shared Media #2]{{dead link|2024-05-01}}
* [https://blogs.secondlife.com/thread/12727 Ajax in Shared Media #3]
* [https://blogs.secondlife.com/thread/12727 Ajax in Shared Media #3]{{dead link|2024-05-01}}
* [https://blogs.secondlife.com/thread/13455 SVG (Scalable Vector Graphics) in Shared Media]
* [https://blogs.secondlife.com/thread/13455 SVG (Scalable Vector Graphics) in Shared Media]{{dead link|2024-05-01}}
* [[Shared_Media_and_data_URI|Shared_Media_and_data_URI]]
* [[Shared Media and data URI]]
* [https://blogs.secondlife.com/community/community/tnt/blog/2010/03/17/viewer-2-tip-shared-media-add-custom-content-with-data-uri-no-webpage-upload-needed Shared Media: Add custom content with data: URI]
* [https://community.secondlife.com/t5/TNT-Second-Life-Tips-Tricks/VIEWER-2-TIP-Shared-Media-Add-custom-content-with-data-URI-no/ba-p/656903 Shared Media: Add custom content with data: URI]{{dead link|2024-05-01}}
* [https://blogs.secondlife.com/message/119704 "Shared Media" - A few facts]
* [https://community.secondlife.com/t5/Viewer-2-0-General/quot-Shared-Media-quot-A-few-facts/m-p/118388#M5185 "Shared Media" - A few facts]{{dead link|2024-05-01}}


===Notes===
{{References}}
[[Category:Shared Media]]
[[Category:Shared Media]]
[[Category:LSL Examples]]

Latest revision as of 02:50, 1 May 2024

How-to's with Media-on-a-Prim

The examples here assume you're comfortable with LSL, and you just need a quick reference for the syntax of handling HTML and JavaScript in LSL for Shared Media scripting. All the examples on this page are ready to copy-n-paste and drop into a prim — no other setup needed. The key syntax in each example is highlighted. There's no error handling in these minimalist examples, so add plenty of your own.

Display plain text — XyText replacement

Plain text
default
{
    state_entry()
    {
        integer face = 4;
        string message = "Hello World";
        llSetPrimMediaParams(face,
            [PRIM_MEDIA_CURRENT_URL, "data:text/html," + message]);
    }
}

Comments:

  • An avatar must press the Reload button on the browser to see this rendered in-world. A page will be auto-loaded if you include the parameter PRIM_MEDIA_AUTO_PLAY, TRUE, and if the avatar has auto-load enabled in preferences.
  • There's no need to add any %-hex-escaping or to use llEscapeURL(); llSetPrimMediaParams(face, [‍PRIM_MEDIA_CURRENT_URL, url]) will automatically escape the string for you.
  • This works by feeding a data URI to the on-prim browser. Think of it as serving the contents of the page in the URL itself. For more information, see here and "Wikipedia logo"here.
  • The in-world browser limits the data URI (or any address) to 1024 bytes after %-hex-escaping and encoded in UTF-8. See below for tricks using external or self-served JavaScript or CSS to leverage the data URI.
  • If the data URI contains just plain text, the prefix "data:text/html," can be abbreviated "data:,".

Force a page reload

Auto-refresh
integer face = 4;
string myURL;
integer seq = 0; // sequence number for unique URLs

default
{
    state_entry()
    {
        llRequestURL();
    }

    http_request(key id, string method, string body)
    {
        if (method == URL_REQUEST_GRANTED) {
            myURL = body;
            llSetPrimMediaParams(face,
                [PRIM_MEDIA_AUTO_PLAY, TRUE,
                 PRIM_MEDIA_CURRENT_URL, myURL]);
            llSetTimerEvent(5.0);
        } else if (method == "GET") {
            llHTTPResponse(id, 200, "Sim FPS: " + (string)llGetRegionFPS());
        }
    }

    timer()
    {
        llSetPrimMediaParams(face,
            [PRIM_MEDIA_CURRENT_URL, myURL + "/?r=" + (string)(++seq)]);
    }
}

Comments:

  • The page will reload automatically if PRIM_MEDIA_AUTO_PLAY is enabled and if the new PRIM_MEDIA_CURRENT_URL is different. This technique appends a short dummy parameter (like "/?r=12") to the end of the URL to make it different each time, forcing a reload. The parameter is otherwise ignored.

Display text with HTML markup

HTML markup
default
{
    state_entry()
    {
        integer face = 4;
        string message = "<i>Hello</i><h2>World</h2>";
        llSetPrimMediaParams(face,
            [PRIM_MEDIA_CURRENT_URL, "data:text/html," + message]);
    }
}

Comments:

  • The text of the data URI will be %-hex-escaped automatically for you, so there's no need to use llEscapeURL(). All you need in the data URI is the prefix data:text/html, plus your HTML.

Change window resolution

Resized web window
default
{
    state_entry()
    {
        integer face = 4;
        string message = "Hello World";
        llSetPrimMediaParams(face,
                [PRIM_MEDIA_CURRENT_URL, "data:text/html," + message,
                PRIM_MEDIA_WIDTH_PIXELS, 128,
                PRIM_MEDIA_HEIGHT_PIXELS, 32]);
    }
}

Comments:

  • The browser will add scroll bars if the rendered HTML doesn't fit within the specified PRIM_MEDIA_* size.

Display an image — works with animated GIFs too

Image on a prim
default
{
    state_entry()
    {
        integer face = 4;
        string imageURL =
          "https://commons.wikimedia.org/wiki/File:Rotating_earth_(large)_transparent.gif";
        string dataURI = "data:text/html,<object data='" + imageURL + "'></object>";
        llSetPrimMediaParams(face,
            [PRIM_MEDIA_CURRENT_URL, dataURI,
             PRIM_MEDIA_WIDTH_PIXELS, 256,
             PRIM_MEDIA_HEIGHT_PIXELS, 256]);
    }
}

Comments:

  • If attempting to display YouTube video full-frame, see this discussion[dead link].
  • For images, you can also use <image src= > like this: string dataURI="data:text/html,<img src='" + imageURL + "'>";
  • Note: The original Wikimedia Commons image given in the code as an example has been deprecated and was replaced by the closest-looking one; if you try this code in-world, it will show up differently than the animated image to the right. — Gwyneth Llewelyn (talk) 04:51, 29 April 2024 (PDT)

Display a background image, tiled

Method #1 — using CSS in a style element in <head>

Tiled background image
default
{
    state_entry()
    {
        integer face = 4;
        string imageURL =
            "https://www.google.com/intl/en_ALL/images/logo.gif";
        string dataURI = "data:text/html,<head>"
            + "<style type='text/css'>body{background-image:url(\""
            + imageURL + "\");}</style></head><body>Hello World</body>";
        llSetPrimMediaParams(face,
                [PRIM_MEDIA_CURRENT_URL, dataURI]);
    }
}

Method #2 — using background attribute in <body>

default
{
    state_entry()
    {
        integer face = 4;
        string imageURL = "https://www.google.com/intl/en_ALL/images/logo.gif";
        string dataURI = "data:text/html,<body background='" + imageURL + "'>"
            + "Hello World"
            + "</body>";
        llSetPrimMediaParams(face,
                [PRIM_MEDIA_CURRENT_URL, dataURI]);
    }
}

Method #3 — using style attribute in <body>

default
{
    state_entry()
    {
        integer face = 4;
        string imageURL = "https://www.google.com/intl/en_ALL/images/logo.gif";
        string dataURI = "data:text/html,<body style='background-image:url(\"" + imageURL + "\")'>"
            + "Hello World"
            + "</body>";
        llSetPrimMediaParams(face,
                [PRIM_MEDIA_CURRENT_URL, dataURI]);
    }
}

Text colors — using element

Font color
default
{
    state_entry()
    {
        integer face = 4;
        string message = "<font color='Red'>Hello World</font>";
        llSetPrimMediaParams(face,
                [PRIM_MEDIA_CURRENT_URL, "data:text/html," + message]);
    }
}

Comments:

  • You can also specify color and other CSS rules in a style attribute in another element. For example, you can replace message in the example above with:
string message = "<h2 style='color:#FF0000'>Hello World</h2>";

Link to external CSS file (forms example)

External CSS file
default
{
    state_entry()
    {
        integer face = 4;
        string externalCSS =
            "https://www.google.com/css/modules/buttons/g-button-chocobo.css";

        string dataURI = "data:text/html,<head><link href='</span>" +
                externalCSS + "' rel='stylesheet' type='text/css' /></head>" +
                "<form><input type='button' value='Click Me' class='g-button' /></form>";

        llSetPrimMediaParams(face,
                [PRIM_MEDIA_CURRENT_URL, dataURI]);
    }
}

Comments:

  • This is one way to work around the 1024-byte limitation of data URIs. The external CSS can be as large as the browser permits. Also see the examples below for putting JavaScript in an external file.
  • The tag <head> and the attribute type='text/css' may be omitted to make the data URI a few bytes shorter.

Calling external JavaScript functions (JQuery example)

Calling external JQuery
integer face = 4;
string externalJavascript =
    "https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js";
default
{
    state_entry()
    {
        string dataURI = "data: text/html," +
            "<head><script src='" + externalJavascript + "'></script></head>" +
            "<button>Toggle</button>" +
            "<p>Hello<br />World</p>" +
            "<script>$('button').click(function () " +
            "{$('p').slideToggle('slow');});</script>";
 
        llSetPrimMediaParams(face,
            [PRIM_MEDIA_CURRENT_URL, dataURI]);
    }
}

Comments:

  • This is a technique for working around the 1024-byte limit for data URIs. When you can't fit all the JavaScript you need into one data URI, just move some JavaScript functions into an external file and give the browser a data URI that references the external file with a URL. In this example above, the JavaScript for JQuery lives in an external file on Google's servers. You just need enough bytes left over in the data URI to put a JavaScript function call with its parameters inside a <script> element.
  • A data URI can be used as a springboard to generate arbitrary complex web pages by making use of these elements:
    • a reference to external CSS with <link href=,
    • a reference to external JavaScript with <script src=, and
    • a function call with parameters to the external JavaScript using <script>someFunction(args);</script>. (In the JQuery example above, the name of the external function is "$".)
  • See the next two examples for variations of this same technique.

Self-served HTML and JavaScript

Example #1 — using script src= and .innerHTML=

Self-served auto-refreshed pages
integer face = 4;
string myURL;

// This can be up to 2KBytes after %-hex-escaping:
string servedPage = "
function checklength(i){if (i<10) {i='0'+i;} return i;}      
function clock(){ 
 var now = new Date();  
 var hours = checklength(now.getHours());  
 var minutes = checklength(now.getMinutes());  
 var seconds = checklength(now.getSeconds());  
 var format = 1; //0=24 hour format, 1=12 hour format   
 var time;  
 if (format == 1) { 
  if (hours >= 12) {  
   if (hours ==12 ) { hours = 12;
   } else { hours = hours-12; } 
   time=hours+':'+minutes+':'+seconds+' PM';   
  } else if(hours < 12) { 
   if (hours ==0) {hours=12;}   
   time=hours+':'+minutes+':'+seconds+' AM';   
  }   
 }  
 if (format == 0) {time= hours+':'+minutes+':'+seconds;}   
 document.getElementById('clock').innerHTML=time;
 setTimeout('clock();', 500); 
} "; // yes, that's one long string.

displayPage()
{
    string dataURI = "data: text/html,<script src='" +
        myURL + "'></script>" +
        "<div id='clock'><script>clock()</script></div>";
    llSetPrimMediaParams(face, [PRIM_MEDIA_CURRENT_URL, dataURI]);
}

default
{
    state_entry()
    {
        llRequestURL();
    }
 
    http_request(key id, string method, string body)
    {
        if (method == URL_REQUEST_GRANTED) {
            myURL = body;
            displayPage();
        } else if (method == "GET") {
            llHTTPResponse(id, 200, servedPage);
        }
    }
}

Comments:

  • This is a technique for trading the 1024-byte limit of data URIs for the 2048-byte-per-page limit that the script can serve itself. As in the previous example, when you can't fit all the JavaScript into one data URI, just move the JavaScript into an external file and reference the file with <script src=URL></script>. When the URL refers to the script's own HTTP-in URL, then the page served can contain up to 2K bytes, so you get at least that many bytes plus whatever logic you can fit into the rest of the data URI. If you move that JavaScript to an external web server, the size of the external JavaScript file can be as large as the client web browser can render.
  • Your script can serve an arbitrary number of different pages through one HTTP-in URL by appending a path or parameter to the URL.
  • This works by assigning a string containing HTML to the .innerHTML contents of the <div> element with id "clock". This achieves something similar to the Ajax-like technique published by Tali Rosca and mentioned here. In those techniques, a string is constructed containing an <a href= tag and an href= that points to the script's HTTP-in URL. That string then replaces the <body> element using:
document.getElementsByTagName('body')[0].innerHTML =new-content

Example #2 — using <body onload= (lag graph example)

Refreshed pages
 integer face = 4;
 string jsURL; // where to fetch external JavaScript
 list numbers;
 integer numSamples = 50;
 
 // This is self-served in this example, but can be
 // moved to an external server:
 //
 string externalJavascript()
 {
     return
         "function bar(widthPct,heightPix) {" +
         " document.writeln(\"<hr style='padding:0;margin:0;" +
         "  margin-top:-1px;text-align:left;align:left;border=0;" +
         "  width:\"+widthPct+\"%;height:\"+heightPix+\"px;" +
         "  background-color:#c22;color:#c22;'>\");}" +
         " function graphBars(arr){for(var i=0;i<arr.length;++i)" +
         "  {bar(arr[i],18);}}";
 }
 
 default
 {
     state_entry()
     {
         llClearPrimMedia(face);
         llRequestURL();
         integer i = numSamples;
         while (--i >= 0) {
             numbers += 0;
         }
     }
 
     timer()
     {
         numbers = llList2List(numbers, 1, -1) + [(integer)(6.0 * (45.0 - llGetRegionFPS()))];
 
         // The dataURI loads external JavaScript functions and calls one with parameters:
 
         string dataURI = "data: text/html," +
             "<head><script src='" + jsURL + "'></script></head></span>" +
             "<body onload=\"graphBars([" + llList2CSV(numbers) + "]);\"></body></span>";
 
         llSetPrimMediaParams(face, [PRIM_MEDIA_CURRENT_URL, dataURI, PRIM_MEDIA_AUTO_PLAY, TRUE]);
     }
 
     http_request(key id, string method, string body)
     {
         if (method == URL_REQUEST_GRANTED) {
             jsURL = body; // self-serve the JavaScript
             llSetTimerEvent(1.0);
         } else if (method == "GET") {
             llHTTPResponse(id, 200, externalJavascript());
         }
     }
 }

Comments:

  • Like the previous example, this is another variation of how to structure a data URI to serve as a springboard for arbitrarily large pages. In this example, the data URI first uses a <script src=...> tag to read a file of external JavaScript functions in the <head> element, and then the onload= attribute in <body> triggers a call to one of the functions which executes a loop creating a bunch of <hr> elements that form the bar chart. The size of HTML generated by the JavaScript can be as large as the browser permits.
  • In this example, jsURL is set to the script's own HTTP-in URL so that the example can be self-contained, but you can point jsURL to an external URL as well. If served by an external server, the JavaScript read in <head> can be as large as the browser permits.
  • <head></head> can be omitted in the data URI surrounding the <script src= tag.

Reverse Ajax - Long-polling the HTTP-in server, chat logger example

Long-polling HTTP-in

// Reverse Ajax: Long-polling HTTP-in.
// Becky Pippen, 2010, contributed to the public domain.

integer face = 4;          // Prim face for Shared Media

string  myURL;             // HTTP-in URL
key inId = NULL_KEY;       // GET request id
list msgQueue = [];        // strings of Javascript

// url is our own HTTP-in url.
// This sets up a bootloader web page like this:
//      <html><body>
//         <div><script id='sc'></script></div>
//         <script> callbacks and poll.beg() defined here </script>
//         <button onclick=poll.beg()>Start</button>
//         <div id='dv'></div>
//      </body></html>
// When the button is pressed, the JS code sets src= on script#sc
// and reattaches the script element to the parent <div> element which
// initiates a GET to the prim's HTTP-in port
//
setDataURI(string url)
{
    string dataURI = "data:text/html,
<!DOCTYPE HTML><html><body><div><script id='sc'></script></div><script>
var poll=function(){var sc=document.getElementById('sc'),t2,seq=0,s0;return{
beg:function(){s0=document.createElement('script');s0.onload=poll.end;t2=setTimeout('poll.end()',20000);
s0.src='" + url + "/?r='+(seq++);sc.parentNode.replaceChild(s0,sc);sc=s0;},
end:function(){clearTimeout(t2);t2=null;sc.onload=null;setTimeout('poll.beg()',500);},};}();</script>
<button id='btn'onclick=poll.beg()>Start</button><div id='dv'></div></body></html>";

    llSetPrimMediaParams(face, [PRIM_MEDIA_CURRENT_URL, dataURI]);
}

// Returns zero or more queued messages. Assumes no single message is
// longer than MAX_SIZE_CHARS (will hang if there is)
//
string popQueuedMessages()
{
    string  totalMsg = "";
    integer totalMsgSize = 0;
    string  nextMsg = "";
    integer nextMsgSize = 0;

    // HTTP response bodies are limited to 2048 bytes after encoding
    // in UTF-8. LSL string sizes are measured in characters, which,
    // in UTF-8, use one byte (for ASCII chars), two bytes (most Latin-1),
    // or three bytes (a few international characters). So, unless
    // you re-write this section so that it measures UTF-8 size, keep
    // MAX_SIZE_CHARS small enough so the text will fit in a response body.
    //
    integer MAX_SIZE_CHARS = 1000; // Max HTTP body size 
 
    integer numMessagesQueued = llGetListLength(msgQueue);
    integer count = 0;
    integer done = FALSE;
    while (!done && count < numMessagesQueued) {
        nextMsg = llList2String(msgQueue, count);
        nextMsgSize = llStringLength(nextMsg);

        if (totalMsgSize + nextMsgSize < MAX_SIZE_CHARS) {
            totalMsg += nextMsg;
            totalMsgSize += nextMsgSize;
            ++count;
        } else {
            done = TRUE;
        }
    }

    // Delete the messages from the queue that we're going to send:
    if (count > 0) {
        msgQueue = llDeleteSubList(msgQueue, 0, count - 1);
    }

    return totalMsg;
}

// Called when there are previous messages still queued, or if there
// is no GET request currently open to respond to.
//
pushMessageToSend(string msg)
{
    msgQueue = msgQueue + [msg]; // last element is the last one stacked

    // See if we can send some messages now:
    if (inId != NULL_KEY) {
        llHTTPResponse(inId, 200, popQueuedMessages());
        inId = NULL_KEY;
    } // else wait for the next incoming GET request
}

// Replaces all occurrences of 'from' with 'to' in 'src'
// From http://snipplr.com/view/13279/lslstrreplace/
//
string str_replace(string subject, string search, string replace)
{
    return llDumpList2String(llParseStringKeepNulls(subject, [search], []), replace);
}

// Optionally filter out characters in text that would mess up the
// web page display. This demo just escapes ' and " and adds a space after '<'.
//
string addSlashes(string s)
{
    return str_replace(str_replace(str_replace(s, "<", "< "), "\"", "\\\""), "'", "\\\'");
}

// This is the main interface for LSL to control the Shared Media web page.
// The messages we send consist of Javascript function statements that the
// browser will evaluate and execute in the context of the web page.
// See sendMessageF() for a similar function with macro replacement.
//
sendMessage(string msg)
{
    // Test for the easy case: if there are no other messages waiting
    // in the queue, and if there is an open GET connection, then just
    // respond immediately:
 
    if (llGetListLength(msgQueue) == 0 && inId != NULL_KEY) {
        // Nothing in the queue and an open GET, so respond immediately:
        llHTTPResponse(inId, 200, msg);
        inId = NULL_KEY;
    } else {
        pushMessageToSend(msg);
    }
}

// Same as sendMessage() but with macro replacements. For each nth string
// element in replacements, replace all occurrences of {@n}. For example,
//     sendMessageF( "alert('{@0} {@1}!')", ["Hello", "World"] );
// will send "alert('Hello World!')" to the web browser.
//
sendMessageF(string msg, list replacements)
{
    integer numrepl = llGetListLength(replacements);
    integer i;
    for (i = 0; i < numrepl; ++i) {
        msg = str_replace(msg, "{@" + (string)i + "}", llList2String(replacements, i));
    }

    sendMessage(msg);
}

// Chat logger demo: writes a new <tr> table row to the web page
// for every line of open chat it hears.
//
webAppInit()
{
    string msg;
    string m0;

    // First, send over a few handy function definitions:

    msg = "function $$(t) { return document.getElementsByTagName(t)[0]; };";
    msg += "function h() { return $$('head'); };";
    msg += "function b() { return $$('body'); };";
    msg += "function e(id) { return document.getElementById(id); };";
    sendMessage(msg);

    // Send some CSS. WebKit is sensitive about appending <style> elements
    // to <head>, so we'll append it to an existing <div> tag in <body> instead.

    msg = "e('dv').innerHTML += \"{@0}\";";
    m0 = "<style>td:nth-child(2) { text-align:right } tr:nth-child(odd) { background-color:#f8e8f8 }</style>";
    sendMessageF(msg, [m0]);

    // Write a <table> element into element div#dv. The lines of chat will
    // become rows in this table appended to tbody#tbd

    msg = "e('dv').innerHTML += \"{@0}\";";
    m0 = "<table><tbody id='tbd'></tbody></table>";
    sendMessageF(msg, [m0]);

    llListen(0, "", NULL_KEY, "");
}

default
{
    state_entry()
    {
        llClearPrimMedia(face);
        llRequestURL();
         
        webAppInit();
    }

    http_request(key id, string method, string body)
    {
        if (method == URL_REQUEST_GRANTED) {
            myURL = body;
            llOwnerSay("myURL=" + myURL);
            setDataURI(myURL);
        } else if (method == "GET") {
            // Either send some queued messages now with llHTTPResponse(),
            // or if there's nothing to do now, save the GET id and
            // wait for somebody to call sendMessage().
            if (llGetListLength(msgQueue) > 0) {
                llHTTPResponse(id, 200, popQueuedMessages());
                inId = NULL_KEY;
            } else {
                inId = id;
            }
        }
    }

    // When we hear chat from name, send a Javascript statement that
    // appends HTML to element #tbd. I.e., we'll make a string of HTML
    // formatted like this:
    //      <tr style="color:hsl(200,100%,30%)">
    //         <td>[01:23]</td>
    //         <td>Avatar Name</td>
    //         <td>the chat text</td>
    //      </tr>
    // and send it wrapped it in a Javascript statement like this:
    //      e('tbd').innerHTML += htmlstring;
    //
    listen(integer chan, string name, key id, string chat)
    {
        integer s = (integer)("0x" + llGetSubString((string)id, 0, 6));
        string hue = (string)(s % 360);
        string color = "hsl(" + hue + ",100%, 30%)";

        string msg = "e('tbd').innerHTML += '{@0}';";
        string m0 = "<tr style=\"color: {@4}\">";
        m0 +=   "<td>{@1}</td>";
        m0 +=   "<td>{@2}:</td>";
        m0 +=   "<td>{@3}</td>";
        m0 += "</tr>";

        string t = llGetSubString(llGetTimestamp(), 11, 15);
        sendMessageF(msg, [m0, "[" + t + "]", name, addSlashes(chat), color]);
    }
}

Comments:

  1. This ""Wikipedia logo"server-push" technique using long-polling gives an LSL script the ability to modify the web page displayed on a prim. The Javascript side always keeps an HTTP GET open to the prim's HTTP-in URL, and the LSL side responds whenever it wants to with a response consisting of strings of Javascript to be executed by the web browser. For example, this LSL code causes an alert box to pop up on the web page:
    sendMessage( "alert()" );
  2. In the LSL listing above, the yellow highlighting shows the most important parts of the HTTP long-polling mechanism. The green highlighting shows the message buffering on top of that. The blue highlighting is the application code that uses long-polling. To use long-polling for a different application, replace the blue parts. The rest is glue.[1]
  3. To avoid some subtle WebKit[2] problems when dynamically appending or replacing the first-level child nodes in <head> or <body>, we've made two special <div> elements on the bootstrap HTML page. The first surrounds the script#sc element that we replace for each GET. The second is at the end of <body> as a convenient place for the application to insert new content that would otherwise go in <body>.
  4. To make sure that there is nearly always a valid GET request open, the Javascript side starts a new GET after receiving a response, or just before the last unanswered GET times out.
  5. There are two timeouts hard-coded in the bootstrap data URI:
    1. The "20000" is the timeout (20 seconds) for when an open GET expires, and should be set to a little less than the minimum of the WebKit outgoing GET request timeout, and the SL-LSL incoming GET timeout. This ensures that GETs are nearly continuous, and the LSL side can always respond to the most recent GET request. This page says the timeout on the LSL side is 25 seconds.
    2. The "500" is a throttle that sets an upper limit to how fast the Javascript re-polls HTTP-in to prevent hammering the simulator. It's a half-second delay after receiving a GET response before starting a new GET.
  6. If there's a little gap between two GETs, or if two overlap a little, the sendMessage() message buffering will compensate by queuing up messages to be sent when the next GET arrives. If there are multiple messages in the queue when a GET arrives, the LSL side will concatenate as many messages as possible and send them together.
  7. On the Javascript side, the GET is not triggered until the <script> element is attached to its parent node with .appendChild() or .replaceChild().
  8. The bootstrap Javascript above in setDataURI() had to be compressed somewhat to fit into the 1024-byte data URI limit. Here's an expanded listing for reference with more descriptive names:
  9. <!DOCTYPE HTML>
    <html>
       <body>
         <div>
           <script id='script'> </script>
         </div>
         <script>
           var poll=function(){
             var script=document.getElementById('script'),
             timeoutId,
             seq=0,
             newScript;
             return {
               beg:function() {                                      // Initiate a long-poll GET
                 newScript=document.createElement('script');        // The response will go here
                 newScript.onload=poll.end;                         // Call poll.end() when we get a response
                 timeoutId=setTimeout('poll.end()',20000);          // ... or if we time out
                 newScript.src=' HTTP-in URL goes here /?r='+(seq++);
                 script.parentNode.replaceChild(newScript,script);  // this triggers the GET
                 script=newScript;},
     
               end: function() {
                 clearTimeout(timeoutId);
                 timeoutId=null;
                 script.onload=null;
                 setTimeout('poll.beg()',500);                      // Wait a bit before re-polling
               },
             };
           }();
         </script>
         <button id='btn' onclick=poll.beg()>Start<button>
         <div id='dv'> </div>
       </body>
    </html>
    

Make TinyURLs by script

string myTinyURL;

default
{
    state_entry()
    {
        llRequestURL();
    }
 
    http_request(key id, string method, string body)
    {
        if (method == URL_REQUEST_GRANTED) {
            // Send our full URL to tinyurl.com for conversion
            // The answer will come back in http_response()
            llHTTPRequest("https://tinyurl.com/api-create.php?url=" + body, [], "");
        } else if (method == "GET") {
            llHTTPResponse(id, 200, "Hello Real World from the Virtual World");
        }
    }

    http_response(key req, integer stat, list met, string body)
    {
        myTinyURL = body;
        llOwnerSay("My HTTP-in TinyURL is: " + myTinyURL + " , Click Me!");
    }
}

Comments:

  • Any URL or data URI can be mapped to a TinyURL. The example above reduces an assigned HTTP-in URL, which is a long one such as:
http://sim4605.agni.lindenlab.com:12046/cap/2b9f06f7-431e-5b0f-9271-2d03bd15370b

into a TinyURL this size:

https://tinyurl.com/26dsnl2b

Development hints

  1. You can develop your HTML and JavaScript outside of Second Life by putting your JavaScript incantations in data URIs that you feed to any web browser. For the most compatible browsers, use Safari or Google Chrome, or any other that uses the WebKit HTML rendering engine. If you can't run Safari or Chrome, try the open source Arora web browser for simulating Media-on-a-Prim: it's based on straight-up WebKit and has the same handy inspector/debugger found in other WebKit browsers.[3]
  2. If you don't have direct access to a web server while developing this stuff, then consider installing a local copy of Apache web server on your computer. You can restrict it to local access. Then you can refer to files on your own computer with URLs that start with http://localhost/..., and PHP scripts can simulate what the HTTP-in server would serve. That lets you, for example, edit myfile.js on your own computer while working on a data URI that contains <script src="http://localhost/myfile.js">. Then when myfile.js is working, you can put the debugged code into an LSL script that will serve it through its HTTP-in port. That's easier than debugging all that in-world.

References and sources

Notes

  1. Unfortunately, at the time of editing/cleaning up the WikiText on this fantastic tutorial, I couldn't figure out a neat way of having three separate colours as highlights on the code. I've placed a request on the MediaWiki talk page for <syntaxhighlight>, but I'm unsure if whatever tricks they suggest will work on the SL Wiki. So, for the time being, this example will remain untouched, as originally written by @Becky PippenGwyneth Llewelyn
  2. It might be the case that the SL Viewer is now based on the Chromium Embedded Framework instead, so Becky's references might not apply any longer. — Gwyneth Llewelyn
  3. Note: Arora seems to be a dead project these days, and I'm personally unsure if LL is still using WebKit or moved to the Chromium Embedded Framework instead. — Gwyneth Llewelyn
  4. Replaced with a link to an equivalent text.
  5. Internal issue, not accessible to most SL residents.