<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[171033] trunk</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.webkit.org/projects/webkit/changeset/171033">171033</a></dd>
<dt>Author</dt> <dd>jer.noble@apple.com</dd>
<dt>Date</dt> <dd>2014-07-12 15:39:43 -0700 (Sat, 12 Jul 2014)</dd>
</dl>

<h3>Log Message</h3>
<pre>[MSE] http/tests/media/media-source/mediasource-duration.html is failing.
https://bugs.webkit.org/show_bug.cgi?id=134852

Reviewed by Eric Carlson.

Source/WebCore:
Fixes the following tests:
http/tests/media/media-source/mediasource-config-change-mp4-a-bitrate.html
http/tests/media/media-source/mediasource-config-change-mp4-av-audio-bitrate.html
http/tests/media/media-source/mediasource-config-change-mp4-av-video-bitrate.html
http/tests/media/media-source/mediasource-config-change-mp4-v-bitrate.html
http/tests/media/media-source/mediasource-config-change-mp4-v-framerate.html
http/tests/media/media-source/mediasource-duration.html
http/tests/media/media-source/mediasource-play.html

The primary change necessary to fix the mediasource-duration.html test was to add support
for delaying the completion of a seek operation until the HTMLMediaElement's readyState
rises to &gt; HAVE_CURRENT_DATA. This is accomplished by modifying MediaSourcePrivate to have
waitForSeekCompleted() and seekCompleted() virtual methods. These are called by MediaSource
when a seek operation results in the current time moving outside the currently buffered time
ranges, and when an append operation results in the readyState changing, respectively.

A number of other drive-by fixes were necessary to get this test fully passing, as noted
below.

Make the MediaSource the primary owner of the media's duration, rather than the MediaSourcePrivate.
Move the MediaSourcePrivateClient pointer to the MediaSourcePrivate from the MediaPlayerPrivate, so
the MediaSource's duration can be retrieved.  While we're at it, do the same thing for buffered.

* Modules/mediasource/MediaSource.cpp:
(WebCore::MediaSource::MediaSource): Initialize m_duration.
(WebCore::MediaSource::duration): Simple accessor.
(WebCore::MediaSource::setDurationInternal): Bring 'duration change algorithm' up to spec.
(WebCore::MediaSource::setReadyState): Reset m_duration on close.
* Modules/mediasource/MediaSource.h:
* platform/graphics/MediaSourcePrivate.h:
* platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaSourceAVFObjC.mm:
(WebCore::MediaPlayerPrivateMediaSourceAVFObjC::load): Do not call setPrivateAndOpen().
(WebCore::MediaPlayerPrivateMediaSourceAVFObjC::durationDouble): Pass through to MediaSourcePrivateAVFObjC.
(WebCore::MediaPlayerPrivateMediaSourceAVFObjC::buffered): Ditto.
* platform/graphics/avfoundation/objc/MediaSourcePrivateAVFObjC.h:
* platform/graphics/avfoundation/objc/MediaSourcePrivateAVFObjC.mm:
(WebCore::MediaSourcePrivateAVFObjC::create): Call setPrivateAndOpen().
(WebCore::MediaSourcePrivateAVFObjC::MediaSourcePrivateAVFObjC): Set m_client.
(WebCore::MediaSourcePrivateAVFObjC::duration): Pass through to MediaSourcePrivateClient.
(WebCore::MediaSourcePrivateAVFObjC::buffered): Ditto.
(WebCore::MediaSourcePrivateAVFObjC::durationChanged): Pass through to MediaPlayerPrivateMediaSourceAVFObjC.
(WebCore::MediaSourcePrivateAVFObjC::setDuration): Deleted.
* platform/graphics/gstreamer/MediaSourceGStreamer.cpp:
(WebCore::MediaSourceGStreamer::open): Pass in MediaSourcePrivateClient.
(WebCore::MediaSourceGStreamer::MediaSourceGStreamer): Initialize m_mediaSource.
(WebCore::MediaSourceGStreamer::durationChanged): Retrieve the duration from MediaSourcePrivateClient.
(WebCore::MediaSourceGStreamer::markEndOfStream): Remove unnecssary ASSERT.
(WebCore::MediaSourceGStreamer::unmarkEndOfStream): Ditto.
(WebCore::MediaSourceGStreamer::setDuration): Deleted.
* platform/graphics/gstreamer/MediaSourceGStreamer.h:
* platform/mock/mediasource/MockMediaPlayerMediaSource.cpp:
(WebCore::MockMediaPlayerMediaSource::load): Do not call setPrivateAndOpen().
(WebCore::MockMediaPlayerMediaSource::buffered): Pass through to MockMediaSourcePrivate.
(WebCore::MockMediaPlayerMediaSource::durationDouble): Ditto.
(WebCore::MockMediaPlayerMediaSource::advanceCurrentTime): Ditto.
* platform/mock/mediasource/MockMediaSourcePrivate.cpp:
(WebCore::MockMediaSourcePrivate::create): Call setPrivateAndOpen().
(WebCore::MockMediaSourcePrivate::MockMediaSourcePrivate): Set m_client.
(WebCore::MockMediaSourcePrivate::duration): Pass through to MediaSourcePrivateClient.
(WebCore::MockMediaSourcePrivate::buffered): Ditto.
(WebCore::MockMediaSourcePrivate::durationChanged): Pass thorugh to MockMediaPlayerMediaSource.
(WebCore::MockMediaSourcePrivate::setDuration): Deleted.

Route seekToTime through MediaSource, rather than through MediaSourcePrivate, so that
the time can be compared against the buffered ranges, and trigger the delay of the seek
operation if necessary. Add a seekTimer to MediaPlayerPrivateMediaSourceAVFObjC, as this
guarantees the order of asynchronous operations, rather than callOnMainThread, which can
cause async operations to occur out of order.

* Modules/mediasource/MediaSource.cpp:
(WebCore::MediaSource::seekToTime): Bring up to spec.
(WebCore::MediaSource::completeSeek): Ditto.
(WebCore::MediaSource::monitorSourceBuffers): Call completeSeek() when appropriate.
* Modules/mediasource/SourceBuffer.cpp:
(WebCore::SourceBuffer::sourceBufferPrivateSeekToTime): Deleted.
(WebCore::SourceBuffer::seekToTime): Renamed from sourceBufferPrivateSeekToTime().
* platform/graphics/MediaSourcePrivate.h:
* platform/graphics/MediaSourcePrivateClient.h:
* platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaSourceAVFObjC.h:
* platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaSourceAVFObjC.mm:
(WebCore::MediaPlayerPrivateMediaSourceAVFObjC::MediaPlayerPrivateMediaSourceAVFObjC): Add seekTimer. Only
    call timeChanged() if no longer seeking, thereby triggering a 'seeked' event.
(WebCore::MediaPlayerPrivateMediaSourceAVFObjC::~MediaPlayerPrivateMediaSourceAVFObjC): Clear m_seekTimer.
(WebCore::MediaPlayerPrivateMediaSourceAVFObjC::seekWithTolerance): Use m_seekTimer.
(WebCore::MediaPlayerPrivateMediaSourceAVFObjC::seekTimerFired): Call seekInternal.
(WebCore::MediaPlayerPrivateMediaSourceAVFObjC::seekInternal): Add logging.
(WebCore::MediaPlayerPrivateMediaSourceAVFObjC::waitForSeekCompleted): Added.
(WebCore::MediaPlayerPrivateMediaSourceAVFObjC::seekCompleted): Added; trigger 'seeked'.
(WebCore::MediaPlayerPrivateMediaSourceAVFObjC::setReadyState): No longer attempt to finish seek when
    readyState changes here; this has been moved up to MediaSource.cpp.
* platform/graphics/avfoundation/objc/MediaSourcePrivateAVFObjC.h:
* platform/graphics/avfoundation/objc/MediaSourcePrivateAVFObjC.mm:
(WebCore::MediaSourcePrivateAVFObjC::waitForSeekCompleted): Pass through to MediaPlayerPrivateMediaSourceAVFObjC.
(WebCore::MediaSourcePrivateAVFObjC::seekCompleted): Ditto.
(WebCore::MediaSourcePrivateAVFObjC::seekToTime): Pass through to MediaSourcePrivateClient.
(WebCore::MediaSourcePrivateAVFObjC::fastSeekTimeForMediaTime): Ditto.
* platform/mock/mediasource/MockMediaPlayerMediaSource.cpp:
(WebCore::MockMediaPlayerMediaSource::MockMediaPlayerMediaSource): Initialize m_seekCompleted.
(WebCore::MockMediaPlayerMediaSource::seeking): Check for an uncompleted seek operation.
(WebCore::MockMediaPlayerMediaSource::seekWithTolerance): Ditto.
(WebCore::MockMediaPlayerMediaSource::waitForSeekCompleted): Added.
(WebCore::MockMediaPlayerMediaSource::seekCompleted): Added; trigger 'seeked'.
* platform/mock/mediasource/MockMediaPlayerMediaSource.h:
* platform/mock/mediasource/MockMediaSourcePrivate.cpp:
(WebCore::MockMediaSourcePrivate::waitForSeekCompleted): Pass through to MockMediaPlayerMediaSource.
(WebCore::MockMediaSourcePrivate::seekCompleted): Ditto.
* platform/mock/mediasource/MockMediaSourcePrivate.h:

Drive-by fixes.

* Modules/mediasource/MediaSource.cpp:
(WebCore::MediaSource::streamEndedWithError): Re-order the steps in streamEndedWithError()
    to avoid the MediaSource being closed and re-opened by the resulting duration change
    operation.
* Modules/mediasource/MediaSource.h:
* Modules/mediasource/SourceBuffer.cpp:
(WebCore::SourceBuffer::remove): Added logging.
(WebCore::SourceBuffer::removeCodedFrames): Ditto.
(WebCore::SourceBuffer::hasFutureTime): Swap an ASSERT for an early-return; it's possible
    for currentTime() to be outside of a buffered area.
* Modules/mediasource/SourceBuffer.h:
* html/HTMLMediaElement.cpp:
(WebCore::HTMLMediaElement::parseAttribute): Do not issue an additional 'timeupdate' event
    after finishSeek() issues one of its own.
* platform/graphics/avfoundation/objc/SourceBufferPrivateAVFObjC.mm:
(WebCore::globalDataParserQueue): Allow parsing operations to happen concurrently on
    background queues.

LayoutTests:
Eliminate flakiness in the mediasource-duration.html test by not playing
the media while testing seeking and duration.
* http/tests/media/media-source/mediasource-duration.html:

Update testharness.js to the latest W3C version:
* http/tests/w3c/resources/testharness.js:</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkLayoutTestsChangeLog">trunk/LayoutTests/ChangeLog</a></li>
<li><a href="#trunkLayoutTestshttptestsmediamediasourcemediasourcedurationhtml">trunk/LayoutTests/http/tests/media/media-source/mediasource-duration.html</a></li>
<li><a href="#trunkLayoutTestshttptestsw3cresourcestestharnessjs">trunk/LayoutTests/http/tests/w3c/resources/testharness.js</a></li>
<li><a href="#trunkSourceWebCoreChangeLog">trunk/Source/WebCore/ChangeLog</a></li>
<li><a href="#trunkSourceWebCoreModulesmediasourceMediaSourcecpp">trunk/Source/WebCore/Modules/mediasource/MediaSource.cpp</a></li>
<li><a href="#trunkSourceWebCoreModulesmediasourceMediaSourceh">trunk/Source/WebCore/Modules/mediasource/MediaSource.h</a></li>
<li><a href="#trunkSourceWebCoreModulesmediasourceSourceBuffercpp">trunk/Source/WebCore/Modules/mediasource/SourceBuffer.cpp</a></li>
<li><a href="#trunkSourceWebCoreModulesmediasourceSourceBufferh">trunk/Source/WebCore/Modules/mediasource/SourceBuffer.h</a></li>
<li><a href="#trunkSourceWebCorehtmlHTMLMediaElementcpp">trunk/Source/WebCore/html/HTMLMediaElement.cpp</a></li>
<li><a href="#trunkSourceWebCoreplatformgraphicsMediaSourcePrivateh">trunk/Source/WebCore/platform/graphics/MediaSourcePrivate.h</a></li>
<li><a href="#trunkSourceWebCoreplatformgraphicsMediaSourcePrivateClienth">trunk/Source/WebCore/platform/graphics/MediaSourcePrivateClient.h</a></li>
<li><a href="#trunkSourceWebCoreplatformgraphicsavfoundationobjcMediaPlayerPrivateMediaSourceAVFObjCh">trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaSourceAVFObjC.h</a></li>
<li><a href="#trunkSourceWebCoreplatformgraphicsavfoundationobjcMediaPlayerPrivateMediaSourceAVFObjCmm">trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaSourceAVFObjC.mm</a></li>
<li><a href="#trunkSourceWebCoreplatformgraphicsavfoundationobjcMediaSourcePrivateAVFObjCh">trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaSourcePrivateAVFObjC.h</a></li>
<li><a href="#trunkSourceWebCoreplatformgraphicsavfoundationobjcMediaSourcePrivateAVFObjCmm">trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaSourcePrivateAVFObjC.mm</a></li>
<li><a href="#trunkSourceWebCoreplatformgraphicsavfoundationobjcSourceBufferPrivateAVFObjCmm">trunk/Source/WebCore/platform/graphics/avfoundation/objc/SourceBufferPrivateAVFObjC.mm</a></li>
<li><a href="#trunkSourceWebCoreplatformgraphicsgstreamerMediaSourceGStreamercpp">trunk/Source/WebCore/platform/graphics/gstreamer/MediaSourceGStreamer.cpp</a></li>
<li><a href="#trunkSourceWebCoreplatformgraphicsgstreamerMediaSourceGStreamerh">trunk/Source/WebCore/platform/graphics/gstreamer/MediaSourceGStreamer.h</a></li>
<li><a href="#trunkSourceWebCoreplatformmockmediasourceMockMediaPlayerMediaSourcecpp">trunk/Source/WebCore/platform/mock/mediasource/MockMediaPlayerMediaSource.cpp</a></li>
<li><a href="#trunkSourceWebCoreplatformmockmediasourceMockMediaPlayerMediaSourceh">trunk/Source/WebCore/platform/mock/mediasource/MockMediaPlayerMediaSource.h</a></li>
<li><a href="#trunkSourceWebCoreplatformmockmediasourceMockMediaSourcePrivatecpp">trunk/Source/WebCore/platform/mock/mediasource/MockMediaSourcePrivate.cpp</a></li>
<li><a href="#trunkSourceWebCoreplatformmockmediasourceMockMediaSourcePrivateh">trunk/Source/WebCore/platform/mock/mediasource/MockMediaSourcePrivate.h</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkLayoutTestsChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/ChangeLog (171032 => 171033)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/ChangeLog        2014-07-12 22:14:44 UTC (rev 171032)
+++ trunk/LayoutTests/ChangeLog        2014-07-12 22:39:43 UTC (rev 171033)
</span><span class="lines">@@ -1,3 +1,17 @@
</span><ins>+2014-07-11  Jer Noble  &lt;jer.noble@apple.com&gt;
+
+        [MSE] http/tests/media/media-source/mediasource-duration.html is failing.
+        https://bugs.webkit.org/show_bug.cgi?id=134852
+
+        Reviewed by Eric Carlson.
+
+        Eliminate flakiness in the mediasource-duration.html test by not playing
+        the media while testing seeking and duration.
+        * http/tests/media/media-source/mediasource-duration.html:
+
+        Update testharness.js to the latest W3C version:
+        * http/tests/w3c/resources/testharness.js:
+
</ins><span class="cx"> 2014-07-11  Zalan Bujtas  &lt;zalan@apple.com&gt;
</span><span class="cx"> 
</span><span class="cx">         REGRESSION (r168868): eBay 'see all' links fail due to different JS bindings conversion behavior.
</span></span></pre></div>
<a id="trunkLayoutTestshttptestsmediamediasourcemediasourcedurationhtml"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/http/tests/media/media-source/mediasource-duration.html (171032 => 171033)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/http/tests/media/media-source/mediasource-duration.html        2014-07-12 22:14:44 UTC (rev 171032)
+++ trunk/LayoutTests/http/tests/media/media-source/mediasource-duration.html        2014-07-12 22:39:43 UTC (rev 171033)
</span><span class="lines">@@ -18,11 +18,8 @@
</span><span class="cx">                   var seekTo = fullDuration / 2.0;
</span><span class="cx">                   var truncatedDuration = seekTo / 2.0;
</span><span class="cx"> 
</span><del>-                  mediaElement.play();
-
</del><span class="cx">                   // Append all the segments
</span><span class="cx">                   test.expectEvent(sourceBuffer, 'updateend', 'sourceBuffer');
</span><del>-                  test.expectEvent(mediaElement, 'playing', 'Playing triggered');
</del><span class="cx">                   sourceBuffer.appendBuffer(mediaData);
</span><span class="cx"> 
</span><span class="cx">                   test.waitForExpectedEvents(function()
</span><span class="lines">@@ -126,11 +123,11 @@
</span><span class="cx"> 
</span><span class="cx">               test.waitForExpectedEvents(function()
</span><span class="cx">               {
</span><del>-                  assert_equals(mediaElement.currentTime, truncatedDuration,
</del><ins>+                  assert_approx_equals(mediaElement.currentTime, truncatedDuration, 0.05,
</ins><span class="cx">                                 'Playback time has reached truncatedDuration');
</span><del>-                  assert_equals(mediaElement.duration, truncatedDuration,
</del><ins>+                  assert_approx_equals(mediaElement.duration, truncatedDuration, 0.05,
</ins><span class="cx">                                 'mediaElement truncatedDuration after seek to it');
</span><del>-                  assert_equals(mediaSource.duration, truncatedDuration,
</del><ins>+                  assert_approx_equals(mediaSource.duration, truncatedDuration, 0.05,
</ins><span class="cx">                                 'mediaSource truncatedDuration after seek to it');
</span><span class="cx">                   assert_false(mediaElement.seeking, 'mediaElement.seeking after seeked to truncatedDuration');
</span><span class="cx"> 
</span><span class="lines">@@ -154,11 +151,8 @@
</span><span class="cx">                   durationchangeEventCounter++;
</span><span class="cx">               });
</span><span class="cx"> 
</span><del>-              mediaElement.play();
-
</del><span class="cx">               // Append all the segments
</span><span class="cx">               test.expectEvent(sourceBuffer, 'updateend', 'sourceBuffer');
</span><del>-              test.expectEvent(mediaElement, 'playing', 'Playing triggered');
</del><span class="cx">               sourceBuffer.appendBuffer(mediaData);
</span><span class="cx"> 
</span><span class="cx">               test.waitForExpectedEvents(function()
</span><span class="lines">@@ -201,7 +195,10 @@
</span><span class="cx">                       ++expectedDurationChangeEventCount;
</span><span class="cx">                   }
</span><span class="cx"> 
</span><ins>+                  mediaElement.play();
+
</ins><span class="cx">                   // Allow media to play to end while counting 'durationchange' events.
</span><ins>+                  test.expectEvent(mediaElement, 'playing', 'Playing triggered');
</ins><span class="cx">                   test.expectEvent(mediaElement, 'ended', 'Playback ended');
</span><span class="cx">                   test.waitForExpectedEvents(function()
</span><span class="cx">                   {
</span><span class="lines">@@ -211,6 +208,7 @@
</span><span class="cx">                   });
</span><span class="cx">               });
</span><span class="cx">           }, 'Test setting same duration multiple times does not fire duplicate durationchange', {timeout: 2500});
</span><ins>+
</ins><span class="cx">         &lt;/script&gt;
</span><span class="cx">     &lt;/body&gt;
</span><span class="cx"> &lt;/html&gt;
</span></span></pre></div>
<a id="trunkLayoutTestshttptestsw3cresourcestestharnessjs"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/http/tests/w3c/resources/testharness.js (171032 => 171033)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/http/tests/w3c/resources/testharness.js        2014-07-12 22:14:44 UTC (rev 171032)
+++ trunk/LayoutTests/http/tests/w3c/resources/testharness.js        2014-07-12 22:39:43 UTC (rev 171033)
</span><span class="lines">@@ -1,2145 +1,2244 @@
</span><del>-/*
-Distributed under both the W3C Test Suite License [1] and the W3C
-3-clause BSD License [2]. To contribute to a W3C Test Suite, see the
-policies and contribution forms [3].
-
-[1] http://www.w3.org/Consortium/Legal/2008/04-testsuite-license
-[2] http://www.w3.org/Consortium/Legal/2008/03-bsd-license
-[3] http://www.w3.org/2004/10/27-testcases
-*/
-
-/*
- * == Introduction ==
- *
- * This file provides a framework for writing testcases. It is intended to
- * provide a convenient API for making common assertions, and to work both
- * for testing synchronous and asynchronous DOM features in a way that
- * promotes clear, robust, tests.
- *
- * == Basic Usage ==
- *
- * To use this file, import the script and the testharnessreport script into
- * the test document:
- * &lt;script src=&quot;/resources/testharness.js&quot;&gt;&lt;/script&gt;
- * &lt;script src=&quot;/resources/testharnessreport.js&quot;&gt;&lt;/script&gt;
- *
- * Within each file one may define one or more tests. Each test is atomic
- * in the sense that a single test has a single result (pass/fail/timeout).
- * Within each test one may have a number of asserts. The test fails at the
- * first failing assert, and the remainder of the test is (typically) not run.
- *
- * If the file containing the tests is a HTML file with an element of id &quot;log&quot;
- * this will be populated with a table containing the test results after all
- * the tests have run.
- *
- * NOTE: By default tests must be created before the load event fires. For ways
- *       to create tests after the load event, see &quot;Determining when all tests
- *       are complete&quot;, below
- *
- * == Synchronous Tests ==
- *
- * To create a synchronous test use the test() function:
- *
- * test(test_function, name, properties)
- *
- * test_function is a function that contains the code to test. For example a
- * trivial passing test would be:
- *
- * test(function() {assert_true(true)}, &quot;assert_true with true&quot;)
- *
- * The function passed in is run in the test() call.
- *
- * properties is an object that overrides default test properties. The
- * recognised properties are:
- *    timeout - the test timeout in ms
- *
- * e.g.
- * test(test_function, &quot;Sample test&quot;, {timeout:1000})
- *
- * would run test_function with a timeout of 1s.
- *
- * Additionally, test-specific metadata can be passed in the properties. These
- * are used when the individual test has different metadata from that stored 
- * in the &lt;head&gt;.
- * The recognized metadata properties are:
- *
- *    help - The url of the part of the specification being tested
- *
- *    assert - A human readable description of what the test is attempting 
- *             to prove
- *
- *    author - Name and contact information for the author of the test in the
- *             format: &quot;Name &lt;email_addr&gt;&quot; or &quot;Name http://contact/url&quot;
- *
- *    flags - space separated list of test flags in addition to any present in
- *            the head metadata
- *
- * == Asynchronous Tests ==
- *
- * Testing asynchronous features is somewhat more complex since the result of
- * a test may depend on one or more events or other callbacks. The API provided
- * for testing these features is indended to be rather low-level but hopefully
- * applicable to many situations.
- *
- * To create a test, one starts by getting a Test object using async_test:
- *
- * async_test(name, properties)
- *
- * e.g.
- * var t = async_test(&quot;Simple async test&quot;)
- *
- * Assertions can be added to the test by calling the step method of the test
- * object with a function containing the test assertions:
- *
- * t.step(function() {assert_true(true)});
- *
- * When all the steps are complete, the done() method must be called:
- *
- * t.done();
- *
- * As a convenience, async_test can also takes a function as first argument.
- * This function is called with the test object as both its `this` object and
- * first argument. The above example can be rewritten as:
- *
- * async_test(function(t) {
- *     object.some_event = function() {
- *         t.step(function (){assert_true(true); t.done();});
- *     };
- * }, &quot;Simple async test&quot;);
- *
- * which avoids cluttering the global scope with references to async
- * tests instances.
- *
- * The properties argument is identical to that for test().
- *
- * In many cases it is convenient to run a step in response to an event or a
- * callback. A convenient method of doing this is through the step_func method
- * which returns a function that, when called runs a test step. For example
- *
- * object.some_event = t.step_func(function(e) {assert_true(e.a)});
- *
- * == Making assertions ==
- *
- * Functions for making assertions start assert_
- * The best way to get a list is to look in this file for functions names
- * matching that pattern. The general signature is
- *
- * assert_something(actual, expected, description)
- *
- * although not all assertions precisely match this pattern e.g. assert_true
- * only takes actual and description as arguments.
- *
- * The description parameter is used to present more useful error messages when
- * a test fails
- *
- * NOTE: All asserts must be located in a test() or a step of an async_test().
- *       asserts outside these places won't be detected correctly by the harness
- *       and may cause a file to stop testing.
- *
- * == Setup ==
- *
- * Sometimes tests require non-trivial setup that may fail. For this purpose
- * there is a setup() function, that may be called with one or two arguments.
- * The two argument version is:
- *
- * setup(func, properties)
- *
- * The one argument versions may omit either argument.
- * func is a function to be run synchronously. setup() becomes a no-op once
- * any tests have returned results. Properties are global properties of the test
- * harness. Currently recognised properties are:
- *
- * timeout - The time in ms after which the harness should stop waiting for
- *           tests to complete (this is different to the per-test timeout
- *           because async tests do not start their timer until .step is called)
- *
- * explicit_done - Wait for an explicit call to done() before declaring all
- *                 tests complete (see below)
- *
- * output_document - The document to which results should be logged. By default
- *                   this is the current document but could be an ancestor
- *                   document in some cases e.g. a SVG test loaded in an HTML
- *                   wrapper
- *
- * explicit_timeout - disable file timeout; only stop waiting for results
- *                    when the timeout() function is called (typically for
- *                    use when integrating with some existing test framework
- *                    that has its own timeout mechanism).
- *
- * == Determining when all tests are complete ==
- *
- * By default the test harness will assume there are no more results to come
- * when:
- * 1) There are no Test objects that have been created but not completed
- * 2) The load event on the document has fired
- *
- * This behaviour can be overridden by setting the explicit_done property to
- * true in a call to setup(). If explicit_done is true, the test harness will
- * not assume it is done until the global done() function is called. Once done()
- * is called, the two conditions above apply like normal.
- *
- * == Generating tests ==
- *
- * NOTE: this functionality may be removed
- *
- * There are scenarios in which is is desirable to create a large number of
- * (synchronous) tests that are internally similar but vary in the parameters
- * used. To make this easier, the generate_tests function allows a single
- * function to be called with each set of parameters in a list:
- *
- * generate_tests(test_function, parameter_lists, properties)
- *
- * For example:
- *
- * generate_tests(assert_equals, [
- *     [&quot;Sum one and one&quot;, 1+1, 2],
- *     [&quot;Sum one and zero&quot;, 1+0, 1]
- *     ])
- *
- * Is equivalent to:
- *
- * test(function() {assert_equals(1+1, 2)}, &quot;Sum one and one&quot;)
- * test(function() {assert_equals(1+0, 1)}, &quot;Sum one and zero&quot;)
- *
- * Note that the first item in each parameter list corresponds to the name of
- * the test.
- *
- * The properties argument is identical to that for test(). This may be a 
- * single object (used for all generated tests) or an array.
- *
- * == Callback API ==
- *
- * The framework provides callbacks corresponding to 3 events:
- *
- * start - happens when the first Test is created
- * result - happens when a test result is recieved
- * complete - happens when all results are recieved
- *
- * The page defining the tests may add callbacks for these events by calling
- * the following methods:
- *
- *   add_start_callback(callback) - callback called with no arguments
- *   add_result_callback(callback) - callback called with a test argument
- *   add_completion_callback(callback) - callback called with an array of tests
- *                                       and an status object
- *
- * tests have the following properties:
- *   status: A status code. This can be compared to the PASS, FAIL, TIMEOUT and
- *           NOTRUN properties on the test object
- *   message: A message indicating the reason for failure. In the future this
- *            will always be a string
- *
- *  The status object gives the overall status of the harness. It has the
- *  following properties:
- *    status: Can be compared to the OK, ERROR and TIMEOUT properties
- *    message: An error message set when the status is ERROR
- *
- * == External API ==
- *
- * In order to collect the results of multiple pages containing tests, the test
- * harness will, when loaded in a nested browsing context, attempt to call
- * certain functions in each ancestor and opener browsing context:
- *
- * start - start_callback
- * result - result_callback
- * complete - completion_callback
- *
- * These are given the same arguments as the corresponding internal callbacks
- * described above.
- *
- * == External API through cross-document messaging ==
- *
- * Where supported, the test harness will also send messages using
- * cross-document messaging to each ancestor and opener browsing context. Since
- * it uses the wildcard keyword (*), cross-origin communication is enabled and
- * script on different origins can collect the results.
- *
- * This API follows similar conventions as those described above only slightly
- * modified to accommodate message event API. Each message is sent by the harness
- * is passed a single vanilla object, available as the `data` property of the
- * event object. These objects are structures as follows:
- *
- * start - { type: &quot;start&quot; }
- * result - { type: &quot;result&quot;, test: Test }
- * complete - { type: &quot;complete&quot;, tests: [Test, ...], status: TestsStatus }
- *
- * == List of assertions ==
- *
- * assert_true(actual, description)
- *   asserts that /actual/ is strictly true
- *
- * assert_false(actual, description)
- *   asserts that /actual/ is strictly false
- *
- * assert_equals(actual, expected, description)
- *   asserts that /actual/ is the same value as /expected/
- *
- * assert_not_equals(actual, expected, description)
- *   asserts that /actual/ is a different value to /expected/. Yes, this means
- *   that &quot;expected&quot; is a misnomer
- *
- * assert_in_array(actual, expected, description)
- *   asserts that /expected/ is an Array, and /actual/ is equal to one of the
- *   members -- expected.indexOf(actual) != -1
- *
- * assert_array_equals(actual, expected, description)
- *   asserts that /actual/ and /expected/ have the same length and the value of
- *   each indexed property in /actual/ is the strictly equal to the corresponding
- *   property value in /expected/
- *
- * assert_approx_equals(actual, expected, epsilon, description)
- *   asserts that /actual/ is a number within +/- /epsilon/ of /expected/
- *
- * assert_regexp_match(actual, expected, description)
- *   asserts that /actual/ matches the regexp /expected/
- *
- * assert_class_string(object, class_name, description)
- *   asserts that the class string of /object/ as returned in
- *   Object.prototype.toString is equal to /class_name/.
- *
- * assert_own_property(object, property_name, description)
- *   assert that object has own property property_name
- *
- * assert_inherits(object, property_name, description)
- *   assert that object does not have an own property named property_name
- *   but that property_name is present in the prototype chain for object
- *
- * assert_idl_attribute(object, attribute_name, description)
- *   assert that an object that is an instance of some interface has the
- *   attribute attribute_name following the conditions specified by WebIDL
- *
- * assert_readonly(object, property_name, description)
- *   assert that property property_name on object is readonly
- *
- * assert_throws(code, func, description)
- *   code - the expected exception:
- *     o string: the thrown exception must be a DOMException with the given
- *               name, e.g., &quot;TimeoutError&quot; (for compatibility with existing
- *               tests, a constant is also supported, e.g., &quot;TIMEOUT_ERR&quot;)
- *     o object: the thrown exception must have a property called &quot;name&quot; that
- *               matches code.name
- *     o null:   allow any exception (in general, one of the options above
- *               should be used)
- *   func - a function that should throw
- *
- * assert_unreached(description)
- *   asserts if called. Used to ensure that some codepath is *not* taken e.g.
- *   an event does not fire.
- *
- * assert_any(assert_func, actual, expected_array, extra_arg_1, ... extra_arg_N)
- *   asserts that one assert_func(actual, expected_array_N, extra_arg1, ..., extra_arg_N)
- *   is true for some expected_array_N in expected_array. This only works for assert_func
- *   with signature assert_func(actual, expected, args_1, ..., args_N). Note that tests
- *   with multiple allowed pass conditions are bad practice unless the spec specifically
- *   allows multiple behaviours. Test authors should not use this method simply to hide 
- *   UA bugs.
- *
- * assert_exists(object, property_name, description)
- *   *** deprecated ***
- *   asserts that object has an own property property_name
- *
- * assert_not_exists(object, property_name, description)
- *   *** deprecated ***
- *   assert that object does not have own property property_name
- */
-
-(function ()
-{
-    var debug = false;
-    // default timeout is 5 seconds, test can override if needed
-    var settings = {
-      output:true,
-      timeout:5000,
-      test_timeout:2000
-    };
-
-    var xhtml_ns = &quot;http://www.w3.org/1999/xhtml&quot;;
-
-    // script_prefix is used by Output.prototype.show_results() to figure out
-    // where to get testharness.css from.  It's enclosed in an extra closure to
-    // not pollute the library's namespace with variables like &quot;src&quot;.
-    var script_prefix = null;
-    (function ()
-    {
-        var scripts = document.getElementsByTagName(&quot;script&quot;);
-        for (var i = 0; i &lt; scripts.length; i++)
-        {
-            if (scripts[i].src)
-            {
-                var src = scripts[i].src;
-            }
-            else if (scripts[i].href)
-            {
-                //SVG case
-                var src = scripts[i].href.baseVal;
-            }
-            if (src &amp;&amp; src.slice(src.length - &quot;testharness.js&quot;.length) === &quot;testharness.js&quot;)
-            {
-                script_prefix = src.slice(0, src.length - &quot;testharness.js&quot;.length);
-                break;
-            }
-        }
-    })();
-
-    /*
-     * API functions
-     */
-
-    var name_counter = 0;
-    function next_default_name()
-    {
-        //Don't use document.title to work around an Opera bug in XHTML documents
-        var prefix = document.getElementsByTagName(&quot;title&quot;).length &gt; 0 ?
-                         document.getElementsByTagName(&quot;title&quot;)[0].firstChild.data :
-                         &quot;Untitled&quot;;
-        var suffix = name_counter &gt; 0 ? &quot; &quot; + name_counter : &quot;&quot;;
-        name_counter++;
-        return prefix + suffix;
-    }
-
-    function test(func, name, properties)
-    {
-        var test_name = name ? name : next_default_name();
-        properties = properties ? properties : {};
-        var test_obj = new Test(test_name, properties);
-        test_obj.step(func);
-        if (test_obj.status === test_obj.NOTRUN) {
-            test_obj.done();
-        }
-    }
-
-    function async_test(func, name, properties)
-    {
-        if (typeof func !== &quot;function&quot;) {
-            properties = name;
-            name = func;
-            func = null;
-        }
-        var test_name = name ? name : next_default_name();
-        properties = properties ? properties : {};
-        var test_obj = new Test(test_name, properties);
-        if (func) {
-            test_obj.step(func, test_obj, test_obj);
-        }
-        return test_obj;
-    }
-
-    function setup(func_or_properties, maybe_properties)
-    {
-        var func = null;
-        var properties = {};
-        if (arguments.length === 2) {
-            func = func_or_properties;
-            properties = maybe_properties;
-        } else if (func_or_properties instanceof Function){
-            func = func_or_properties;
-        } else {
-            properties = func_or_properties;
-        }
-        tests.setup(func, properties);
-        output.setup(properties);
-    }
-
-    function done() {
-        tests.end_wait();
-    }
-
-    function generate_tests(func, args, properties) {
-        forEach(args, function(x, i)
-                {
-                    var name = x[0];
-                    test(function()
-                         {
-                             func.apply(this, x.slice(1));
-                         }, 
-                         name, 
-                         Array.isArray(properties) ? properties[i] : properties);
-                });
-    }
-
-    function on_event(object, event, callback)
-    {
-      object.addEventListener(event, callback, false);
-    }
-
-    expose(test, 'test');
-    expose(async_test, 'async_test');
-    expose(generate_tests, 'generate_tests');
-    expose(setup, 'setup');
-    expose(done, 'done');
-    expose(on_event, 'on_event');
-
-    /*
-     * Return a string truncated to the given length, with ... added at the end
-     * if it was longer.
-     */
-    function truncate(s, len)
-    {
-        if (s.length &gt; len) {
-            return s.substring(0, len - 3) + &quot;...&quot;;
-        }
-        return s;
-    }
-
-    /*
-     * Convert a value to a nice, human-readable string
-     */
-    function format_value(val)
-    {
-        if (Array.isArray(val))
-        {
-            return &quot;[&quot; + val.map(format_value).join(&quot;, &quot;) + &quot;]&quot;;
-        }
-
-        switch (typeof val)
-        {
-        case &quot;string&quot;:
-            val = val.replace(&quot;\\&quot;, &quot;\\\\&quot;);
-            for (var i = 0; i &lt; 32; i++)
-            {
-                var replace = &quot;\\&quot;;
-                switch (i) {
-                case 0: replace += &quot;0&quot;; break;
-                case 1: replace += &quot;x01&quot;; break;
-                case 2: replace += &quot;x02&quot;; break;
-                case 3: replace += &quot;x03&quot;; break;
-                case 4: replace += &quot;x04&quot;; break;
-                case 5: replace += &quot;x05&quot;; break;
-                case 6: replace += &quot;x06&quot;; break;
-                case 7: replace += &quot;x07&quot;; break;
-                case 8: replace += &quot;b&quot;; break;
-                case 9: replace += &quot;t&quot;; break;
-                case 10: replace += &quot;n&quot;; break;
-                case 11: replace += &quot;v&quot;; break;
-                case 12: replace += &quot;f&quot;; break;
-                case 13: replace += &quot;r&quot;; break;
-                case 14: replace += &quot;x0e&quot;; break;
-                case 15: replace += &quot;x0f&quot;; break;
-                case 16: replace += &quot;x10&quot;; break;
-                case 17: replace += &quot;x11&quot;; break;
-                case 18: replace += &quot;x12&quot;; break;
-                case 19: replace += &quot;x13&quot;; break;
-                case 20: replace += &quot;x14&quot;; break;
-                case 21: replace += &quot;x15&quot;; break;
-                case 22: replace += &quot;x16&quot;; break;
-                case 23: replace += &quot;x17&quot;; break;
-                case 24: replace += &quot;x18&quot;; break;
-                case 25: replace += &quot;x19&quot;; break;
-                case 26: replace += &quot;x1a&quot;; break;
-                case 27: replace += &quot;x1b&quot;; break;
-                case 28: replace += &quot;x1c&quot;; break;
-                case 29: replace += &quot;x1d&quot;; break;
-                case 30: replace += &quot;x1e&quot;; break;
-                case 31: replace += &quot;x1f&quot;; break;
-                }
-                val = val.replace(RegExp(String.fromCharCode(i), &quot;g&quot;), replace);
-            }
-            return '&quot;' + val.replace(/&quot;/g, '\\&quot;') + '&quot;';
-        case &quot;boolean&quot;:
-        case &quot;undefined&quot;:
-            return String(val);
-        case &quot;number&quot;:
-            // In JavaScript, -0 === 0 and String(-0) == &quot;0&quot;, so we have to
-            // special-case.
-            if (val === -0 &amp;&amp; 1/val === -Infinity)
-            {
-                return &quot;-0&quot;;
-            }
-            return String(val);
-        case &quot;object&quot;:
-            if (val === null)
-            {
-                return &quot;null&quot;;
-            }
-
-            // Special-case Node objects, since those come up a lot in my tests.  I
-            // ignore namespaces.  I use duck-typing instead of instanceof, because
-            // instanceof doesn't work if the node is from another window (like an
-            // iframe's contentWindow):
-            // http://www.w3.org/Bugs/Public/show_bug.cgi?id=12295
-            if (&quot;nodeType&quot; in val
-            &amp;&amp; &quot;nodeName&quot; in val
-            &amp;&amp; &quot;nodeValue&quot; in val
-            &amp;&amp; &quot;childNodes&quot; in val)
-            {
-                switch (val.nodeType)
-                {
-                case Node.ELEMENT_NODE:
-                    var ret = &quot;&lt;&quot; + val.tagName.toLowerCase();
-                    for (var i = 0; i &lt; val.attributes.length; i++)
-                    {
-                        ret += &quot; &quot; + val.attributes[i].name + '=&quot;' + val.attributes[i].value + '&quot;';
-                    }
-                    ret += &quot;&gt;&quot; + val.innerHTML + &quot;&lt;/&quot; + val.tagName.toLowerCase() + &quot;&gt;&quot;;
-                    return &quot;Element node &quot; + truncate(ret, 60);
-                case Node.TEXT_NODE:
-                    return 'Text node &quot;' + truncate(val.data, 60) + '&quot;';
-                case Node.PROCESSING_INSTRUCTION_NODE:
-                    return &quot;ProcessingInstruction node with target &quot; + format_value(truncate(val.target, 60)) + &quot; and data &quot; + format_value(truncate(val.data, 60));
-                case Node.COMMENT_NODE:
-                    return &quot;Comment node &lt;!--&quot; + truncate(val.data, 60) + &quot;--&gt;&quot;;
-                case Node.DOCUMENT_NODE:
-                    return &quot;Document node with &quot; + val.childNodes.length + (val.childNodes.length == 1 ? &quot; child&quot; : &quot; children&quot;);
-                case Node.DOCUMENT_TYPE_NODE:
-                    return &quot;DocumentType node&quot;;
-                case Node.DOCUMENT_FRAGMENT_NODE:
-                    return &quot;DocumentFragment node with &quot; + val.childNodes.length + (val.childNodes.length == 1 ? &quot; child&quot; : &quot; children&quot;);
-                default:
-                    return &quot;Node object of unknown type&quot;;
-                }
-            }
-
-            // Fall through to default
-        default:
-            return typeof val + ' &quot;' + truncate(String(val), 60) + '&quot;';
-        }
-    }
-    expose(format_value, &quot;format_value&quot;);
-
-    /*
-     * Assertions
-     */
-
-    function assert_true(actual, description)
-    {
-        assert(actual === true, &quot;assert_true&quot;, description,
-                                &quot;expected true got ${actual}&quot;, {actual:actual});
-    };
-    expose(assert_true, &quot;assert_true&quot;);
-
-    function assert_false(actual, description)
-    {
-        assert(actual === false, &quot;assert_false&quot;, description,
-                                 &quot;expected false got ${actual}&quot;, {actual:actual});
-    };
-    expose(assert_false, &quot;assert_false&quot;);
-
-    function same_value(x, y) {
-        if (y !== y)
-        {
-            //NaN case
-            return x !== x;
-        }
-        else if (x === 0 &amp;&amp; y === 0) {
-            //Distinguish +0 and -0
-            return 1/x === 1/y;
-        }
-        else
-        {
-            //typical case
-            return x === y;
-        }
-    }
-
-    function assert_equals(actual, expected, description)
-    {
-         /*
-          * Test if two primitives are equal or two objects
-          * are the same object
-          */
-        if (typeof actual != typeof expected)
-        {
-            assert(false, &quot;assert_equals&quot;, description,
-                          &quot;expected (&quot; + typeof expected + &quot;) ${expected} but got (&quot; + typeof actual + &quot;) ${actual}&quot;,
-                          {expected:expected, actual:actual});
-            return;
-        }
-        assert(same_value(actual, expected), &quot;assert_equals&quot;, description,
-                                             &quot;expected ${expected} but got ${actual}&quot;,
-                                             {expected:expected, actual:actual});
-    };
-    expose(assert_equals, &quot;assert_equals&quot;);
-
-    function assert_not_equals(actual, expected, description)
-    {
-         /*
-          * Test if two primitives are unequal or two objects
-          * are different objects
-          */
-        assert(!same_value(actual, expected), &quot;assert_not_equals&quot;, description,
-                                              &quot;got disallowed value ${actual}&quot;,
-                                              {actual:actual});
-    };
-    expose(assert_not_equals, &quot;assert_not_equals&quot;);
-
-    function assert_in_array(actual, expected, description)
-    {
-        assert(expected.indexOf(actual) != -1, &quot;assert_in_array&quot;, description,
-                                               &quot;value ${actual} not in array ${expected}&quot;,
-                                               {actual:actual, expected:expected});
-    }
-    expose(assert_in_array, &quot;assert_in_array&quot;);
-
-    function assert_object_equals(actual, expected, description)
-    {
-         //This needs to be improved a great deal
-         function check_equal(actual, expected, stack)
-         {
-             stack.push(actual);
-
-             var p;
-             for (p in actual)
-             {
-                 assert(expected.hasOwnProperty(p), &quot;assert_object_equals&quot;, description,
-                                                    &quot;unexpected property ${p}&quot;, {p:p});
-
-                 if (typeof actual[p] === &quot;object&quot; &amp;&amp; actual[p] !== null)
-                 {
-                     if (stack.indexOf(actual[p]) === -1)
-                     {
-                         check_equal(actual[p], expected[p], stack);
-                     }
-                 }
-                 else
-                 {
-                     assert(same_value(actual[p], expected[p]), &quot;assert_object_equals&quot;, description,
-                                                       &quot;property ${p} expected ${expected} got ${actual}&quot;,
-                                                       {p:p, expected:expected, actual:actual});
-                 }
-             }
-             for (p in expected)
-             {
-                 assert(actual.hasOwnProperty(p),
-                        &quot;assert_object_equals&quot;, description,
-                        &quot;expected property ${p} missing&quot;, {p:p});
-             }
-             stack.pop();
-         }
-         check_equal(actual, expected, []);
-    };
-    expose(assert_object_equals, &quot;assert_object_equals&quot;);
-
-    function assert_array_equals(actual, expected, description)
-    {
-        assert(actual.length === expected.length,
-               &quot;assert_array_equals&quot;, description,
-               &quot;lengths differ, expected ${expected} got ${actual}&quot;,
-               {expected:expected.length, actual:actual.length});
-
-        for (var i=0; i &lt; actual.length; i++)
-        {
-            assert(actual.hasOwnProperty(i) === expected.hasOwnProperty(i),
-                   &quot;assert_array_equals&quot;, description,
-                   &quot;property ${i}, property expected to be $expected but was $actual&quot;,
-                   {i:i, expected:expected.hasOwnProperty(i) ? &quot;present&quot; : &quot;missing&quot;,
-                   actual:actual.hasOwnProperty(i) ? &quot;present&quot; : &quot;missing&quot;});
-            assert(same_value(expected[i], actual[i]),
-                   &quot;assert_array_equals&quot;, description,
-                   &quot;property ${i}, expected ${expected} but got ${actual}&quot;,
-                   {i:i, expected:expected[i], actual:actual[i]});
-        }
-    }
-    expose(assert_array_equals, &quot;assert_array_equals&quot;);
-
-    function assert_approx_equals(actual, expected, epsilon, description)
-    {
-        /*
-         * Test if two primitive numbers are equal withing +/- epsilon
-         */
-        assert(typeof actual === &quot;number&quot;,
-               &quot;assert_approx_equals&quot;, description,
-               &quot;expected a number but got a ${type_actual}&quot;,
-               {type_actual:typeof actual});
-
-        assert(Math.abs(actual - expected) &lt;= epsilon,
-               &quot;assert_approx_equals&quot;, description,
-               &quot;expected ${expected} +/- ${epsilon} but got ${actual}&quot;,
-               {expected:expected, actual:actual, epsilon:epsilon});
-    };
-    expose(assert_approx_equals, &quot;assert_approx_equals&quot;);
-
-    function assert_regexp_match(actual, expected, description) {
-        /*
-         * Test if a string (actual) matches a regexp (expected)
-         */
-        assert(expected.test(actual),
-               &quot;assert_regexp_match&quot;, description,
-               &quot;expected ${expected} but got ${actual}&quot;,
-               {expected:expected, actual:actual});
-    }
-    expose(assert_regexp_match, &quot;assert_regexp_match&quot;);
-
-    function assert_class_string(object, class_string, description) {
-        assert_equals({}.toString.call(object), &quot;[object &quot; + class_string + &quot;]&quot;,
-                      description);
-    }
-    expose(assert_class_string, &quot;assert_class_string&quot;);
-
-
-    function _assert_own_property(name) {
-        return function(object, property_name, description)
-        {
-            assert(object.hasOwnProperty(property_name),
-                   name, description,
-                   &quot;expected property ${p} missing&quot;, {p:property_name});
-        };
-    }
-    expose(_assert_own_property(&quot;assert_exists&quot;), &quot;assert_exists&quot;);
-    expose(_assert_own_property(&quot;assert_own_property&quot;), &quot;assert_own_property&quot;);
-
-    function assert_not_exists(object, property_name, description)
-    {
-        assert(!object.hasOwnProperty(property_name),
-               &quot;assert_not_exists&quot;, description,
-               &quot;unexpected property ${p} found&quot;, {p:property_name});
-    };
-    expose(assert_not_exists, &quot;assert_not_exists&quot;);
-
-    function _assert_inherits(name) {
-        return function (object, property_name, description)
-        {
-            assert(typeof object === &quot;object&quot;,
-                   name, description,
-                   &quot;provided value is not an object&quot;);
-
-            assert(&quot;hasOwnProperty&quot; in object,
-                   name, description,
-                   &quot;provided value is an object but has no hasOwnProperty method&quot;);
-
-            assert(!object.hasOwnProperty(property_name),
-                   name, description,
-                   &quot;property ${p} found on object expected in prototype chain&quot;,
-                   {p:property_name});
-
-            assert(property_name in object,
-                   name, description,
-                   &quot;property ${p} not found in prototype chain&quot;,
-                   {p:property_name});
-        };
-    }
-    expose(_assert_inherits(&quot;assert_inherits&quot;), &quot;assert_inherits&quot;);
-    expose(_assert_inherits(&quot;assert_idl_attribute&quot;), &quot;assert_idl_attribute&quot;);
-
-    function assert_readonly(object, property_name, description)
-    {
-         var initial_value = object[property_name];
-         try {
-             //Note that this can have side effects in the case where
-             //the property has PutForwards
-             object[property_name] = initial_value + &quot;a&quot;; //XXX use some other value here?
-             assert(same_value(object[property_name], initial_value),
-                    &quot;assert_readonly&quot;, description,
-                    &quot;changing property ${p} succeeded&quot;,
-                    {p:property_name});
-         }
-         finally
-         {
-             object[property_name] = initial_value;
-         }
-    };
-    expose(assert_readonly, &quot;assert_readonly&quot;);
-
-    function assert_throws(code, func, description)
-    {
-        try
-        {
-            func.call(this);
-            assert(false, &quot;assert_throws&quot;, description,
-                   &quot;${func} did not throw&quot;, {func:func});
-        }
-        catch(e)
-        {
-            if (e instanceof AssertionError) {
-                throw(e);
-            }
-            if (code === null)
-            {
-                return;
-            }
-            if (typeof code === &quot;object&quot;)
-            {
-                assert(typeof e == &quot;object&quot; &amp;&amp; &quot;name&quot; in e &amp;&amp; e.name == code.name,
-                       &quot;assert_throws&quot;, description,
-                       &quot;${func} threw ${actual} (${actual_name}) expected ${expected} (${expected_name})&quot;,
-                                    {func:func, actual:e, actual_name:e.name,
-                                     expected:code,
-                                     expected_name:code.name});
-                return;
-            }
-
-            var code_name_map = {
-                INDEX_SIZE_ERR: 'IndexSizeError',
-                HIERARCHY_REQUEST_ERR: 'HierarchyRequestError',
-                WRONG_DOCUMENT_ERR: 'WrongDocumentError',
-                INVALID_CHARACTER_ERR: 'InvalidCharacterError',
-                NO_MODIFICATION_ALLOWED_ERR: 'NoModificationAllowedError',
-                NOT_FOUND_ERR: 'NotFoundError',
-                NOT_SUPPORTED_ERR: 'NotSupportedError',
-                INVALID_STATE_ERR: 'InvalidStateError',
-                SYNTAX_ERR: 'SyntaxError',
-                INVALID_MODIFICATION_ERR: 'InvalidModificationError',
-                NAMESPACE_ERR: 'NamespaceError',
-                INVALID_ACCESS_ERR: 'InvalidAccessError',
-                TYPE_MISMATCH_ERR: 'TypeMismatchError',
-                SECURITY_ERR: 'SecurityError',
-                NETWORK_ERR: 'NetworkError',
-                ABORT_ERR: 'AbortError',
-                URL_MISMATCH_ERR: 'URLMismatchError',
-                QUOTA_EXCEEDED_ERR: 'QuotaExceededError',
-                TIMEOUT_ERR: 'TimeoutError',
-                INVALID_NODE_TYPE_ERR: 'InvalidNodeTypeError',
-                DATA_CLONE_ERR: 'DataCloneError'
-            };
-
-            var name = code in code_name_map ? code_name_map[code] : code;
-
-            var name_code_map = {
-                IndexSizeError: 1,
-                HierarchyRequestError: 3,
-                WrongDocumentError: 4,
-                InvalidCharacterError: 5,
-                NoModificationAllowedError: 7,
-                NotFoundError: 8,
-                NotSupportedError: 9,
-                InvalidStateError: 11,
-                SyntaxError: 12,
-                InvalidModificationError: 13,
-                NamespaceError: 14,
-                InvalidAccessError: 15,
-                TypeMismatchError: 17,
-                SecurityError: 18,
-                NetworkError: 19,
-                AbortError: 20,
-                URLMismatchError: 21,
-                QuotaExceededError: 22,
-                TimeoutError: 23,
-                InvalidNodeTypeError: 24,
-                DataCloneError: 25,
-
-                UnknownError: 0,
-                ConstraintError: 0,
-                DataError: 0,
-                TransactionInactiveError: 0,
-                ReadOnlyError: 0,
-                VersionError: 0
-            };
-
-            if (!(name in name_code_map))
-            {
-                throw new AssertionError('Test bug: unrecognized DOMException code &quot;' + code + '&quot; passed to assert_throws()');
-            }
-
-            var required_props = { code: name_code_map[name] };
-
-            if (required_props.code === 0
-            || (&quot;name&quot; in e &amp;&amp; e.name !== e.name.toUpperCase() &amp;&amp; e.name !== &quot;DOMException&quot;))
-            {
-                // New style exception: also test the name property.
-                required_props.name = name;
-            }
-
-            //We'd like to test that e instanceof the appropriate interface,
-            //but we can't, because we don't know what window it was created
-            //in.  It might be an instanceof the appropriate interface on some
-            //unknown other window.  TODO: Work around this somehow?
-
-            assert(typeof e == &quot;object&quot;,
-                   &quot;assert_throws&quot;, description,
-                   &quot;${func} threw ${e} with type ${type}, not an object&quot;,
-                   {func:func, e:e, type:typeof e});
-
-            for (var prop in required_props)
-            {
-                assert(typeof e == &quot;object&quot; &amp;&amp; prop in e &amp;&amp; e[prop] == required_props[prop],
-                       &quot;assert_throws&quot;, description,
-                       &quot;${func} threw ${e} that is not a DOMException &quot; + code + &quot;: property ${prop} is equal to ${actual}, expected ${expected}&quot;,
-                       {func:func, e:e, prop:prop, actual:e[prop], expected:required_props[prop]});
-            }
-        }
-    }
-    expose(assert_throws, &quot;assert_throws&quot;);
-
-    function assert_unreached(description) {
-         assert(false, &quot;assert_unreached&quot;, description,
-                &quot;Reached unreachable code&quot;);
-    }
-    expose(assert_unreached, &quot;assert_unreached&quot;);
-
-    function assert_any(assert_func, actual, expected_array) 
-    {
-        var args = [].slice.call(arguments, 3)
-        var errors = []
-        var passed = false;
-        forEach(expected_array, 
-                function(expected)
-                {
-                    try {
-                        assert_func.apply(this, [actual, expected].concat(args))
-                        passed = true;
-                    } catch(e) {
-                        errors.push(e.message);
-                    }
-                });
-        if (!passed) {
-            throw new AssertionError(errors.join(&quot;\n\n&quot;));
-        }
-    }
-    expose(assert_any, &quot;assert_any&quot;);
-
-    function Test(name, properties)
-    {
-        this.name = name;
-        this.status = this.NOTRUN;
-        this.timeout_id = null;
-        this.is_done = false;
-
-        this.properties = properties;
-        this.timeout_length = properties.timeout ? properties.timeout : settings.test_timeout;
-
-        this.message = null;
-
-        var this_obj = this;
-        this.steps = [];
-
-        tests.push(this);
-    }
-
-    Test.statuses = {
-        PASS:0,
-        FAIL:1,
-        TIMEOUT:2,
-        NOTRUN:3
-    };
-
-    Test.prototype = merge({}, Test.statuses);
-
-    Test.prototype.structured_clone = function()
-    {
-        if(!this._structured_clone)
-        {
-            var msg = this.message;
-            msg = msg ? String(msg) : msg;
-            this._structured_clone = merge({
-                name:String(this.name),
-                status:this.status,
-                message:msg
-            }, Test.statuses);
-        }
-        return this._structured_clone;
-    };
-
-    Test.prototype.step = function(func, this_obj)
-    {
-        //In case the test has already failed
-        if (this.status !== this.NOTRUN)
-        {
-          return;
-        }
-
-        tests.started = true;
-
-        if (this.timeout_id === null) {
-            this.set_timeout();
-        }
-
-        this.steps.push(func);
-
-        if (arguments.length === 1)
-        {
-            this_obj = this;
-        }
-
-        try
-        {
-            func.apply(this_obj, Array.prototype.slice.call(arguments, 2));
-        }
-        catch(e)
-        {
-            //This can happen if something called synchronously invoked another
-            //step
-            if (this.status !== this.NOTRUN)
-            {
-                return;
-            }
-            this.status = this.FAIL;
-            this.message = (typeof e === &quot;object&quot; &amp;&amp; e !== null) ? e.message : e;
-            if (typeof e.stack != &quot;undefined&quot; &amp;&amp; typeof e.message == &quot;string&quot;) {
-                //Try to make it more informative for some exceptions, at least
-                //in Gecko and WebKit.  This results in a stack dump instead of
-                //just errors like &quot;Cannot read property 'parentNode' of null&quot;
-                //or &quot;root is null&quot;.  Makes it a lot longer, of course.
-                this.message += &quot;(stack: &quot; + e.stack + &quot;)&quot;;
-            }
-            this.done();
-            if (debug &amp;&amp; e.constructor !== AssertionError) {
-                throw e;
-            }
-        }
-    };
-
-    Test.prototype.step_func = function(func, this_obj)
-    {
-        var test_this = this;
-
-        if (arguments.length === 1)
-        {
-            this_obj = test_this;
-        }
-
-        return function()
-        {
-            test_this.step.apply(test_this, [func, this_obj].concat(
-                Array.prototype.slice.call(arguments)));
-        };
-    };
-
-    Test.prototype.step_func_done = function(func, this_obj)
-    {
-        var test_this = this;
-
-        if (arguments.length === 1)
-        {
-            this_obj = test_this;
-        }
-
-        return function()
-        {
-            test_this.step.apply(test_this, [func, this_obj].concat(
-                Array.prototype.slice.call(arguments)));
-            test_this.done();
-        };
-    };
-
-    Test.prototype.set_timeout = function()
-    {
-        var this_obj = this;
-        this.timeout_id = setTimeout(function()
-                                     {
-                                         this_obj.timeout();
-                                     }, this.timeout_length);
-    };
-
-    Test.prototype.timeout = function()
-    {
-        this.status = this.TIMEOUT;
-        this.timeout_id = null;
-        this.message = &quot;Test timed out&quot;;
-        this.done();
-    };
-
-    Test.prototype.done = function()
-    {
-        if (this.is_done) {
-            return;
-        }
-        clearTimeout(this.timeout_id);
-        if (this.status === this.NOTRUN)
-        {
-            this.status = this.PASS;
-        }
-        this.is_done = true;
-        tests.result(this);
-    };
-
-
-    /*
-     * Harness
-     */
-
-    function TestsStatus()
-    {
-        this.status = null;
-        this.message = null;
-    }
-
-    TestsStatus.statuses = {
-        OK:0,
-        ERROR:1,
-        TIMEOUT:2
-    };
-
-    TestsStatus.prototype = merge({}, TestsStatus.statuses);
-
-    TestsStatus.prototype.structured_clone = function()
-    {
-        if(!this._structured_clone)
-        {
-            var msg = this.message;
-            msg = msg ? String(msg) : msg;
-            this._structured_clone = merge({
-                status:this.status,
-                message:msg
-            }, TestsStatus.statuses);
-        }
-        return this._structured_clone;
-    };
-
-    function Tests()
-    {
-        this.tests = [];
-        this.num_pending = 0;
-
-        this.phases = {
-            INITIAL:0,
-            SETUP:1,
-            HAVE_TESTS:2,
-            HAVE_RESULTS:3,
-            COMPLETE:4
-        };
-        this.phase = this.phases.INITIAL;
-
-        this.properties = {};
-
-        //All tests can't be done until the load event fires
-        this.all_loaded = false;
-        this.wait_for_finish = false;
-        this.processing_callbacks = false;
-
-        this.timeout_length = settings.timeout;
-        this.timeout_id = null;
-
-        this.start_callbacks = [];
-        this.test_done_callbacks = [];
-        this.all_done_callbacks = [];
-
-        this.status = new TestsStatus();
-
-        var this_obj = this;
-
-        on_event(window, &quot;load&quot;,
-                 function()
-                 {
-                     this_obj.all_loaded = true;
-                     if (this_obj.all_done())
-                     {
-                         this_obj.complete();
-                     }
-                 });
-
-        this.set_timeout();
-    }
-
-    Tests.prototype.setup = function(func, properties)
-    {
-        if (this.phase &gt;= this.phases.HAVE_RESULTS)
-        {
-            return;
-        }
-        if (this.phase &lt; this.phases.SETUP)
-        {
-            this.phase = this.phases.SETUP;
-        }
-
-        for (var p in properties)
-        {
-            if (properties.hasOwnProperty(p))
-            {
-                this.properties[p] = properties[p];
-            }
-        }
-
-        if (properties.timeout)
-        {
-            this.timeout_length = properties.timeout;
-        }
-        if (properties.explicit_done)
-        {
-            this.wait_for_finish = true;
-        }
-        if (properties.explicit_timeout) {
-            this.timeout_length = null;
-        }
-
-        if (func)
-        {
-            try
-            {
-                func();
-            } catch(e)
-            {
-                this.status.status = this.status.ERROR;
-                this.status.message = e;
-            };
-        }
-        this.set_timeout();
-    };
-
-    Tests.prototype.set_timeout = function()
-    {
-        var this_obj = this;
-        clearTimeout(this.timeout_id);
-        if (this.timeout_length !== null)
-        {
-            this.timeout_id = setTimeout(function() {
-                                             this_obj.timeout();
-                                         }, this.timeout_length);
-        }
-    };
-
-    Tests.prototype.timeout = function() {
-        this.status.status = this.status.TIMEOUT;
-        this.complete();
-    };
-
-    Tests.prototype.end_wait = function()
-    {
-        this.wait_for_finish = false;
-        if (this.all_done()) {
-            this.complete();
-        }
-    };
-
-    Tests.prototype.push = function(test)
-    {
-        if (this.phase &lt; this.phases.HAVE_TESTS) {
-            this.start();
-        }
-        this.num_pending++;
-        this.tests.push(test);
-    };
-
-    Tests.prototype.all_done = function() {
-        return (this.all_loaded &amp;&amp; this.num_pending === 0 &amp;&amp;
-                !this.wait_for_finish &amp;&amp; !this.processing_callbacks);
-    };
-
-    Tests.prototype.start = function() {
-        this.phase = this.phases.HAVE_TESTS;
-        this.notify_start();
-    };
-
-    Tests.prototype.notify_start = function() {
-        var this_obj = this;
-        forEach (this.start_callbacks,
-                 function(callback)
-                 {
-                     callback(this_obj.properties);
-                 });
-        forEach_windows(
-                function(w, is_same_origin)
-                {
-                    if(is_same_origin &amp;&amp; w.start_callback)
-                    {
-                        try
-                        {
-                            w.start_callback(this_obj.properties);
-                        }
-                        catch(e)
-                        {
-                            if (debug)
-                            {
-                                throw(e);
-                            }
-                        }
-                    }
-                    if (supports_post_message(w))
-                    {
-                        w.postMessage({
-                            type: &quot;start&quot;,
-                            properties: this_obj.properties
-                        }, &quot;*&quot;);
-                    }
-                });
-    };
-
-    Tests.prototype.result = function(test)
-    {
-        if (this.phase &gt; this.phases.HAVE_RESULTS)
-        {
-            return;
-        }
-        this.phase = this.phases.HAVE_RESULTS;
-        this.num_pending--;
-        this.notify_result(test);
-    };
-
-    Tests.prototype.notify_result = function(test) {
-        var this_obj = this;
-        this.processing_callbacks = true;
-        forEach(this.test_done_callbacks,
-                function(callback)
-                {
-                    callback(test, this_obj);
-                });
-
-        forEach_windows(
-                function(w, is_same_origin)
-                {
-                    if(is_same_origin &amp;&amp; w.result_callback)
-                    {
-                        try
-                        {
-                            w.result_callback(test);
-                        }
-                        catch(e)
-                        {
-                            if(debug) {
-                                throw e;
-                            }
-                        }
-                    }
-                    if (supports_post_message(w))
-                    {
-                        w.postMessage({
-                            type: &quot;result&quot;,
-                            test: test.structured_clone()
-                        }, &quot;*&quot;);
-                    }
-                });
-        this.processing_callbacks = false;
-        if (this_obj.all_done())
-        {
-            this_obj.complete();
-        }
-    };
-
-    Tests.prototype.complete = function() {
-        if (this.phase === this.phases.COMPLETE) {
-            return;
-        }
-        this.phase = this.phases.COMPLETE;
-        var this_obj = this;
-        this.tests.forEach(
-            function(x)
-            {
-                if(x.status === x.NOTRUN)
-                {
-                    this_obj.notify_result(x);
-                }
-            }
-        );
-        this.notify_complete();
-    };
-
-    Tests.prototype.notify_complete = function()
-    {
-        clearTimeout(this.timeout_id);
-        var this_obj = this;
-        var tests = map(this_obj.tests,
-                        function(test)
-                        {
-                            return test.structured_clone();
-                        });
-        if (this.status.status === null)
-        {
-            this.status.status = this.status.OK;
-        }
-
-        forEach (this.all_done_callbacks,
-                 function(callback)
-                 {
-                     callback(this_obj.tests, this_obj.status);
-                 });
-
-        forEach_windows(
-                function(w, is_same_origin)
-                {
-                    if(is_same_origin &amp;&amp; w.completion_callback)
-                    {
-                        try
-                        {
-                            w.completion_callback(this_obj.tests, this_obj.status);
-                        }
-                        catch(e)
-                        {
-                            if (debug)
-                            {
-                                throw e;
-                            }
-                        }
-                    }
-                    if (supports_post_message(w))
-                    {
-                        w.postMessage({
-                            type: &quot;complete&quot;,
-                            tests: tests,
-                            status: this_obj.status.structured_clone()
-                        }, &quot;*&quot;);
-                    }
-                });
-    };
-
-    var tests = new Tests();
-
-    function timeout() {
-        if (tests.timeout_length === null)
-        {
-            tests.timeout();
-        }
-    }
-    expose(timeout, 'timeout');
-
-    function add_start_callback(callback) {
-        tests.start_callbacks.push(callback);
-    }
-
-    function add_result_callback(callback)
-    {
-        tests.test_done_callbacks.push(callback);
-    }
-
-    function add_completion_callback(callback)
-    {
-       tests.all_done_callbacks.push(callback);
-    }
-
-    expose(add_start_callback, 'add_start_callback');
-    expose(add_result_callback, 'add_result_callback');
-    expose(add_completion_callback, 'add_completion_callback');
-
-    /*
-     * Output listener
-    */
-
-    function Output() {
-      this.output_document = document;
-      this.output_node = null;
-      this.done_count = 0;
-      this.enabled = settings.output;
-      this.phase = this.INITIAL;
-    }
-
-    Output.prototype.INITIAL = 0;
-    Output.prototype.STARTED = 1;
-    Output.prototype.HAVE_RESULTS = 2;
-    Output.prototype.COMPLETE = 3;
-
-    Output.prototype.setup = function(properties) {
-        if (this.phase &gt; this.INITIAL) {
-            return;
-        }
-
-        //If output is disabled in testharnessreport.js the test shouldn't be
-        //able to override that
-        this.enabled = this.enabled &amp;&amp; (properties.hasOwnProperty(&quot;output&quot;) ?
-                                        properties.output : settings.output);
-    };
-
-    Output.prototype.init = function(properties)
-    {
-        if (this.phase &gt;= this.STARTED) {
-            return;
-        }
-        if (properties.output_document) {
-            this.output_document = properties.output_document;
-        } else {
-            this.output_document = document;
-        }
-        this.phase = this.STARTED;
-    };
-
-    Output.prototype.resolve_log = function()
-    {
-        var output_document;
-        if (typeof this.output_document === &quot;function&quot;)
-        {
-            output_document = this.output_document.apply(undefined);
-        } else
-        {
-            output_document = this.output_document;
-        }
-        if (!output_document)
-        {
-            return;
-        }
-        var node = output_document.getElementById(&quot;log&quot;);
-        if (node)
-        {
-            this.output_document = output_document;
-            this.output_node = node;
-        }
-    };
-
-    Output.prototype.show_status = function(test)
-    {
-        if (this.phase &lt; this.STARTED)
-        {
-            this.init();
-        }
-        if (!this.enabled)
-        {
-            return;
-        }
-        if (this.phase &lt; this.HAVE_RESULTS)
-        {
-            this.resolve_log();
-            this.phase = this.HAVE_RESULTS;
-        }
-        this.done_count++;
-        if (this.output_node)
-        {
-            if (this.done_count &lt; 100
-            || (this.done_count &lt; 1000 &amp;&amp; this.done_count % 100 == 0)
-            || this.done_count % 1000 == 0) {
-                this.output_node.textContent = &quot;Running, &quot;
-                    + this.done_count + &quot; complete, &quot;
-                    + tests.num_pending + &quot; remain&quot;;
-            }
-        }
-    };
-
-    Output.prototype.show_results = function (tests, harness_status)
-    {
-        if (this.phase &gt;= this.COMPLETE) {
-            return;
-        }
-        if (!this.enabled)
-        {
-            return;
-        }
-        if (!this.output_node) {
-            this.resolve_log();
-        }
-        this.phase = this.COMPLETE;
-
-        var log = this.output_node;
-        if (!log)
-        {
-            return;
-        }
-        var output_document = this.output_document;
-
-        while (log.lastChild)
-        {
-            log.removeChild(log.lastChild);
-        }
-
-        if (script_prefix != null) {
-            var stylesheet = output_document.createElementNS(xhtml_ns, &quot;link&quot;);
-            stylesheet.setAttribute(&quot;rel&quot;, &quot;stylesheet&quot;);
-            stylesheet.setAttribute(&quot;href&quot;, script_prefix + &quot;testharness.css&quot;);
-            var heads = output_document.getElementsByTagName(&quot;head&quot;);
-            if (heads.length) {
-                heads[0].appendChild(stylesheet);
-            }
-        }
-
-        var status_text = {};
-        status_text[Test.prototype.PASS] = &quot;Pass&quot;;
-        status_text[Test.prototype.FAIL] = &quot;Fail&quot;;
-        status_text[Test.prototype.TIMEOUT] = &quot;Timeout&quot;;
-        status_text[Test.prototype.NOTRUN] = &quot;Not Run&quot;;
-
-        var status_number = {};
-        forEach(tests, function(test) {
-                    var status = status_text[test.status];
-                    if (status_number.hasOwnProperty(status))
-                    {
-                        status_number[status] += 1;
-                    } else {
-                        status_number[status] = 1;
-                    }
-                });
-
-        function status_class(status)
-        {
-            return status.replace(/\s/g, '').toLowerCase();
-        }
-
-        var summary_template = [&quot;section&quot;, {&quot;id&quot;:&quot;summary&quot;},
-                                [&quot;h2&quot;, {}, &quot;Summary&quot;],
-                                [&quot;p&quot;, {}, &quot;Found ${num_tests} tests&quot;],
-                                function(vars) {
-                                    var rv = [[&quot;div&quot;, {}]];
-                                    var i=0;
-                                    while (status_text.hasOwnProperty(i)) {
-                                        if (status_number.hasOwnProperty(status_text[i])) {
-                                            var status = status_text[i];
-                                            rv[0].push([&quot;div&quot;, {&quot;class&quot;:status_class(status)},
-                                                        [&quot;label&quot;, {},
-                                                         [&quot;input&quot;, {type:&quot;checkbox&quot;, checked:&quot;checked&quot;}],
-                                                         status_number[status] + &quot; &quot; + status]]);
-                                        }
-                                        i++;
-                                    }
-                                    return rv;
-                                }];
-
-        log.appendChild(render(summary_template, {num_tests:tests.length}, output_document));
-
-        forEach(output_document.querySelectorAll(&quot;section#summary label&quot;),
-                function(element)
-                {
-                    on_event(element, &quot;click&quot;,
-                             function(e)
-                             {
-                                 if (output_document.getElementById(&quot;results&quot;) === null)
-                                 {
-                                     e.preventDefault();
-                                     return;
-                                 }
-                                 var result_class = element.parentNode.getAttribute(&quot;class&quot;);
-                                 var style_element = output_document.querySelector(&quot;style#hide-&quot; + result_class);
-                                 var input_element = element.querySelector(&quot;input&quot;);
-                                 if (!style_element &amp;&amp; !input_element.checked) {
-                                     style_element = output_document.createElementNS(xhtml_ns, &quot;style&quot;);
-                                     style_element.id = &quot;hide-&quot; + result_class;
-                                     style_element.textContent = &quot;table#results &gt; tbody &gt; tr.&quot;+result_class+&quot;{display:none}&quot;;
-                                     output_document.body.appendChild(style_element);
-                                 } else if (style_element &amp;&amp; input_element.checked) {
-                                     style_element.parentNode.removeChild(style_element);
-                                 }
-                             });
-                });
-
-        // This use of innerHTML plus manual escaping is not recommended in
-        // general, but is necessary here for performance.  Using textContent
-        // on each individual &lt;td&gt; adds tens of seconds of execution time for
-        // large test suites (tens of thousands of tests).
-        function escape_html(s)
-        {
-            return s.replace(/\&amp;/g, &quot;&amp;amp;&quot;)
-                .replace(/&lt;/g, &quot;&amp;lt;&quot;)
-                .replace(/&quot;/g, &quot;&amp;quot;&quot;)
-                .replace(/'/g, &quot;&amp;#39;&quot;);
-        }
-
-        function has_assertions()
-        {
-            for (var i = 0; i &lt; tests.length; i++) {
-                if (tests[i].properties.hasOwnProperty(&quot;assert&quot;)) {
-                    return true;
-                }
-            }
-            return false;
-        }
-        
-        function get_assertion(test)
-        {
-            if (test.properties.hasOwnProperty(&quot;assert&quot;)) {
-                if (Array.isArray(test.properties.assert)) {
-                    return test.properties.assert.join(' ');
-                }
-                return test.properties.assert;
-            }
-            return '';
-        }
-        
-        log.appendChild(document.createElementNS(xhtml_ns, &quot;section&quot;));
-        var assertions = has_assertions();
-        var html = &quot;&lt;h2&gt;Details&lt;/h2&gt;&lt;table id='results' &quot; + (assertions ? &quot;class='assertions'&quot; : &quot;&quot; ) + &quot;&gt;&quot;
-            + &quot;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Result&lt;/th&gt;&lt;th&gt;Test Name&lt;/th&gt;&quot;
-            + (assertions ? &quot;&lt;th&gt;Assertion&lt;/th&gt;&quot; : &quot;&quot;)
-            + &quot;&lt;th&gt;Message&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&quot;
-            + &quot;&lt;tbody&gt;&quot;;
-        for (var i = 0; i &lt; tests.length; i++) {
-            html += '&lt;tr class=&quot;'
-                + escape_html(status_class(status_text[tests[i].status]))
-                + '&quot;&gt;&lt;td&gt;'
-                + escape_html(status_text[tests[i].status])
-                + &quot;&lt;/td&gt;&lt;td&gt;&quot;
-                + escape_html(tests[i].name)
-                + &quot;&lt;/td&gt;&lt;td&gt;&quot;
-                + (assertions ? escape_html(get_assertion(tests[i])) + &quot;&lt;/td&gt;&lt;td&gt;&quot; : &quot;&quot;)
-                + escape_html(tests[i].message ? tests[i].message : &quot; &quot;)
-                + &quot;&lt;/td&gt;&lt;/tr&gt;&quot;;
-        }
-        html += &quot;&lt;/tbody&gt;&lt;/table&gt;&quot;;
-        try {
-            log.lastChild.innerHTML = html;
-        } catch (e) {
-            log.appendChild(document.createElementNS(xhtml_ns, &quot;p&quot;))
-               .textContent = &quot;Setting innerHTML for the log threw an exception.&quot;;
-            log.appendChild(document.createElementNS(xhtml_ns, &quot;pre&quot;))
-               .textContent = html;
-        }
-    };
-
-    var output = new Output();
-    add_start_callback(function (properties) {output.init(properties);});
-    add_result_callback(function (test) {output.show_status(tests);});
-    add_completion_callback(function (tests, harness_status) {output.show_results(tests, harness_status);});
-
-    /*
-     * Template code
-     *
-     * A template is just a javascript structure. An element is represented as:
-     *
-     * [tag_name, {attr_name:attr_value}, child1, child2]
-     *
-     * the children can either be strings (which act like text nodes), other templates or
-     * functions (see below)
-     *
-     * A text node is represented as
-     *
-     * [&quot;{text}&quot;, value]
-     *
-     * String values have a simple substitution syntax; ${foo} represents a variable foo.
-     *
-     * It is possible to embed logic in templates by using a function in a place where a
-     * node would usually go. The function must either return part of a template or null.
-     *
-     * In cases where a set of nodes are required as output rather than a single node
-     * with children it is possible to just use a list
-     * [node1, node2, node3]
-     *
-     * Usage:
-     *
-     * render(template, substitutions) - take a template and an object mapping
-     * variable names to parameters and return either a DOM node or a list of DOM nodes
-     *
-     * substitute(template, substitutions) - take a template and variable mapping object,
-     * make the variable substitutions and return the substituted template
-     *
-     */
-
-    function is_single_node(template)
-    {
-        return typeof template[0] === &quot;string&quot;;
-    }
-
-    function substitute(template, substitutions)
-    {
-        if (typeof template === &quot;function&quot;) {
-            var replacement = template(substitutions);
-            if (replacement)
-            {
-                var rv = substitute(replacement, substitutions);
-                return rv;
-            }
-            else
-            {
-                return null;
-            }
-        }
-        else if (is_single_node(template))
-        {
-            return substitute_single(template, substitutions);
-        }
-        else
-        {
-            return filter(map(template, function(x) {
-                                  return substitute(x, substitutions);
-                              }), function(x) {return x !== null;});
-        }
-    }
-
-    function substitute_single(template, substitutions)
-    {
-        var substitution_re = /\${([^ }]*)}/g;
-
-        function do_substitution(input) {
-            var components = input.split(substitution_re);
-            var rv = [];
-            for (var i=0; i&lt;components.length; i+=2)
-            {
-                rv.push(components[i]);
-                if (components[i+1])
-                {
-                    rv.push(String(substitutions[components[i+1]]));
-                }
-            }
-            return rv;
-        }
-
-        var rv = [];
-        rv.push(do_substitution(String(template[0])).join(&quot;&quot;));
-
-        if (template[0] === &quot;{text}&quot;) {
-            substitute_children(template.slice(1), rv);
-        } else {
-            substitute_attrs(template[1], rv);
-            substitute_children(template.slice(2), rv);
-        }
-
-        function substitute_attrs(attrs, rv)
-        {
-            rv[1] = {};
-            for (var name in template[1])
-            {
-                if (attrs.hasOwnProperty(name))
-                {
-                    var new_name = do_substitution(name).join(&quot;&quot;);
-                    var new_value = do_substitution(attrs[name]).join(&quot;&quot;);
-                    rv[1][new_name] = new_value;
-                };
-            }
-        }
-
-        function substitute_children(children, rv)
-        {
-            for (var i=0; i&lt;children.length; i++)
-            {
-                if (children[i] instanceof Object) {
-                    var replacement = substitute(children[i], substitutions);
-                    if (replacement !== null)
-                    {
-                        if (is_single_node(replacement))
-                        {
-                            rv.push(replacement);
-                        }
-                        else
-                        {
-                            extend(rv, replacement);
-                        }
-                    }
-                }
-                else
-                {
-                    extend(rv, do_substitution(String(children[i])));
-                }
-            }
-            return rv;
-        }
-
-        return rv;
-    }
-
- function make_dom_single(template, doc)
- {
-     var output_document = doc || document;
-     if (template[0] === &quot;{text}&quot;)
-     {
-         var element = output_document.createTextNode(&quot;&quot;);
-         for (var i=1; i&lt;template.length; i++)
-         {
-             element.data += template[i];
-         }
-     }
-     else
-     {
-         var element = output_document.createElementNS(xhtml_ns, template[0]);
-         for (var name in template[1]) {
-             if (template[1].hasOwnProperty(name))
-             {
-                 element.setAttribute(name, template[1][name]);
-             }
-         }
-         for (var i=2; i&lt;template.length; i++)
-         {
-             if (template[i] instanceof Object)
-             {
-                 var sub_element = make_dom(template[i]);
-                 element.appendChild(sub_element);
-             }
-             else
-             {
-                 var text_node = output_document.createTextNode(template[i]);
-                 element.appendChild(text_node);
-             }
-         }
-     }
-
-     return element;
- }
-
-
-
- function make_dom(template, substitutions, output_document)
-    {
-        if (is_single_node(template))
-        {
-            return make_dom_single(template, output_document);
-        }
-        else
-        {
-            return map(template, function(x) {
-                           return make_dom_single(x, output_document);
-                       });
-        }
-    }
-
- function render(template, substitutions, output_document)
-    {
-        return make_dom(substitute(template, substitutions), output_document);
-    }
-
-    /*
-     * Utility funcions
-     */
-    function assert(expected_true, function_name, description, error, substitutions)
-    {
-        if (expected_true !== true)
-        {
-            throw new AssertionError(make_message(function_name, description,
-                                                  error, substitutions));
-        }
-    }
-
-    function AssertionError(message)
-    {
-        this.message = message;
-    }
-
-    function make_message(function_name, description, error, substitutions)
-    {
-        for (var p in substitutions) {
-            if (substitutions.hasOwnProperty(p)) {
-                substitutions[p] = format_value(substitutions[p]);
-            }
-        }
-        var node_form = substitute([&quot;{text}&quot;, &quot;${function_name}: ${description}&quot; + error],
-                                   merge({function_name:function_name,
-                                          description:(description?description + &quot; &quot;:&quot;&quot;)},
-                                          substitutions));
-        return node_form.slice(1).join(&quot;&quot;);
-    }
-
-    function filter(array, callable, thisObj) {
-        var rv = [];
-        for (var i=0; i&lt;array.length; i++)
-        {
-            if (array.hasOwnProperty(i))
-            {
-                var pass = callable.call(thisObj, array[i], i, array);
-                if (pass) {
-                    rv.push(array[i]);
-                }
-            }
-        }
-        return rv;
-    }
-
-    function map(array, callable, thisObj)
-    {
-        var rv = [];
-        rv.length = array.length;
-        for (var i=0; i&lt;array.length; i++)
-        {
-            if (array.hasOwnProperty(i))
-            {
-                rv[i] = callable.call(thisObj, array[i], i, array);
-            }
-        }
-        return rv;
-    }
-
-    function extend(array, items)
-    {
-        Array.prototype.push.apply(array, items);
-    }
-
-    function forEach (array, callback, thisObj)
-    {
-        for (var i=0; i&lt;array.length; i++)
-        {
-            if (array.hasOwnProperty(i))
-            {
-                callback.call(thisObj, array[i], i, array);
-            }
-        }
-    }
-
-    function merge(a,b)
-    {
-        var rv = {};
-        var p;
-        for (p in a)
-        {
-            rv[p] = a[p];
-        }
-        for (p in b) {
-            rv[p] = b[p];
-        }
-        return rv;
-    }
-
-    function expose(object, name)
-    {
-        var components = name.split(&quot;.&quot;);
-        var target = window;
-        for (var i=0; i&lt;components.length - 1; i++)
-        {
-            if (!(components[i] in target))
-            {
-                target[components[i]] = {};
-            }
-            target = target[components[i]];
-        }
-        target[components[components.length - 1]] = object;
-    }
-
-    function forEach_windows(callback) {
-        // Iterate of the the windows [self ... top, opener]. The callback is passed
-        // two objects, the first one is the windows object itself, the second one
-        // is a boolean indicating whether or not its on the same origin as the
-        // current window.
-        var cache = forEach_windows.result_cache;
-        if (!cache) {
-            cache = [[self, true]];
-            var w = self;
-            var i = 0;
-            var so;
-            var origins = location.ancestorOrigins;
-            while (w != w.parent)
-            {
-                w = w.parent;
-                // In WebKit, calls to parent windows' properties that aren't on the same
-                // origin cause an error message to be displayed in the error console but
-                // don't throw an exception. This is a deviation from the current HTML5
-                // spec. See: https://bugs.webkit.org/show_bug.cgi?id=43504
-                // The problem with WebKit's behavior is that it pollutes the error console
-                // with error messages that can't be caught.
-                //
-                // This issue can be mitigated by relying on the (for now) proprietary
-                // `location.ancestorOrigins` property which returns an ordered list of
-                // the origins of enclosing windows. See:
-                // http://trac.webkit.org/changeset/113945.
-                if(origins) {
-                    so = (location.origin == origins[i]);
-                }
-                else
-                {
-                    so = is_same_origin(w);
-                }
-                cache.push([w, so]);
-                i++;
-            }
-            w = window.opener;
-            if(w)
-            {
-                // window.opener isn't included in the `location.ancestorOrigins` prop.
-                // We'll just have to deal with a simple check and an error msg on WebKit
-                // browsers in this case.
-                cache.push([w, is_same_origin(w)]);
-            }
-            forEach_windows.result_cache = cache;
-        }
-
-        forEach(cache,
-                function(a)
-                {
-                    callback.apply(null, a);
-                });
-    }
-
-    function is_same_origin(w) {
-        try {
-            'random_prop' in w;
-            return true;
-        } catch(e) {
-            return false;
-        }
-    }
-
-    function supports_post_message(w)
-    {
-        var supports;
-        var type;
-        // Given IE  implements postMessage across nested iframes but not across
-        // windows or tabs, you can't infer cross-origin communication from the presence
-        // of postMessage on the current window object only.
-        //
-        // Touching the postMessage prop on a window can throw if the window is
-        // not from the same origin AND post message is not supported in that
-        // browser. So just doing an existence test here won't do, you also need
-        // to wrap it in a try..cacth block.
-        try
-        {
-            type = typeof w.postMessage;
-            if (type === &quot;function&quot;)
-            {
-                supports = true;
-            }
-            // IE8 supports postMessage, but implements it as a host object which
-            // returns &quot;object&quot; as its `typeof`.
-            else if (type === &quot;object&quot;)
-            {
-                supports = true;
-            }
-            // This is the case where postMessage isn't supported AND accessing a
-            // window property across origins does NOT throw (e.g. old Safari browser).
-            else
-            {
-                supports = false;
-            }
-        }
-        catch(e) {
-            // This is the case where postMessage isn't supported AND accessing a
-            // window property across origins throws (e.g. old Firefox browser).
-            supports = false;
-        }
-        return supports;
-    }
-})();
-// vim: set expandtab shiftwidth=4 tabstop=4:
</del><ins>+/*
+Distributed under both the W3C Test Suite License [1] and the W3C
+3-clause BSD License [2]. To contribute to a W3C Test Suite, see the
+policies and contribution forms [3].
+
+[1] http://www.w3.org/Consortium/Legal/2008/04-testsuite-license
+[2] http://www.w3.org/Consortium/Legal/2008/03-bsd-license
+[3] http://www.w3.org/2004/10/27-testcases
+*/
+
+/*
+ * == Introduction ==
+ *
+ * This file provides a framework for writing testcases. It is intended to
+ * provide a convenient API for making common assertions, and to work both
+ * for testing synchronous and asynchronous DOM features in a way that
+ * promotes clear, robust, tests.
+ *
+ * == Basic Usage ==
+ *
+ * To use this file, import the script and the testharnessreport script into
+ * the test document:
+ * &lt;script src=&quot;/resources/testharness.js&quot;&gt;&lt;/script&gt;
+ * &lt;script src=&quot;/resources/testharnessreport.js&quot;&gt;&lt;/script&gt;
+ *
+ * Within each file one may define one or more tests. Each test is atomic
+ * in the sense that a single test has a single result (pass/fail/timeout).
+ * Within each test one may have a number of asserts. The test fails at the
+ * first failing assert, and the remainder of the test is (typically) not run.
+ *
+ * If the file containing the tests is a HTML file with an element of id &quot;log&quot;
+ * this will be populated with a table containing the test results after all
+ * the tests have run.
+ *
+ * NOTE: By default tests must be created before the load event fires. For ways
+ *       to create tests after the load event, see &quot;Determining when all tests
+ *       are complete&quot;, below
+ *
+ * == Synchronous Tests ==
+ *
+ * To create a synchronous test use the test() function:
+ *
+ * test(test_function, name, properties)
+ *
+ * test_function is a function that contains the code to test. For example a
+ * trivial passing test would be:
+ *
+ * test(function() {assert_true(true)}, &quot;assert_true with true&quot;)
+ *
+ * The function passed in is run in the test() call.
+ *
+ * properties is an object that overrides default test properties. The
+ * recognised properties are:
+ *    timeout - the test timeout in ms
+ *
+ * e.g.
+ * test(test_function, &quot;Sample test&quot;, {timeout:1000})
+ *
+ * would run test_function with a timeout of 1s.
+ *
+ * Additionally, test-specific metadata can be passed in the properties. These
+ * are used when the individual test has different metadata from that stored
+ * in the &lt;head&gt;.
+ * The recognized metadata properties are:
+ *
+ *    help - The url or an array of urls of the part(s) of the specification(s)
+ *           being tested
+ *
+ *    assert - A human readable description of what the test is attempting
+ *             to prove
+ *
+ *    author - Name and contact information for the author of the test in the
+ *             format: &quot;Name &lt;email_addr&gt;&quot; or &quot;Name http://contact/url&quot;
+ *
+ * == Asynchronous Tests ==
+ *
+ * Testing asynchronous features is somewhat more complex since the result of
+ * a test may depend on one or more events or other callbacks. The API provided
+ * for testing these features is indended to be rather low-level but hopefully
+ * applicable to many situations.
+ *
+ * To create a test, one starts by getting a Test object using async_test:
+ *
+ * async_test(name, properties)
+ *
+ * e.g.
+ * var t = async_test(&quot;Simple async test&quot;)
+ *
+ * Assertions can be added to the test by calling the step method of the test
+ * object with a function containing the test assertions:
+ *
+ * t.step(function() {assert_true(true)});
+ *
+ * When all the steps are complete, the done() method must be called:
+ *
+ * t.done();
+ *
+ * As a convenience, async_test can also takes a function as first argument.
+ * This function is called with the test object as both its `this` object and
+ * first argument. The above example can be rewritten as:
+ *
+ * async_test(function(t) {
+ *     object.some_event = function() {
+ *         t.step(function (){assert_true(true); t.done();});
+ *     };
+ * }, &quot;Simple async test&quot;);
+ *
+ * which avoids cluttering the global scope with references to async
+ * tests instances.
+ *
+ * The properties argument is identical to that for test().
+ *
+ * In many cases it is convenient to run a step in response to an event or a
+ * callback. A convenient method of doing this is through the step_func method
+ * which returns a function that, when called runs a test step. For example
+ *
+ * object.some_event = t.step_func(function(e) {assert_true(e.a)});
+ *
+ * == Making assertions ==
+ *
+ * Functions for making assertions start assert_
+ * The best way to get a list is to look in this file for functions names
+ * matching that pattern. The general signature is
+ *
+ * assert_something(actual, expected, description)
+ *
+ * although not all assertions precisely match this pattern e.g. assert_true
+ * only takes actual and description as arguments.
+ *
+ * The description parameter is used to present more useful error messages when
+ * a test fails
+ *
+ * NOTE: All asserts must be located in a test() or a step of an async_test().
+ *       asserts outside these places won't be detected correctly by the harness
+ *       and may cause a file to stop testing.
+ *
+ * == Cleanup ==
+ *
+ * Occasionally tests may create state that will persist beyond the test itself.
+ * In order to ensure that tests are independent, such state should be cleaned
+ * up once the test has a result. This can be achieved by adding cleanup
+ * callbacks to the test. Such callbacks are registered using the add_cleanup
+ * function on the test object. All registered callbacks will be run as soon as
+ * the test result is known. For example
+ * test(function() {
+ *          window.some_global = &quot;example&quot;;
+ *          this.add_cleanup(function() {delete window.some_global});
+ *          assert_true(false);
+ *      });
+ *
+ * == Harness Timeout ==
+ *
+ * The overall harness admits two timeout values &quot;normal&quot; (the
+ * default) and &quot;long&quot;, used for tests which have an unusually long
+ * runtime. After the timeout is reached, the harness will stop
+ * waiting for further async tests to complete. By default the
+ * timeouts are set to 10s and 60s, respectively, but may be changed
+ * when the test is run on hardware with different performance
+ * characteristics to a common desktop computer.  In order to opt-in
+ * to the longer test timeout, the test must specify a meta element:
+ * &lt;meta name=&quot;timeout&quot; content=&quot;long&quot;&gt;
+ *
+ * == Setup ==
+ *
+ * Sometimes tests require non-trivial setup that may fail. For this purpose
+ * there is a setup() function, that may be called with one or two arguments.
+ * The two argument version is:
+ *
+ * setup(func, properties)
+ *
+ * The one argument versions may omit either argument.
+ * func is a function to be run synchronously. setup() becomes a no-op once
+ * any tests have returned results. Properties are global properties of the test
+ * harness. Currently recognised properties are:
+ *
+ *
+ * explicit_done - Wait for an explicit call to done() before declaring all
+ *                 tests complete (see below)
+ *
+ * output_document - The document to which results should be logged. By default
+ *                   this is the current document but could be an ancestor
+ *                   document in some cases e.g. a SVG test loaded in an HTML
+ *                   wrapper
+ *
+ * explicit_timeout - disable file timeout; only stop waiting for results
+ *                    when the timeout() function is called (typically for
+ *                    use when integrating with some existing test framework
+ *                    that has its own timeout mechanism).
+ *
+ * allow_uncaught_exception - don't treat an uncaught exception as an error;
+ *                            needed when e.g. testing the window.onerror
+ *                            handler.
+ *
+ * timeout_multiplier - Multiplier to apply to per-test timeouts.
+ *
+ * == Determining when all tests are complete ==
+ *
+ * By default the test harness will assume there are no more results to come
+ * when:
+ * 1) There are no Test objects that have been created but not completed
+ * 2) The load event on the document has fired
+ *
+ * This behaviour can be overridden by setting the explicit_done property to
+ * true in a call to setup(). If explicit_done is true, the test harness will
+ * not assume it is done until the global done() function is called. Once done()
+ * is called, the two conditions above apply like normal.
+ *
+ * == Generating tests ==
+ *
+ * NOTE: this functionality may be removed
+ *
+ * There are scenarios in which is is desirable to create a large number of
+ * (synchronous) tests that are internally similar but vary in the parameters
+ * used. To make this easier, the generate_tests function allows a single
+ * function to be called with each set of parameters in a list:
+ *
+ * generate_tests(test_function, parameter_lists, properties)
+ *
+ * For example:
+ *
+ * generate_tests(assert_equals, [
+ *     [&quot;Sum one and one&quot;, 1+1, 2],
+ *     [&quot;Sum one and zero&quot;, 1+0, 1]
+ *     ])
+ *
+ * Is equivalent to:
+ *
+ * test(function() {assert_equals(1+1, 2)}, &quot;Sum one and one&quot;)
+ * test(function() {assert_equals(1+0, 1)}, &quot;Sum one and zero&quot;)
+ *
+ * Note that the first item in each parameter list corresponds to the name of
+ * the test.
+ *
+ * The properties argument is identical to that for test(). This may be a
+ * single object (used for all generated tests) or an array.
+ *
+ * == Callback API ==
+ *
+ * The framework provides callbacks corresponding to 3 events:
+ *
+ * start - happens when the first Test is created
+ * result - happens when a test result is recieved
+ * complete - happens when all results are recieved
+ *
+ * The page defining the tests may add callbacks for these events by calling
+ * the following methods:
+ *
+ *   add_start_callback(callback) - callback called with no arguments
+ *   add_result_callback(callback) - callback called with a test argument
+ *   add_completion_callback(callback) - callback called with an array of tests
+ *                                       and an status object
+ *
+ * tests have the following properties:
+ *   status: A status code. This can be compared to the PASS, FAIL, TIMEOUT and
+ *           NOTRUN properties on the test object
+ *   message: A message indicating the reason for failure. In the future this
+ *            will always be a string
+ *
+ *  The status object gives the overall status of the harness. It has the
+ *  following properties:
+ *    status: Can be compared to the OK, ERROR and TIMEOUT properties
+ *    message: An error message set when the status is ERROR
+ *
+ * == External API ==
+ *
+ * In order to collect the results of multiple pages containing tests, the test
+ * harness will, when loaded in a nested browsing context, attempt to call
+ * certain functions in each ancestor and opener browsing context:
+ *
+ * start - start_callback
+ * result - result_callback
+ * complete - completion_callback
+ *
+ * These are given the same arguments as the corresponding internal callbacks
+ * described above.
+ *
+ * == External API through cross-document messaging ==
+ *
+ * Where supported, the test harness will also send messages using
+ * cross-document messaging to each ancestor and opener browsing context. Since
+ * it uses the wildcard keyword (*), cross-origin communication is enabled and
+ * script on different origins can collect the results.
+ *
+ * This API follows similar conventions as those described above only slightly
+ * modified to accommodate message event API. Each message is sent by the harness
+ * is passed a single vanilla object, available as the `data` property of the
+ * event object. These objects are structures as follows:
+ *
+ * start - { type: &quot;start&quot; }
+ * result - { type: &quot;result&quot;, test: Test }
+ * complete - { type: &quot;complete&quot;, tests: [Test, ...], status: TestsStatus }
+ *
+ * == List of assertions ==
+ *
+ * assert_true(actual, description)
+ *   asserts that /actual/ is strictly true
+ *
+ * assert_false(actual, description)
+ *   asserts that /actual/ is strictly false
+ *
+ * assert_equals(actual, expected, description)
+ *   asserts that /actual/ is the same value as /expected/
+ *
+ * assert_not_equals(actual, expected, description)
+ *   asserts that /actual/ is a different value to /expected/. Yes, this means
+ *   that &quot;expected&quot; is a misnomer
+ *
+ * assert_in_array(actual, expected, description)
+ *   asserts that /expected/ is an Array, and /actual/ is equal to one of the
+ *   members -- expected.indexOf(actual) != -1
+ *
+ * assert_array_equals(actual, expected, description)
+ *   asserts that /actual/ and /expected/ have the same length and the value of
+ *   each indexed property in /actual/ is the strictly equal to the corresponding
+ *   property value in /expected/
+ *
+ * assert_approx_equals(actual, expected, epsilon, description)
+ *   asserts that /actual/ is a number within +/- /epsilon/ of /expected/
+ *
+ * assert_less_than(actual, expected, description)
+ *   asserts that /actual/ is a number less than /expected/
+ *
+ * assert_greater_than(actual, expected, description)
+ *   asserts that /actual/ is a number greater than /expected/
+ *
+ * assert_less_than_equal(actual, expected, description)
+ *   asserts that /actual/ is a number less than or equal to /expected/
+ *
+ * assert_greater_than_equal(actual, expected, description)
+ *   asserts that /actual/ is a number greater than or equal to /expected/
+ *
+ * assert_regexp_match(actual, expected, description)
+ *   asserts that /actual/ matches the regexp /expected/
+ *
+ * assert_class_string(object, class_name, description)
+ *   asserts that the class string of /object/ as returned in
+ *   Object.prototype.toString is equal to /class_name/.
+ *
+ * assert_own_property(object, property_name, description)
+ *   assert that object has own property property_name
+ *
+ * assert_inherits(object, property_name, description)
+ *   assert that object does not have an own property named property_name
+ *   but that property_name is present in the prototype chain for object
+ *
+ * assert_idl_attribute(object, attribute_name, description)
+ *   assert that an object that is an instance of some interface has the
+ *   attribute attribute_name following the conditions specified by WebIDL
+ *
+ * assert_readonly(object, property_name, description)
+ *   assert that property property_name on object is readonly
+ *
+ * assert_throws(code, func, description)
+ *   code - the expected exception:
+ *     o string: the thrown exception must be a DOMException with the given
+ *               name, e.g., &quot;TimeoutError&quot; (for compatibility with existing
+ *               tests, a constant is also supported, e.g., &quot;TIMEOUT_ERR&quot;)
+ *     o object: the thrown exception must have a property called &quot;name&quot; that
+ *               matches code.name
+ *     o null:   allow any exception (in general, one of the options above
+ *               should be used)
+ *   func - a function that should throw
+ *
+ * assert_unreached(description)
+ *   asserts if called. Used to ensure that some codepath is *not* taken e.g.
+ *   an event does not fire.
+ *
+ * assert_any(assert_func, actual, expected_array, extra_arg_1, ... extra_arg_N)
+ *   asserts that one assert_func(actual, expected_array_N, extra_arg1, ..., extra_arg_N)
+ *   is true for some expected_array_N in expected_array. This only works for assert_func
+ *   with signature assert_func(actual, expected, args_1, ..., args_N). Note that tests
+ *   with multiple allowed pass conditions are bad practice unless the spec specifically
+ *   allows multiple behaviours. Test authors should not use this method simply to hide
+ *   UA bugs.
+ *
+ * assert_exists(object, property_name, description)
+ *   *** deprecated ***
+ *   asserts that object has an own property property_name
+ *
+ * assert_not_exists(object, property_name, description)
+ *   *** deprecated ***
+ *   assert that object does not have own property property_name
+ */
+
+(function ()
+{
+    var debug = false;
+    // default timeout is 10 seconds, test can override if needed
+    var settings = {
+        output:true,
+        harness_timeout:{
+            &quot;normal&quot;:10000,
+            &quot;long&quot;:60000
+        },
+        test_timeout:null
+    };
+
+    var xhtml_ns = &quot;http://www.w3.org/1999/xhtml&quot;;
+
+    // script_prefix is used by Output.prototype.show_results() to figure out
+    // where to get testharness.css from.  It's enclosed in an extra closure to
+    // not pollute the library's namespace with variables like &quot;src&quot;.
+    var script_prefix = null;
+    (function ()
+    {
+        var scripts = document.getElementsByTagName(&quot;script&quot;);
+        for (var i = 0; i &lt; scripts.length; i++) {
+            if (scripts[i].src) {
+                var src = scripts[i].src;
+            } else if (scripts[i].href) {
+                //SVG case
+                var src = scripts[i].href.baseVal;
+            }
+
+            if (src &amp;&amp; src.slice(src.length - &quot;testharness.js&quot;.length) === &quot;testharness.js&quot;) {
+                script_prefix = src.slice(0, src.length - &quot;testharness.js&quot;.length);
+                break;
+            }
+        }
+    })();
+
+    /*
+     * API functions
+     */
+
+    var name_counter = 0;
+    function next_default_name()
+    {
+        //Don't use document.title to work around an Opera bug in XHTML documents
+        var title = document.getElementsByTagName(&quot;title&quot;)[0];
+        var prefix = (title &amp;&amp; title.firstChild &amp;&amp; title.firstChild.data) || &quot;Untitled&quot;;
+        var suffix = name_counter &gt; 0 ? &quot; &quot; + name_counter : &quot;&quot;;
+        name_counter++;
+        return prefix + suffix;
+    }
+
+    function test(func, name, properties)
+    {
+        var test_name = name ? name : next_default_name();
+        properties = properties ? properties : {};
+        var test_obj = new Test(test_name, properties);
+        test_obj.step(func, test_obj, test_obj);
+        if (test_obj.phase === test_obj.phases.STARTED) {
+            test_obj.done();
+        }
+    }
+
+    function async_test(func, name, properties)
+    {
+        if (typeof func !== &quot;function&quot;) {
+            properties = name;
+            name = func;
+            func = null;
+        }
+        var test_name = name ? name : next_default_name();
+        properties = properties ? properties : {};
+        var test_obj = new Test(test_name, properties);
+        if (func) {
+            test_obj.step(func, test_obj, test_obj);
+        }
+        return test_obj;
+    }
+
+    function setup(func_or_properties, maybe_properties)
+    {
+        var func = null;
+        var properties = {};
+        if (arguments.length === 2) {
+            func = func_or_properties;
+            properties = maybe_properties;
+        } else if (func_or_properties instanceof Function) {
+            func = func_or_properties;
+        } else {
+            properties = func_or_properties;
+        }
+        tests.setup(func, properties);
+        output.setup(properties);
+    }
+
+    function done() {
+        tests.end_wait();
+    }
+
+    function generate_tests(func, args, properties) {
+        forEach(args, function(x, i)
+                {
+                    var name = x[0];
+                    test(function()
+                         {
+                             func.apply(this, x.slice(1));
+                         },
+                         name,
+                         Array.isArray(properties) ? properties[i] : properties);
+                });
+    }
+
+    function on_event(object, event, callback)
+    {
+        object.addEventListener(event, callback, false);
+    }
+
+    expose(test, 'test');
+    expose(async_test, 'async_test');
+    expose(generate_tests, 'generate_tests');
+    expose(setup, 'setup');
+    expose(done, 'done');
+    expose(on_event, 'on_event');
+
+    /*
+     * Return a string truncated to the given length, with ... added at the end
+     * if it was longer.
+     */
+    function truncate(s, len)
+    {
+        if (s.length &gt; len) {
+            return s.substring(0, len - 3) + &quot;...&quot;;
+        }
+        return s;
+    }
+
+    /*
+     * Return true if object is probably a Node object.
+     */
+    function is_node(object)
+    {
+        // I use duck-typing instead of instanceof, because
+        // instanceof doesn't work if the node is from another window (like an
+        // iframe's contentWindow):
+        // http://www.w3.org/Bugs/Public/show_bug.cgi?id=12295
+        if (&quot;nodeType&quot; in object
+        &amp;&amp; &quot;nodeName&quot; in object
+        &amp;&amp; &quot;nodeValue&quot; in object
+        &amp;&amp; &quot;childNodes&quot; in object) {
+            try {
+                object.nodeType;
+            } catch (e) {
+                // The object is probably Node.prototype or another prototype
+                // object that inherits from it, and not a Node instance.
+                return false;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /*
+     * Convert a value to a nice, human-readable string
+     */
+    function format_value(val, seen)
+    {
+        if (!seen) {
+            seen = [];
+        }
+        if (typeof val === &quot;object&quot; &amp;&amp; val !== null) {
+            if (seen.indexOf(val) &gt;= 0) {
+                return &quot;[...]&quot;;
+            }
+            seen.push(val);
+        }
+        if (Array.isArray(val)) {
+            return &quot;[&quot; + val.map(function(x) {return format_value(x, seen)}).join(&quot;, &quot;) + &quot;]&quot;;
+        }
+
+        switch (typeof val) {
+        case &quot;string&quot;:
+            val = val.replace(&quot;\\&quot;, &quot;\\\\&quot;);
+            for (var i = 0; i &lt; 32; i++) {
+                var replace = &quot;\\&quot;;
+                switch (i) {
+                case 0: replace += &quot;0&quot;; break;
+                case 1: replace += &quot;x01&quot;; break;
+                case 2: replace += &quot;x02&quot;; break;
+                case 3: replace += &quot;x03&quot;; break;
+                case 4: replace += &quot;x04&quot;; break;
+                case 5: replace += &quot;x05&quot;; break;
+                case 6: replace += &quot;x06&quot;; break;
+                case 7: replace += &quot;x07&quot;; break;
+                case 8: replace += &quot;b&quot;; break;
+                case 9: replace += &quot;t&quot;; break;
+                case 10: replace += &quot;n&quot;; break;
+                case 11: replace += &quot;v&quot;; break;
+                case 12: replace += &quot;f&quot;; break;
+                case 13: replace += &quot;r&quot;; break;
+                case 14: replace += &quot;x0e&quot;; break;
+                case 15: replace += &quot;x0f&quot;; break;
+                case 16: replace += &quot;x10&quot;; break;
+                case 17: replace += &quot;x11&quot;; break;
+                case 18: replace += &quot;x12&quot;; break;
+                case 19: replace += &quot;x13&quot;; break;
+                case 20: replace += &quot;x14&quot;; break;
+                case 21: replace += &quot;x15&quot;; break;
+                case 22: replace += &quot;x16&quot;; break;
+                case 23: replace += &quot;x17&quot;; break;
+                case 24: replace += &quot;x18&quot;; break;
+                case 25: replace += &quot;x19&quot;; break;
+                case 26: replace += &quot;x1a&quot;; break;
+                case 27: replace += &quot;x1b&quot;; break;
+                case 28: replace += &quot;x1c&quot;; break;
+                case 29: replace += &quot;x1d&quot;; break;
+                case 30: replace += &quot;x1e&quot;; break;
+                case 31: replace += &quot;x1f&quot;; break;
+                }
+                val = val.replace(RegExp(String.fromCharCode(i), &quot;g&quot;), replace);
+            }
+            return '&quot;' + val.replace(/&quot;/g, '\\&quot;') + '&quot;';
+        case &quot;boolean&quot;:
+        case &quot;undefined&quot;:
+            return String(val);
+        case &quot;number&quot;:
+            // In JavaScript, -0 === 0 and String(-0) == &quot;0&quot;, so we have to
+            // special-case.
+            if (val === -0 &amp;&amp; 1/val === -Infinity) {
+                return &quot;-0&quot;;
+            }
+            return String(val);
+        case &quot;object&quot;:
+            if (val === null) {
+                return &quot;null&quot;;
+            }
+
+            // Special-case Node objects, since those come up a lot in my tests.  I
+            // ignore namespaces.
+            if (is_node(val)) {
+                switch (val.nodeType) {
+                case Node.ELEMENT_NODE:
+                    var ret = &quot;&lt;&quot; + val.tagName.toLowerCase();
+                    for (var i = 0; i &lt; val.attributes.length; i++) {
+                        ret += &quot; &quot; + val.attributes[i].name + '=&quot;' + val.attributes[i].value + '&quot;';
+                    }
+                    ret += &quot;&gt;&quot; + val.innerHTML + &quot;&lt;/&quot; + val.tagName.toLowerCase() + &quot;&gt;&quot;;
+                    return &quot;Element node &quot; + truncate(ret, 60);
+                case Node.TEXT_NODE:
+                    return 'Text node &quot;' + truncate(val.data, 60) + '&quot;';
+                case Node.PROCESSING_INSTRUCTION_NODE:
+                    return &quot;ProcessingInstruction node with target &quot; + format_value(truncate(val.target, 60)) + &quot; and data &quot; + format_value(truncate(val.data, 60));
+                case Node.COMMENT_NODE:
+                    return &quot;Comment node &lt;!--&quot; + truncate(val.data, 60) + &quot;--&gt;&quot;;
+                case Node.DOCUMENT_NODE:
+                    return &quot;Document node with &quot; + val.childNodes.length + (val.childNodes.length == 1 ? &quot; child&quot; : &quot; children&quot;);
+                case Node.DOCUMENT_TYPE_NODE:
+                    return &quot;DocumentType node&quot;;
+                case Node.DOCUMENT_FRAGMENT_NODE:
+                    return &quot;DocumentFragment node with &quot; + val.childNodes.length + (val.childNodes.length == 1 ? &quot; child&quot; : &quot; children&quot;);
+                default:
+                    return &quot;Node object of unknown type&quot;;
+                }
+            }
+
+            // Fall through to default
+        default:
+            return typeof val + ' &quot;' + truncate(String(val), 60) + '&quot;';
+        }
+    }
+    expose(format_value, &quot;format_value&quot;);
+
+    /*
+     * Assertions
+     */
+
+    function assert_true(actual, description)
+    {
+        assert(actual === true, &quot;assert_true&quot;, description,
+                                &quot;expected true got ${actual}&quot;, {actual:actual});
+    };
+    expose(assert_true, &quot;assert_true&quot;);
+
+    function assert_false(actual, description)
+    {
+        assert(actual === false, &quot;assert_false&quot;, description,
+                                 &quot;expected false got ${actual}&quot;, {actual:actual});
+    };
+    expose(assert_false, &quot;assert_false&quot;);
+
+    function same_value(x, y) {
+        if (y !== y) {
+            //NaN case
+            return x !== x;
+        }
+        if (x === 0 &amp;&amp; y === 0) {
+            //Distinguish +0 and -0
+            return 1/x === 1/y;
+        }
+        return x === y;
+    }
+
+    function assert_equals(actual, expected, description)
+    {
+         /*
+          * Test if two primitives are equal or two objects
+          * are the same object
+          */
+        if (typeof actual != typeof expected) {
+            assert(false, &quot;assert_equals&quot;, description,
+                          &quot;expected (&quot; + typeof expected + &quot;) ${expected} but got (&quot; + typeof actual + &quot;) ${actual}&quot;,
+                          {expected:expected, actual:actual});
+            return;
+        }
+        assert(same_value(actual, expected), &quot;assert_equals&quot;, description,
+                                             &quot;expected ${expected} but got ${actual}&quot;,
+                                             {expected:expected, actual:actual});
+    };
+    expose(assert_equals, &quot;assert_equals&quot;);
+
+    function assert_not_equals(actual, expected, description)
+    {
+         /*
+          * Test if two primitives are unequal or two objects
+          * are different objects
+          */
+        assert(!same_value(actual, expected), &quot;assert_not_equals&quot;, description,
+                                              &quot;got disallowed value ${actual}&quot;,
+                                              {actual:actual});
+    };
+    expose(assert_not_equals, &quot;assert_not_equals&quot;);
+
+    function assert_in_array(actual, expected, description)
+    {
+        assert(expected.indexOf(actual) != -1, &quot;assert_in_array&quot;, description,
+                                               &quot;value ${actual} not in array ${expected}&quot;,
+                                               {actual:actual, expected:expected});
+    }
+    expose(assert_in_array, &quot;assert_in_array&quot;);
+
+    function assert_object_equals(actual, expected, description)
+    {
+         //This needs to be improved a great deal
+         function check_equal(actual, expected, stack)
+         {
+             stack.push(actual);
+
+             var p;
+             for (p in actual) {
+                 assert(expected.hasOwnProperty(p), &quot;assert_object_equals&quot;, description,
+                                                    &quot;unexpected property ${p}&quot;, {p:p});
+
+                 if (typeof actual[p] === &quot;object&quot; &amp;&amp; actual[p] !== null) {
+                     if (stack.indexOf(actual[p]) === -1) {
+                         check_equal(actual[p], expected[p], stack);
+                     }
+                 } else {
+                     assert(same_value(actual[p], expected[p]), &quot;assert_object_equals&quot;, description,
+                                                       &quot;property ${p} expected ${expected} got ${actual}&quot;,
+                                                       {p:p, expected:expected, actual:actual});
+                 }
+             }
+             for (p in expected) {
+                 assert(actual.hasOwnProperty(p),
+                        &quot;assert_object_equals&quot;, description,
+                        &quot;expected property ${p} missing&quot;, {p:p});
+             }
+             stack.pop();
+         }
+         check_equal(actual, expected, []);
+    };
+    expose(assert_object_equals, &quot;assert_object_equals&quot;);
+
+    function assert_array_equals(actual, expected, description)
+    {
+        assert(actual.length === expected.length,
+               &quot;assert_array_equals&quot;, description,
+               &quot;lengths differ, expected ${expected} got ${actual}&quot;,
+               {expected:expected.length, actual:actual.length});
+
+        for (var i = 0; i &lt; actual.length; i++) {
+            assert(actual.hasOwnProperty(i) === expected.hasOwnProperty(i),
+                   &quot;assert_array_equals&quot;, description,
+                   &quot;property ${i}, property expected to be $expected but was $actual&quot;,
+                   {i:i, expected:expected.hasOwnProperty(i) ? &quot;present&quot; : &quot;missing&quot;,
+                   actual:actual.hasOwnProperty(i) ? &quot;present&quot; : &quot;missing&quot;});
+            assert(same_value(expected[i], actual[i]),
+                   &quot;assert_array_equals&quot;, description,
+                   &quot;property ${i}, expected ${expected} but got ${actual}&quot;,
+                   {i:i, expected:expected[i], actual:actual[i]});
+        }
+    }
+    expose(assert_array_equals, &quot;assert_array_equals&quot;);
+
+    function assert_approx_equals(actual, expected, epsilon, description)
+    {
+        /*
+         * Test if two primitive numbers are equal withing +/- epsilon
+         */
+        assert(typeof actual === &quot;number&quot;,
+               &quot;assert_approx_equals&quot;, description,
+               &quot;expected a number but got a ${type_actual}&quot;,
+               {type_actual:typeof actual});
+
+        assert(Math.abs(actual - expected) &lt;= epsilon,
+               &quot;assert_approx_equals&quot;, description,
+               &quot;expected ${expected} +/- ${epsilon} but got ${actual}&quot;,
+               {expected:expected, actual:actual, epsilon:epsilon});
+    };
+    expose(assert_approx_equals, &quot;assert_approx_equals&quot;);
+
+    function assert_less_than(actual, expected, description)
+    {
+        /*
+         * Test if a primitive number is less than another
+         */
+        assert(typeof actual === &quot;number&quot;,
+               &quot;assert_less_than&quot;, description,
+               &quot;expected a number but got a ${type_actual}&quot;,
+               {type_actual:typeof actual});
+
+        assert(actual &lt; expected,
+               &quot;assert_less_than&quot;, description,
+               &quot;expected a number less than ${expected} but got ${actual}&quot;,
+               {expected:expected, actual:actual});
+    };
+    expose(assert_less_than, &quot;assert_less_than&quot;);
+
+    function assert_greater_than(actual, expected, description)
+    {
+        /*
+         * Test if a primitive number is greater than another
+         */
+        assert(typeof actual === &quot;number&quot;,
+               &quot;assert_greater_than&quot;, description,
+               &quot;expected a number but got a ${type_actual}&quot;,
+               {type_actual:typeof actual});
+
+        assert(actual &gt; expected,
+               &quot;assert_greater_than&quot;, description,
+               &quot;expected a number greater than ${expected} but got ${actual}&quot;,
+               {expected:expected, actual:actual});
+    };
+    expose(assert_greater_than, &quot;assert_greater_than&quot;);
+
+    function assert_less_than_equal(actual, expected, description)
+    {
+        /*
+         * Test if a primitive number is less than or equal to another
+         */
+        assert(typeof actual === &quot;number&quot;,
+               &quot;assert_less_than_equal&quot;, description,
+               &quot;expected a number but got a ${type_actual}&quot;,
+               {type_actual:typeof actual});
+
+        assert(actual &lt;= expected,
+               &quot;assert_less_than&quot;, description,
+               &quot;expected a number less than or equal to ${expected} but got ${actual}&quot;,
+               {expected:expected, actual:actual});
+    };
+    expose(assert_less_than_equal, &quot;assert_less_than_equal&quot;);
+
+    function assert_greater_than_equal(actual, expected, description)
+    {
+        /*
+         * Test if a primitive number is greater than or equal to another
+         */
+        assert(typeof actual === &quot;number&quot;,
+               &quot;assert_greater_than_equal&quot;, description,
+               &quot;expected a number but got a ${type_actual}&quot;,
+               {type_actual:typeof actual});
+
+        assert(actual &gt;= expected,
+               &quot;assert_greater_than_equal&quot;, description,
+               &quot;expected a number greater than or equal to ${expected} but got ${actual}&quot;,
+               {expected:expected, actual:actual});
+    };
+    expose(assert_greater_than_equal, &quot;assert_greater_than_equal&quot;);
+
+    function assert_regexp_match(actual, expected, description) {
+        /*
+         * Test if a string (actual) matches a regexp (expected)
+         */
+        assert(expected.test(actual),
+               &quot;assert_regexp_match&quot;, description,
+               &quot;expected ${expected} but got ${actual}&quot;,
+               {expected:expected, actual:actual});
+    }
+    expose(assert_regexp_match, &quot;assert_regexp_match&quot;);
+
+    function assert_class_string(object, class_string, description) {
+        assert_equals({}.toString.call(object), &quot;[object &quot; + class_string + &quot;]&quot;,
+                      description);
+    }
+    expose(assert_class_string, &quot;assert_class_string&quot;);
+
+
+    function _assert_own_property(name) {
+        return function(object, property_name, description)
+        {
+            assert(object.hasOwnProperty(property_name),
+                   name, description,
+                   &quot;expected property ${p} missing&quot;, {p:property_name});
+        };
+    }
+    expose(_assert_own_property(&quot;assert_exists&quot;), &quot;assert_exists&quot;);
+    expose(_assert_own_property(&quot;assert_own_property&quot;), &quot;assert_own_property&quot;);
+
+    function assert_not_exists(object, property_name, description)
+    {
+        assert(!object.hasOwnProperty(property_name),
+               &quot;assert_not_exists&quot;, description,
+               &quot;unexpected property ${p} found&quot;, {p:property_name});
+    };
+    expose(assert_not_exists, &quot;assert_not_exists&quot;);
+
+    function _assert_inherits(name) {
+        return function (object, property_name, description)
+        {
+            assert(typeof object === &quot;object&quot;,
+                   name, description,
+                   &quot;provided value is not an object&quot;);
+
+            assert(&quot;hasOwnProperty&quot; in object,
+                   name, description,
+                   &quot;provided value is an object but has no hasOwnProperty method&quot;);
+
+            assert(!object.hasOwnProperty(property_name),
+                   name, description,
+                   &quot;property ${p} found on object expected in prototype chain&quot;,
+                   {p:property_name});
+
+            assert(property_name in object,
+                   name, description,
+                   &quot;property ${p} not found in prototype chain&quot;,
+                   {p:property_name});
+        };
+    }
+    expose(_assert_inherits(&quot;assert_inherits&quot;), &quot;assert_inherits&quot;);
+    expose(_assert_inherits(&quot;assert_idl_attribute&quot;), &quot;assert_idl_attribute&quot;);
+
+    function assert_readonly(object, property_name, description)
+    {
+         var initial_value = object[property_name];
+         try {
+             //Note that this can have side effects in the case where
+             //the property has PutForwards
+             object[property_name] = initial_value + &quot;a&quot;; //XXX use some other value here?
+             assert(same_value(object[property_name], initial_value),
+                    &quot;assert_readonly&quot;, description,
+                    &quot;changing property ${p} succeeded&quot;,
+                    {p:property_name});
+         } finally {
+             object[property_name] = initial_value;
+         }
+    };
+    expose(assert_readonly, &quot;assert_readonly&quot;);
+
+    function assert_throws(code, func, description)
+    {
+        try {
+            func.call(this);
+            assert(false, &quot;assert_throws&quot;, description,
+                   &quot;${func} did not throw&quot;, {func:func});
+        } catch (e) {
+            if (e instanceof AssertionError) {
+                throw e;
+            }
+            if (code === null) {
+                return;
+            }
+            if (typeof code === &quot;object&quot;) {
+                assert(typeof e == &quot;object&quot; &amp;&amp; &quot;name&quot; in e &amp;&amp; e.name == code.name,
+                       &quot;assert_throws&quot;, description,
+                       &quot;${func} threw ${actual} (${actual_name}) expected ${expected} (${expected_name})&quot;,
+                                    {func:func, actual:e, actual_name:e.name,
+                                     expected:code,
+                                     expected_name:code.name});
+                return;
+            }
+
+            var code_name_map = {
+                INDEX_SIZE_ERR: 'IndexSizeError',
+                HIERARCHY_REQUEST_ERR: 'HierarchyRequestError',
+                WRONG_DOCUMENT_ERR: 'WrongDocumentError',
+                INVALID_CHARACTER_ERR: 'InvalidCharacterError',
+                NO_MODIFICATION_ALLOWED_ERR: 'NoModificationAllowedError',
+                NOT_FOUND_ERR: 'NotFoundError',
+                NOT_SUPPORTED_ERR: 'NotSupportedError',
+                INVALID_STATE_ERR: 'InvalidStateError',
+                SYNTAX_ERR: 'SyntaxError',
+                INVALID_MODIFICATION_ERR: 'InvalidModificationError',
+                NAMESPACE_ERR: 'NamespaceError',
+                INVALID_ACCESS_ERR: 'InvalidAccessError',
+                TYPE_MISMATCH_ERR: 'TypeMismatchError',
+                SECURITY_ERR: 'SecurityError',
+                NETWORK_ERR: 'NetworkError',
+                ABORT_ERR: 'AbortError',
+                URL_MISMATCH_ERR: 'URLMismatchError',
+                QUOTA_EXCEEDED_ERR: 'QuotaExceededError',
+                TIMEOUT_ERR: 'TimeoutError',
+                INVALID_NODE_TYPE_ERR: 'InvalidNodeTypeError',
+                DATA_CLONE_ERR: 'DataCloneError'
+            };
+
+            var name = code in code_name_map ? code_name_map[code] : code;
+
+            var name_code_map = {
+                IndexSizeError: 1,
+                HierarchyRequestError: 3,
+                WrongDocumentError: 4,
+                InvalidCharacterError: 5,
+                NoModificationAllowedError: 7,
+                NotFoundError: 8,
+                NotSupportedError: 9,
+                InvalidStateError: 11,
+                SyntaxError: 12,
+                InvalidModificationError: 13,
+                NamespaceError: 14,
+                InvalidAccessError: 15,
+                TypeMismatchError: 17,
+                SecurityError: 18,
+                NetworkError: 19,
+                AbortError: 20,
+                URLMismatchError: 21,
+                QuotaExceededError: 22,
+                TimeoutError: 23,
+                InvalidNodeTypeError: 24,
+                DataCloneError: 25,
+
+                UnknownError: 0,
+                ConstraintError: 0,
+                DataError: 0,
+                TransactionInactiveError: 0,
+                ReadOnlyError: 0,
+                VersionError: 0
+            };
+
+            if (!(name in name_code_map)) {
+                throw new AssertionError('Test bug: unrecognized DOMException code &quot;' + code + '&quot; passed to assert_throws()');
+            }
+
+            var required_props = { code: name_code_map[name] };
+
+            if (required_props.code === 0
+            || (&quot;name&quot; in e &amp;&amp; e.name !== e.name.toUpperCase() &amp;&amp; e.name !== &quot;DOMException&quot;)) {
+                // New style exception: also test the name property.
+                required_props.name = name;
+            }
+
+            //We'd like to test that e instanceof the appropriate interface,
+            //but we can't, because we don't know what window it was created
+            //in.  It might be an instanceof the appropriate interface on some
+            //unknown other window.  TODO: Work around this somehow?
+
+            assert(typeof e == &quot;object&quot;,
+                   &quot;assert_throws&quot;, description,
+                   &quot;${func} threw ${e} with type ${type}, not an object&quot;,
+                   {func:func, e:e, type:typeof e});
+
+            for (var prop in required_props) {
+                assert(typeof e == &quot;object&quot; &amp;&amp; prop in e &amp;&amp; e[prop] == required_props[prop],
+                       &quot;assert_throws&quot;, description,
+                       &quot;${func} threw ${e} that is not a DOMException &quot; + code + &quot;: property ${prop} is equal to ${actual}, expected ${expected}&quot;,
+                       {func:func, e:e, prop:prop, actual:e[prop], expected:required_props[prop]});
+            }
+        }
+    }
+    expose(assert_throws, &quot;assert_throws&quot;);
+
+    function assert_unreached(description) {
+         assert(false, &quot;assert_unreached&quot;, description,
+                &quot;Reached unreachable code&quot;);
+    }
+    expose(assert_unreached, &quot;assert_unreached&quot;);
+
+    function assert_any(assert_func, actual, expected_array)
+    {
+        var args = [].slice.call(arguments, 3)
+        var errors = []
+        var passed = false;
+        forEach(expected_array,
+                function(expected)
+                {
+                    try {
+                        assert_func.apply(this, [actual, expected].concat(args))
+                        passed = true;
+                    } catch (e) {
+                        errors.push(e.message);
+                    }
+                });
+        if (!passed) {
+            throw new AssertionError(errors.join(&quot;\n\n&quot;));
+        }
+    }
+    expose(assert_any, &quot;assert_any&quot;);
+
+    function Test(name, properties)
+    {
+        this.name = name;
+
+        this.phases = {
+            INITIAL:0,
+            STARTED:1,
+            HAS_RESULT:2,
+            COMPLETE:3
+        };
+        this.phase = this.phases.INITIAL;
+
+        this.status = this.NOTRUN;
+        this.timeout_id = null;
+
+        this.properties = properties;
+        var timeout = properties.timeout ? properties.timeout : settings.test_timeout
+        if (timeout != null) {
+            this.timeout_length = timeout * tests.timeout_multiplier;
+        } else {
+            this.timeout_length = null;
+        }
+
+        this.message = null;
+
+        var this_obj = this;
+        this.steps = [];
+
+        this.cleanup_callbacks = [];
+
+        tests.push(this);
+    }
+
+    Test.statuses = {
+        PASS:0,
+        FAIL:1,
+        TIMEOUT:2,
+        NOTRUN:3
+    };
+
+    Test.prototype = merge({}, Test.statuses);
+
+    Test.prototype.structured_clone = function()
+    {
+        if (!this._structured_clone) {
+            var msg = this.message;
+            msg = msg ? String(msg) : msg;
+            this._structured_clone = merge({
+                name:String(this.name),
+                status:this.status,
+                message:msg
+            }, Test.statuses);
+        }
+        return this._structured_clone;
+    };
+
+    Test.prototype.step = function(func, this_obj)
+    {
+        if (this.phase &gt; this.phases.STARTED) {
+            return;
+        }
+        this.phase = this.phases.STARTED;
+        //If we don't get a result before the harness times out that will be a test timout
+        this.set_status(this.TIMEOUT, &quot;Test timed out&quot;);
+
+        tests.started = true;
+
+        if (this.timeout_id === null) {
+            this.set_timeout();
+        }
+
+        this.steps.push(func);
+
+        if (arguments.length === 1) {
+            this_obj = this;
+        }
+
+        try {
+            return func.apply(this_obj, Array.prototype.slice.call(arguments, 2));
+        } catch (e) {
+            if (this.phase &gt;= this.phases.HAS_RESULT) {
+                return;
+            }
+            var message = (typeof e === &quot;object&quot; &amp;&amp; e !== null) ? e.message : e;
+            if (typeof e.stack != &quot;undefined&quot; &amp;&amp; typeof e.message == &quot;string&quot;) {
+                //Try to make it more informative for some exceptions, at least
+                //in Gecko and WebKit.  This results in a stack dump instead of
+                //just errors like &quot;Cannot read property 'parentNode' of null&quot;
+                //or &quot;root is null&quot;.  Makes it a lot longer, of course.
+                message += &quot;(stack: &quot; + e.stack + &quot;)&quot;;
+            }
+            this.set_status(this.FAIL, message);
+            this.phase = this.phases.HAS_RESULT;
+            this.done();
+        }
+    };
+
+    Test.prototype.step_func = function(func, this_obj)
+    {
+        var test_this = this;
+
+        if (arguments.length === 1) {
+            this_obj = test_this;
+        }
+
+        return function()
+        {
+            test_this.step.apply(test_this, [func, this_obj].concat(
+                Array.prototype.slice.call(arguments)));
+        };
+    };
+
+    Test.prototype.step_func_done = function(func, this_obj)
+    {
+        var test_this = this;
+
+        if (arguments.length === 1) {
+            this_obj = test_this;
+        }
+
+        return function()
+        {
+            if (func) {
+                test_this.step.apply(test_this, [func, this_obj].concat(
+                    Array.prototype.slice.call(arguments)));
+            }
+            test_this.done();
+        };
+    }
+
+    Test.prototype.add_cleanup = function(callback) {
+        this.cleanup_callbacks.push(callback);
+    }
+
+    Test.prototype.set_timeout = function()
+    {
+        if (this.timeout_length !== null) {
+            var this_obj = this;
+            this.timeout_id = setTimeout(function()
+                                         {
+                                             this_obj.timeout();
+                                         }, this.timeout_length);
+        }
+    }
+
+    Test.prototype.set_status = function(status, message)
+    {
+        this.status = status;
+        this.message = message;
+    }
+
+    Test.prototype.timeout = function()
+    {
+        this.timeout_id = null;
+        this.set_status(this.TIMEOUT, &quot;Test timed out&quot;)
+        this.phase = this.phases.HAS_RESULT;
+        this.done();
+    };
+
+    Test.prototype.done = function()
+    {
+        if (this.phase == this.phases.COMPLETE) {
+            return;
+        }
+
+        if (this.phase &lt;= this.phases.STARTED) {
+            this.set_status(this.PASS, null);
+        }
+
+        if (this.status == this.NOTRUN) {
+            alert(this.phase);
+        }
+
+        this.phase = this.phases.COMPLETE;
+
+        clearTimeout(this.timeout_id);
+        tests.result(this);
+        this.cleanup();
+    };
+
+    Test.prototype.cleanup = function() {
+        forEach(this.cleanup_callbacks,
+                function(cleanup_callback) {
+                    cleanup_callback()
+                });
+    }
+
+    /*
+     * Harness
+     */
+
+    function TestsStatus()
+    {
+        this.status = null;
+        this.message = null;
+    }
+
+    TestsStatus.statuses = {
+        OK:0,
+        ERROR:1,
+        TIMEOUT:2
+    };
+
+    TestsStatus.prototype = merge({}, TestsStatus.statuses);
+
+    TestsStatus.prototype.structured_clone = function()
+    {
+        if (!this._structured_clone) {
+            var msg = this.message;
+            msg = msg ? String(msg) : msg;
+            this._structured_clone = merge({
+                status:this.status,
+                message:msg
+            }, TestsStatus.statuses);
+        }
+        return this._structured_clone;
+    };
+
+    function Tests()
+    {
+        this.tests = [];
+        this.num_pending = 0;
+
+        this.phases = {
+            INITIAL:0,
+            SETUP:1,
+            HAVE_TESTS:2,
+            HAVE_RESULTS:3,
+            COMPLETE:4
+        };
+        this.phase = this.phases.INITIAL;
+
+        this.properties = {};
+
+        //All tests can't be done until the load event fires
+        this.all_loaded = false;
+        this.wait_for_finish = false;
+        this.processing_callbacks = false;
+
+        this.allow_uncaught_exception = false;
+
+        this.timeout_multiplier = 1;
+        this.timeout_length = this.get_timeout();
+        this.timeout_id = null;
+
+        this.start_callbacks = [];
+        this.test_done_callbacks = [];
+        this.all_done_callbacks = [];
+
+        this.status = new TestsStatus();
+
+        var this_obj = this;
+
+        on_event(window, &quot;load&quot;,
+                 function()
+                 {
+                     this_obj.all_loaded = true;
+                     if (this_obj.all_done())
+                     {
+                         this_obj.complete();
+                     }
+                 });
+
+        this.set_timeout();
+    }
+
+    Tests.prototype.setup = function(func, properties)
+    {
+        if (this.phase &gt;= this.phases.HAVE_RESULTS) {
+            return;
+        }
+
+        if (this.phase &lt; this.phases.SETUP) {
+            this.phase = this.phases.SETUP;
+        }
+
+        this.properties = properties;
+
+        for (var p in properties) {
+            if (properties.hasOwnProperty(p)) {
+                var value = properties[p]
+                if (p == &quot;allow_uncaught_exception&quot;) {
+                    this.allow_uncaught_exception = value;
+                } else if (p == &quot;explicit_done&quot; &amp;&amp; value) {
+                    this.wait_for_finish = true;
+                } else if (p == &quot;explicit_timeout&quot; &amp;&amp; value) {
+                    this.timeout_length = null;
+                    if (this.timeout_id)
+                    {
+                        clearTimeout(this.timeout_id);
+                    }
+                } else if (p == &quot;timeout_multiplier&quot;) {
+                    this.timeout_multiplier = value;
+                }
+            }
+        }
+
+        if (func) {
+            try {
+                func();
+            } catch (e) {
+                this.status.status = this.status.ERROR;
+                this.status.message = String(e);
+            };
+        }
+        this.set_timeout();
+    };
+
+    Tests.prototype.get_timeout = function()
+    {
+        var metas = document.getElementsByTagName(&quot;meta&quot;);
+        for (var i = 0; i &lt; metas.length; i++) {
+            if (metas[i].name == &quot;timeout&quot;) {
+                if (metas[i].content == &quot;long&quot;) {
+                    return settings.harness_timeout.long;
+                }
+                break;
+            }
+        }
+        return settings.harness_timeout.normal;
+    }
+
+    Tests.prototype.set_timeout = function()
+    {
+        var this_obj = this;
+        clearTimeout(this.timeout_id);
+        if (this.timeout_length !== null) {
+            this.timeout_id = setTimeout(function() {
+                                             this_obj.timeout();
+                                         }, this.timeout_length);
+        }
+    };
+
+    Tests.prototype.timeout = function() {
+        this.status.status = this.status.TIMEOUT;
+        this.complete();
+    };
+
+    Tests.prototype.end_wait = function()
+    {
+        this.wait_for_finish = false;
+        if (this.all_done()) {
+            this.complete();
+        }
+    };
+
+    Tests.prototype.push = function(test)
+    {
+        if (this.phase &lt; this.phases.HAVE_TESTS) {
+            this.start();
+        }
+        this.num_pending++;
+        this.tests.push(test);
+    };
+
+    Tests.prototype.all_done = function() {
+        return (this.all_loaded &amp;&amp; this.num_pending === 0 &amp;&amp;
+                !this.wait_for_finish &amp;&amp; !this.processing_callbacks);
+    };
+
+    Tests.prototype.start = function() {
+        this.phase = this.phases.HAVE_TESTS;
+        this.notify_start();
+    };
+
+    Tests.prototype.notify_start = function() {
+        var this_obj = this;
+        forEach (this.start_callbacks,
+                 function(callback)
+                 {
+                     callback(this_obj.properties);
+                 });
+        forEach_windows(
+                function(w, is_same_origin)
+                {
+                    if (is_same_origin &amp;&amp; w.start_callback) {
+                        try {
+                            w.start_callback(this_obj.properties);
+                        } catch (e) {
+                            if (debug) {
+                                throw e;
+                            }
+                        }
+                    }
+                    if (supports_post_message(w) &amp;&amp; w !== self) {
+                        w.postMessage({
+                            type: &quot;start&quot;,
+                            properties: this_obj.properties
+                        }, &quot;*&quot;);
+                    }
+                });
+    };
+
+    Tests.prototype.result = function(test)
+    {
+        if (this.phase &gt; this.phases.HAVE_RESULTS) {
+            return;
+        }
+        this.phase = this.phases.HAVE_RESULTS;
+        this.num_pending--;
+        this.notify_result(test);
+    };
+
+    Tests.prototype.notify_result = function(test) {
+        var this_obj = this;
+        this.processing_callbacks = true;
+        forEach(this.test_done_callbacks,
+                function(callback)
+                {
+                    callback(test, this_obj);
+                });
+
+        forEach_windows(
+                function(w, is_same_origin)
+                {
+                    if (is_same_origin &amp;&amp; w.result_callback) {
+                        try {
+                            w.result_callback(test);
+                        } catch (e) {
+                            if (debug) {
+                                throw e;
+                            }
+                        }
+                    }
+                    if (supports_post_message(w) &amp;&amp; w !== self) {
+                        w.postMessage({
+                            type: &quot;result&quot;,
+                            test: test.structured_clone()
+                        }, &quot;*&quot;);
+                    }
+                });
+        this.processing_callbacks = false;
+        if (this_obj.all_done()) {
+            this_obj.complete();
+        }
+    };
+
+    Tests.prototype.complete = function() {
+        if (this.phase === this.phases.COMPLETE) {
+            return;
+        }
+        this.phase = this.phases.COMPLETE;
+        var this_obj = this;
+        this.tests.forEach(
+            function(x)
+            {
+                if (x.status === x.NOTRUN) {
+                    this_obj.notify_result(x);
+                    x.cleanup();
+                }
+            }
+        );
+        this.notify_complete();
+    };
+
+    Tests.prototype.notify_complete = function()
+    {
+        clearTimeout(this.timeout_id);
+        var this_obj = this;
+        var tests = map(this_obj.tests,
+                        function(test)
+                        {
+                            return test.structured_clone();
+                        });
+        if (this.status.status === null) {
+            this.status.status = this.status.OK;
+        }
+
+        forEach (this.all_done_callbacks,
+                 function(callback)
+                 {
+                     callback(this_obj.tests, this_obj.status);
+                 });
+
+        forEach_windows(
+                function(w, is_same_origin)
+                {
+                    if (is_same_origin &amp;&amp; w.completion_callback) {
+                        try {
+                            w.completion_callback(this_obj.tests, this_obj.status);
+                        } catch (e) {
+                            if (debug) {
+                                throw e;
+                            }
+                        }
+                    }
+                    if (supports_post_message(w) &amp;&amp; w !== self) {
+                        w.postMessage({
+                            type: &quot;complete&quot;,
+                            tests: tests,
+                            status: this_obj.status.structured_clone()
+                        }, &quot;*&quot;);
+                    }
+                });
+    };
+
+    var tests = new Tests();
+
+    window.onerror = function(msg) {
+        if (!tests.allow_uncaught_exception) {
+            tests.status.status = tests.status.ERROR;
+            tests.status.message = msg;
+            tests.complete();
+        }
+    }
+
+    function timeout() {
+        if (tests.timeout_length === null) {
+            tests.timeout();
+        }
+    }
+    expose(timeout, 'timeout');
+
+    function add_start_callback(callback) {
+        tests.start_callbacks.push(callback);
+    }
+
+    function add_result_callback(callback)
+    {
+        tests.test_done_callbacks.push(callback);
+    }
+
+    function add_completion_callback(callback)
+    {
+       tests.all_done_callbacks.push(callback);
+    }
+
+    expose(add_start_callback, 'add_start_callback');
+    expose(add_result_callback, 'add_result_callback');
+    expose(add_completion_callback, 'add_completion_callback');
+
+    /*
+     * Output listener
+    */
+
+    function Output() {
+        this.output_document = document;
+        this.output_node = null;
+        this.done_count = 0;
+        this.enabled = settings.output;
+        this.phase = this.INITIAL;
+    }
+
+    Output.prototype.INITIAL = 0;
+    Output.prototype.STARTED = 1;
+    Output.prototype.HAVE_RESULTS = 2;
+    Output.prototype.COMPLETE = 3;
+
+    Output.prototype.setup = function(properties) {
+        if (this.phase &gt; this.INITIAL) {
+            return;
+        }
+
+        //If output is disabled in testharnessreport.js the test shouldn't be
+        //able to override that
+        this.enabled = this.enabled &amp;&amp; (properties.hasOwnProperty(&quot;output&quot;) ?
+                                        properties.output : settings.output);
+    };
+
+    Output.prototype.init = function(properties)
+    {
+        if (this.phase &gt;= this.STARTED) {
+            return;
+        }
+        if (properties.output_document) {
+            this.output_document = properties.output_document;
+        } else {
+            this.output_document = document;
+        }
+        this.phase = this.STARTED;
+    };
+
+    Output.prototype.resolve_log = function()
+    {
+        var output_document;
+        if (typeof this.output_document === &quot;function&quot;) {
+            output_document = this.output_document.apply(undefined);
+        } else {
+            output_document = this.output_document;
+        }
+        if (!output_document) {
+            return;
+        }
+        var node = output_document.getElementById(&quot;log&quot;);
+        if (node) {
+            this.output_document = output_document;
+            this.output_node = node;
+        }
+    };
+
+    Output.prototype.show_status = function(test)
+    {
+        if (this.phase &lt; this.STARTED) {
+            this.init();
+        }
+        if (!this.enabled) {
+            return;
+        }
+        if (this.phase &lt; this.HAVE_RESULTS) {
+            this.resolve_log();
+            this.phase = this.HAVE_RESULTS;
+        }
+        this.done_count++;
+        if (this.output_node) {
+            if (this.done_count &lt; 100
+            || (this.done_count &lt; 1000 &amp;&amp; this.done_count % 100 == 0)
+            || this.done_count % 1000 == 0) {
+                this.output_node.textContent = &quot;Running, &quot;
+                    + this.done_count + &quot; complete, &quot;
+                    + tests.num_pending + &quot; remain&quot;;
+            }
+        }
+    };
+
+    Output.prototype.show_results = function (tests, harness_status)
+    {
+        if (this.phase &gt;= this.COMPLETE) {
+            return;
+        }
+        if (!this.enabled) {
+            return;
+        }
+        if (!this.output_node) {
+            this.resolve_log();
+        }
+        this.phase = this.COMPLETE;
+
+        var log = this.output_node;
+        if (!log) {
+            return;
+        }
+        var output_document = this.output_document;
+
+        while (log.lastChild) {
+            log.removeChild(log.lastChild);
+        }
+
+        if (script_prefix != null) {
+            var stylesheet = output_document.createElementNS(xhtml_ns, &quot;link&quot;);
+            stylesheet.setAttribute(&quot;rel&quot;, &quot;stylesheet&quot;);
+            stylesheet.setAttribute(&quot;href&quot;, script_prefix + &quot;testharness.css&quot;);
+            var heads = output_document.getElementsByTagName(&quot;head&quot;);
+            if (heads.length) {
+                heads[0].appendChild(stylesheet);
+            }
+        }
+
+        var status_text_harness = {};
+        status_text_harness[harness_status.OK] = &quot;OK&quot;;
+        status_text_harness[harness_status.ERROR] = &quot;Error&quot;;
+        status_text_harness[harness_status.TIMEOUT] = &quot;Timeout&quot;;
+
+        var status_text = {};
+        status_text[Test.prototype.PASS] = &quot;Pass&quot;;
+        status_text[Test.prototype.FAIL] = &quot;Fail&quot;;
+        status_text[Test.prototype.TIMEOUT] = &quot;Timeout&quot;;
+        status_text[Test.prototype.NOTRUN] = &quot;Not Run&quot;;
+
+        var status_number = {};
+        forEach(tests,
+                function(test) {
+                    var status = status_text[test.status];
+                    if (status_number.hasOwnProperty(status)) {
+                        status_number[status] += 1;
+                    } else {
+                        status_number[status] = 1;
+                    }
+                });
+
+        function status_class(status)
+        {
+            return status.replace(/\s/g, '').toLowerCase();
+        }
+
+        var summary_template = [&quot;section&quot;, {&quot;id&quot;:&quot;summary&quot;},
+                                [&quot;h2&quot;, {}, &quot;Summary&quot;],
+                                function(vars)
+                                {
+                                    if (harness_status.status === harness_status.OK) {
+                                        return null;
+                                    }
+
+                                    var status = status_text_harness[harness_status.status];
+                                    var rv = [[&quot;p&quot;, {&quot;class&quot;:status_class(status)}]];
+
+                                    if (harness_status.status === harness_status.ERROR) {
+                                        rv[0].push(&quot;Harness encountered an error:&quot;);
+                                        rv.push([&quot;pre&quot;, {}, harness_status.message]);
+                                    } else if (harness_status.status === harness_status.TIMEOUT) {
+                                        rv[0].push(&quot;Harness timed out.&quot;);
+                                    } else {
+                                        rv[0].push(&quot;Harness got an unexpected status.&quot;);
+                                    }
+
+                                    return rv;
+                                },
+                                [&quot;p&quot;, {}, &quot;Found ${num_tests} tests&quot;],
+                                function(vars) {
+                                    var rv = [[&quot;div&quot;, {}]];
+                                    var i = 0;
+                                    while (status_text.hasOwnProperty(i)) {
+                                        if (status_number.hasOwnProperty(status_text[i])) {
+                                            var status = status_text[i];
+                                            rv[0].push([&quot;div&quot;, {&quot;class&quot;:status_class(status)},
+                                                        [&quot;label&quot;, {},
+                                                         [&quot;input&quot;, {type:&quot;checkbox&quot;, checked:&quot;checked&quot;}],
+                                                         status_number[status] + &quot; &quot; + status]]);
+                                        }
+                                        i++;
+                                    }
+                                    return rv;
+                                }];
+
+        log.appendChild(render(summary_template, {num_tests:tests.length}, output_document));
+
+        forEach(output_document.querySelectorAll(&quot;section#summary label&quot;),
+                function(element)
+                {
+                    on_event(element, &quot;click&quot;,
+                             function(e)
+                             {
+                                 if (output_document.getElementById(&quot;results&quot;) === null) {
+                                     e.preventDefault();
+                                     return;
+                                 }
+                                 var result_class = element.parentNode.getAttribute(&quot;class&quot;);
+                                 var style_element = output_document.querySelector(&quot;style#hide-&quot; + result_class);
+                                 var input_element = element.querySelector(&quot;input&quot;);
+                                 if (!style_element &amp;&amp; !input_element.checked) {
+                                     style_element = output_document.createElementNS(xhtml_ns, &quot;style&quot;);
+                                     style_element.id = &quot;hide-&quot; + result_class;
+                                     style_element.textContent = &quot;table#results &gt; tbody &gt; tr.&quot;+result_class+&quot;{display:none}&quot;;
+                                     output_document.body.appendChild(style_element);
+                                 } else if (style_element &amp;&amp; input_element.checked) {
+                                     style_element.parentNode.removeChild(style_element);
+                                 }
+                             });
+                });
+
+        // This use of innerHTML plus manual escaping is not recommended in
+        // general, but is necessary here for performance.  Using textContent
+        // on each individual &lt;td&gt; adds tens of seconds of execution time for
+        // large test suites (tens of thousands of tests).
+        function escape_html(s)
+        {
+            return s.replace(/\&amp;/g, &quot;&amp;amp;&quot;)
+                .replace(/&lt;/g, &quot;&amp;lt;&quot;)
+                .replace(/&quot;/g, &quot;&amp;quot;&quot;)
+                .replace(/'/g, &quot;&amp;#39;&quot;);
+        }
+
+        function has_assertions()
+        {
+            for (var i = 0; i &lt; tests.length; i++) {
+                if (tests[i].properties.hasOwnProperty(&quot;assert&quot;)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        function get_assertion(test)
+        {
+            if (test.properties.hasOwnProperty(&quot;assert&quot;)) {
+                if (Array.isArray(test.properties.assert)) {
+                    return test.properties.assert.join(' ');
+                }
+                return test.properties.assert;
+            }
+            return '';
+        }
+
+        log.appendChild(document.createElementNS(xhtml_ns, &quot;section&quot;));
+        var assertions = has_assertions();
+        var html = &quot;&lt;h2&gt;Details&lt;/h2&gt;&lt;table id='results' &quot; + (assertions ? &quot;class='assertions'&quot; : &quot;&quot; ) + &quot;&gt;&quot;
+            + &quot;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Result&lt;/th&gt;&lt;th&gt;Test Name&lt;/th&gt;&quot;
+            + (assertions ? &quot;&lt;th&gt;Assertion&lt;/th&gt;&quot; : &quot;&quot;)
+            + &quot;&lt;th&gt;Message&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&quot;
+            + &quot;&lt;tbody&gt;&quot;;
+        for (var i = 0; i &lt; tests.length; i++) {
+            html += '&lt;tr class=&quot;'
+                + escape_html(status_class(status_text[tests[i].status]))
+                + '&quot;&gt;&lt;td&gt;'
+                + escape_html(status_text[tests[i].status])
+                + &quot;&lt;/td&gt;&lt;td&gt;&quot;
+                + escape_html(tests[i].name)
+                + &quot;&lt;/td&gt;&lt;td&gt;&quot;
+                + (assertions ? escape_html(get_assertion(tests[i])) + &quot;&lt;/td&gt;&lt;td&gt;&quot; : &quot;&quot;)
+                + escape_html(tests[i].message ? tests[i].message : &quot; &quot;)
+                + &quot;&lt;/td&gt;&lt;/tr&gt;&quot;;
+        }
+        html += &quot;&lt;/tbody&gt;&lt;/table&gt;&quot;;
+        try {
+            log.lastChild.innerHTML = html;
+        } catch (e) {
+            log.appendChild(document.createElementNS(xhtml_ns, &quot;p&quot;))
+               .textContent = &quot;Setting innerHTML for the log threw an exception.&quot;;
+            log.appendChild(document.createElementNS(xhtml_ns, &quot;pre&quot;))
+               .textContent = html;
+        }
+    };
+
+    var output = new Output();
+    add_start_callback(function (properties) {output.init(properties);});
+    add_result_callback(function (test) {output.show_status(tests);});
+    add_completion_callback(function (tests, harness_status) {output.show_results(tests, harness_status);});
+
+    /*
+     * Template code
+     *
+     * A template is just a javascript structure. An element is represented as:
+     *
+     * [tag_name, {attr_name:attr_value}, child1, child2]
+     *
+     * the children can either be strings (which act like text nodes), other templates or
+     * functions (see below)
+     *
+     * A text node is represented as
+     *
+     * [&quot;{text}&quot;, value]
+     *
+     * String values have a simple substitution syntax; ${foo} represents a variable foo.
+     *
+     * It is possible to embed logic in templates by using a function in a place where a
+     * node would usually go. The function must either return part of a template or null.
+     *
+     * In cases where a set of nodes are required as output rather than a single node
+     * with children it is possible to just use a list
+     * [node1, node2, node3]
+     *
+     * Usage:
+     *
+     * render(template, substitutions) - take a template and an object mapping
+     * variable names to parameters and return either a DOM node or a list of DOM nodes
+     *
+     * substitute(template, substitutions) - take a template and variable mapping object,
+     * make the variable substitutions and return the substituted template
+     *
+     */
+
+    function is_single_node(template)
+    {
+        return typeof template[0] === &quot;string&quot;;
+    }
+
+    function substitute(template, substitutions)
+    {
+        if (typeof template === &quot;function&quot;) {
+            var replacement = template(substitutions);
+            if (!replacement) {
+                return null;
+            }
+
+            return substitute(replacement, substitutions);
+        }
+
+        if (is_single_node(template)) {
+            return substitute_single(template, substitutions);
+        }
+
+        return filter(map(template, function(x) {
+                              return substitute(x, substitutions);
+                          }), function(x) {return x !== null;});
+    }
+
+    function substitute_single(template, substitutions)
+    {
+        var substitution_re = /\${([^ }]*)}/g;
+
+        function do_substitution(input) {
+            var components = input.split(substitution_re);
+            var rv = [];
+            for (var i = 0; i &lt; components.length; i += 2) {
+                rv.push(components[i]);
+                if (components[i + 1]) {
+                    rv.push(String(substitutions[components[i + 1]]));
+                }
+            }
+            return rv;
+        }
+
+        var rv = [];
+        rv.push(do_substitution(String(template[0])).join(&quot;&quot;));
+
+        if (template[0] === &quot;{text}&quot;) {
+            substitute_children(template.slice(1), rv);
+        } else {
+            substitute_attrs(template[1], rv);
+            substitute_children(template.slice(2), rv);
+        }
+
+        function substitute_attrs(attrs, rv)
+        {
+            rv[1] = {};
+            for (var name in template[1]) {
+                if (attrs.hasOwnProperty(name)) {
+                    var new_name = do_substitution(name).join(&quot;&quot;);
+                    var new_value = do_substitution(attrs[name]).join(&quot;&quot;);
+                    rv[1][new_name] = new_value;
+                };
+            }
+        }
+
+        function substitute_children(children, rv)
+        {
+            for (var i = 0; i &lt; children.length; i++) {
+                if (children[i] instanceof Object) {
+                    var replacement = substitute(children[i], substitutions);
+                    if (replacement !== null) {
+                        if (is_single_node(replacement)) {
+                            rv.push(replacement);
+                        } else {
+                            extend(rv, replacement);
+                        }
+                    }
+                } else {
+                    extend(rv, do_substitution(String(children[i])));
+                }
+            }
+            return rv;
+        }
+
+        return rv;
+    }
+
+    function make_dom_single(template, doc)
+    {
+        var output_document = doc || document;
+        if (template[0] === &quot;{text}&quot;) {
+            var element = output_document.createTextNode(&quot;&quot;);
+            for (var i = 1; i &lt; template.length; i++) {
+                element.data += template[i];
+            }
+        } else {
+            var element = output_document.createElementNS(xhtml_ns, template[0]);
+            for (var name in template[1]) {
+                if (template[1].hasOwnProperty(name)) {
+                    element.setAttribute(name, template[1][name]);
+                }
+            }
+            for (var i = 2; i &lt; template.length; i++) {
+                if (template[i] instanceof Object) {
+                    var sub_element = make_dom(template[i]);
+                    element.appendChild(sub_element);
+                } else {
+                    var text_node = output_document.createTextNode(template[i]);
+                    element.appendChild(text_node);
+                }
+            }
+        }
+
+        return element;
+    }
+
+
+
+    function make_dom(template, substitutions, output_document)
+    {
+        if (is_single_node(template)) {
+            return make_dom_single(template, output_document);
+        }
+
+        return map(template, function(x) {
+                       return make_dom_single(x, output_document);
+                   });
+    }
+
+    function render(template, substitutions, output_document)
+    {
+        return make_dom(substitute(template, substitutions), output_document);
+    }
+
+    /*
+     * Utility funcions
+     */
+    function assert(expected_true, function_name, description, error, substitutions)
+    {
+        if (expected_true !== true) {
+            throw new AssertionError(make_message(function_name, description,
+                                                  error, substitutions));
+        }
+    }
+
+    function AssertionError(message)
+    {
+        this.message = message;
+    }
+
+    function make_message(function_name, description, error, substitutions)
+    {
+        for (var p in substitutions) {
+            if (substitutions.hasOwnProperty(p)) {
+                substitutions[p] = format_value(substitutions[p]);
+            }
+        }
+        var node_form = substitute([&quot;{text}&quot;, &quot;${function_name}: ${description}&quot; + error],
+                                   merge({function_name:function_name,
+                                          description:(description?description + &quot; &quot;:&quot;&quot;)},
+                                          substitutions));
+        return node_form.slice(1).join(&quot;&quot;);
+    }
+
+    function filter(array, callable, thisObj) {
+        var rv = [];
+        for (var i = 0; i &lt; array.length; i++) {
+            if (array.hasOwnProperty(i)) {
+                var pass = callable.call(thisObj, array[i], i, array);
+                if (pass) {
+                    rv.push(array[i]);
+                }
+            }
+        }
+        return rv;
+    }
+
+    function map(array, callable, thisObj)
+    {
+        var rv = [];
+        rv.length = array.length;
+        for (var i = 0; i &lt; array.length; i++) {
+            if (array.hasOwnProperty(i)) {
+                rv[i] = callable.call(thisObj, array[i], i, array);
+            }
+        }
+        return rv;
+    }
+
+    function extend(array, items)
+    {
+        Array.prototype.push.apply(array, items);
+    }
+
+    function forEach (array, callback, thisObj)
+    {
+        for (var i = 0; i &lt; array.length; i++) {
+            if (array.hasOwnProperty(i)) {
+                callback.call(thisObj, array[i], i, array);
+            }
+        }
+    }
+
+    function merge(a,b)
+    {
+        var rv = {};
+        var p;
+        for (p in a) {
+            rv[p] = a[p];
+        }
+        for (p in b) {
+            rv[p] = b[p];
+        }
+        return rv;
+    }
+
+    function expose(object, name)
+    {
+        var components = name.split(&quot;.&quot;);
+        var target = window;
+        for (var i = 0; i &lt; components.length - 1; i++) {
+            if (!(components[i] in target)) {
+                target[components[i]] = {};
+            }
+            target = target[components[i]];
+        }
+        target[components[components.length - 1]] = object;
+    }
+
+    function forEach_windows(callback) {
+        // Iterate of the the windows [self ... top, opener]. The callback is passed
+        // two objects, the first one is the windows object itself, the second one
+        // is a boolean indicating whether or not its on the same origin as the
+        // current window.
+        var cache = forEach_windows.result_cache;
+        if (!cache) {
+            cache = [[self, true]];
+            var w = self;
+            var i = 0;
+            var so;
+            var origins = location.ancestorOrigins;
+            while (w != w.parent) {
+                w = w.parent;
+                // In WebKit, calls to parent windows' properties that aren't on the same
+                // origin cause an error message to be displayed in the error console but
+                // don't throw an exception. This is a deviation from the current HTML5
+                // spec. See: https://bugs.webkit.org/show_bug.cgi?id=43504
+                // The problem with WebKit's behavior is that it pollutes the error console
+                // with error messages that can't be caught.
+                //
+                // This issue can be mitigated by relying on the (for now) proprietary
+                // `location.ancestorOrigins` property which returns an ordered list of
+                // the origins of enclosing windows. See:
+                // http://trac.webkit.org/changeset/113945.
+                if (origins) {
+                    so = (location.origin == origins[i]);
+                } else {
+                    so = is_same_origin(w);
+                }
+                cache.push([w, so]);
+                i++;
+            }
+            w = window.opener;
+            if (w) {
+                // window.opener isn't included in the `location.ancestorOrigins` prop.
+                // We'll just have to deal with a simple check and an error msg on WebKit
+                // browsers in this case.
+                cache.push([w, is_same_origin(w)]);
+            }
+            forEach_windows.result_cache = cache;
+        }
+
+        forEach(cache,
+                function(a)
+                {
+                    callback.apply(null, a);
+                });
+    }
+
+    function is_same_origin(w) {
+        try {
+            'random_prop' in w;
+            return true;
+        } catch (e) {
+            return false;
+        }
+    }
+
+    function supports_post_message(w)
+    {
+        var supports;
+        var type;
+        // Given IE  implements postMessage across nested iframes but not across
+        // windows or tabs, you can't infer cross-origin communication from the presence
+        // of postMessage on the current window object only.
+        //
+        // Touching the postMessage prop on a window can throw if the window is
+        // not from the same origin AND post message is not supported in that
+        // browser. So just doing an existence test here won't do, you also need
+        // to wrap it in a try..cacth block.
+        try {
+            type = typeof w.postMessage;
+            if (type === &quot;function&quot;) {
+                supports = true;
+            }
+
+            // IE8 supports postMessage, but implements it as a host object which
+            // returns &quot;object&quot; as its `typeof`.
+            else if (type === &quot;object&quot;) {
+                supports = true;
+            }
+
+            // This is the case where postMessage isn't supported AND accessing a
+            // window property across origins does NOT throw (e.g. old Safari browser).
+            else {
+                supports = false;
+            }
+        } catch (e) {
+            // This is the case where postMessage isn't supported AND accessing a
+            // window property across origins throws (e.g. old Firefox browser).
+            supports = false;
+        }
+        return supports;
+    }
+})();
+// vim: set expandtab shiftwidth=4 tabstop=4:
</ins></span></pre></div>
<a id="trunkSourceWebCoreChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/ChangeLog (171032 => 171033)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/ChangeLog        2014-07-12 22:14:44 UTC (rev 171032)
+++ trunk/Source/WebCore/ChangeLog        2014-07-12 22:39:43 UTC (rev 171033)
</span><span class="lines">@@ -1,3 +1,138 @@
</span><ins>+2014-07-11  Jer Noble  &lt;jer.noble@apple.com&gt;
+
+        [MSE] http/tests/media/media-source/mediasource-duration.html is failing.
+        https://bugs.webkit.org/show_bug.cgi?id=134852
+
+        Reviewed by Eric Carlson.
+
+        Fixes the following tests:
+        http/tests/media/media-source/mediasource-config-change-mp4-a-bitrate.html
+        http/tests/media/media-source/mediasource-config-change-mp4-av-audio-bitrate.html
+        http/tests/media/media-source/mediasource-config-change-mp4-av-video-bitrate.html
+        http/tests/media/media-source/mediasource-config-change-mp4-v-bitrate.html
+        http/tests/media/media-source/mediasource-config-change-mp4-v-framerate.html
+        http/tests/media/media-source/mediasource-duration.html
+        http/tests/media/media-source/mediasource-play.html
+
+        The primary change necessary to fix the mediasource-duration.html test was to add support
+        for delaying the completion of a seek operation until the HTMLMediaElement's readyState
+        rises to &gt; HAVE_CURRENT_DATA. This is accomplished by modifying MediaSourcePrivate to have
+        waitForSeekCompleted() and seekCompleted() virtual methods. These are called by MediaSource
+        when a seek operation results in the current time moving outside the currently buffered time
+        ranges, and when an append operation results in the readyState changing, respectively.
+
+        A number of other drive-by fixes were necessary to get this test fully passing, as noted
+        below.
+
+        Make the MediaSource the primary owner of the media's duration, rather than the MediaSourcePrivate.
+        Move the MediaSourcePrivateClient pointer to the MediaSourcePrivate from the MediaPlayerPrivate, so
+        the MediaSource's duration can be retrieved.  While we're at it, do the same thing for buffered.
+
+        * Modules/mediasource/MediaSource.cpp:
+        (WebCore::MediaSource::MediaSource): Initialize m_duration.
+        (WebCore::MediaSource::duration): Simple accessor.
+        (WebCore::MediaSource::setDurationInternal): Bring 'duration change algorithm' up to spec.
+        (WebCore::MediaSource::setReadyState): Reset m_duration on close.
+        * Modules/mediasource/MediaSource.h:
+        * platform/graphics/MediaSourcePrivate.h:
+        * platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaSourceAVFObjC.mm:
+        (WebCore::MediaPlayerPrivateMediaSourceAVFObjC::load): Do not call setPrivateAndOpen().
+        (WebCore::MediaPlayerPrivateMediaSourceAVFObjC::durationDouble): Pass through to MediaSourcePrivateAVFObjC.
+        (WebCore::MediaPlayerPrivateMediaSourceAVFObjC::buffered): Ditto.
+        * platform/graphics/avfoundation/objc/MediaSourcePrivateAVFObjC.h:
+        * platform/graphics/avfoundation/objc/MediaSourcePrivateAVFObjC.mm:
+        (WebCore::MediaSourcePrivateAVFObjC::create): Call setPrivateAndOpen().
+        (WebCore::MediaSourcePrivateAVFObjC::MediaSourcePrivateAVFObjC): Set m_client.
+        (WebCore::MediaSourcePrivateAVFObjC::duration): Pass through to MediaSourcePrivateClient.
+        (WebCore::MediaSourcePrivateAVFObjC::buffered): Ditto.
+        (WebCore::MediaSourcePrivateAVFObjC::durationChanged): Pass through to MediaPlayerPrivateMediaSourceAVFObjC.
+        (WebCore::MediaSourcePrivateAVFObjC::setDuration): Deleted.
+        * platform/graphics/gstreamer/MediaSourceGStreamer.cpp:
+        (WebCore::MediaSourceGStreamer::open): Pass in MediaSourcePrivateClient.
+        (WebCore::MediaSourceGStreamer::MediaSourceGStreamer): Initialize m_mediaSource.
+        (WebCore::MediaSourceGStreamer::durationChanged): Retrieve the duration from MediaSourcePrivateClient.
+        (WebCore::MediaSourceGStreamer::markEndOfStream): Remove unnecssary ASSERT.
+        (WebCore::MediaSourceGStreamer::unmarkEndOfStream): Ditto.
+        (WebCore::MediaSourceGStreamer::setDuration): Deleted.
+        * platform/graphics/gstreamer/MediaSourceGStreamer.h:
+        * platform/mock/mediasource/MockMediaPlayerMediaSource.cpp:
+        (WebCore::MockMediaPlayerMediaSource::load): Do not call setPrivateAndOpen().
+        (WebCore::MockMediaPlayerMediaSource::buffered): Pass through to MockMediaSourcePrivate.
+        (WebCore::MockMediaPlayerMediaSource::durationDouble): Ditto.
+        (WebCore::MockMediaPlayerMediaSource::advanceCurrentTime): Ditto.
+        * platform/mock/mediasource/MockMediaSourcePrivate.cpp:
+        (WebCore::MockMediaSourcePrivate::create): Call setPrivateAndOpen().
+        (WebCore::MockMediaSourcePrivate::MockMediaSourcePrivate): Set m_client.
+        (WebCore::MockMediaSourcePrivate::duration): Pass through to MediaSourcePrivateClient.
+        (WebCore::MockMediaSourcePrivate::buffered): Ditto.
+        (WebCore::MockMediaSourcePrivate::durationChanged): Pass thorugh to MockMediaPlayerMediaSource.
+        (WebCore::MockMediaSourcePrivate::setDuration): Deleted.
+
+        Route seekToTime through MediaSource, rather than through MediaSourcePrivate, so that
+        the time can be compared against the buffered ranges, and trigger the delay of the seek
+        operation if necessary. Add a seekTimer to MediaPlayerPrivateMediaSourceAVFObjC, as this
+        guarantees the order of asynchronous operations, rather than callOnMainThread, which can
+        cause async operations to occur out of order.
+
+        * Modules/mediasource/MediaSource.cpp:
+        (WebCore::MediaSource::seekToTime): Bring up to spec.
+        (WebCore::MediaSource::completeSeek): Ditto.
+        (WebCore::MediaSource::monitorSourceBuffers): Call completeSeek() when appropriate.
+        * Modules/mediasource/SourceBuffer.cpp:
+        (WebCore::SourceBuffer::sourceBufferPrivateSeekToTime): Deleted.
+        (WebCore::SourceBuffer::seekToTime): Renamed from sourceBufferPrivateSeekToTime().
+        * platform/graphics/MediaSourcePrivate.h:
+        * platform/graphics/MediaSourcePrivateClient.h:
+        * platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaSourceAVFObjC.h:
+        * platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaSourceAVFObjC.mm:
+        (WebCore::MediaPlayerPrivateMediaSourceAVFObjC::MediaPlayerPrivateMediaSourceAVFObjC): Add seekTimer. Only
+            call timeChanged() if no longer seeking, thereby triggering a 'seeked' event.
+        (WebCore::MediaPlayerPrivateMediaSourceAVFObjC::~MediaPlayerPrivateMediaSourceAVFObjC): Clear m_seekTimer.
+        (WebCore::MediaPlayerPrivateMediaSourceAVFObjC::seekWithTolerance): Use m_seekTimer.
+        (WebCore::MediaPlayerPrivateMediaSourceAVFObjC::seekTimerFired): Call seekInternal.
+        (WebCore::MediaPlayerPrivateMediaSourceAVFObjC::seekInternal): Add logging.
+        (WebCore::MediaPlayerPrivateMediaSourceAVFObjC::waitForSeekCompleted): Added.
+        (WebCore::MediaPlayerPrivateMediaSourceAVFObjC::seekCompleted): Added; trigger 'seeked'.
+        (WebCore::MediaPlayerPrivateMediaSourceAVFObjC::setReadyState): No longer attempt to finish seek when
+            readyState changes here; this has been moved up to MediaSource.cpp.
+        * platform/graphics/avfoundation/objc/MediaSourcePrivateAVFObjC.h:
+        * platform/graphics/avfoundation/objc/MediaSourcePrivateAVFObjC.mm:
+        (WebCore::MediaSourcePrivateAVFObjC::waitForSeekCompleted): Pass through to MediaPlayerPrivateMediaSourceAVFObjC.
+        (WebCore::MediaSourcePrivateAVFObjC::seekCompleted): Ditto.
+        (WebCore::MediaSourcePrivateAVFObjC::seekToTime): Pass through to MediaSourcePrivateClient.
+        (WebCore::MediaSourcePrivateAVFObjC::fastSeekTimeForMediaTime): Ditto.
+        * platform/mock/mediasource/MockMediaPlayerMediaSource.cpp:
+        (WebCore::MockMediaPlayerMediaSource::MockMediaPlayerMediaSource): Initialize m_seekCompleted.
+        (WebCore::MockMediaPlayerMediaSource::seeking): Check for an uncompleted seek operation. 
+        (WebCore::MockMediaPlayerMediaSource::seekWithTolerance): Ditto.
+        (WebCore::MockMediaPlayerMediaSource::waitForSeekCompleted): Added.
+        (WebCore::MockMediaPlayerMediaSource::seekCompleted): Added; trigger 'seeked'.
+        * platform/mock/mediasource/MockMediaPlayerMediaSource.h:
+        * platform/mock/mediasource/MockMediaSourcePrivate.cpp:
+        (WebCore::MockMediaSourcePrivate::waitForSeekCompleted): Pass through to MockMediaPlayerMediaSource.
+        (WebCore::MockMediaSourcePrivate::seekCompleted): Ditto.
+        * platform/mock/mediasource/MockMediaSourcePrivate.h:
+
+        Drive-by fixes.
+
+        * Modules/mediasource/MediaSource.cpp:
+        (WebCore::MediaSource::streamEndedWithError): Re-order the steps in streamEndedWithError()
+            to avoid the MediaSource being closed and re-opened by the resulting duration change
+            operation.
+        * Modules/mediasource/MediaSource.h:
+        * Modules/mediasource/SourceBuffer.cpp:
+        (WebCore::SourceBuffer::remove): Added logging.
+        (WebCore::SourceBuffer::removeCodedFrames): Ditto.
+        (WebCore::SourceBuffer::hasFutureTime): Swap an ASSERT for an early-return; it's possible
+            for currentTime() to be outside of a buffered area.
+        * Modules/mediasource/SourceBuffer.h:
+        * html/HTMLMediaElement.cpp:
+        (WebCore::HTMLMediaElement::parseAttribute): Do not issue an additional 'timeupdate' event
+            after finishSeek() issues one of its own.
+        * platform/graphics/avfoundation/objc/SourceBufferPrivateAVFObjC.mm:
+        (WebCore::globalDataParserQueue): Allow parsing operations to happen concurrently on
+            background queues.
+
</ins><span class="cx"> 2014-07-12  Eric Carlson  &lt;eric.carlson@apple.com&gt;
</span><span class="cx"> 
</span><span class="cx">         [iOS] update control type when playback state changes
</span></span></pre></div>
<a id="trunkSourceWebCoreModulesmediasourceMediaSourcecpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/Modules/mediasource/MediaSource.cpp (171032 => 171033)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/Modules/mediasource/MediaSource.cpp        2014-07-12 22:14:44 UTC (rev 171032)
+++ trunk/Source/WebCore/Modules/mediasource/MediaSource.cpp        2014-07-12 22:39:43 UTC (rev 171033)
</span><span class="lines">@@ -76,6 +76,8 @@
</span><span class="cx"> MediaSource::MediaSource(ScriptExecutionContext&amp; context)
</span><span class="cx">     : ActiveDOMObject(&amp;context)
</span><span class="cx">     , m_mediaElement(0)
</span><ins>+    , m_duration(std::numeric_limits&lt;double&gt;::quiet_NaN())
+    , m_pendingSeekTime(MediaTime::invalidTime())
</ins><span class="cx">     , m_readyState(closedKeyword())
</span><span class="cx">     , m_asyncEventQueue(*this)
</span><span class="cx"> {
</span><span class="lines">@@ -128,7 +130,7 @@
</span><span class="cx"> 
</span><span class="cx"> double MediaSource::duration() const
</span><span class="cx"> {
</span><del>-    return isClosed() ? std::numeric_limits&lt;float&gt;::quiet_NaN() : m_private-&gt;duration().toDouble();
</del><ins>+    return m_duration;
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> double MediaSource::currentTime() const
</span><span class="lines">@@ -180,6 +182,60 @@
</span><span class="cx">     return PlatformTimeRanges::create(intersectionRanges-&gt;ranges());
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+void MediaSource::seekToTime(const MediaTime&amp; time)
+{
+    // 2.4.3 Seeking
+    // https://dvcs.w3.org/hg/html-media/raw-file/tip/media-source/media-source.html#mediasource-seeking
+
+    m_pendingSeekTime = time;
+
+    // Run the following steps as part of the &quot;Wait until the user agent has established whether or not the
+    // media data for the new playback position is available, and, if it is, until it has decoded enough data
+    // to play back that position&quot; step of the seek algorithm:
+    // 1. The media element looks for media segments containing the new playback position in each SourceBuffer
+    // object in activeSourceBuffers.
+    for (auto&amp; sourceBuffer : *m_activeSourceBuffers) {
+        // â†³ If one or more of the objects in activeSourceBuffers is missing media segments for the new
+        // playback position
+        if (!sourceBuffer-&gt;buffered()-&gt;ranges().contain(time)) {
+            // 1.1 Set the HTMLMediaElement.readyState attribute to HAVE_METADATA.
+            m_private-&gt;setReadyState(MediaPlayer::HaveMetadata);
+
+            // 1.2 The media element waits until an appendBuffer() or an appendStream() call causes the coded
+            // frame processing algorithm to set the HTMLMediaElement.readyState attribute to a value greater
+            // than HAVE_METADATA.
+            LOG(MediaSource, &quot;MediaSource::seekToTime(%p) - waitForSeekCompleted()&quot;, this);
+            m_private-&gt;waitForSeekCompleted();
+            return;
+        }
+        // â†³ Otherwise
+        // Continue
+    }
+
+    completeSeek();
+}
+
+void MediaSource::completeSeek()
+{
+    // 2.4.3 Seeking, ctd.
+    // https://dvcs.w3.org/hg/html-media/raw-file/tip/media-source/media-source.html#mediasource-seeking
+
+    ASSERT(m_pendingSeekTime.isValid());
+
+    // 2. The media element resets all decoders and initializes each one with data from the appropriate
+    // initialization segment.
+    // 3. The media element feeds coded frames from the active track buffers into the decoders starting
+    // with the closest random access point before the new playback position.
+    for (auto&amp; sourceBuffer : *m_activeSourceBuffers)
+        sourceBuffer-&gt;seekToTime(m_pendingSeekTime);
+
+    // 4. Resume the seek algorithm at the &quot;Await a stable state&quot; step.
+    m_private-&gt;seekCompleted();
+
+    m_pendingSeekTime = MediaTime::invalidTime();
+    monitorSourceBuffers();
+}
+
</ins><span class="cx"> void MediaSource::monitorSourceBuffers()
</span><span class="cx"> {
</span><span class="cx">     // 2.4.4 SourceBuffer Monitoring
</span><span class="lines">@@ -217,6 +273,9 @@
</span><span class="cx">         // 3. Playback may resume at this point if it was previously suspended by a transition to HAVE_CURRENT_DATA.
</span><span class="cx">         m_private-&gt;setReadyState(MediaPlayer::HaveEnoughData);
</span><span class="cx"> 
</span><ins>+        if (m_pendingSeekTime.isValid())
+            completeSeek();
+
</ins><span class="cx">         // 4. Abort these steps.
</span><span class="cx">         return;
</span><span class="cx">     }
</span><span class="lines">@@ -231,6 +290,9 @@
</span><span class="cx">         // 3. Playback may resume at this point if it was previously suspended by a transition to HAVE_CURRENT_DATA.
</span><span class="cx">         m_private-&gt;setReadyState(MediaPlayer::HaveFutureData);
</span><span class="cx"> 
</span><ins>+        if (m_pendingSeekTime.isValid())
+            completeSeek();
+
</ins><span class="cx">         // 4. Abort these steps.
</span><span class="cx">         return;
</span><span class="cx">     }
</span><span class="lines">@@ -246,7 +308,10 @@
</span><span class="cx">     // 3. Playback is suspended at this point since the media element doesn't have enough data to
</span><span class="cx">     // advance the media timeline.
</span><span class="cx">     m_private-&gt;setReadyState(MediaPlayer::HaveCurrentData);
</span><del>-    
</del><ins>+
+    if (m_pendingSeekTime.isValid())
+        completeSeek();
+
</ins><span class="cx">     // 4. Abort these steps.
</span><span class="cx"> }
</span><span class="cx"> 
</span><span class="lines">@@ -283,10 +348,38 @@
</span><span class="cx"> 
</span><span class="cx"> void MediaSource::setDurationInternal(double duration)
</span><span class="cx"> {
</span><del>-    m_private-&gt;setDuration(MediaTime::createWithDouble(duration));
</del><ins>+    // Duration Change Algorithm
+    // https://dvcs.w3.org/hg/html-media/raw-file/tip/media-source/media-source.html#duration-change-algorithm
+
+    // 1. If the current value of duration is equal to new duration, then return.
+    if (duration == m_duration)
+        return;
+
+    // 2. Set old duration to the current value of duration.
+    double oldDuration = m_duration;
+
+    // 3. Update duration to new duration.
+    m_duration = duration;
+
+    // 4. If the new duration is less than old duration, then call remove(new duration, old duration)
+    // on all objects in sourceBuffers.
+    if (!isnan(oldDuration) &amp;&amp; duration &lt; oldDuration) {
+        for (auto&amp; sourceBuffer : *m_sourceBuffers)
+            sourceBuffer-&gt;remove(duration, oldDuration, IGNORE_EXCEPTION);
+    }
+
+    // 5. If a user agent is unable to partially render audio frames or text cues that start before and end after the
+    // duration, then run the following steps:
+    // 5.1 Update new duration to the highest end time reported by the buffered attribute across all SourceBuffer objects
+    // in sourceBuffers.
+    // 5.2 Update duration to new duration.
+    // NOTE: Assume UA is able to partially render audio frames.
+
+    // 6. Update the media controller duration to new duration and run the HTMLMediaElement duration change algorithm.
+    LOG(MediaSource, &quot;MediaSource::setDurationInternal(%p) - duration(%g)&quot;, this, duration);
+    m_private-&gt;durationChanged();
</ins><span class="cx"> }
</span><span class="cx"> 
</span><del>-
</del><span class="cx"> void MediaSource::setReadyState(const AtomicString&amp; state)
</span><span class="cx"> {
</span><span class="cx">     ASSERT(state == openKeyword() || state == closedKeyword() || state == endedKeyword());
</span><span class="lines">@@ -297,6 +390,7 @@
</span><span class="cx">     if (state == closedKeyword()) {
</span><span class="cx">         m_private.clear();
</span><span class="cx">         m_mediaElement = 0;
</span><ins>+        m_duration = std::numeric_limits&lt;double&gt;::quiet_NaN();
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     if (oldState == state)
</span><span class="lines">@@ -344,19 +438,18 @@
</span><span class="cx">     DEPRECATED_DEFINE_STATIC_LOCAL(const AtomicString, decode, (&quot;decode&quot;, AtomicString::ConstructFromLiteral));
</span><span class="cx"> 
</span><span class="cx">     // 2.4.7 https://dvcs.w3.org/hg/html-media/raw-file/tip/media-source/media-source.html#end-of-stream-algorithm
</span><del>-    // 1. Change the readyState attribute value to &quot;ended&quot;.
-    // 2. Queue a task to fire a simple event named sourceended at the MediaSource.
-    setReadyState(endedKeyword());
</del><span class="cx"> 
</span><span class="cx">     // 3.
</span><span class="cx">     if (error.isEmpty()) {
</span><span class="cx">         // â†³ If error is not set, is null, or is an empty string
</span><del>-        // 1. Run the duration change algorithm with new duration set to the highest end timestamp
-        // across all SourceBuffer objects in sourceBuffers.
-        MediaTime maxEndTimestamp;
-        for (auto it = m_sourceBuffers-&gt;begin(), end = m_sourceBuffers-&gt;end(); it != end; ++it)
-            maxEndTimestamp = std::max((*it)-&gt;highestPresentationEndTimestamp(), maxEndTimestamp);
-        m_private-&gt;setDuration(maxEndTimestamp);
</del><ins>+        // 1. Run the duration change algorithm with new duration set to the highest end time reported by
+        // the buffered attribute across all SourceBuffer objects in sourceBuffers.
+        double maxEndTime = 0;
+        for (auto&amp; sourceBuffer : *m_sourceBuffers) {
+            if (auto length = sourceBuffer-&gt;buffered()-&gt;length())
+                maxEndTime = std::max(sourceBuffer-&gt;buffered()-&gt;end(length - 1, IGNORE_EXCEPTION), maxEndTime);
+        }
+        setDurationInternal(maxEndTime);
</ins><span class="cx"> 
</span><span class="cx">         // 2. Notify the media element that it now has all of the media data.
</span><span class="cx">         m_private-&gt;markEndOfStream(MediaSourcePrivate::EosNoError);
</span><span class="lines">@@ -396,6 +489,13 @@
</span><span class="cx">         //   Throw an INVALID_ACCESS_ERR exception.
</span><span class="cx">         ec = INVALID_ACCESS_ERR;
</span><span class="cx">     }
</span><ins>+
+    // NOTE: Do steps 1 &amp; 2 after step 3 to avoid the MediaSource's readyState being re-opened by a
+    // remove() operation resulting from a duration change.
+    // FIXME: Re-number or update this section once &lt;https://www.w3.org/Bugs/Public/show_bug.cgi?id=26316&gt; is resolved.
+    // 1. Change the readyState attribute value to &quot;ended&quot;.
+    // 2. Queue a task to fire a simple event named sourceended at the MediaSource.
+    setReadyState(endedKeyword());
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> SourceBuffer* MediaSource::addSourceBuffer(const String&amp; type, ExceptionCode&amp; ec)
</span></span></pre></div>
<a id="trunkSourceWebCoreModulesmediasourceMediaSourceh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/Modules/mediasource/MediaSource.h (171032 => 171033)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/Modules/mediasource/MediaSource.h        2014-07-12 22:14:44 UTC (rev 171032)
+++ trunk/Source/WebCore/Modules/mediasource/MediaSource.h        2014-07-12 22:39:43 UTC (rev 171033)
</span><span class="lines">@@ -72,11 +72,13 @@
</span><span class="cx">     virtual void setPrivateAndOpen(PassRef&lt;MediaSourcePrivate&gt;) override;
</span><span class="cx">     virtual double duration() const override;
</span><span class="cx">     virtual std::unique_ptr&lt;PlatformTimeRanges&gt; buffered() const override;
</span><ins>+    virtual void seekToTime(const MediaTime&amp;) override;
</ins><span class="cx"> 
</span><span class="cx">     bool attachToElement(HTMLMediaElement*);
</span><span class="cx">     void close();
</span><span class="cx">     bool isClosed() const;
</span><span class="cx">     void monitorSourceBuffers();
</span><ins>+    void completeSeek();
</ins><span class="cx"> 
</span><span class="cx">     void setDuration(double, ExceptionCode&amp;);
</span><span class="cx">     void setDurationInternal(double);
</span><span class="lines">@@ -127,6 +129,8 @@
</span><span class="cx">     RefPtr&lt;SourceBufferList&gt; m_sourceBuffers;
</span><span class="cx">     RefPtr&lt;SourceBufferList&gt; m_activeSourceBuffers;
</span><span class="cx">     HTMLMediaElement* m_mediaElement;
</span><ins>+    double m_duration;
+    MediaTime m_pendingSeekTime;
</ins><span class="cx">     AtomicString m_readyState;
</span><span class="cx">     GenericEventQueue m_asyncEventQueue;
</span><span class="cx"> };
</span></span></pre></div>
<a id="trunkSourceWebCoreModulesmediasourceSourceBuffercpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/Modules/mediasource/SourceBuffer.cpp (171032 => 171033)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/Modules/mediasource/SourceBuffer.cpp        2014-07-12 22:14:44 UTC (rev 171032)
+++ trunk/Source/WebCore/Modules/mediasource/SourceBuffer.cpp        2014-07-12 22:39:43 UTC (rev 171033)
</span><span class="lines">@@ -233,6 +233,7 @@
</span><span class="cx"> 
</span><span class="cx"> void SourceBuffer::remove(double start, double end, ExceptionCode&amp; ec)
</span><span class="cx"> {
</span><ins>+    LOG(MediaSource, &quot;SourceBuffer::remove(%p) - start(%s), end(%s)&quot;, this, toString(start).utf8().data(), toString(end).utf8().data());
</ins><span class="cx">     // Section 3.2 remove() method steps.
</span><span class="cx">     // 1. If start is negative or greater than duration, then throw an InvalidAccessError exception and abort these steps.
</span><span class="cx">     // 2. If end is less than or equal to start, then throw an InvalidAccessError exception and abort these steps.
</span><span class="lines">@@ -308,9 +309,9 @@
</span><span class="cx">     m_source = 0;
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-void SourceBuffer::sourceBufferPrivateSeekToTime(SourceBufferPrivate*, const MediaTime&amp; time)
</del><ins>+void SourceBuffer::seekToTime(const MediaTime&amp; time)
</ins><span class="cx"> {
</span><del>-    LOG(MediaSource, &quot;SourceBuffer::sourceBufferPrivateSeekToTime(%p) - time(%s)&quot;, this, toString(time).utf8().data());
</del><ins>+    LOG(MediaSource, &quot;SourceBuffer::seekToTime(%p) - time(%s)&quot;, this, toString(time).utf8().data());
</ins><span class="cx"> 
</span><span class="cx">     for (auto&amp; trackBufferPair : m_trackBufferMap) {
</span><span class="cx">         TrackBuffer&amp; trackBuffer = trackBufferPair.value;
</span><span class="lines">@@ -318,8 +319,6 @@
</span><span class="cx"> 
</span><span class="cx">         reenqueueMediaForTime(trackBuffer, trackID, time);
</span><span class="cx">     }
</span><del>-
-    m_source-&gt;monitorSourceBuffers();
</del><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> MediaTime SourceBuffer::sourceBufferPrivateFastSeekTimeForMediaTime(SourceBufferPrivate*, const MediaTime&amp; targetTime, const MediaTime&amp; negativeThreshold, const MediaTime&amp; positiveThreshold)
</span><span class="lines">@@ -527,6 +526,8 @@
</span><span class="cx"> 
</span><span class="cx"> void SourceBuffer::removeCodedFrames(const MediaTime&amp; start, const MediaTime&amp; end)
</span><span class="cx"> {
</span><ins>+    LOG(MediaSource, &quot;SourceBuffer::removeCodedFrames(%p) - start(%s), end(%s)&quot;, this, toString(start).utf8().data(), toString(end).utf8().data());
+
</ins><span class="cx">     // 3.5.9 Coded Frame Removal Algorithm
</span><span class="cx">     // https://dvcs.w3.org/hg/html-media/raw-file/tip/media-source/media-source.html#sourcebuffer-coded-frame-removal
</span><span class="cx"> 
</span><span class="lines">@@ -1449,7 +1450,8 @@
</span><span class="cx">         return false;
</span><span class="cx"> 
</span><span class="cx">     size_t found = ranges.find(nearest);
</span><del>-    ASSERT(found != notFound);
</del><ins>+    if (found == notFound)
+        return false;
</ins><span class="cx"> 
</span><span class="cx">     bool ignoredValid = false;
</span><span class="cx">     return ranges.end(found, ignoredValid) - currentTime &gt; currentTimeFudgeFactor();
</span></span></pre></div>
<a id="trunkSourceWebCoreModulesmediasourceSourceBufferh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/Modules/mediasource/SourceBuffer.h (171032 => 171033)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/Modules/mediasource/SourceBuffer.h        2014-07-12 22:14:44 UTC (rev 171032)
+++ trunk/Source/WebCore/Modules/mediasource/SourceBuffer.h        2014-07-12 22:39:43 UTC (rev 171033)
</span><span class="lines">@@ -77,6 +77,7 @@
</span><span class="cx">     void abortIfUpdating();
</span><span class="cx">     void removedFromMediaSource();
</span><span class="cx">     const MediaTime&amp; highestPresentationEndTimestamp() const { return m_highestPresentationEndTimestamp; }
</span><ins>+    void seekToTime(const MediaTime&amp;);
</ins><span class="cx"> 
</span><span class="cx"> #if ENABLE(VIDEO_TRACK)
</span><span class="cx">     VideoTrackList* videoTracks();
</span><span class="lines">@@ -116,7 +117,6 @@
</span><span class="cx">     virtual bool sourceBufferPrivateHasAudio(const SourceBufferPrivate*) const override;
</span><span class="cx">     virtual bool sourceBufferPrivateHasVideo(const SourceBufferPrivate*) const override;
</span><span class="cx">     virtual void sourceBufferPrivateDidBecomeReadyForMoreSamples(SourceBufferPrivate*, AtomicString trackID) override;
</span><del>-    virtual void sourceBufferPrivateSeekToTime(SourceBufferPrivate*, const MediaTime&amp;);
</del><span class="cx">     virtual MediaTime sourceBufferPrivateFastSeekTimeForMediaTime(SourceBufferPrivate*, const MediaTime&amp;, const MediaTime&amp; negativeThreshold, const MediaTime&amp; positiveThreshold);
</span><span class="cx">     virtual void sourceBufferPrivateAppendComplete(SourceBufferPrivate*, AppendResult) override;
</span><span class="cx">     virtual void sourceBufferPrivateDidReceiveRenderingError(SourceBufferPrivate*, int errorCode) override;
</span></span></pre></div>
<a id="trunkSourceWebCorehtmlHTMLMediaElementcpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/html/HTMLMediaElement.cpp (171032 => 171033)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/html/HTMLMediaElement.cpp        2014-07-12 22:14:44 UTC (rev 171032)
+++ trunk/Source/WebCore/html/HTMLMediaElement.cpp        2014-07-12 22:39:43 UTC (rev 171033)
</span><span class="lines">@@ -4091,7 +4091,8 @@
</span><span class="cx">     // Always call scheduleTimeupdateEvent when the media engine reports a time discontinuity, 
</span><span class="cx">     // it will only queue a 'timeupdate' event if we haven't already posted one at the current
</span><span class="cx">     // movie time.
</span><del>-    scheduleTimeupdateEvent(false);
</del><ins>+    else
+        scheduleTimeupdateEvent(false);
</ins><span class="cx"> 
</span><span class="cx">     double now = currentTime();
</span><span class="cx">     double dur = duration();
</span></span></pre></div>
<a id="trunkSourceWebCoreplatformgraphicsMediaSourcePrivateh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/platform/graphics/MediaSourcePrivate.h (171032 => 171033)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/platform/graphics/MediaSourcePrivate.h        2014-07-12 22:14:44 UTC (rev 171032)
+++ trunk/Source/WebCore/platform/graphics/MediaSourcePrivate.h        2014-07-12 22:39:43 UTC (rev 171033)
</span><span class="lines">@@ -51,14 +51,16 @@
</span><span class="cx"> 
</span><span class="cx">     enum AddStatus { Ok, NotSupported, ReachedIdLimit };
</span><span class="cx">     virtual AddStatus addSourceBuffer(const ContentType&amp;, RefPtr&lt;SourceBufferPrivate&gt;&amp;) = 0;
</span><del>-    virtual MediaTime duration() = 0;
-    virtual void setDuration(const MediaTime&amp;) = 0;
</del><ins>+    virtual void durationChanged() = 0;
</ins><span class="cx">     enum EndOfStreamStatus { EosNoError, EosNetworkError, EosDecodeError };
</span><span class="cx">     virtual void markEndOfStream(EndOfStreamStatus) = 0;
</span><span class="cx">     virtual void unmarkEndOfStream() = 0;
</span><span class="cx"> 
</span><span class="cx">     virtual MediaPlayer::ReadyState readyState() const = 0;
</span><span class="cx">     virtual void setReadyState(MediaPlayer::ReadyState) = 0;
</span><ins>+
+    virtual void waitForSeekCompleted() = 0;
+    virtual void seekCompleted() = 0;
</ins><span class="cx"> };
</span><span class="cx"> 
</span><span class="cx"> }
</span></span></pre></div>
<a id="trunkSourceWebCoreplatformgraphicsMediaSourcePrivateClienth"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/platform/graphics/MediaSourcePrivateClient.h (171032 => 171033)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/platform/graphics/MediaSourcePrivateClient.h        2014-07-12 22:14:44 UTC (rev 171032)
+++ trunk/Source/WebCore/platform/graphics/MediaSourcePrivateClient.h        2014-07-12 22:39:43 UTC (rev 171033)
</span><span class="lines">@@ -42,6 +42,7 @@
</span><span class="cx">     virtual void setPrivateAndOpen(PassRef&lt;MediaSourcePrivate&gt;) = 0;
</span><span class="cx">     virtual double duration() const = 0;
</span><span class="cx">     virtual std::unique_ptr&lt;PlatformTimeRanges&gt; buffered() const = 0;
</span><ins>+    virtual void seekToTime(const MediaTime&amp;) = 0;
</ins><span class="cx"> };
</span><span class="cx"> 
</span><span class="cx"> }
</span></span></pre></div>
<a id="trunkSourceWebCoreplatformgraphicsavfoundationobjcMediaPlayerPrivateMediaSourceAVFObjCh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaSourceAVFObjC.h (171032 => 171033)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaSourceAVFObjC.h        2014-07-12 22:14:44 UTC (rev 171032)
+++ trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaSourceAVFObjC.h        2014-07-12 22:39:43 UTC (rev 171033)
</span><span class="lines">@@ -65,6 +65,8 @@
</span><span class="cx">     void setNetworkState(MediaPlayer::NetworkState);
</span><span class="cx"> 
</span><span class="cx">     void seekInternal();
</span><ins>+    void waitForSeekCompleted();
+    void seekCompleted();
</ins><span class="cx">     void setLoadingProgresssed(bool flag) { m_loadingProgressed = flag; }
</span><span class="cx">     void setHasAvailableVideoFrame(bool flag) { m_hasAvailableVideoFrame = flag; }
</span><span class="cx">     void durationChanged();
</span><span class="lines">@@ -157,6 +159,7 @@
</span><span class="cx">     void ensureLayer();
</span><span class="cx">     void destroyLayer();
</span><span class="cx">     bool shouldBePlaying() const;
</span><ins>+    void seekTimerFired(Timer&lt;MediaPlayerPrivateMediaSourceAVFObjC&gt;&amp;);
</ins><span class="cx"> 
</span><span class="cx">     // MediaPlayer Factory Methods
</span><span class="cx">     static PassOwnPtr&lt;MediaPlayerPrivateInterface&gt; create(MediaPlayer*);
</span><span class="lines">@@ -182,7 +185,6 @@
</span><span class="cx"> 
</span><span class="cx">     MediaPlayer* m_player;
</span><span class="cx">     WeakPtrFactory&lt;MediaPlayerPrivateMediaSourceAVFObjC&gt; m_weakPtrFactory;
</span><del>-    RefPtr&lt;MediaSourcePrivateClient&gt; m_mediaSource;
</del><span class="cx">     RefPtr&lt;MediaSourcePrivateAVFObjC&gt; m_mediaSourcePrivate;
</span><span class="cx">     RetainPtr&lt;AVAsset&gt; m_asset;
</span><span class="cx">     RetainPtr&lt;AVSampleBufferDisplayLayer&gt; m_sampleBufferDisplayLayer;
</span><span class="lines">@@ -190,6 +192,7 @@
</span><span class="cx">     RetainPtr&lt;AVSampleBufferRenderSynchronizer&gt; m_synchronizer;
</span><span class="cx">     RetainPtr&lt;id&gt; m_timeJumpedObserver;
</span><span class="cx">     RetainPtr&lt;id&gt; m_durationObserver;
</span><ins>+    Timer&lt;MediaPlayerPrivateMediaSourceAVFObjC&gt; m_seekTimer;
</ins><span class="cx">     MediaPlayer::NetworkState m_networkState;
</span><span class="cx">     MediaPlayer::ReadyState m_readyState;
</span><span class="cx">     double m_rate;
</span></span></pre></div>
<a id="trunkSourceWebCoreplatformgraphicsavfoundationobjcMediaPlayerPrivateMediaSourceAVFObjCmm"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaSourceAVFObjC.mm (171032 => 171033)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaSourceAVFObjC.mm        2014-07-12 22:14:44 UTC (rev 171032)
+++ trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaSourceAVFObjC.mm        2014-07-12 22:39:43 UTC (rev 171033)
</span><span class="lines">@@ -29,6 +29,7 @@
</span><span class="cx"> #if ENABLE(MEDIA_SOURCE) &amp;&amp; USE(AVFOUNDATION)
</span><span class="cx"> 
</span><span class="cx"> #import &quot;CDMSession.h&quot;
</span><ins>+#import &quot;Logging.h&quot;
</ins><span class="cx"> #import &quot;MediaSourcePrivateAVFObjC.h&quot;
</span><span class="cx"> #import &quot;MediaSourcePrivateClient.h&quot;
</span><span class="cx"> #import &quot;MediaTimeMac.h&quot;
</span><span class="lines">@@ -134,6 +135,7 @@
</span><span class="cx">     : m_player(player)
</span><span class="cx">     , m_weakPtrFactory(this)
</span><span class="cx">     , m_synchronizer(adoptNS([[getAVSampleBufferRenderSynchronizerClass() alloc] init]))
</span><ins>+    , m_seekTimer(this, &amp;MediaPlayerPrivateMediaSourceAVFObjC::seekTimerFired)
</ins><span class="cx">     , m_networkState(MediaPlayer::Empty)
</span><span class="cx">     , m_readyState(MediaPlayer::HaveNothing)
</span><span class="cx">     , m_rate(1)
</span><span class="lines">@@ -149,19 +151,24 @@
</span><span class="cx">     // addPeriodicTimeObserverForInterval: throws an exception if you pass a non-numeric CMTime, so just use
</span><span class="cx">     // an arbitrarily large time value of once an hour:
</span><span class="cx">     __block auto weakThis = createWeakPtr();
</span><del>-    m_timeJumpedObserver = [m_synchronizer addPeriodicTimeObserverForInterval:toCMTime(MediaTime::createWithDouble(3600)) queue:dispatch_get_main_queue() usingBlock:^(CMTime){
</del><ins>+    m_timeJumpedObserver = [m_synchronizer addPeriodicTimeObserverForInterval:toCMTime(MediaTime::createWithDouble(3600)) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
+#if LOG_DISABLED
+        UNUSED_PARAM(time);
+#endif
</ins><span class="cx">         // FIXME: Remove the below once &lt;rdar://problem/15798050&gt; is fixed.
</span><span class="cx">         if (!weakThis)
</span><span class="cx">             return;
</span><span class="cx"> 
</span><span class="cx">         if (m_seeking &amp;&amp; !m_pendingSeek) {
</span><ins>+            LOG(MediaSource, &quot;MediaPlayerPrivateMediaSourceAVFObjC::m_timeJumpedObserver(%p) - time(%s)&quot;, weakThis.get(), toString(toMediaTime(time)).utf8().data());
</ins><span class="cx">             m_seeking = false;
</span><ins>+
</ins><span class="cx">             if (shouldBePlaying())
</span><span class="cx">                 [m_synchronizer setRate:m_rate];
</span><ins>+            if (!seeking())
+                m_player-&gt;timeChanged();
</ins><span class="cx">         }
</span><span class="cx"> 
</span><del>-        m_player-&gt;timeChanged();
-
</del><span class="cx">         if (m_pendingSeek)
</span><span class="cx">             seekInternal();
</span><span class="cx">     }];
</span><span class="lines">@@ -177,6 +184,8 @@
</span><span class="cx">         [m_synchronizer removeTimeObserver:m_timeJumpedObserver.get()];
</span><span class="cx">     if (m_durationObserver)
</span><span class="cx">         [m_synchronizer removeTimeObserver:m_durationObserver.get()];
</span><ins>+
+    m_seekTimer.stop();
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> #pragma mark -
</span><span class="lines">@@ -286,14 +295,11 @@
</span><span class="cx">     m_player-&gt;networkStateChanged();
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-void MediaPlayerPrivateMediaSourceAVFObjC::load(const String&amp; url, MediaSourcePrivateClient* source)
</del><ins>+void MediaPlayerPrivateMediaSourceAVFObjC::load(const String&amp; url, MediaSourcePrivateClient* client)
</ins><span class="cx"> {
</span><span class="cx">     UNUSED_PARAM(url);
</span><span class="cx"> 
</span><del>-    m_mediaSource = source;
-    m_mediaSourcePrivate = MediaSourcePrivateAVFObjC::create(this);
-
-    m_mediaSource-&gt;setPrivateAndOpen(*m_mediaSourcePrivate);
</del><ins>+    m_mediaSourcePrivate = MediaSourcePrivateAVFObjC::create(this, client);
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> void MediaPlayerPrivateMediaSourceAVFObjC::cancelLoad()
</span><span class="lines">@@ -406,7 +412,7 @@
</span><span class="cx"> 
</span><span class="cx"> double MediaPlayerPrivateMediaSourceAVFObjC::durationDouble() const
</span><span class="cx"> {
</span><del>-    return m_mediaSource ? m_mediaSource-&gt;duration() : 0;
</del><ins>+    return m_mediaSourcePrivate ? m_mediaSourcePrivate-&gt;duration().toDouble() : 0;
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> MediaTime MediaPlayerPrivateMediaSourceAVFObjC::currentMediaTime() const
</span><span class="lines">@@ -431,18 +437,21 @@
</span><span class="cx"> 
</span><span class="cx"> void MediaPlayerPrivateMediaSourceAVFObjC::seekWithTolerance(double time, double negativeThreshold, double positiveThreshold)
</span><span class="cx"> {
</span><ins>+    LOG(MediaSource, &quot;MediaPlayerPrivateMediaSourceAVFObjC::seekWithTolerance(%p) - time(%s), negativeThreshold(%s), positiveThreshold(%s)&quot;, this, toString(time).utf8().data(), toString(negativeThreshold).utf8().data(), toString(positiveThreshold).utf8().data());
</ins><span class="cx">     m_seeking = true;
</span><del>-    m_seekCompleted = false;
</del><span class="cx">     auto weakThis = createWeakPtr();
</span><span class="cx">     m_pendingSeek = std::make_unique&lt;PendingSeek&gt;(MediaTime::createWithDouble(time), MediaTime::createWithDouble(negativeThreshold), MediaTime::createWithDouble(positiveThreshold));
</span><span class="cx"> 
</span><del>-    callOnMainThread([weakThis] {
-        if (!weakThis)
-            return;
-        weakThis.get()-&gt;seekInternal();
-    });
</del><ins>+    if (m_seekTimer.isActive())
+        m_seekTimer.stop();
+    m_seekTimer.startOneShot(0);
</ins><span class="cx"> }
</span><span class="cx"> 
</span><ins>+void MediaPlayerPrivateMediaSourceAVFObjC::seekTimerFired(Timer&lt;MediaPlayerPrivateMediaSourceAVFObjC&gt;&amp;)
+{
+    seekInternal();
+}
+
</ins><span class="cx"> void MediaPlayerPrivateMediaSourceAVFObjC::seekInternal()
</span><span class="cx"> {
</span><span class="cx">     std::unique_ptr&lt;PendingSeek&gt; pendingSeek;
</span><span class="lines">@@ -460,10 +469,30 @@
</span><span class="cx">     else
</span><span class="cx">         seekTime = m_mediaSourcePrivate-&gt;fastSeekTimeForMediaTime(pendingSeek-&gt;targetTime, pendingSeek-&gt;positiveThreshold, pendingSeek-&gt;negativeThreshold);
</span><span class="cx"> 
</span><ins>+    LOG(MediaSource, &quot;MediaPlayerPrivateMediaSourceAVFObjC::seekInternal(%p) - seekTime(%s)&quot;, this, toString(seekTime).utf8().data());
+
</ins><span class="cx">     [m_synchronizer setRate:0 time:toCMTime(seekTime)];
</span><span class="cx">     m_mediaSourcePrivate-&gt;seekToTime(seekTime);
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+void MediaPlayerPrivateMediaSourceAVFObjC::waitForSeekCompleted()
+{
+    if (!m_seeking)
+        return;
+    LOG(MediaSource, &quot;MediaPlayerPrivateMediaSourceAVFObjC::waitForSeekCompleted(%p)&quot;, this);
+    m_seekCompleted = false;
+}
+
+void MediaPlayerPrivateMediaSourceAVFObjC::seekCompleted()
+{
+    if (m_seekCompleted)
+        return;
+    LOG(MediaSource, &quot;MediaPlayerPrivateMediaSourceAVFObjC::seekCompleted(%p)&quot;, this);
+    m_seekCompleted = true;
+    if (!m_seeking)
+        m_player-&gt;timeChanged();
+}
+
</ins><span class="cx"> bool MediaPlayerPrivateMediaSourceAVFObjC::seeking() const
</span><span class="cx"> {
</span><span class="cx">     return m_seeking &amp;&amp; !m_seekCompleted;
</span><span class="lines">@@ -503,7 +532,7 @@
</span><span class="cx"> 
</span><span class="cx"> std::unique_ptr&lt;PlatformTimeRanges&gt; MediaPlayerPrivateMediaSourceAVFObjC::buffered() const
</span><span class="cx"> {
</span><del>-    return m_mediaSource ? m_mediaSource-&gt;buffered() : PlatformTimeRanges::create();
</del><ins>+    return m_mediaSourcePrivate ? m_mediaSourcePrivate-&gt;buffered() : PlatformTimeRanges::create();
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> bool MediaPlayerPrivateMediaSourceAVFObjC::didLoadingProgress() const
</span><span class="lines">@@ -636,8 +665,10 @@
</span><span class="cx">     auto weakThis = createWeakPtr();
</span><span class="cx">     NSArray* times = @[[NSValue valueWithCMTime:toCMTime(duration)]];
</span><span class="cx">     m_durationObserver = [m_synchronizer addBoundaryTimeObserverForTimes:times queue:dispatch_get_main_queue() usingBlock:[weakThis] {
</span><del>-        if (weakThis)
</del><ins>+        if (weakThis) {
</ins><span class="cx">             weakThis-&gt;pauseInternal();
</span><ins>+            weakThis-&gt;m_player-&gt;timeChanged();
+        }
</ins><span class="cx">     }];
</span><span class="cx"> 
</span><span class="cx">     if (m_playing &amp;&amp; duration &lt;= currentMediaTime())
</span><span class="lines">@@ -676,11 +707,6 @@
</span><span class="cx"> 
</span><span class="cx">     m_readyState = readyState;
</span><span class="cx"> 
</span><del>-    if (!m_seekCompleted &amp;&amp; m_readyState &gt;= MediaPlayer::HaveCurrentData) {
-        m_seekCompleted = true;
-        m_player-&gt;timeChanged();
-    }
-
</del><span class="cx">     if (shouldBePlaying())
</span><span class="cx">         [m_synchronizer setRate:m_rate];
</span><span class="cx">     else
</span></span></pre></div>
<a id="trunkSourceWebCoreplatformgraphicsavfoundationobjcMediaSourcePrivateAVFObjCh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaSourcePrivateAVFObjC.h (171032 => 171033)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaSourcePrivateAVFObjC.h        2014-07-12 22:14:44 UTC (rev 171032)
+++ trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaSourcePrivateAVFObjC.h        2014-07-12 22:39:43 UTC (rev 171033)
</span><span class="lines">@@ -45,30 +45,35 @@
</span><span class="cx"> 
</span><span class="cx"> class CDMSession;
</span><span class="cx"> class MediaPlayerPrivateMediaSourceAVFObjC;
</span><ins>+class MediaSourcePrivateClient;
</ins><span class="cx"> class SourceBufferPrivateAVFObjC;
</span><span class="cx"> class TimeRanges;
</span><span class="cx"> 
</span><span class="cx"> class MediaSourcePrivateAVFObjC final : public MediaSourcePrivate {
</span><span class="cx"> public:
</span><del>-    static RefPtr&lt;MediaSourcePrivateAVFObjC&gt; create(MediaPlayerPrivateMediaSourceAVFObjC*);
</del><ins>+    static RefPtr&lt;MediaSourcePrivateAVFObjC&gt; create(MediaPlayerPrivateMediaSourceAVFObjC*, MediaSourcePrivateClient*);
</ins><span class="cx">     virtual ~MediaSourcePrivateAVFObjC();
</span><span class="cx"> 
</span><span class="cx">     MediaPlayerPrivateMediaSourceAVFObjC* player() const { return m_player; }
</span><span class="cx">     const Vector&lt;SourceBufferPrivateAVFObjC*&gt;&amp; activeSourceBuffers() const { return m_activeSourceBuffers; }
</span><span class="cx"> 
</span><span class="cx">     virtual AddStatus addSourceBuffer(const ContentType&amp;, RefPtr&lt;SourceBufferPrivate&gt;&amp;) override;
</span><del>-    virtual MediaTime duration() override;
-    virtual void setDuration(const MediaTime&amp;) override;
</del><ins>+    virtual void durationChanged() override;
</ins><span class="cx">     virtual void markEndOfStream(EndOfStreamStatus) override;
</span><span class="cx">     virtual void unmarkEndOfStream() override;
</span><span class="cx">     virtual MediaPlayer::ReadyState readyState() const override;
</span><span class="cx">     virtual void setReadyState(MediaPlayer::ReadyState) override;
</span><ins>+    virtual void waitForSeekCompleted() override;
+    virtual void seekCompleted() override;
</ins><span class="cx"> 
</span><ins>+    MediaTime duration();
+    std::unique_ptr&lt;PlatformTimeRanges&gt; buffered();
+
</ins><span class="cx">     bool hasAudio() const;
</span><span class="cx">     bool hasVideo() const;
</span><span class="cx"> 
</span><del>-    void seekToTime(MediaTime);
-    MediaTime fastSeekTimeForMediaTime(MediaTime, MediaTime negativeThreshold, MediaTime positiveThreshold);
</del><ins>+    void seekToTime(const MediaTime&amp;);
+    MediaTime fastSeekTimeForMediaTime(const MediaTime&amp;, const MediaTime&amp; negativeThreshold, const MediaTime&amp; positiveThreshold);
</ins><span class="cx">     IntSize naturalSize() const;
</span><span class="cx"> 
</span><span class="cx"> #if ENABLE(ENCRYPTED_MEDIA_V2)
</span><span class="lines">@@ -76,7 +81,7 @@
</span><span class="cx"> #endif
</span><span class="cx"> 
</span><span class="cx"> private:
</span><del>-    MediaSourcePrivateAVFObjC(MediaPlayerPrivateMediaSourceAVFObjC*);
</del><ins>+    MediaSourcePrivateAVFObjC(MediaPlayerPrivateMediaSourceAVFObjC*, MediaSourcePrivateClient*);
</ins><span class="cx"> 
</span><span class="cx">     void sourceBufferPrivateDidChangeActiveState(SourceBufferPrivateAVFObjC*, bool active);
</span><span class="cx">     void sourceBufferPrivateDidReceiveInitializationSegment(SourceBufferPrivateAVFObjC*);
</span><span class="lines">@@ -89,7 +94,7 @@
</span><span class="cx">     friend class SourceBufferPrivateAVFObjC;
</span><span class="cx"> 
</span><span class="cx">     MediaPlayerPrivateMediaSourceAVFObjC* m_player;
</span><del>-    MediaTime m_duration;
</del><ins>+    RefPtr&lt;MediaSourcePrivateClient&gt; m_client;
</ins><span class="cx">     Vector&lt;RefPtr&lt;SourceBufferPrivateAVFObjC&gt;&gt; m_sourceBuffers;
</span><span class="cx">     Vector&lt;SourceBufferPrivateAVFObjC*&gt; m_activeSourceBuffers;
</span><span class="cx">     Deque&lt;SourceBufferPrivateAVFObjC*&gt; m_sourceBuffersNeedingSessions;
</span></span></pre></div>
<a id="trunkSourceWebCoreplatformgraphicsavfoundationobjcMediaSourcePrivateAVFObjCmm"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaSourcePrivateAVFObjC.mm (171032 => 171033)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaSourcePrivateAVFObjC.mm        2014-07-12 22:14:44 UTC (rev 171032)
+++ trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaSourcePrivateAVFObjC.mm        2014-07-12 22:39:43 UTC (rev 171033)
</span><span class="lines">@@ -32,6 +32,7 @@
</span><span class="cx"> #import &quot;ContentType.h&quot;
</span><span class="cx"> #import &quot;ExceptionCodePlaceholder.h&quot;
</span><span class="cx"> #import &quot;MediaPlayerPrivateMediaSourceAVFObjC.h&quot;
</span><ins>+#import &quot;MediaSourcePrivateClient.h&quot;
</ins><span class="cx"> #import &quot;SourceBufferPrivateAVFObjC.h&quot;
</span><span class="cx"> #import &quot;SoftLinking.h&quot;
</span><span class="cx"> #import &lt;objc/runtime.h&gt;
</span><span class="lines">@@ -43,14 +44,16 @@
</span><span class="cx"> #pragma mark -
</span><span class="cx"> #pragma mark MediaSourcePrivateAVFObjC
</span><span class="cx"> 
</span><del>-RefPtr&lt;MediaSourcePrivateAVFObjC&gt; MediaSourcePrivateAVFObjC::create(MediaPlayerPrivateMediaSourceAVFObjC* parent)
</del><ins>+RefPtr&lt;MediaSourcePrivateAVFObjC&gt; MediaSourcePrivateAVFObjC::create(MediaPlayerPrivateMediaSourceAVFObjC* parent, MediaSourcePrivateClient* client)
</ins><span class="cx"> {
</span><del>-    return adoptRef(new MediaSourcePrivateAVFObjC(parent));
</del><ins>+    RefPtr&lt;MediaSourcePrivateAVFObjC&gt; mediaSourcePrivate = adoptRef(new MediaSourcePrivateAVFObjC(parent, client));
+    client-&gt;setPrivateAndOpen(*mediaSourcePrivate);
+    return mediaSourcePrivate;
</ins><span class="cx"> }
</span><span class="cx"> 
</span><del>-MediaSourcePrivateAVFObjC::MediaSourcePrivateAVFObjC(MediaPlayerPrivateMediaSourceAVFObjC* parent)
</del><ins>+MediaSourcePrivateAVFObjC::MediaSourcePrivateAVFObjC(MediaPlayerPrivateMediaSourceAVFObjC* parent, MediaSourcePrivateClient* client)
</ins><span class="cx">     : m_player(parent)
</span><del>-    , m_duration(MediaTime::invalidTime())
</del><ins>+    , m_client(client)
</ins><span class="cx">     , m_isEnded(false)
</span><span class="cx"> {
</span><span class="cx"> }
</span><span class="lines">@@ -91,15 +94,16 @@
</span><span class="cx"> 
</span><span class="cx"> MediaTime MediaSourcePrivateAVFObjC::duration()
</span><span class="cx"> {
</span><del>-    return m_duration;
</del><ins>+    return MediaTime::createWithDouble(m_client-&gt;duration());
</ins><span class="cx"> }
</span><span class="cx"> 
</span><del>-void MediaSourcePrivateAVFObjC::setDuration(const MediaTime&amp; duration)
</del><ins>+std::unique_ptr&lt;PlatformTimeRanges&gt; MediaSourcePrivateAVFObjC::buffered()
</ins><span class="cx"> {
</span><del>-    if (duration == m_duration)
-        return;
</del><ins>+    return m_client-&gt;buffered();
+}
</ins><span class="cx"> 
</span><del>-    m_duration = duration;
</del><ins>+void MediaSourcePrivateAVFObjC::durationChanged()
+{
</ins><span class="cx">     m_player-&gt;durationChanged();
</span><span class="cx"> }
</span><span class="cx"> 
</span><span class="lines">@@ -126,6 +130,16 @@
</span><span class="cx">     m_player-&gt;setReadyState(readyState);
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+void MediaSourcePrivateAVFObjC::waitForSeekCompleted()
+{
+    m_player-&gt;waitForSeekCompleted();
+}
+
+void MediaSourcePrivateAVFObjC::seekCompleted()
+{
+    m_player-&gt;seekCompleted();
+}
+
</ins><span class="cx"> void MediaSourcePrivateAVFObjC::sourceBufferPrivateDidChangeActiveState(SourceBufferPrivateAVFObjC* buffer, bool active)
</span><span class="cx"> {
</span><span class="cx">     if (active &amp;&amp; !m_activeSourceBuffers.contains(buffer))
</span><span class="lines">@@ -178,13 +192,12 @@
</span><span class="cx">     return std::any_of(m_activeSourceBuffers.begin(), m_activeSourceBuffers.end(), MediaSourcePrivateAVFObjCHasVideo);
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-void MediaSourcePrivateAVFObjC::seekToTime(MediaTime time)
</del><ins>+void MediaSourcePrivateAVFObjC::seekToTime(const MediaTime&amp; time)
</ins><span class="cx"> {
</span><del>-    for (auto&amp; buffer : m_activeSourceBuffers)
-        buffer-&gt;seekToTime(time);
</del><ins>+    m_client-&gt;seekToTime(time);
</ins><span class="cx"> }
</span><span class="cx"> 
</span><del>-MediaTime MediaSourcePrivateAVFObjC::fastSeekTimeForMediaTime(MediaTime targetTime, MediaTime negativeThreshold, MediaTime positiveThreshold)
</del><ins>+MediaTime MediaSourcePrivateAVFObjC::fastSeekTimeForMediaTime(const MediaTime&amp; targetTime, const MediaTime&amp; negativeThreshold, const MediaTime&amp; positiveThreshold)
</ins><span class="cx"> {
</span><span class="cx">     MediaTime seekTime = targetTime;
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkSourceWebCoreplatformgraphicsavfoundationobjcSourceBufferPrivateAVFObjCmm"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/platform/graphics/avfoundation/objc/SourceBufferPrivateAVFObjC.mm (171032 => 171033)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/platform/graphics/avfoundation/objc/SourceBufferPrivateAVFObjC.mm        2014-07-12 22:14:44 UTC (rev 171032)
+++ trunk/Source/WebCore/platform/graphics/avfoundation/objc/SourceBufferPrivateAVFObjC.mm        2014-07-12 22:39:43 UTC (rev 171033)
</span><span class="lines">@@ -122,6 +122,7 @@
</span><span class="cx"> - (void)appendStreamData:(NSData *)data;
</span><span class="cx"> - (void)setShouldProvideMediaData:(BOOL)shouldProvideMediaData forTrackID:(CMPersistentTrackID)trackID;
</span><span class="cx"> - (BOOL)shouldProvideMediaDataForTrackID:(CMPersistentTrackID)trackID;
</span><ins>+- (void)providePendingMediaData;
</ins><span class="cx"> - (void)processContentKeyResponseData:(NSData *)contentKeyResponseData forTrackID:(CMPersistentTrackID)trackID;
</span><span class="cx"> - (void)processContentKeyResponseError:(NSError *)error forTrackID:(CMPersistentTrackID)trackID;
</span><span class="cx"> - (void)renewExpiringContentKeyResponseDataForTrackID:(CMPersistentTrackID)trackID;
</span><span class="lines">@@ -682,7 +683,7 @@
</span><span class="cx">     static dispatch_queue_t globalQueue;
</span><span class="cx">     static dispatch_once_t onceToken;
</span><span class="cx">     dispatch_once(&amp;onceToken, ^{
</span><del>-        globalQueue = dispatch_queue_create(&quot;SourceBufferPrivateAVFObjC data parser queue&quot;, DISPATCH_QUEUE_SERIAL);
</del><ins>+        globalQueue = dispatch_queue_create(&quot;SourceBufferPrivateAVFObjC data parser queue&quot;, DISPATCH_QUEUE_CONCURRENT);
</ins><span class="cx">     });
</span><span class="cx">     return globalQueue;
</span><span class="cx"> }
</span></span></pre></div>
<a id="trunkSourceWebCoreplatformgraphicsgstreamerMediaSourceGStreamercpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/platform/graphics/gstreamer/MediaSourceGStreamer.cpp (171032 => 171033)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/platform/graphics/gstreamer/MediaSourceGStreamer.cpp        2014-07-12 22:14:44 UTC (rev 171032)
+++ trunk/Source/WebCore/platform/graphics/gstreamer/MediaSourceGStreamer.cpp        2014-07-12 22:39:43 UTC (rev 171033)
</span><span class="lines">@@ -42,13 +42,16 @@
</span><span class="cx"> 
</span><span class="cx"> void MediaSourceGStreamer::open(MediaSourcePrivateClient* mediaSource, WebKitMediaSrc* src)
</span><span class="cx"> {
</span><del>-    mediaSource-&gt;setPrivateAndOpen(adoptRef(*new MediaSourceGStreamer(src)));
</del><ins>+    ASSERT(mediaSource);
+    mediaSource-&gt;setPrivateAndOpen(adoptRef(*new MediaSourceGStreamer(mediaSource, src)));
</ins><span class="cx"> }
</span><span class="cx"> 
</span><del>-MediaSourceGStreamer::MediaSourceGStreamer(WebKitMediaSrc* src)
</del><ins>+MediaSourceGStreamer::MediaSourceGStreamer(MediaSourcePrivateClient* mediaSource, WebKitMediaSrc* src)
</ins><span class="cx">     : m_client(adoptRef(new MediaSourceClientGstreamer(src)))
</span><ins>+    , m_mediaSource(mediaSource)
</ins><span class="cx">     , m_readyState(MediaPlayer::HaveNothing)
</span><span class="cx"> {
</span><ins>+    ASSERT(m_client);
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> MediaSourceGStreamer::~MediaSourceGStreamer()
</span><span class="lines">@@ -61,22 +64,18 @@
</span><span class="cx">     return MediaSourceGStreamer::Ok;
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-void MediaSourceGStreamer::setDuration(const MediaTime&amp; duration)
</del><ins>+void MediaSourceGStreamer::durationChanged()
</ins><span class="cx"> {
</span><del>-    ASSERT(m_client);
-    m_duration = duration;
-    m_client-&gt;didReceiveDuration(duration.toDouble());
</del><ins>+    m_client-&gt;didReceiveDuration(m_mediaSource-&gt;duration());
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> void MediaSourceGStreamer::markEndOfStream(EndOfStreamStatus)
</span><span class="cx"> {
</span><del>-    ASSERT(m_client);
</del><span class="cx">     m_client-&gt;didFinishLoading(0);
</span><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> void MediaSourceGStreamer::unmarkEndOfStream()
</span><span class="cx"> {
</span><del>-    ASSERT(m_client);
</del><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> }
</span></span></pre></div>
<a id="trunkSourceWebCoreplatformgraphicsgstreamerMediaSourceGStreamerh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/platform/graphics/gstreamer/MediaSourceGStreamer.h (171032 => 171033)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/platform/graphics/gstreamer/MediaSourceGStreamer.h        2014-07-12 22:14:44 UTC (rev 171032)
+++ trunk/Source/WebCore/platform/graphics/gstreamer/MediaSourceGStreamer.h        2014-07-12 22:39:43 UTC (rev 171033)
</span><span class="lines">@@ -42,18 +42,21 @@
</span><span class="cx"> public:
</span><span class="cx">     static void open(MediaSourcePrivateClient*, WebKitMediaSrc*);
</span><span class="cx">     ~MediaSourceGStreamer();
</span><del>-    AddStatus addSourceBuffer(const ContentType&amp;, RefPtr&lt;SourceBufferPrivate&gt;&amp;);
-    MediaTime duration() { return m_duration; }
-    void setDuration(const MediaTime&amp;);
-    void markEndOfStream(EndOfStreamStatus);
-    void unmarkEndOfStream();
-    MediaPlayer::ReadyState readyState() const { return m_readyState; }
-    void setReadyState(MediaPlayer::ReadyState readyState) { m_readyState = readyState; }
</del><span class="cx"> 
</span><span class="cx"> private:
</span><ins>+    // MediaSourcePrivate
+    virtual AddStatus addSourceBuffer(const ContentType&amp;, RefPtr&lt;SourceBufferPrivate&gt;&amp;) override;
+    virtual void durationChanged() override;
+    virtual void markEndOfStream(EndOfStreamStatus) override;
+    virtual void unmarkEndOfStream() override;
+    virtual MediaPlayer::ReadyState readyState() const override { return m_readyState; }
+    virtual void setReadyState(MediaPlayer::ReadyState readyState) override { m_readyState = readyState; }
+    virtual void waitForSeekCompleted() override { }
+    virtual void seekCompleted() override { }
+
</ins><span class="cx">     RefPtr&lt;MediaSourceClientGstreamer&gt; m_client;
</span><del>-    MediaSourceGStreamer(WebKitMediaSrc*);
-    MediaTime m_duration;
</del><ins>+    MediaSourcePrivateClient* m_mediaSource;
+    MediaSourceGStreamer(MediaSourcePrivateClient*, WebKitMediaSrc*);
</ins><span class="cx">     MediaPlayer::ReadyState m_readyState;
</span><span class="cx"> };
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkSourceWebCoreplatformmockmediasourceMockMediaPlayerMediaSourcecpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/platform/mock/mediasource/MockMediaPlayerMediaSource.cpp (171032 => 171033)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/platform/mock/mediasource/MockMediaPlayerMediaSource.cpp        2014-07-12 22:14:44 UTC (rev 171032)
+++ trunk/Source/WebCore/platform/mock/mediasource/MockMediaPlayerMediaSource.cpp        2014-07-12 22:39:43 UTC (rev 171033)
</span><span class="lines">@@ -84,10 +84,10 @@
</span><span class="cx"> MockMediaPlayerMediaSource::MockMediaPlayerMediaSource(MediaPlayer* player)
</span><span class="cx">     : m_player(player)
</span><span class="cx">     , m_currentTime(MediaTime::zeroTime())
</span><del>-    , m_duration(MediaTime::zeroTime())
</del><span class="cx">     , m_readyState(MediaPlayer::HaveNothing)
</span><span class="cx">     , m_networkState(MediaPlayer::Empty)
</span><span class="cx">     , m_playing(false)
</span><ins>+    , m_seekCompleted(true)
</ins><span class="cx"> {
</span><span class="cx"> }
</span><span class="cx"> 
</span><span class="lines">@@ -102,9 +102,7 @@
</span><span class="cx"> 
</span><span class="cx"> void MockMediaPlayerMediaSource::load(const String&amp;, MediaSourcePrivateClient* source)
</span><span class="cx"> {
</span><del>-    m_mediaSource = source;
-    m_mediaSourcePrivate = MockMediaSourcePrivate::create(this);
-    m_mediaSource-&gt;setPrivateAndOpen(*m_mediaSourcePrivate);
</del><ins>+    m_mediaSourcePrivate = MockMediaSourcePrivate::create(this, source);
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> void MockMediaPlayerMediaSource::cancelLoad()
</span><span class="lines">@@ -143,7 +141,7 @@
</span><span class="cx"> 
</span><span class="cx"> bool MockMediaPlayerMediaSource::seeking() const
</span><span class="cx"> {
</span><del>-    return false;
</del><ins>+    return !m_seekCompleted;
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> bool MockMediaPlayerMediaSource::paused() const
</span><span class="lines">@@ -168,8 +166,8 @@
</span><span class="cx"> 
</span><span class="cx"> std::unique_ptr&lt;PlatformTimeRanges&gt; MockMediaPlayerMediaSource::buffered() const
</span><span class="cx"> {
</span><del>-    if (m_mediaSource)
-        return m_mediaSource-&gt;buffered();
</del><ins>+    if (m_mediaSourcePrivate)
+        return m_mediaSourcePrivate-&gt;buffered();
</ins><span class="cx"> 
</span><span class="cx">     return PlatformTimeRanges::create();
</span><span class="cx"> }
</span><span class="lines">@@ -194,7 +192,7 @@
</span><span class="cx"> 
</span><span class="cx"> double MockMediaPlayerMediaSource::durationDouble() const
</span><span class="cx"> {
</span><del>-    return m_duration.toDouble();
</del><ins>+    return m_mediaSourcePrivate ? m_mediaSourcePrivate-&gt;duration() : 0;
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> void MockMediaPlayerMediaSource::seekWithTolerance(double time, double negativeTolerance, double positiveTolerance)
</span><span class="lines">@@ -204,18 +202,21 @@
</span><span class="cx">         m_mediaSourcePrivate-&gt;seekToTime(MediaTime::createWithDouble(time));
</span><span class="cx">     } else
</span><span class="cx">         m_currentTime = m_mediaSourcePrivate-&gt;seekToTime(MediaTime::createWithDouble(time), MediaTime::createWithDouble(negativeTolerance), MediaTime::createWithDouble(positiveTolerance));
</span><del>-    m_player-&gt;timeChanged();
</del><span class="cx"> 
</span><del>-    if (m_playing)
-        callOnMainThread(bind(&amp;MockMediaPlayerMediaSource::advanceCurrentTime, this));
</del><ins>+    if (m_seekCompleted) {
+        m_player-&gt;timeChanged();
+
+        if (m_playing)
+            callOnMainThread(bind(&amp;MockMediaPlayerMediaSource::advanceCurrentTime, this));
+    }
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> void MockMediaPlayerMediaSource::advanceCurrentTime()
</span><span class="cx"> {
</span><del>-    if (!m_mediaSource)
</del><ins>+    if (!m_mediaSourcePrivate)
</ins><span class="cx">         return;
</span><span class="cx"> 
</span><del>-    auto buffered = m_mediaSource-&gt;buffered();
</del><ins>+    auto buffered = m_mediaSourcePrivate-&gt;buffered();
</ins><span class="cx">     size_t pos = buffered-&gt;find(m_currentTime);
</span><span class="cx">     if (pos == notFound)
</span><span class="cx">         return;
</span><span class="lines">@@ -252,6 +253,23 @@
</span><span class="cx">     m_player-&gt;networkStateChanged();
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+void MockMediaPlayerMediaSource::waitForSeekCompleted()
+{
+    m_seekCompleted = false;
+}
+
+void MockMediaPlayerMediaSource::seekCompleted()
+{
+    if (m_seekCompleted)
+        return;
+    m_seekCompleted = true;
+
+    m_player-&gt;timeChanged();
+
+    if (m_playing)
+        callOnMainThread(bind(&amp;MockMediaPlayerMediaSource::advanceCurrentTime, this));
+}
+
</ins><span class="cx"> unsigned long MockMediaPlayerMediaSource::totalVideoFrames()
</span><span class="cx"> {
</span><span class="cx">     return m_mediaSourcePrivate ? m_mediaSourcePrivate-&gt;totalVideoFrames() : 0;
</span></span></pre></div>
<a id="trunkSourceWebCoreplatformmockmediasourceMockMediaPlayerMediaSourceh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/platform/mock/mediasource/MockMediaPlayerMediaSource.h (171032 => 171033)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/platform/mock/mediasource/MockMediaPlayerMediaSource.h        2014-07-12 22:14:44 UTC (rev 171032)
+++ trunk/Source/WebCore/platform/mock/mediasource/MockMediaPlayerMediaSource.h        2014-07-12 22:39:43 UTC (rev 171033)
</span><span class="lines">@@ -52,6 +52,8 @@
</span><span class="cx">     virtual MediaPlayer::ReadyState readyState() const override;
</span><span class="cx">     void setReadyState(MediaPlayer::ReadyState);
</span><span class="cx">     void setNetworkState(MediaPlayer::NetworkState);
</span><ins>+    void waitForSeekCompleted();
+    void seekCompleted();
</ins><span class="cx"> 
</span><span class="cx"> private:
</span><span class="cx">     MockMediaPlayerMediaSource(MediaPlayer*);
</span><span class="lines">@@ -83,7 +85,6 @@
</span><span class="cx">     virtual double totalFrameDelay() override;
</span><span class="cx"> 
</span><span class="cx">     MediaPlayer* m_player;
</span><del>-    RefPtr&lt;MediaSourcePrivateClient&gt; m_mediaSource;
</del><span class="cx">     RefPtr&lt;MockMediaSourcePrivate&gt; m_mediaSourcePrivate;
</span><span class="cx"> 
</span><span class="cx">     MediaTime m_currentTime;
</span><span class="lines">@@ -91,6 +92,7 @@
</span><span class="cx">     MediaPlayer::ReadyState m_readyState;
</span><span class="cx">     MediaPlayer::NetworkState m_networkState;
</span><span class="cx">     bool m_playing;
</span><ins>+    bool m_seekCompleted;
</ins><span class="cx"> };
</span><span class="cx"> 
</span><span class="cx"> }
</span></span></pre></div>
<a id="trunkSourceWebCoreplatformmockmediasourceMockMediaSourcePrivatecpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/platform/mock/mediasource/MockMediaSourcePrivate.cpp (171032 => 171033)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/platform/mock/mediasource/MockMediaSourcePrivate.cpp        2014-07-12 22:14:44 UTC (rev 171032)
+++ trunk/Source/WebCore/platform/mock/mediasource/MockMediaSourcePrivate.cpp        2014-07-12 22:39:43 UTC (rev 171033)
</span><span class="lines">@@ -30,19 +30,22 @@
</span><span class="cx"> 
</span><span class="cx"> #include &quot;ContentType.h&quot;
</span><span class="cx"> #include &quot;ExceptionCodePlaceholder.h&quot;
</span><ins>+#include &quot;MediaSourcePrivateClient.h&quot;
</ins><span class="cx"> #include &quot;MockMediaPlayerMediaSource.h&quot;
</span><span class="cx"> #include &quot;MockSourceBufferPrivate.h&quot;
</span><span class="cx"> 
</span><span class="cx"> namespace WebCore {
</span><span class="cx"> 
</span><del>-RefPtr&lt;MockMediaSourcePrivate&gt; MockMediaSourcePrivate::create(MockMediaPlayerMediaSource* parent)
</del><ins>+RefPtr&lt;MockMediaSourcePrivate&gt; MockMediaSourcePrivate::create(MockMediaPlayerMediaSource* parent, MediaSourcePrivateClient* client)
</ins><span class="cx"> {
</span><del>-    return adoptRef(new MockMediaSourcePrivate(parent));
</del><ins>+    RefPtr&lt;MockMediaSourcePrivate&gt; mediaSourcePrivate = adoptRef(new MockMediaSourcePrivate(parent, client));
+    client-&gt;setPrivateAndOpen(*mediaSourcePrivate);
+    return mediaSourcePrivate;
</ins><span class="cx"> }
</span><span class="cx"> 
</span><del>-MockMediaSourcePrivate::MockMediaSourcePrivate(MockMediaPlayerMediaSource* parent)
</del><ins>+MockMediaSourcePrivate::MockMediaSourcePrivate(MockMediaPlayerMediaSource* parent, MediaSourcePrivateClient* client)
</ins><span class="cx">     : m_player(parent)
</span><del>-    , m_duration(MediaTime::invalidTime())
</del><ins>+    , m_client(client)
</ins><span class="cx">     , m_isEnded(false)
</span><span class="cx">     , m_totalVideoFrames(0)
</span><span class="cx">     , m_droppedVideoFrames(0)
</span><span class="lines">@@ -84,18 +87,19 @@
</span><span class="cx">     m_sourceBuffers.remove(pos);
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-MediaTime MockMediaSourcePrivate::duration()
</del><ins>+double MockMediaSourcePrivate::duration()
</ins><span class="cx"> {
</span><del>-    return m_duration;
</del><ins>+    return m_client-&gt;duration();
</ins><span class="cx"> }
</span><span class="cx"> 
</span><del>-void MockMediaSourcePrivate::setDuration(const MediaTime&amp; duration)
</del><ins>+std::unique_ptr&lt;PlatformTimeRanges&gt; MockMediaSourcePrivate::buffered()
</ins><span class="cx"> {
</span><del>-    if (duration == m_duration)
-        return;
</del><ins>+    return m_client-&gt;buffered();
+}
</ins><span class="cx"> 
</span><del>-    m_duration = duration;
-    m_player-&gt;updateDuration(duration);
</del><ins>+void MockMediaSourcePrivate::durationChanged()
+{
+    m_player-&gt;updateDuration(MediaTime::createWithDouble(duration()));
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> void MockMediaSourcePrivate::markEndOfStream(EndOfStreamStatus status)
</span><span class="lines">@@ -120,6 +124,16 @@
</span><span class="cx">     m_player-&gt;setReadyState(readyState);
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+void MockMediaSourcePrivate::waitForSeekCompleted()
+{
+    m_player-&gt;waitForSeekCompleted();
+}
+
+void MockMediaSourcePrivate::seekCompleted()
+{
+    m_player-&gt;seekCompleted();
+}
+
</ins><span class="cx"> void MockMediaSourcePrivate::sourceBufferPrivateDidChangeActiveState(MockSourceBufferPrivate* buffer, bool active)
</span><span class="cx"> {
</span><span class="cx">     if (active &amp;&amp; !m_activeSourceBuffers.contains(buffer))
</span></span></pre></div>
<a id="trunkSourceWebCoreplatformmockmediasourceMockMediaSourcePrivateh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/platform/mock/mediasource/MockMediaSourcePrivate.h (171032 => 171033)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/platform/mock/mediasource/MockMediaSourcePrivate.h        2014-07-12 22:14:44 UTC (rev 171032)
+++ trunk/Source/WebCore/platform/mock/mediasource/MockMediaSourcePrivate.h        2014-07-12 22:39:43 UTC (rev 171033)
</span><span class="lines">@@ -39,7 +39,7 @@
</span><span class="cx"> 
</span><span class="cx"> class MockMediaSourcePrivate final : public MediaSourcePrivate {
</span><span class="cx"> public:
</span><del>-    static RefPtr&lt;MockMediaSourcePrivate&gt; create(MockMediaPlayerMediaSource*);
</del><ins>+    static RefPtr&lt;MockMediaSourcePrivate&gt; create(MockMediaPlayerMediaSource*, MediaSourcePrivateClient*);
</ins><span class="cx">     virtual ~MockMediaSourcePrivate();
</span><span class="cx"> 
</span><span class="cx">     const Vector&lt;MockSourceBufferPrivate*&gt;&amp; activeSourceBuffers() const { return m_activeSourceBuffers; }
</span><span class="lines">@@ -47,6 +47,9 @@
</span><span class="cx">     bool hasAudio() const;
</span><span class="cx">     bool hasVideo() const;
</span><span class="cx"> 
</span><ins>+    double duration();
+    std::unique_ptr&lt;PlatformTimeRanges&gt; buffered();
+
</ins><span class="cx">     MockMediaPlayerMediaSource* player() const { return m_player; }
</span><span class="cx"> 
</span><span class="cx">     void seekToTime(const MediaTime&amp;);
</span><span class="lines">@@ -63,16 +66,17 @@
</span><span class="cx">     void incrementTotalFrameDelayBy(double delay) { m_totalFrameDelay += delay; }
</span><span class="cx"> 
</span><span class="cx"> private:
</span><del>-    MockMediaSourcePrivate(MockMediaPlayerMediaSource*);
</del><ins>+    MockMediaSourcePrivate(MockMediaPlayerMediaSource*, MediaSourcePrivateClient*);
</ins><span class="cx"> 
</span><span class="cx">     // MediaSourcePrivate Overrides
</span><span class="cx">     virtual AddStatus addSourceBuffer(const ContentType&amp;, RefPtr&lt;SourceBufferPrivate&gt;&amp;) override;
</span><del>-    virtual MediaTime duration() override;
-    virtual void setDuration(const MediaTime&amp;) override;
</del><ins>+    virtual void durationChanged() override;
</ins><span class="cx">     virtual void markEndOfStream(EndOfStreamStatus) override;
</span><span class="cx">     virtual void unmarkEndOfStream() override;
</span><span class="cx">     virtual MediaPlayer::ReadyState readyState() const override;
</span><span class="cx">     virtual void setReadyState(MediaPlayer::ReadyState) override;
</span><ins>+    virtual void waitForSeekCompleted() override;
+    virtual void seekCompleted() override;
</ins><span class="cx"> 
</span><span class="cx">     void sourceBufferPrivateDidChangeActiveState(MockSourceBufferPrivate*, bool active);
</span><span class="cx">     void removeSourceBuffer(SourceBufferPrivate*);
</span><span class="lines">@@ -80,7 +84,7 @@
</span><span class="cx">     friend class MockSourceBufferPrivate;
</span><span class="cx"> 
</span><span class="cx">     MockMediaPlayerMediaSource* m_player;
</span><del>-    MediaTime m_duration;
</del><ins>+    RefPtr&lt;MediaSourcePrivateClient&gt; m_client;
</ins><span class="cx">     Vector&lt;RefPtr&lt;MockSourceBufferPrivate&gt;&gt; m_sourceBuffers;
</span><span class="cx">     Vector&lt;MockSourceBufferPrivate*&gt; m_activeSourceBuffers;
</span><span class="cx">     bool m_isEnded;
</span></span></pre>
</div>
</div>

</body>
</html>