Threadsafe URIs in Gecko

Valentin Goșu

valentin@mozilla.com

nsIURI is not thread safe

Hack around it:

 

Bounce to the main thread to access the URI

because addons

var url = {
    counter: 0,
    password: "",
    asciiSpec:
        function() { counter++; return "spec" + counter; },
    QueryInterface: XPCOMUtils.generateQI([Ci.nsIURI])
}

We now control all 11 nsIURI implementations

nsStandardURL
  -> SubstitutingURL
nsSimpleURI
  -> nsSimpleNestedURI
      -> nsNestedAboutURI
  -> nsHostObjectURI
  -> nsJSURI
nsJARURI
NullPrincipalURI
nsMozIconURI
  -> nsNestedMozIconURI

6864 lines containing "nsIURI"

Hurray for Quantum!

Why don't we just add a Mutex?

// Thread 1
url.spec = "http://test.com/path"
if (url.scheme == "http") {
    url.hostname = "other.com"
}
// We expect url.spec == "http://other.com/path"
// Thread 2

url.spec = "ftp://kernel.org/file"

// We expect url.spec == "ftp://kernel.org/file"
Actual results may be any of:
"http://other.com/path"
"ftp://kernel.org/file"
"ftp://other.com/file"

Solution: make nsIURI immutable

interface nsIURI : nsISupports
{
    [...]
    nsIURIMutator mutate();
}
interface nsIURIMutator : nsISupports
{
    nsIURIMutator setScheme(in AUTF8String aScheme);
    nsIURIMutator setUserPass(in AUTF8String aUserPass);
    [...]
    nsIURI finalize();
}

Step 1: Add new nsIURIMutator API [done]*

Step 2: Update all the places where we call nsIURI setters

Step 2b: Add warnings/assertions for all nsIURI setters

Step 3: Make nsIURI attributes readonly

 

Follow up:

Step 4: Centralize all URI parsers

Step 5: Replace all with rust-url

Plan:

function DO_NOT_DO_THIS(uri) {
    uri.query = "hello";
    uri.ref = "bla";
    uri.scheme = "ftp";
    return uri; // you just changed the initial URI!
}

function oldWay(uri) {
    let newURI = uri.clone();
    newURI.query = "hello";
    newURI.ref = "bla";
    newURI.scheme = "ftp";
    return newURI;
}

function betterWay(uri) {
    return uri.mutate()
              .setQuery("hello")
              .setRef("bla")
              .setScheme("ftp")
              .finalize();
}
nsresult DO_NOT_DO_THIS(nsIURI* aURI, nsIURI** changedURI) {
    nsCOMPtr<nsIURI> newURI = aURI;
    nsresult rv = newURI->SetRef(NS_LITERAL_CSTRING("test"));
    newURI.forget(changedURI); // we changed the initial URI
    return rv;
}

nsresult OldWay(nsIURI* aURI, nsIURI** changedURI) {
    nsCOMPtr<nsIURI> newURI;
    nsresult rv = aURI->Clone(getter_AddRefs(newURI));
    NS_ENSURE_SUCCESS(rv, rv);
    rv = newURI->SetRef(NS_LITERAL_CSTRING("test"));
    newURI.forget(changedURI);
    return rv;
}

#include "nsIURIMutator.h"
nsresult BetterWay(nsIURI* aURI, nsIURI** changedURI) {
    nsCOMPtr<nsIURI> newURI;
    nsresult rv = NS_MutateURI(uri)
                    .SetRef(NS_LITERAL_CSTRING("test"));
                    .Finalize(newURI);
    newURI.forget(changedURI);
    return rv;
}
#include "nsIURIMutator.h"
nsresult BetterWay(nsIURI* aURI, nsIURI** changedURI) {
    nsCOMPtr<nsIURI> newURI;
    nsresult rv = NS_MutateURI(uri)
                    .SetRef(NS_LITERAL_CSTRING("test"));
                    .Finalize(newURI);
    newURI.forget(changedURI);
    return rv;
}

#include "nsIURIMutator.h"
nsresult EvenBetterWay(nsIURI* aURI, nsIURI** changedURI) {
    // after patch lands
    return NS_MutateURI(uri)
             .SetRef(NS_LITERAL_CSTRING("test"));
             .Finalize(changedURI);
}

Help us make nsIURI threadsafe by:

  • Write new code using the nsIURIMutator API
  • Change old code to use nsIURIMutator

Helpful resources:

nsIURIMutator.idl

TestStandardURL.cpp::Mutator

test_uri_mutator.js

Threadsafe URIs in Gecko

By Valentin Gosu

Threadsafe URIs in Gecko

  • 2,321