<!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>[175430] trunk/Source/WebKit2</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/175430">175430</a></dd>
<dt>Author</dt> <dd>jer.noble@apple.com</dd>
<dt>Date</dt> <dd>2014-10-31 16:07:07 -0700 (Fri, 31 Oct 2014)</dd>
</dl>

<h3>Log Message</h3>
<pre>[WK2] Send origin &amp; deletion requests to WebProcessess in additon to the DatabaseProcess.
https://bugs.webkit.org/show_bug.cgi?id=138212

Reviewed by Brady Eidson.

Now that the WebProcess has its own WebOriginDataManager, WebOriginDataManagerProxy needs to
send its origin and deletion messages to all the WebProcesses. This necessetates synchronizing
all the various process's callbacks so that the final callback is only triggered once all the
messaged processes reply.

Add a simple class, CallbackSynchronizer, which will keep track of outstanding process callbacks
and which will trigger the final callback once all of them finish.

* UIProcess/WebOriginDataManagerProxy.cpp:
(WebKit::CallbackSynchronizer::create):
(WebKit::CallbackSynchronizer::~CallbackSynchronizer):
(WebKit::CallbackSynchronizer::taskStarted):
(WebKit::CallbackSynchronizer::taskCompleted):
(WebKit::CallbackSynchronizer::CallbackSynchronizer):
(WebKit::createSynchronizedCallback):
(WebKit::sendMessageToAllProcessesInContext):
(WebKit::WebOriginDataManagerProxy::getOrigins):
(WebKit::WebOriginDataManagerProxy::deleteEntriesForOrigin):
(WebKit::WebOriginDataManagerProxy::deleteEntriesModifiedBetweenDates):
(WebKit::WebOriginDataManagerProxy::deleteAllEntries):

Drive-by fix: check the correct enum value for WebMedia origin requests.

* WebProcess/WebProcess.cpp:
(WebKit::WebProcess::getOrigins):
(WebKit::WebProcess::deleteEntriesForOrigin):
(WebKit::WebProcess::deleteEntriesModifiedBetweenDates):
(WebKit::WebProcess::deleteAllEntries):</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkSourceWebKit2ChangeLog">trunk/Source/WebKit2/ChangeLog</a></li>
<li><a href="#trunkSourceWebKit2UIProcessWebOriginDataManagerProxycpp">trunk/Source/WebKit2/UIProcess/WebOriginDataManagerProxy.cpp</a></li>
<li><a href="#trunkSourceWebKit2WebProcessWebProcesscpp">trunk/Source/WebKit2/WebProcess/WebProcess.cpp</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkSourceWebKit2ChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebKit2/ChangeLog (175429 => 175430)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebKit2/ChangeLog        2014-10-31 22:42:13 UTC (rev 175429)
+++ trunk/Source/WebKit2/ChangeLog        2014-10-31 23:07:07 UTC (rev 175430)
</span><span class="lines">@@ -1,3 +1,39 @@
</span><ins>+2014-10-30  Jer Noble  &lt;jer.noble@apple.com&gt;
+
+        [WK2] Send origin &amp; deletion requests to WebProcessess in additon to the DatabaseProcess.
+        https://bugs.webkit.org/show_bug.cgi?id=138212
+
+        Reviewed by Brady Eidson.
+
+        Now that the WebProcess has its own WebOriginDataManager, WebOriginDataManagerProxy needs to
+        send its origin and deletion messages to all the WebProcesses. This necessetates synchronizing
+        all the various process's callbacks so that the final callback is only triggered once all the
+        messaged processes reply.
+
+        Add a simple class, CallbackSynchronizer, which will keep track of outstanding process callbacks
+        and which will trigger the final callback once all of them finish.
+
+        * UIProcess/WebOriginDataManagerProxy.cpp:
+        (WebKit::CallbackSynchronizer::create):
+        (WebKit::CallbackSynchronizer::~CallbackSynchronizer):
+        (WebKit::CallbackSynchronizer::taskStarted):
+        (WebKit::CallbackSynchronizer::taskCompleted):
+        (WebKit::CallbackSynchronizer::CallbackSynchronizer):
+        (WebKit::createSynchronizedCallback):
+        (WebKit::sendMessageToAllProcessesInContext):
+        (WebKit::WebOriginDataManagerProxy::getOrigins):
+        (WebKit::WebOriginDataManagerProxy::deleteEntriesForOrigin):
+        (WebKit::WebOriginDataManagerProxy::deleteEntriesModifiedBetweenDates):
+        (WebKit::WebOriginDataManagerProxy::deleteAllEntries):
+
+        Drive-by fix: check the correct enum value for WebMedia origin requests.
+
+        * WebProcess/WebProcess.cpp:
+        (WebKit::WebProcess::getOrigins):
+        (WebKit::WebProcess::deleteEntriesForOrigin):
+        (WebKit::WebProcess::deleteEntriesModifiedBetweenDates):
+        (WebKit::WebProcess::deleteAllEntries):
+
</ins><span class="cx"> 2014-10-31  Tim Horton  &lt;timothy_horton@apple.com&gt;
</span><span class="cx"> 
</span><span class="cx">         REGRESSION (r175376): Occasional null deref when doing a dictionary lookup
</span></span></pre></div>
<a id="trunkSourceWebKit2UIProcessWebOriginDataManagerProxycpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebKit2/UIProcess/WebOriginDataManagerProxy.cpp (175429 => 175430)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebKit2/UIProcess/WebOriginDataManagerProxy.cpp        2014-10-31 22:42:13 UTC (rev 175429)
+++ trunk/Source/WebKit2/UIProcess/WebOriginDataManagerProxy.cpp        2014-10-31 23:07:07 UTC (rev 175430)
</span><span class="lines">@@ -84,27 +84,112 @@
</span><span class="cx">     API::Object::deref();
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-void WebOriginDataManagerProxy::getOrigins(WKOriginDataTypes types, std::function&lt;void (API::Array*, CallbackBase::Error)&gt; callbackFunction)
</del><ins>+class CallbackSynchronizer : public RefCounted&lt;CallbackSynchronizer&gt; {
+public:
+    static PassRefPtr&lt;CallbackSynchronizer&gt; create(const std::function&lt;void(const CallbackBase::Error&amp;)&gt;&amp; callback)
+    {
+        return adoptRef(new CallbackSynchronizer(callback));
+    }
+
+    ~CallbackSynchronizer()
+    {
+        ASSERT(!m_count);
+        ASSERT(!m_callback);
+    }
+
+    void taskStarted()
+    {
+        ++m_count;
+    }
+
+    void taskCompleted(const CallbackBase::Error&amp; error)
+    {
+        if (error != CallbackBase::Error::None)
+            m_error = error;
+
+        ASSERT(m_count);
+        if (!--m_count) {
+            ASSERT(m_callback);
+            m_callback(m_error);
+            m_callback = nullptr;
+        }
+    }
+
+protected:
+    CallbackSynchronizer(const std::function&lt;void(const CallbackBase::Error&amp;)&gt;&amp; callback)
+        : m_count(0)
+        , m_callback(callback)
+        , m_error(CallbackBase::Error::None)
+    {
+        ASSERT(m_callback);
+    }
+
+    unsigned m_count;
+    std::function&lt;void(const CallbackBase::Error&amp;)&gt; m_callback;
+    CallbackBase::Error m_error;
+};
+
+static std::pair&lt;RefPtr&lt;CallbackSynchronizer&gt;, VoidCallback::CallbackFunction&gt; createSynchronizedCallback(typename VoidCallback::CallbackFunction callback)
</ins><span class="cx"> {
</span><del>-    // FIXME: Right now we only support IndexedDatabase data so we know that we're only sending this request to the DatabaseProcess.
-    // That's why having one single callback works.
-    // In the future when we message N-processes we'll have to wait for all N replies before responding to the client.
-    if (!(types &amp; kWKIndexedDatabaseData)) {
-        callbackFunction(nullptr, CallbackBase::Error::None);
</del><ins>+    RefPtr&lt;CallbackSynchronizer&gt; synchronizer = CallbackSynchronizer::create(callback);
+    VoidCallback::CallbackFunction synchronizedCallback = [synchronizer](CallbackBase::Error error) {
+        synchronizer-&gt;taskCompleted(error);
+    };
+
+    return std::make_pair(synchronizer, synchronizedCallback);
+}
+
+static std::pair&lt;RefPtr&lt;CallbackSynchronizer&gt;, ArrayCallback::CallbackFunction&gt; createSynchronizedCallback(typename ArrayCallback::CallbackFunction callback)
+{
+    RefPtr&lt;API::Array&gt; aggregateArray = API::Array::create();
+    RefPtr&lt;CallbackSynchronizer&gt; synchronizer = CallbackSynchronizer::create([aggregateArray, callback](const CallbackBase::Error&amp; error) {
+        callback(aggregateArray.get(), error);
+    });
+
+    ArrayCallback::CallbackFunction synchronizedCallback = [aggregateArray, synchronizer](API::Array* array, CallbackBase::Error error) {
+        if (array)
+            aggregateArray-&gt;elements().appendVector(array-&gt;elements());
+        synchronizer-&gt;taskCompleted(error);
+    };
+
+    return std::make_pair(synchronizer, synchronizedCallback);
+}
+
+template &lt;typename CallbackType, typename MessageType, typename... Parameters&gt;
+static void sendMessageToAllProcessesInContext(WebContext* context, typename CallbackType::CallbackFunction callback, HashMap&lt;uint64_t, RefPtr&lt;CallbackType&gt;&gt;&amp; callbackStorage, Parameters... parameters)
+{
+    if (!context) {
+        CallbackType::create(callback)-&gt;invalidate();
</ins><span class="cx">         return;
</span><span class="cx">     }
</span><span class="cx"> 
</span><del>-    RefPtr&lt;ArrayCallback&gt; callback = ArrayCallback::create(WTF::move(callbackFunction));
</del><ins>+    auto synchronizerAndCallback = createSynchronizedCallback(callback);
+    RefPtr&lt;CallbackSynchronizer&gt; synchronizer = synchronizerAndCallback.first;
+    auto perProcessCallback = synchronizerAndCallback.second;
</ins><span class="cx"> 
</span><del>-    if (!context()) {
-        callback-&gt;invalidate();
-        return;
</del><ins>+    for (auto&amp; process : context-&gt;processes()) {
+        if (!process || !process-&gt;canSendMessage())
+            continue;
+
+        synchronizer-&gt;taskStarted();
+        RefPtr&lt;CallbackType&gt; callback = CallbackType::create(perProcessCallback);
+        uint64_t callbackID = callback-&gt;callbackID();
+        callbackStorage.set(callbackID, callback.release());
+        process-&gt;send(MessageType(parameters..., callbackID), 0);
</ins><span class="cx">     }
</span><span class="cx"> 
</span><del>-    uint64_t callbackID = callback-&gt;callbackID();
-    m_arrayCallbacks.set(callbackID, callback.release());
</del><ins>+    {
+        synchronizer-&gt;taskStarted();
+        RefPtr&lt;CallbackType&gt; callback = CallbackType::create(perProcessCallback);
+        uint64_t callbackID = callback-&gt;callbackID();
+        callbackStorage.set(callbackID, callback.release());
+        context-&gt;sendToDatabaseProcessRelaunchingIfNecessary(MessageType(parameters..., callbackID));
+    }
+}
</ins><span class="cx"> 
</span><del>-    context()-&gt;sendToDatabaseProcessRelaunchingIfNecessary(Messages::WebOriginDataManager::GetOrigins(types, callbackID));
</del><ins>+void WebOriginDataManagerProxy::getOrigins(WKOriginDataTypes types, std::function&lt;void (API::Array*, CallbackBase::Error)&gt; callbackFunction)
+{
+    sendMessageToAllProcessesInContext&lt;ArrayCallback, Messages::WebOriginDataManager::GetOrigins&gt;(context(), callbackFunction, m_arrayCallbacks, types);
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> void WebOriginDataManagerProxy::didGetOrigins(IPC::Connection* connection, const Vector&lt;SecurityOriginData&gt;&amp; originIdentifiers, uint64_t callbackID)
</span><span class="lines">@@ -123,53 +208,17 @@
</span><span class="cx"> 
</span><span class="cx"> void WebOriginDataManagerProxy::deleteEntriesForOrigin(WKOriginDataTypes types, WebSecurityOrigin* origin, std::function&lt;void (CallbackBase::Error)&gt; callbackFunction)
</span><span class="cx"> {
</span><del>-    // FIXME: Right now we only support IndexedDatabase data so we know that we're only sending this request to the DatabaseProcess.
-    // That's why having one single callback works.
-    // In the future when we message N-processes we'll have to wait for all N replies before responding to the client.
-    if (!(types &amp; kWKIndexedDatabaseData)) {
-        callbackFunction(CallbackBase::Error::None);
-        return;
-    }
-
-    RefPtr&lt;VoidCallback&gt; callback = VoidCallback::create(WTF::move(callbackFunction));
-
-    if (!context()) {
-        callback-&gt;invalidate();
-        return;
-    }
-
-    uint64_t callbackID = callback-&gt;callbackID();
-    m_voidCallbacks.set(callbackID, callback.release());
-
</del><span class="cx">     SecurityOriginData securityOriginData;
</span><span class="cx">     securityOriginData.protocol = origin-&gt;securityOrigin().protocol();
</span><span class="cx">     securityOriginData.host = origin-&gt;securityOrigin().host();
</span><span class="cx">     securityOriginData.port = origin-&gt;securityOrigin().port();
</span><span class="cx"> 
</span><del>-    context()-&gt;sendToDatabaseProcessRelaunchingIfNecessary(Messages::WebOriginDataManager::DeleteEntriesForOrigin(types, securityOriginData, callbackID));
</del><ins>+    sendMessageToAllProcessesInContext&lt;VoidCallback, Messages::WebOriginDataManager::DeleteEntriesForOrigin&gt;(context(), callbackFunction, m_voidCallbacks, types, securityOriginData);
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> void WebOriginDataManagerProxy::deleteEntriesModifiedBetweenDates(WKOriginDataTypes types, double startDate, double endDate, std::function&lt;void (CallbackBase::Error)&gt; callbackFunction)
</span><span class="cx"> {
</span><del>-    // FIXME: Right now we only support IndexedDatabase data so we know that we're only sending this request to the DatabaseProcess.
-    // That's why having one single callback works.
-    // In the future when we message N-processes we'll have to wait for all N replies before responding to the client.
-    if (!(types &amp; kWKIndexedDatabaseData)) {
-        callbackFunction(CallbackBase::Error::None);
-        return;
-    }
-
-    RefPtr&lt;VoidCallback&gt; callback = VoidCallback::create(WTF::move(callbackFunction));
-
-    if (!context()) {
-        callback-&gt;invalidate();
-        return;
-    }
-
-    uint64_t callbackID = callback-&gt;callbackID();
-    m_voidCallbacks.set(callbackID, callback.release());
-
-    context()-&gt;sendToDatabaseProcessRelaunchingIfNecessary(Messages::WebOriginDataManager::DeleteEntriesModifiedBetweenDates(types, startDate, endDate, callbackID));
</del><ins>+    sendMessageToAllProcessesInContext&lt;VoidCallback, Messages::WebOriginDataManager::DeleteEntriesModifiedBetweenDates&gt;(context(), callbackFunction, m_voidCallbacks, types, startDate, endDate);
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> void WebOriginDataManagerProxy::didDeleteEntries(IPC::Connection* connection, uint64_t callbackID)
</span><span class="lines">@@ -181,25 +230,7 @@
</span><span class="cx"> 
</span><span class="cx"> void WebOriginDataManagerProxy::deleteAllEntries(WKOriginDataTypes types, std::function&lt;void (CallbackBase::Error)&gt; callbackFunction)
</span><span class="cx"> {
</span><del>-    // FIXME: Right now we only support IndexedDatabase data so we know that we're only sending this request to the DatabaseProcess.
-    // That's why having one single callback works.
-    // In the future when we message N-processes we'll have to wait for all N replies before responding to the client.
-    if (!(types &amp; kWKIndexedDatabaseData)) {
-        callbackFunction(CallbackBase::Error::None);
-        return;
-    }
-
-    RefPtr&lt;VoidCallback&gt; callback = VoidCallback::create(WTF::move(callbackFunction));
-
-    if (!context()) {
-        callback-&gt;invalidate();
-        return;
-    }
-
-    uint64_t callbackID = callback-&gt;callbackID();
-    m_voidCallbacks.set(callbackID, callback.release());
-
-    context()-&gt;sendToDatabaseProcessRelaunchingIfNecessary(Messages::WebOriginDataManager::DeleteAllEntries(types, callbackID));
</del><ins>+    sendMessageToAllProcessesInContext&lt;VoidCallback, Messages::WebOriginDataManager::DeleteAllEntries&gt;(context(), callbackFunction, m_voidCallbacks, types);
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> void WebOriginDataManagerProxy::didDeleteAllEntries(IPC::Connection* connection, uint64_t callbackID)
</span></span></pre></div>
<a id="trunkSourceWebKit2WebProcessWebProcesscpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebKit2/WebProcess/WebProcess.cpp (175429 => 175430)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebKit2/WebProcess/WebProcess.cpp        2014-10-31 22:42:13 UTC (rev 175429)
+++ trunk/Source/WebKit2/WebProcess/WebProcess.cpp        2014-10-31 23:07:07 UTC (rev 175430)
</span><span class="lines">@@ -1254,7 +1254,7 @@
</span><span class="cx"> 
</span><span class="cx"> void WebProcess::getOrigins(WKOriginDataTypes types, std::function&lt;void(const Vector&lt;SecurityOriginData&gt;&amp;)&gt; completion)
</span><span class="cx"> {
</span><del>-    if (!(types &amp; kWKWebSQLDatabaseOriginData)) {
</del><ins>+    if (!(types &amp; kWKMediaKeyStorageOriginData)) {
</ins><span class="cx">         completion(Vector&lt;SecurityOriginData&gt;());
</span><span class="cx">         return;
</span><span class="cx">     }
</span><span class="lines">@@ -1270,7 +1270,7 @@
</span><span class="cx"> 
</span><span class="cx"> void WebProcess::deleteEntriesForOrigin(WKOriginDataTypes types, const SecurityOriginData&amp; origin, std::function&lt;void()&gt; completion)
</span><span class="cx"> {
</span><del>-    if (!(types &amp; kWKWebSQLDatabaseOriginData)) {
</del><ins>+    if (!(types &amp; kWKMediaKeyStorageOriginData)) {
</ins><span class="cx">         completion();
</span><span class="cx">         return;
</span><span class="cx">     }
</span><span class="lines">@@ -1287,7 +1287,7 @@
</span><span class="cx"> 
</span><span class="cx"> void WebProcess::deleteEntriesModifiedBetweenDates(WKOriginDataTypes types, double startDate, double endDate, std::function&lt;void()&gt; completion)
</span><span class="cx"> {
</span><del>-    if (!(types &amp; kWKWebSQLDatabaseOriginData)) {
</del><ins>+    if (!(types &amp; kWKMediaKeyStorageOriginData)) {
</ins><span class="cx">         completion();
</span><span class="cx">         return;
</span><span class="cx">     }
</span><span class="lines">@@ -1304,7 +1304,7 @@
</span><span class="cx"> 
</span><span class="cx"> void WebProcess::deleteAllEntries(WKOriginDataTypes types, std::function&lt;void()&gt; completion)
</span><span class="cx"> {
</span><del>-    if (!(types &amp; kWKWebSQLDatabaseOriginData)) {
</del><ins>+    if (!(types &amp; kWKMediaKeyStorageOriginData)) {
</ins><span class="cx">         completion();
</span><span class="cx">         return;
</span><span class="cx">     }
</span></span></pre>
</div>
</div>

</body>
</html>