form - Dave's Blog

Search
My timeline on Mastodon

GoBack/GoForward in Win10 UWP WebView

2018 Oct 23, 9:18

The GoBack and GoForward methods on the UWP WebView (x-ms-webview in HTML, Windows.UI.Xaml.Controls.WebView in XAML, and Windows.Web.UI.Interop.WebViewControl in Win32) act the same as the Back and Forward buttons in the Edge browser. They don't necessarily change the top level document of the WebView. If inside the webview an iframe navigates then that navigation will be recorded in the forward/back history and the GoBack / GoForward call may result in navigating that iframe. This makes sense as an end user using the Edge browser since if I click a link to navigate one place and then hit Back I expect to sort of undo that most recent navigation regardless of if that navigation happened in an iframe or the top level document.

If that doesn't make sense for your application and you want to navigate forward or back ignoring iframe navigates, unfortunately there's no perfect workaround.

One workaround could be to try calling GoBack and then checking if a FrameNavigationStarting event fires or a NavigationStarting event fires. If a frame navigates then try calling GoBack again. There could be async races in this case since other navigates could come in and send you the wrong signal and interrupt your multi step GoBack operation.

You could also try keeping track of all top level document navigations and manually navigate back to the URIs you care about. However, GoBack and GoForward also restore some amount of user state (form fills etc) in addition to navigating. Manually calling navigate will not give this same behavior.

PermalinkCommentsuri uwp webview

Win10 PWA Terminology

2018 May 31, 8:26

Folks familiar with JavaScript UWP apps in Win10 have often been confused by what PWAs in Win10 actually are. TLDR: PWAs in Win10 are simply JavaScript UWP apps. The main difference between these JS UWP Apps and our non-PWA JS UWP apps are our target end developer audience, and how we get Win10 PWAs into the Microsoft Store. See this Win10 blog post on PWAs on Win10 for related info.

Web App

On the web a subset of web sites are web apps. These are web sites that have app like behavior - that is a user might call it an app like Outlook, Maps or Gmail. And they may also have a W3C app manifest.

A subset of web apps are progressive web apps. Progressive web apps are web apps that have a W3C app manifest and a service worker. Various OSes are beginning to support PWAs as first class apps on their platform. This is true for Win10 as well in which PWAs are run as a WWA.

Windows Web App

In Win10 a WWA (Windows Web App) is an unofficial term for a JavaScript UWP app. These are UWP apps so they have an AppxManifest.xml, they are packaged in an Appx package, they run in an App Container, they use WinRT APIs, and are installed via the Microsoft Store. Specific to WWAs though, is that the AppxManifest.xml specifies a StartPage attribute identifying some HTML content to be used as the app. When the app is activated the OS will create a WWAHost.exe process that hosts the HTML content using the EdgeHtml rendering engine.

Packaged vs Hosted Web App

Within that we have a notion of a packaged web app and an HWA (hosted web app). There's no real technical distinction for the end developer between these two. The only real difference is whether the StartPage identifies remote HTML content on the web (HWA), or packaged HTML content from the app's appx package (packaged web app). An end developer may create an app that is a mix of these as well, with HTML content in the package and HTML content from the web. These terms are more like ends on a continuum and identifying two different developer scenarios since the underlying technical aspect is pretty much identical.

Win10 PWA

Win10 PWAs are simply HWAs that specify a StartPage of a URI for a PWA on the web. These are still JavaScript UWP apps with all the same behavior and abilities as other UWP apps. We have two ways of getting PWAs into the Microsoft Store as Win10 PWAs. The first is PWA Builder which is a tool that helps PWA end developers create and submit to the Microsoft Store a Win10 PWA appx package. The second is a crawler that runs over the web looking for PWAs which we convert and submit to the Store using an automated PWA Builder-like tool to create a Win10 PWA from PWAs on the web (see Welcoming PWAs to Win10 for more info). In both cases the conversion involves examining the PWAs W3C app manifest and producing a corresponding AppxManifest.xml. Not all features supported by AppxManifest.xml are also available in the W3c app manifest. But the result of PWA Builder can be a working starting point for end developers who can then update the AppxManifest.xml as they like to support features like share targets or others not available in W3C app manifests.

PermalinkCommentsJS pwa uwp web

Tiny browser features: JSBrowser zoom

2018 May 10, 3:49

JSBrowser is a basic browser built as a Win10 JavaScript UWP app around the WebView HTML element. Its fun and relatively simple to implement tiny browser features in JavaScript and in this post I'm implementing zoom.

My plan to implement zoom is to add a zoom slider to the settings div that controls the scale of the WebView element via CSS transform. My resulting zoom change is in git and you can try the whole thing out in my JSBrowser fork.

Slider

I can implement the zoom settings slider as a range type input HTML element. This conveniently provides me a min, max, and step property and suits exactly my purposes. I chose some values that I thought would be reasonable so the browser can scale between half to 3x by increments of one quarter. This is a tiny browser feature after all so there's no custom zoom entry.

<a><label for="webviewZoom">Zoom</label><input type="range" min="50" max="300" step="25" value="100" id="webviewZoom" /></a>

To let the user know this slider is for controlling zoom, I make a label HTML element that says Zoom. The label HTML element has a for attribute which takes the id of another HTML element. This lets the browser know what the label is labelling and lets the browser do things like when the label is clicked to put focus on the slider.

Scale

There are no explicit scale APIs for WebView so to change the size of the content in the WebView we use CSS.

        this.applyWebviewZoom = state => {
const minValue = this.webviewZoom.getAttribute("min");
const maxValue = this.webviewZoom.getAttribute("max");
const scaleValue = Math.max(Math.min(parseInt(this.webviewZoom.value, 10), maxValue), minValue) / 100;

// Use setAttribute so they all change together to avoid weird visual glitches
this.webview.setAttribute("style", [
["width", (100 / scaleValue) + "%"],
["height", "calc(" + (-40 / scaleValue) + "px + " + (100 / scaleValue) + "%)"],
["transform", "scale(" + scaleValue + ")"]
].map(pair => pair[0] + ": " + pair[1]).join("; "));
};

Because the user changes the scale at runtime I accordingly replace the static CSS for the WebView element with the script above to programmatically modify the style of the WebView. I change the style with one setAttribute call to do my best to avoid the browser performing unnecessary work or displaying the WebView in an intermediate and incomplete state. Applying the scale to the element is as simple as adding 'transform: scale(X)' but then there are two interesting problems.

The first is that the size of the WebView is also scaled not just the content within it. To keep the WebView the same effective size so that it still fits properly into our browser UI, we must compensate for the scale in the WebView width and height. Accordingly, you can see that we scale up by scaleValue and then in width and height we divide by the scaleValue.

transform-origin: 0% 0%;

The other issue is that by default the scale transform's origin is the center of the WebView element. This means when scaled up all sides of the WebView would expand out. But when modifying the width and height those apply relative to the upper left of the element so our inverse scale application to the width and height above aren't quite enough. We also have to change the origin of the scale transform to match the origin of the changes to the width and height.

PermalinkCommentsbrowser css-transform javascript JS jsbrowser uwp webview win10

Application Content URI Rules wildcard syntax

2017 May 31, 4:48

Application Content URI Rules (ACUR from now on) defines the bounds of the web that make up the Microsoft Store application. Package content via the ms-appx URI scheme is automatically considered part of the app. But if you have content on the web via http or https you can use ACUR to declare to Windows that those URIs are also part of your application. When your app navigates to URIs on the web those URIs will be matched against the ACUR to determine if they are part of your app or not. The documentation for how matching is done on the wildcard URIs in the ACUR Rule elements is not very helpful on MSDN so here are some notes.

Rules

You can have up to 100 Rule XML elements per ApplicationContentUriRules element. Each has a Match attribute that can be up to 2084 characters long. The content of the Match attribute is parsed with CreateUri and when matching against URIs on the web additional wildcard processing is performed. I’ll call the URI from the ACUR Rule the rule URI and the URI we compare it to found during app navigation the navigation URI.

The rule URI is matched to a navigation URI by URI component: scheme, username, password, host, port, path, query, and fragment. If a component does not exist on the rule URI then it matches any value of that component in the navigation URI. For example, a rule URI with no fragment will match a navigation URI with no fragment, with an empty string fragment, or a fragment with any value in it.

Asterisk

Each component except the port may have up to 8 asterisks. Two asterisks in a row counts as an escape and will match 1 literal asterisk. For scheme, username, password, query and fragment the asterisk matches whatever it can within the component.

Host

For the host, if the host consists of exactly one single asterisk then it matches anything. Otherwise an asterisk in a host only matches within its domain name label. For example, http://*.example.com will match http://a.example.com/ but not http://b.a.example.com/ or http://example.com/. And http://*/ will match http://example.com, http://a.example.com/, and http://b.a.example.com/. However the Store places restrictions on submitting apps that use the http://* rule or rules with an asterisk in the second effective domain name label. For example, http://*.com is also restricted for Store submission.

Path

For the path, an asterisk matches within the path segment. For example, http://example.com/a/*/c will match http://example.com/a/b/c and http://example.com/a//c but not http://example.com/a/b/b/c or http://example.com/a/c

Additionally for the path, if the path ends with a slash then it matches any path that starts with that same path. For example, http://example.com/a/ will match http://example.com/a/b and http://example.com/a/b/c/d/e/, but not http://example.com/b/.

If the path doesn’t end with a slash then there is no suffix matching performed. For example, http://example.com/a will match only http://example.com/a and no URIs with a different path.

As a part of parsing the rule URI and the navigation URI, CreateUri will perform URI normalization and so the hostname and scheme will be made lower case (casing matters in all other parts of the URI and case sensitive comparisons will be performed), IDN normalization will be performed, ‘.’ and ‘..’ path segments will be resolved and other normalizations as described in the CreateUri documentation.

PermalinkCommentsapplication-content-uri-rules programming windows windows-store

Tweet from Baldur Bjarnason

2016 Dec 8, 10:14
This would be a very nice addition to the platform.
PermalinkComments

Let's Encrypt NearlyFreeSpeech.net Update

2016 Nov 5, 8:59

Since I had last posted about using Let's Encrypt with NearlyFreeSpeech, NFS has changed their process for setting TLS info. Instead of putting the various files in /home/protected/ssl and submitting an assistance request, now there is a command to submit the certificate info and a webpage for submitting the certificate info.

The webpage is https://members.nearlyfreespeech.net/{username}/sites/{sitename}/add_tls and has a textbox for you to paste in all the cert info in PEM form into the textbox. The domain key, the domain certificate, and the Let's Encrypt intermediate cert must be pasted into the textbox and submitted.

Alternatively, that same info may be provided as standard input to nfsn -i set-tls

To renew my certificate with the updated NFS process I followed the commands from Andrei Damian-Fekete's script which depends on acme_tiny.py:

python acme_tiny.py --account-key account.key --csr domain.csr --acme-dir /home/public/.well-known/acme-challenge/ > signed.crt
wget -O - https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem > intermediate.pem
cat domain.key signed.crt intermediate.pem > chained.pem
nfsn -i set-tls < chained.pem
Because my certificate had already expired I needed to comment out the section in acme_tiny.py that validates the challenge file. The filenames in the above map to the following:
  • signed.crt is the Let's Encrypt provided certificate
  • account.key is the user private key registered with LE
  • domain.csr is the cert request
  • domain.key is the key for the domain cert
PermalinkCommentscertificate lets-encrypt nearlyfreespeech.net

Tweet from David Risney

2016 Nov 3, 3:57
@FakeUnicode At least some of that is from https://tools.ietf.org/html/rfc3986 . For unreserved characters (a-z0-9._-~) normal form is decoded.
PermalinkComments

Tweet from emily schechter

2016 Sep 8, 1:12
in Chrome 56, we'll mark HTTP pages with password or credit card form fields as "not secure". turn on HTTPS before! https://security.googleblog.com/2016/09/moving-towards-more-secure-web.html 
PermalinkComments

Data breakpoints in JavaScript

2016 Jun 17, 5:44

The other day I had to debug a JavaScript UWA that was failing when trying to use an undefined property. In a previous OS build this code would run and the property was defined. I wanted something similar to windbg/cdb's ba command that lets me set a breakpoint on read or writes to a memory location so I could see what was creating the object in the previous OS build and what that code was doing now in the current OS build. I couldn't find such a breakpoint mechanism in Visual Studio or F12 so I wrote a little script to approximate JavaScript data breakpoints.

The script creates a stub object with a getter and setter. It actually performs the get or set but also calls debugger; to break in the debugger. In order to handle my case of needing to break when window.object1.object2 was created or accessed, I further had it recursively set up such stub objects for the matching property names.

Its not perfect because it is an enumerable property and shows up in hasOwnProperty and likely other places. But for your average code that checks for the existence of a property via if (object.property) it works well.

PermalinkCommentsdebug debugging javascript

Windows Store App WebView Cross Origin XMLHttpRequest Behavior

2016 Jun 2, 6:45

TL;DR: Web content in a JavaScript Windows Store app or WebView in a Windows Store app that has full access to WinRT also gets to use XHR unrestricted by cross origin checks.

By default web content in a WebView control in a Windows Store App has the same sort of limitations as that web content in a web browser. However, if you give the URI of that web content full access to WinRT, then the web content also gains the ability to use XMLHttpRequest unrestricted by cross origin checks. This means no CORS checks and no OPTIONS requests. This only works if the web content's URI matches a Rule in the ApplicationContentUriRules of your app's manifest and that Rule declares WindowsRuntimeAccess="all". If it declares WinRT access as 'None' or 'AllowForWebOnly' then XHR acts as it normally does.

In terms of security, if you've already given a page access to all of WinRT which includes the HttpRequest class and other networking classes that don't perform cross origin checks, then allowing XHR to skip CORS doesn't make things worse.

PermalinkCommentsjavascript uwa uwp web webview windows winrt xhr

4 people are living in an isolated habitat for 30 days. Why? Science!

2016 Feb 1, 3:27

nasa:

This 30 day mission will help our researchers learn how isolation and close quarters affect individual and group behavior. This study at our Johnson Space Center prepares us for long duration space missions, like a trip to an asteroid or even to Mars.

image

The Human Research Exploration Analog (HERA) that the crew members will be living in is one compact, science-making house. But unlike in a normal house, these inhabitants won’t go outside for 30 days. Their communication with the rest of planet Earth will also be very limited, and they won’t have any access to internet. So no checking social media kids!

The only people they will talk with regularly are mission control and each other.

image

The crew member selection process is based on a number of criteria, including the same criteria for astronaut selection.

What will they be doing?

Because this mission simulates a 715-day journey to a Near-Earth asteroid, the four crew members will complete activities similar to what would happen during an outbound transit, on location at the asteroid, and the return transit phases of a mission (just in a bit of an accelerated timeframe). This simulation means that even when communicating with mission control, there will be a delay on all communications ranging from 1 to 10 minutes each way. The crew will also perform virtual spacewalk missions once they reach their destination, where they will inspect the asteroid and collect samples from it. 

A few other details:

  • The crew follows a timeline that is similar to one used for the ISS crew.
  • They work 16 hours a day, Monday through Friday. This includes time for daily planning, conferences, meals and exercises.  
  • They will be growing and taking care of plants and brine shrimp, which they will analyze and document.

But beware! While we do all we can to avoid crises during missions, crews need to be able to respond in the event of an emergency. The HERA crew will conduct a couple of emergency scenario simulations, including one that will require them to maneuver through a debris field during the Earth-bound phase of the mission. 

image

Throughout the mission, researchers will gather information about cohabitation, teamwork, team cohesion, mood, performance and overall well-being. The crew members will be tracked by numerous devices that each capture different types of data.

image

Past HERA crew members wore a sensor that recorded heart rate, distance, motion and sound intensity. When crew members were working together, the sensor would also record their proximity as well, helping investigators learn about team cohesion.

Researchers also learned about how crew members react to stress by recording and analyzing verbal interactions and by analyzing “markers” in blood and saliva samples.

image

In total, this mission will include 19 individual investigations across key human research elements. From psychological to physiological experiments, the crew members will help prepare us for future missions.

Make sure to follow us on Tumblr for your regular dose of space: http://nasa.tumblr.com

PermalinkComments

4 people are living in an isolated habitat for 30 days. Why? Science!

2016 Feb 1, 3:27

nasa:

This 30 day mission will help our researchers learn how isolation and close quarters affect individual and group behavior. This study at our Johnson Space Center prepares us for long duration space missions, like a trip to an asteroid or even to Mars.

image

The Human Research Exploration Analog (HERA) that the crew members will be living in is one compact, science-making house. But unlike in a normal house, these inhabitants won’t go outside for 30 days. Their communication with the rest of planet Earth will also be very limited, and they won’t have any access to internet. So no checking social media kids!

The only people they will talk with regularly are mission control and each other.

image

The crew member selection process is based on a number of criteria, including the same criteria for astronaut selection.

What will they be doing?

Because this mission simulates a 715-day journey to a Near-Earth asteroid, the four crew members will complete activities similar to what would happen during an outbound transit, on location at the asteroid, and the return transit phases of a mission (just in a bit of an accelerated timeframe). This simulation means that even when communicating with mission control, there will be a delay on all communications ranging from 1 to 10 minutes each way. The crew will also perform virtual spacewalk missions once they reach their destination, where they will inspect the asteroid and collect samples from it. 

A few other details:

  • The crew follows a timeline that is similar to one used for the ISS crew.
  • They work 16 hours a day, Monday through Friday. This includes time for daily planning, conferences, meals and exercises.  
  • They will be growing and taking care of plants and brine shrimp, which they will analyze and document.

But beware! While we do all we can to avoid crises during missions, crews need to be able to respond in the event of an emergency. The HERA crew will conduct a couple of emergency scenario simulations, including one that will require them to maneuver through a debris field during the Earth-bound phase of the mission. 

image

Throughout the mission, researchers will gather information about cohabitation, teamwork, team cohesion, mood, performance and overall well-being. The crew members will be tracked by numerous devices that each capture different types of data.

image

Past HERA crew members wore a sensor that recorded heart rate, distance, motion and sound intensity. When crew members were working together, the sensor would also record their proximity as well, helping investigators learn about team cohesion.

Researchers also learned about how crew members react to stress by recording and analyzing verbal interactions and by analyzing “markers” in blood and saliva samples.

image

In total, this mission will include 19 individual investigations across key human research elements. From psychological to physiological experiments, the crew members will help prepare us for future missions.

Make sure to follow us on Tumblr for your regular dose of space: http://nasa.tumblr.com

PermalinkComments

Retweet of FxSiteCompat

2016 Jan 27, 7:44
#Firefox 46 Developer Edition is out! Non-HTTPS pages w/ login form are now marked insecure https://www.fxsitecompat.com/versions/46/  pic.twitter.com/o2WZ6K2KxN
PermalinkComments

JavaScript Types and WinRT Types

2016 Jan 21, 5:35

MSDN covers the topic of JavaScript and WinRT type conversions provided by Chakra (JavaScript Representation of Windows Runtime Types and Considerations when Using the Windows Runtime API), but for the questions I get about it I’ll try to lay out some specifics of that discussion more plainly. I’ve made a TL;DR JavaScript types and WinRT types summary table.

WinRT Conversion JavaScript
Struct ↔️ JavaScript object with matching property names
Class or interface instance JavaScript object with matching property names
Windows.Foundation.Collections.IPropertySet JavaScript object with arbitrary property names
Any DOM object

Chakra, the JavaScript engine powering the Edge browser and JavaScript Windows Store apps, does the work to project WinRT into JavaScript. It is responsible for, among other things, converting back and forth between JavaScript types and WinRT types. Some basics are intuitive, like a JavaScript string is converted back and forth with WinRT’s string representation. For other basic types check out the MSDN links at the top of the page. For structs, interface instances, class instances, and objects things are more complicated.

A struct, class instance, or interface instance in WinRT is projected into JavaScript as a JavaScript object with corresponding property names and values. This JavaScript object representation of a WinRT type can be passed into other WinRT APIs that take the same underlying type as a parameter. This JavaScript object is special in that Chakra keeps a reference to the underlying WinRT object and so it can be reused with other WinRT APIs.

However, if you start with plain JavaScript objects and want to interact with WinRT APIs that take non-basic WinRT types, your options are less plentiful. You can use a plain JavaScript object as a WinRT struct, so long as the property names on the JavaScript object match the WinRT struct’s. Chakra will implicitly create an instance of the WinRT struct for you when you call a WinRT method that takes that WinRT struct as a parameter and fill in the WinRT struct’s values with the values from the corresponding properties on your JavaScript object.

// C# WinRT component
public struct ExampleStruct
{
public string String;
public int Int;
}

public sealed class ExampleStructContainer
{
ExampleStruct value;
public void Set(ExampleStruct value)
{
this.value = value;
}

public ExampleStruct Get()
{
return this.value;
}
}

// JS code
var structContainer = new ExampleWinRTComponent.ExampleNamespace.ExampleStructContainer();
structContainer.set({ string: "abc", int: 123 });
console.log("structContainer.get(): " + JSON.stringify(structContainer.get()));
// structContainer.get(): {"string":"abc","int":123}

You cannot have a plain JavaScript object and use it as a WinRT class instance or WinRT interface instance. Chakra does not provide such a conversion even with ES6 classes.

You cannot take a JavaScript object with arbitrary property names that are unknown at compile time and don’t correspond to a specific WinRT struct and pass that into a WinRT method. If you need to do this, you have to write additional JavaScript code to explicitly convert your arbitrary JavaScript object into an array of property name and value pairs or something else that could be represented in WinRT.

However, the other direction you can do. An instance of a Windows.Foundation.Collections.IPropertySet implementation in WinRT is projected into JavaScript as a JavaScript object with property names and values corresponding to the key and value pairs in the IPropertySet. In this way you can project a WinRT object as a JavaScript object with arbitrary property names and types. But again, the reverse is not possible. Chakra will not convert an arbitrary JavaScript object into an IPropertySet.

// C# WinRT component
public sealed class PropertySetContainer
{
private Windows.Foundation.Collections.IPropertySet otherValue = null;

public Windows.Foundation.Collections.IPropertySet other
{
get
{
return otherValue;
}
set
{
otherValue = value;
}
}
}

public sealed class PropertySet : Windows.Foundation.Collections.IPropertySet
{
private IDictionary map = new Dictionary();

public PropertySet()
{
map.Add("abc", "def");
map.Add("ghi", "jkl");
map.Add("mno", "pqr");
}
// ... rest of PropertySet implementation is simple wrapper around the map member.


// JS code
var propertySet = new ExampleWinRTComponent.ExampleNamespace.PropertySet();
console.log("propertySet: " + JSON.stringify(propertySet));
// propertySet: {"abc":"def","ghi":"jkl","mno":"pqr"}

var propertySetContainer = new ExampleWinRTComponent.ExampleNamespace.PropertySetContainer();
propertySetContainer.other = propertySet;
console.log("propertySetContainer.other: " + JSON.stringify(propertySetContainer.other));
// propertySetContainer.other: {"abc":"def","ghi":"jkl","mno":"pqr"}

try {
propertySetContainer.other = { "123": "456", "789": "012" };
}
catch (e) {
console.error("Error setting propertySetContainer.other: " + e);
// Error setting propertySetContainer.other: TypeError: Type mismatch
}

There’s also no way to implicitly convert a DOM object into a WinRT type. If you want to write third party WinRT code that interacts with the DOM, you must do so indirectly and explicitly in JavaScript code that is interacting with your third party WinRT. You’ll have to extract the information you want from your DOM objects to pass into WinRT methods and similarly have to pass messages out from WinRT that say what actions the JavaScript should perform on the DOM.

PermalinkCommentschakra development javascript winrt

Tweet from David_Risney

2015 Oct 7, 3:41
Panos Panay had an awesome performance at the Windows 10 Devices event yesterday! https://twitter.com/verge/status/651494054975434752 …
PermalinkComments

Tweet from David_Risney

2015 Jul 1, 11:26
I had an ophthalmic migraine this morning. Black and white zig zags forming a crescent took 20mins expanding across my left field of vision.
PermalinkComments

Tweet from David_Risney

2015 Jun 14, 9:53
Fallout4 on XB1 supports PC mods. Due to platform similarity? Possible for PS4? https://twitter.com/Polygon/status/610494038685417472 …
PermalinkComments

laughingsquid: Live Band Performs ‘Mario Kart’ Song as Man...

2015 May 27, 3:11


laughingsquid:

Live Band Performs ‘Mario Kart’ Song as Man Races Down the Video Game Track

PermalinkComments

laughingsquid: Live Band Performs ‘Mario Kart’ Song as Man...

2015 May 27, 3:11


laughingsquid:

Live Band Performs ‘Mario Kart’ Song as Man Races Down the Video Game Track

PermalinkComments

Retweet of TheAtlantic

2015 Mar 20, 4:03
A new 3-D printer pulls fully formed objects out of liquid at 100 times the speed http://theatln.tc/199X3VM  pic.twitter.com/cMb6HSthL7
PermalinkComments
Older Entries Creative Commons License Some rights reserved.