backout bug 879963 (changesets 4a3befee43f1, 09c9359bdd43, 32ffcd6db605) due to Nightly crashiness, see bug 896200.
authorJonathan Kew <jkew@mozilla.com>
Tue, 23 Jul 2013 09:38:57 +0100
changeset 151837 9fc53053c489f95d18aac1e418844bafbce8a9a6
parent 151836 93937caf5d5731d6e55cddf52234bcfe6b4f4323
child 151838 bf53001aa2c795cd6bf525f2bbbb6eb82df6af36
push id2859
push userakeybl@mozilla.com
push dateMon, 16 Sep 2013 19:14:59 +0000
treeherdermozilla-beta@87d3c51cd2bf [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs879963, 896200
milestone25.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
backout bug 879963 (changesets 4a3befee43f1, 09c9359bdd43, 32ffcd6db605) due to Nightly crashiness, see bug 896200.
gfx/thebes/gfxUserFontSet.cpp
gfx/thebes/gfxUserFontSet.h
layout/style/nsFontFaceLoader.cpp
layout/style/test/Makefile.in
layout/style/test/redundant_font_download.sjs
layout/style/test/test_redundant_font_download.html
--- a/gfx/thebes/gfxUserFontSet.cpp
+++ b/gfx/thebes/gfxUserFontSet.cpp
@@ -39,66 +39,41 @@ gfxUserFontSet::GetUserFontsLog()
 #define LOG_ENABLED() PR_LOG_TEST(GetUserFontsLog(), PR_LOG_DEBUG)
 
 static uint64_t sFontSetGeneration = 0;
 
 // TODO: support for unicode ranges not yet implemented
 
 gfxProxyFontEntry::gfxProxyFontEntry(const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
              uint32_t aWeight,
-             int32_t aStretch,
+             uint32_t aStretch,
              uint32_t aItalicStyle,
              const nsTArray<gfxFontFeature>& aFeatureSettings,
              uint32_t aLanguageOverride,
              gfxSparseBitSet *aUnicodeRanges)
     : gfxFontEntry(NS_LITERAL_STRING("Proxy")),
       mLoadingState(NOT_LOADING),
       mUnsupportedFormat(false),
       mLoader(nullptr)
 {
     mIsProxy = true;
     mSrcList = aFontFaceSrcList;
     mSrcIndex = 0;
     mWeight = aWeight;
     mStretch = aStretch;
-    // XXX Currently, we don't distinguish 'italic' and 'oblique' styles;
-    // we need to fix this. (Bug 543715)
     mItalic = (aItalicStyle & (NS_FONT_STYLE_ITALIC | NS_FONT_STYLE_OBLIQUE)) != 0;
     mFeatureSettings.AppendElements(aFeatureSettings);
     mLanguageOverride = aLanguageOverride;
     mIsUserFont = true;
 }
 
 gfxProxyFontEntry::~gfxProxyFontEntry()
 {
 }
 
-bool
-gfxProxyFontEntry::Matches(const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
-                           uint32_t aWeight,
-                           int32_t aStretch,
-                           uint32_t aItalicStyle,
-                           const nsTArray<gfxFontFeature>& aFeatureSettings,
-                           uint32_t aLanguageOverride,
-                           gfxSparseBitSet *aUnicodeRanges)
-{
-    // XXX font entries don't distinguish italic from oblique (bug 543715)
-    bool isItalic =
-        (aItalicStyle & (NS_FONT_STYLE_ITALIC | NS_FONT_STYLE_OBLIQUE)) != 0;
-
-    return mWeight == aWeight &&
-           mStretch == aStretch &&
-           mItalic == isItalic &&
-           mFeatureSettings == aFeatureSettings &&
-           mLanguageOverride == aLanguageOverride &&
-           mSrcList == aFontFaceSrcList;
-           // XXX once we support unicode-range (bug 475891),
-           // we'll need to compare that here as well
-}
-
 gfxFont*
 gfxProxyFontEntry::CreateFontInstance(const gfxFontStyle *aFontStyle, bool aNeedsBold)
 {
     // cannot create an actual font for a proxy entry
     return nullptr;
 }
 
 gfxUserFontSet::gfxUserFontSet()
@@ -110,74 +85,44 @@ gfxUserFontSet::gfxUserFontSet()
 gfxUserFontSet::~gfxUserFontSet()
 {
 }
 
 gfxFontEntry*
 gfxUserFontSet::AddFontFace(const nsAString& aFamilyName,
                             const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
                             uint32_t aWeight,
-                            int32_t aStretch,
+                            uint32_t aStretch,
                             uint32_t aItalicStyle,
                             const nsTArray<gfxFontFeature>& aFeatureSettings,
                             const nsString& aLanguageOverride,
                             gfxSparseBitSet *aUnicodeRanges)
 {
+    gfxProxyFontEntry *proxyEntry = nullptr;
+
     nsAutoString key(aFamilyName);
     ToLowerCase(key);
 
     bool found;
 
     if (aWeight == 0)
         aWeight = NS_FONT_WEIGHT_NORMAL;
 
     // stretch, italic/oblique ==> zero implies normal
 
     gfxMixedFontFamily *family = mFontFamilies.GetWeak(key, &found);
     if (!family) {
         family = new gfxMixedFontFamily(aFamilyName);
         mFontFamilies.Put(key, family);
     }
 
+    // construct a new face and add it into the family
     uint32_t languageOverride =
         gfxFontStyle::ParseFontLanguageOverride(aLanguageOverride);
-
-    // If there's already a proxy in the family whose descriptors all match,
-    // we can just move it to the end of the list instead of adding a new
-    // face that will always "shadow" the old one.
-    // Note that we can't do this for "real" (non-proxy) entries, even if the
-    // style descriptors match, as they might have had a different source list,
-    // but we no longer have the old source list available to check.
-    nsTArray<nsRefPtr<gfxFontEntry> >& fontList = family->GetFontList();
-    for (uint32_t i = 0, count = fontList.Length(); i < count; i++) {
-        if (!fontList[i]->mIsProxy) {
-            continue;
-        }
-
-        gfxProxyFontEntry *existingProxyEntry =
-            static_cast<gfxProxyFontEntry*>(fontList[i].get());
-        if (!existingProxyEntry->Matches(aFontFaceSrcList,
-                                         aWeight, aStretch, aItalicStyle,
-                                         aFeatureSettings, languageOverride,
-                                         aUnicodeRanges)) {
-            continue;
-        }
-
-        // We've found an entry that matches the new face exactly, so advance
-        // it to the end of the list.
-        // (Hold a strong reference while doing this, in case the only thing
-        // keeping the proxyFontEntry alive is the reference from family!)
-        nsRefPtr<gfxFontEntry> ref(existingProxyEntry);
-        family->RemoveFontEntry(existingProxyEntry);
-        family->AddFontEntry(existingProxyEntry);
-        return existingProxyEntry;
-    }
-
-    // construct a new face and add it into the family
-    gfxProxyFontEntry *proxyEntry =
+    proxyEntry =
         new gfxProxyFontEntry(aFontFaceSrcList, aWeight, aStretch,
                               aItalicStyle,
                               aFeatureSettings,
                               languageOverride,
                               aUnicodeRanges);
     family->AddFontEntry(proxyEntry);
 #ifdef PR_LOGGING
     if (LOG_ENABLED()) {
--- a/gfx/thebes/gfxUserFontSet.h
+++ b/gfx/thebes/gfxUserFontSet.h
@@ -36,30 +36,16 @@ struct gfxFontFaceSrc {
     uint32_t               mFormatFlags;
 
     nsString               mLocalName;     // full font name if local
     nsCOMPtr<nsIURI>       mURI;           // uri if url
     nsCOMPtr<nsIURI>       mReferrer;      // referrer url if url
     nsCOMPtr<nsIPrincipal> mOriginPrincipal; // principal if url
 };
 
-inline bool
-operator==(const gfxFontFaceSrc& a, const gfxFontFaceSrc& b)
-{
-    bool equals;
-    return (a.mIsLocal && b.mIsLocal &&
-            a.mLocalName == b.mLocalName) ||
-           (!a.mIsLocal && !b.mIsLocal &&
-            a.mUseOriginPrincipal == b.mUseOriginPrincipal &&
-            a.mFormatFlags == b.mFormatFlags &&
-            NS_SUCCEEDED(a.mURI->Equals(b.mURI, &equals)) && equals &&
-            NS_SUCCEEDED(a.mReferrer->Equals(b.mReferrer, &equals)) && equals &&
-            a.mOriginPrincipal->Equals(b.mOriginPrincipal));
-}
-
 // Subclassed to store platform-specific code cleaned out when font entry is
 // deleted.
 // Lifetime: from when platform font is created until it is deactivated.
 // If the platform does not need to add any platform-specific code/data here,
 // then the gfxUserFontSet will allocate a base gfxUserFontData and attach
 // to the entry to track the basic user font info fields here.
 class gfxUserFontData {
 public:
@@ -177,24 +163,23 @@ public:
         STATUS_LOADED,
         STATUS_FORMAT_NOT_SUPPORTED,
         STATUS_ERROR,
         STATUS_END_OF_LIST
     };
 
 
     // add in a font face
-    // weight - 0 == unknown, [100, 900] otherwise (multiples of 100)
-    // stretch = [NS_FONT_STRETCH_ULTRA_CONDENSED, NS_FONT_STRETCH_ULTRA_EXPANDED]
+    // weight, stretch - 0 == unknown, [1, 9] otherwise
     // italic style = constants in gfxFontConstants.h, e.g. NS_FONT_STYLE_NORMAL
     // TODO: support for unicode ranges not yet implemented
     gfxFontEntry *AddFontFace(const nsAString& aFamilyName,
                               const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
                               uint32_t aWeight,
-                              int32_t aStretch,
+                              uint32_t aStretch,
                               uint32_t aItalicStyle,
                               const nsTArray<gfxFontFeature>& aFeatureSettings,
                               const nsString& aLanguageOverride,
                               gfxSparseBitSet *aUnicodeRanges = nullptr);
 
     // add in a font face for which we have the gfxFontEntry already
     void AddFontFace(const nsAString& aFamilyName, gfxFontEntry* aFontEntry);
 
@@ -438,33 +423,24 @@ private:
 // acts a placeholder until the real font is downloaded
 
 class gfxProxyFontEntry : public gfxFontEntry {
     friend class gfxUserFontSet;
 
 public:
     gfxProxyFontEntry(const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
                       uint32_t aWeight,
-                      int32_t aStretch,
+                      uint32_t aStretch,
                       uint32_t aItalicStyle,
                       const nsTArray<gfxFontFeature>& aFeatureSettings,
                       uint32_t aLanguageOverride,
                       gfxSparseBitSet *aUnicodeRanges);
 
     virtual ~gfxProxyFontEntry();
 
-    // Return whether the entry matches the given list of attributes
-    bool Matches(const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
-                 uint32_t aWeight,
-                 int32_t aStretch,
-                 uint32_t aItalicStyle,
-                 const nsTArray<gfxFontFeature>& aFeatureSettings,
-                 uint32_t aLanguageOverride,
-                 gfxSparseBitSet *aUnicodeRanges);
-
     virtual gfxFont *CreateFontInstance(const gfxFontStyle *aFontStyle, bool aNeedsBold);
 
     // note that code depends on the ordering of these values!
     enum LoadingState {
         NOT_LOADING = 0,     // not started to load any font resources yet
         LOADING_STARTED,     // loading has started; hide fallback font
         LOADING_ALMOST_DONE, // timeout happened but we're nearly done,
                              // so keep hiding fallback font
--- a/layout/style/nsFontFaceLoader.cpp
+++ b/layout/style/nsFontFaceLoader.cpp
@@ -408,64 +408,49 @@ nsUserFontSet::StartLoad(gfxMixedFontFam
 static PLDHashOperator DetachFontEntries(const nsAString& aKey,
                                          nsRefPtr<gfxMixedFontFamily>& aFamily,
                                          void* aUserArg)
 {
   aFamily->DetachFontEntries();
   return PL_DHASH_NEXT;
 }
 
-static PLDHashOperator RemoveIfEmpty(const nsAString& aKey,
-                                     nsRefPtr<gfxMixedFontFamily>& aFamily,
-                                     void* aUserArg)
-{
-  return aFamily->GetFontList().Length() ? PL_DHASH_NEXT : PL_DHASH_REMOVE;
-}
-
 bool
 nsUserFontSet::UpdateRules(const nsTArray<nsFontFaceRuleContainer>& aRules)
 {
   bool modified = false;
 
-  // The @font-face rules that make up the user font set have changed,
-  // so we need to update the set. However, we want to preserve existing
-  // font entries wherever possible, so that we don't discard and then
-  // re-download resources in the (common) case where at least some of the
-  // same rules are still present.
+  // destroy any current loaders, as the entries they refer to
+  // may be about to get replaced
+  if (mLoaders.Count() > 0) {
+    modified = true; // trigger reflow so that any necessary downloads
+                        // will be reinitiated
+  }
+  mLoaders.EnumerateEntries(DestroyIterator, nullptr);
 
   nsTArray<FontFaceRuleRecord> oldRules;
   mRules.SwapElements(oldRules);
 
-  // Remove faces from the font family records; we need to re-insert them
-  // because we might end up with faces in a different order even if they're
-  // the same font entries as before. (The order can affect font selection
-  // where multiple faces match the requested style, perhaps with overlapping
-  // unicode-range coverage.)
+  // destroy the font family records; we need to re-create them
+  // because we might end up with faces in a different order,
+  // even if they're the same font entries as before
   mFontFamilies.Enumerate(DetachFontEntries, nullptr);
+  mFontFamilies.Clear();
 
   for (uint32_t i = 0, i_end = aRules.Length(); i < i_end; ++i) {
-    // Insert each rule into our list, migrating old font entries if possible
+    // insert each rule into our list, migrating old font entries if possible
     // rather than creating new ones; set  modified  to true if we detect
-    // that rule ordering has changed, or if a new entry is created.
+    // that rule ordering has changed, or if a new entry is created
     InsertRule(aRules[i].mRule, aRules[i].mSheetType, oldRules, modified);
   }
 
-  // Remove any residual families that have no font entries (i.e., they were
-  // not defined at all by the updated set of @font-face rules).
-  mFontFamilies.Enumerate(RemoveIfEmpty, nullptr);
-
-  // If any rules are left in the old list, note that the set has changed
-  // (even if the new set was built entirely by migrating old font entries).
+  // if any rules are left in the old list, note that the set has changed
   if (oldRules.Length() > 0) {
     modified = true;
-    // Any in-progress loaders for obsolete rules should be cancelled,
-    // as the resource being downloaded will no longer be required.
-    // We need to explicitly remove any loaders here, otherwise the loaders
-    // will keep their "orphaned" font entries alive until they complete,
-    // even after the oldRules array is deleted.
+    // any in-progress loaders for obsolete rules should be cancelled
     size_t count = oldRules.Length();
     for (size_t i = 0; i < count; ++i) {
       gfxFontEntry *fe = oldRules[i].mFontEntry;
       if (!fe->mIsProxy) {
         continue;
       }
       gfxProxyFontEntry *proxy = static_cast<gfxProxyFontEntry*>(fe);
       if (proxy->mLoader != nullptr) {
@@ -525,17 +510,17 @@ nsUserFontSet::InsertRule(nsCSSFontFaceR
       }
       return;
     }
   }
 
   // this is a new rule:
 
   uint32_t weight = NS_STYLE_FONT_WEIGHT_NORMAL;
-  int32_t stretch = NS_STYLE_FONT_STRETCH_NORMAL;
+  uint32_t stretch = NS_STYLE_FONT_STRETCH_NORMAL;
   uint32_t italicStyle = NS_STYLE_FONT_STYLE_NORMAL;
   nsString languageOverride;
 
   // set up weight
   aRule->GetDesc(eCSSFontDesc_Weight, val);
   unit = val.GetUnit();
   if (unit == eCSSUnit_Integer || unit == eCSSUnit_Enumerated) {
     weight = val.GetIntValue();
--- a/layout/style/test/Makefile.in
+++ b/layout/style/test/Makefile.in
@@ -140,18 +140,16 @@ MOCHITEST_FILES =	test_acid3_test46.html
 		test_parse_rule.html \
 		test_parse_url.html \
 		test_parser_diagnostics_unprintables.html \
 		test_pixel_lengths.html \
 		test_pointer-events.html \
 		test_property_database.html \
 		test_priority_preservation.html \
 		test_property_syntax_errors.html \
-		test_redundant_font_download.html \
-		redundant_font_download.sjs \
 		test_rem_unit.html \
 		test_rule_insertion.html \
 		test_rule_serialization.html \
 		test_rules_out_of_sheets.html \
 		test_selectors.html \
 		test_selectors_on_anonymous_content.html \
 		test_shorthand_property_getters.html \
 		test_specified_value_serialization.html \
deleted file mode 100644
--- a/layout/style/test/redundant_font_download.sjs
+++ /dev/null
@@ -1,60 +0,0 @@
-const BinaryOutputStream =
-  Components.Constructor("@mozilla.org/binaryoutputstream;1",
-                         "nsIBinaryOutputStream",
-                         "setOutputStream");
-
-// this is simply a hex dump of a red square .PNG image
-const RED_SQUARE =
-  [
-    0x89,  0x50,  0x4E,  0x47,  0x0D,  0x0A,  0x1A,  0x0A,  0x00,  0x00,
-    0x00,  0x0D,  0x49,  0x48,  0x44,  0x52,  0x00,  0x00,  0x00,  0x20,
-    0x00,  0x00,  0x00,  0x20,  0x08,  0x02,  0x00,  0x00,  0x00,  0xFC,
-    0x18,  0xED,  0xA3,  0x00,  0x00,  0x00,  0x01,  0x73,  0x52,  0x47,
-    0x42,  0x00,  0xAE,  0xCE,  0x1C,  0xE9,  0x00,  0x00,  0x00,  0x28,
-    0x49,  0x44,  0x41,  0x54,  0x48,  0xC7,  0xED,  0xCD,  0x41,  0x0D,
-    0x00,  0x00,  0x08,  0x04,  0xA0,  0xD3,  0xFE,  0x9D,  0x35,  0x85,
-    0x0F,  0x37,  0x28,  0x40,  0x4D,  0x6E,  0x75,  0x04,  0x02,  0x81,
-    0x40,  0x20,  0x10,  0x08,  0x04,  0x02,  0xC1,  0x93,  0x60,  0x01,
-    0xA3,  0xC4,  0x01,  0x3F,  0x58,  0x1D,  0xEF,  0x27,  0x00,  0x00,
-    0x00,  0x00,  0x49,  0x45,  0x4E,  0x44,  0xAE,  0x42,  0x60,  0x82
-  ];
-
-function handleRequest(request, response)
-{
-  var query = {};
-  request.queryString.split('&').forEach(function (val) {
-    var [name, value] = val.split('=');
-    query[name] = unescape(value);
-  });
-
-  response.setHeader("Cache-Control", "no-cache");
-
-  response.setStatusLine(request.httpVersion, 200, "OK");
-  response.setHeader("Content-Type", "text/plain", false);
-
-  var log = getState("bug-879963-request-log") || "";
-
-  var stream = new BinaryOutputStream(response.bodyOutputStream);
-
-  if (query["q"] == "init") {
-    log = "init"; // initialize the log, and return a PNG image
-    response.setHeader("Content-Type", "image/png", false);
-    stream.writeByteArray(RED_SQUARE, RED_SQUARE.length);
-  } else if (query["q"] == "image") {
-    log = log + ";" + query["q"];
-    response.setHeader("Content-Type", "image/png", false);
-    stream.writeByteArray(RED_SQUARE, RED_SQUARE.length);
-  } else if (query["q"] == "font") {
-    log = log + ";" + query["q"];
-    // we don't provide a real font; that's ok, OTS will just reject it
-    response.write("Junk");
-  } else if (query["q"] == "report") {
-    // don't include the actual "report" request in the log we return
-    response.write(log);
-  } else {
-    log = log + ";" + query["q"];
-    response.setStatusLine(request.httpVersion, 404, "Not Found");
-  }
-
-  setState("bug-879963-request-log", log);
-}
deleted file mode 100644
--- a/layout/style/test/test_redundant_font_download.html
+++ /dev/null
@@ -1,130 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=879963 -->
-<head>
-  <meta charset="utf-8">
-  <title>Test for bug 879963</title>
-  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-</head>
-
-<body>
-  <!-- Two <style> elements with identical @font-face rules.
-       Although multiple @font-face at-rules for the same family are legal,
-       and add faces to the family, we should NOT download the same resource
-       twice just because we have a duplicate face entry. -->
-  <style type="text/css">
-    @font-face {
-      font-family: foo;
-      src: url("redundant_font_download.sjs?q=font");
-    }
-    .test {
-      font-family: foo;
-    }
-  </style>
-
-  <style type="text/css">
-    @font-face {
-      font-family: foo;
-      src: url("redundant_font_download.sjs?q=font");
-    }
-    .test {
-      font-family: foo;
-    }
-  </style>
-
-  <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=879963">Mozilla Bug 879963</a>
-
-  <div>
-    <!-- the 'init' request returns an image (just so we can see it's working)
-         and initializes the request logging in our sjs server -->
-    <img src="redundant_font_download.sjs?q=init">
-  </div>
-
-  <div id="test">
-    Test
-  </div>
-
-  <div>
-    <img id="image2" src="">
-  </div>
-
-  <script type="application/javascript">
-    // helper to retrieve the server's request log as text
-    function getRequestLog() {
-      var xmlHttp = new XMLHttpRequest();
-      xmlHttp.open("GET", "redundant_font_download.sjs?q=report", false);
-      xmlHttp.send(null);
-      return xmlHttp.responseText;
-    }
-
-    // retrieve just the most recent request the server has seen
-    function getLastRequest() {
-      return getRequestLog().split(";").pop();
-    }
-
-    // poll the server at intervals of 'delay' ms until it has seen a specific request,
-    // or until maxTime ms have passed
-    function waitForRequest(request, delay, maxTime, func) {
-      timeLimit = Date.now() + maxTime;
-      var intervalId = window.setInterval(function() {
-        var req = getLastRequest();
-        if (req == request || Date.now() > timeLimit) {
-          window.clearInterval(intervalId);
-          func();
-        }
-      }.bind(this), delay);
-    }
-
-    // initially disable the second of the <style> elements,
-    // so we only have a single copy of the font-face
-    document.getElementsByTagName("style")[1].disabled = true;
-
-    SimpleTest.waitForExplicitFinish();
-
-    // We perform a series of actions that trigger requests to the .sjs server,
-    // and poll the server's request log at each stage to check that it has
-    // seen the request we expected before we proceed to the next step.
-    function startTest() {
-      is(getRequestLog(), "init", "request log has been initialized");
-
-      // this should trigger a font download
-      document.getElementById("test").className = "test";
-
-      // wait to confirm that the server has received the request
-      waitForRequest("font", 10, 5000, continueTest1);
-    }
-
-    function continueTest1() {
-      is(getRequestLog(), "init;font", "server received font request");
-
-      // trigger a request for the second image, to provide an explicit
-      // delimiter in the log before we enable the second @font-face rule
-      document.getElementById("image2").src = "redundant_font_download.sjs?q=image";
-
-      waitForRequest("image", 10, 5000, continueTest2);
-    }
-
-    function continueTest2() {
-      is(getRequestLog(), "init;font;image", "server received image request");
-
-      // enable the second <style> element: we should NOT see a second font request,
-      // we expect waitForRequest to time out instead
-      document.getElementsByTagName("style")[1].disabled = false;
-
-      waitForRequest("font", 10, 1000, continueTest3);
-    }
-
-    function continueTest3() {
-      is(getRequestLog(), "init;font;image", "should NOT have re-requested the font");
-
-      SimpleTest.finish();
-    }
-
-    waitForRequest("init", 10, 5000, startTest);
-
-  </script>
-
-</body>
-
-</html>