Title: [205211] trunk
Revision
205211
Author
[email protected]
Date
2016-08-30 17:27:27 -0700 (Tue, 30 Aug 2016)

Log Message

Web Inspector: Add resource timing model with timing information
https://bugs.webkit.org/show_bug.cgi?id=161314

Patch by Johan K. Jensen <[email protected]> on 2016-08-30
Reviewed by Joseph Pecoraro.

Source/WebInspectorUI:

Add a resource timing data model and populate it with info from the
response from the backend.

* UserInterface/Controllers/FrameResourceManager.js:
(WebInspector.FrameResourceManager.prototype.resourceRequestWasServedFromMemoryCache):
(WebInspector.FrameResourceManager.prototype.resourceRequestDidReceiveResponse):
Forward timing data from response to Resource.js.

* UserInterface/Main.html: Add new ResourceTimingData.js.
* UserInterface/Test.html: Add new ResourceTimingData.js.

* UserInterface/Models/Resource.js:
(WebInspector.Resource): Instantiate ResourceTimingData object.

(WebInspector.Resource.prototype.get timing):
(WebInspector.Resource.prototype.get firstTimestamp):
(WebInspector.Resource.prototype.get lastTimestamp):
(WebInspector.Resource.prototype.get duration):
(WebInspector.Resource.prototype.get latency):
(WebInspector.Resource.prototype.get receiveDuration):
Update getters to use new timing model.

(WebInspector.Resource.prototype.updateForResponse):
Update timing object with info from response.

(WebInspector.Resource.prototype.markAsFinished):
Log response end time.

* UserInterface/Models/ResourceTimelineRecord.js:
(WebInspector.ResourceTimelineRecord.prototype.get startTime):
(WebInspector.ResourceTimelineRecord.prototype.get activeStartTime):
(WebInspector.ResourceTimelineRecord.prototype.get endTime):
Update getters to use new timing model.

* UserInterface/Models/ResourceTimingData.js: Added.
(WebInspector.ResourceTimingData):
(WebInspector.ResourceTimingData.fromPayload):
(WebInspector.ResourceTimingData.prototype.get startTime):
(WebInspector.ResourceTimingData.prototype.get domainLookupStart):
(WebInspector.ResourceTimingData.prototype.get domainLookupEnd):
(WebInspector.ResourceTimingData.prototype.get connectStart):
(WebInspector.ResourceTimingData.prototype.get connectEnd):
(WebInspector.ResourceTimingData.prototype.get secureConnectionStart):
(WebInspector.ResourceTimingData.prototype.get requestStart):
(WebInspector.ResourceTimingData.prototype.get responseStart):
(WebInspector.ResourceTimingData.prototype.get responseEnd):
(WebInspector.ResourceTimingData.prototype.markResponseEndTime):
Add new ResourceTimingData model and fall back on old timestamps
for when data is unavailable.

LayoutTests:

Add tests for the Resource Timing Data model.

* http/tests/inspector/network/resource-timing-expected.txt: Added.
* http/tests/inspector/network/resource-timing.html: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (205210 => 205211)


--- trunk/LayoutTests/ChangeLog	2016-08-31 00:15:50 UTC (rev 205210)
+++ trunk/LayoutTests/ChangeLog	2016-08-31 00:27:27 UTC (rev 205211)
@@ -1,3 +1,15 @@
+2016-08-30  Johan K. Jensen  <[email protected]>
+
+        Web Inspector: Add resource timing model with timing information
+        https://bugs.webkit.org/show_bug.cgi?id=161314
+
+        Reviewed by Joseph Pecoraro.
+
+        Add tests for the Resource Timing Data model.
+
+        * http/tests/inspector/network/resource-timing-expected.txt: Added.
+        * http/tests/inspector/network/resource-timing.html: Added.
+
 2016-08-30  Chris Dumez  <[email protected]>
 
         Object.setPrototypeOf() should throw when used on a cross-origin Window / Location object

Added: trunk/LayoutTests/http/tests/inspector/network/resource-timing-expected.txt (0 => 205211)


--- trunk/LayoutTests/http/tests/inspector/network/resource-timing-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/http/tests/inspector/network/resource-timing-expected.txt	2016-08-31 00:27:27 UTC (rev 205211)
@@ -0,0 +1,19 @@
+Tests that a resource has timing information.
+
+
+== Running test suite: ResourceTimingData
+-- Running test case: CheckResourceTimingInformationForResource
+PASS: Newly added resource should have a resource timing model.
+PASS: Newly added resource should have a start time.
+PASS: Resource should now contain timing information.
+PASS: Resource should have a start time.
+PASS: Resource should have a request start time.
+PASS: Resource should have a response start time.
+PASS: domainLookupStart and domainLookupEnd should both be NaN or a number.
+PASS: connectStart and connectEnd should both be NaN or a number.
+PASS: requestStart should come after startTime.
+PASS: A secure connection should be reused or secureConnectionStart should come after connectStart.
+PASS: responseStart should come after requestStart.
+PASS: responseEnd should not be available yet.
+PASS: responseEnd should come after responseStart.
+

Added: trunk/LayoutTests/http/tests/inspector/network/resource-timing.html (0 => 205211)


--- trunk/LayoutTests/http/tests/inspector/network/resource-timing.html	                        (rev 0)
+++ trunk/LayoutTests/http/tests/inspector/network/resource-timing.html	2016-08-31 00:27:27 UTC (rev 205211)
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<script src=""
+<script>
+function createRequest() {
+    let img = document.createElement("img");
+    img.src = ""
+    document.body.appendChild(img);
+}
+
+function test() {
+    let suite = InspectorTest.createAsyncSuite("ResourceTimingData");
+    InspectorTest.debug();
+    suite.addTestCase({
+        name: "CheckResourceTimingInformationForResource",
+        description: "Check if a resource has timing information.",
+        test: (resolve, reject) => {
+            InspectorTest.evaluateInPage("createRequest()");
+            WebInspector.Frame.singleFireEventListener(WebInspector.Frame.Event.ResourceWasAdded, (event) => {
+                let resource = event.data.resource;
+
+                InspectorTest.expectThat(resource.timingData instanceof WebInspector.ResourceTimingData, "Newly added resource should have a resource timing model.");
+                InspectorTest.expectThat(resource.timingData.startTime, "Newly added resource should have a start time.");
+
+                resource.singleFireEventListener(WebInspector.Resource.Event.ResponseReceived, (event) => {
+                    let timingData = resource.timingData;
+
+                    InspectorTest.expectThat(timingData, "Resource should now contain timing information.");
+                    InspectorTest.expectThat(timingData.startTime > 0, "Resource should have a start time.");
+                    InspectorTest.expectThat(timingData.requestStart > 0, "Resource should have a request start time.");
+                    InspectorTest.expectThat(timingData.responseStart > 0, "Resource should have a response start time.");
+
+                    InspectorTest.expectThat(typeof timingData.domainLookupStart === "number" && typeof timingData.domainLookupEnd === "number", "domainLookupStart and domainLookupEnd should both be NaN or a number.");
+                    InspectorTest.expectThat(typeof timingData.connectStart === "number" && typeof timingData.connectStart === "number", "connectStart and connectEnd should both be NaN or a number.");
+
+                    InspectorTest.expectThat(timingData.startTime <= timingData.requestStart, "requestStart should come after startTime.");
+                    InspectorTest.expectThat(isNaN(timingData.secureConnectionStart) || timingData.connectStart <= timingData.secureConnectionStart, "A secure connection should be reused or secureConnectionStart should come after connectStart.");
+                    InspectorTest.expectThat(timingData.requestStart <= timingData.responseStart, "responseStart should come after requestStart.");
+                    InspectorTest.expectThat(isNaN(timingData.responseEnd), "responseEnd should not be available yet.");
+                });
+
+                resource.singleFireEventListener(WebInspector.Resource.Event.LoadingDidFinish, (event) => {
+                    let timingData = resource.timingData;
+                    InspectorTest.expectThat(timingData.responseStart <= timingData.responseEnd, "responseEnd should come after responseStart.");
+                    resolve();
+                });
+            });
+        }
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body _onload_="runTest()">
+    <p>Tests that a resource has timing information.</p>
+</body>
+</html>

Modified: trunk/Source/WebInspectorUI/ChangeLog (205210 => 205211)


--- trunk/Source/WebInspectorUI/ChangeLog	2016-08-31 00:15:50 UTC (rev 205210)
+++ trunk/Source/WebInspectorUI/ChangeLog	2016-08-31 00:27:27 UTC (rev 205211)
@@ -1,3 +1,60 @@
+2016-08-30  Johan K. Jensen  <[email protected]>
+
+        Web Inspector: Add resource timing model with timing information
+        https://bugs.webkit.org/show_bug.cgi?id=161314
+
+        Reviewed by Joseph Pecoraro.
+
+        Add a resource timing data model and populate it with info from the
+        response from the backend.
+
+        * UserInterface/Controllers/FrameResourceManager.js:
+        (WebInspector.FrameResourceManager.prototype.resourceRequestWasServedFromMemoryCache):
+        (WebInspector.FrameResourceManager.prototype.resourceRequestDidReceiveResponse):
+        Forward timing data from response to Resource.js.
+
+        * UserInterface/Main.html: Add new ResourceTimingData.js.
+        * UserInterface/Test.html: Add new ResourceTimingData.js.
+
+        * UserInterface/Models/Resource.js:
+        (WebInspector.Resource): Instantiate ResourceTimingData object.
+
+        (WebInspector.Resource.prototype.get timing):
+        (WebInspector.Resource.prototype.get firstTimestamp):
+        (WebInspector.Resource.prototype.get lastTimestamp):
+        (WebInspector.Resource.prototype.get duration):
+        (WebInspector.Resource.prototype.get latency):
+        (WebInspector.Resource.prototype.get receiveDuration):
+        Update getters to use new timing model.
+
+        (WebInspector.Resource.prototype.updateForResponse):
+        Update timing object with info from response.
+
+        (WebInspector.Resource.prototype.markAsFinished):
+        Log response end time.
+
+        * UserInterface/Models/ResourceTimelineRecord.js:
+        (WebInspector.ResourceTimelineRecord.prototype.get startTime):
+        (WebInspector.ResourceTimelineRecord.prototype.get activeStartTime):
+        (WebInspector.ResourceTimelineRecord.prototype.get endTime):
+        Update getters to use new timing model.
+
+        * UserInterface/Models/ResourceTimingData.js: Added.
+        (WebInspector.ResourceTimingData):
+        (WebInspector.ResourceTimingData.fromPayload):
+        (WebInspector.ResourceTimingData.prototype.get startTime):
+        (WebInspector.ResourceTimingData.prototype.get domainLookupStart):
+        (WebInspector.ResourceTimingData.prototype.get domainLookupEnd):
+        (WebInspector.ResourceTimingData.prototype.get connectStart):
+        (WebInspector.ResourceTimingData.prototype.get connectEnd):
+        (WebInspector.ResourceTimingData.prototype.get secureConnectionStart):
+        (WebInspector.ResourceTimingData.prototype.get requestStart):
+        (WebInspector.ResourceTimingData.prototype.get responseStart):
+        (WebInspector.ResourceTimingData.prototype.get responseEnd):
+        (WebInspector.ResourceTimingData.prototype.markResponseEndTime):
+        Add new ResourceTimingData model and fall back on old timestamps
+        for when data is unavailable.
+
 2016-08-30  Alex Christensen  <[email protected]>
 
         Fix WebInspectorUI in internal Windows build

Modified: trunk/Source/WebInspectorUI/UserInterface/Controllers/FrameResourceManager.js (205210 => 205211)


--- trunk/Source/WebInspectorUI/UserInterface/Controllers/FrameResourceManager.js	2016-08-31 00:15:50 UTC (rev 205210)
+++ trunk/Source/WebInspectorUI/UserInterface/Controllers/FrameResourceManager.js	2016-08-31 00:27:27 UTC (rev 205211)
@@ -236,7 +236,7 @@
         var response = cachedResourcePayload.response;
         var resource = this._addNewResourceToFrame(requestIdentifier, frameIdentifier, loaderIdentifier, cachedResourcePayload.url, cachedResourcePayload.type, "GET", null, null, elapsedTime, null, null, initiatorSourceCodeLocation);
         resource.markAsCached();
-        resource.updateForResponse(cachedResourcePayload.url, response.mimeType, cachedResourcePayload.type, response.headers, response.status, response.statusText, elapsedTime);
+        resource.updateForResponse(cachedResourcePayload.url, response.mimeType, cachedResourcePayload.type, response.headers, response.status, response.statusText, elapsedTime, response.timing);
         resource.increaseSize(cachedResourcePayload.bodySize, elapsedTime);
         resource.increaseTransferSize(cachedResourcePayload.bodySize);
         resource.markAsFinished(elapsedTime);
@@ -288,7 +288,7 @@
         if (response.fromDiskCache)
             resource.markAsCached();
 
-        resource.updateForResponse(response.url, response.mimeType, type, response.headers, response.status, response.statusText, elapsedTime);
+        resource.updateForResponse(response.url, response.mimeType, type, response.headers, response.status, response.statusText, elapsedTime, response.timing);
     }
 
     resourceRequestDidReceiveData(requestIdentifier, dataLength, encodedDataLength, timestamp)

Modified: trunk/Source/WebInspectorUI/UserInterface/Main.html (205210 => 205211)


--- trunk/Source/WebInspectorUI/UserInterface/Main.html	2016-08-31 00:15:50 UTC (rev 205210)
+++ trunk/Source/WebInspectorUI/UserInterface/Main.html	2016-08-31 00:27:27 UTC (rev 205211)
@@ -357,6 +357,7 @@
     <script src=""
     <script src=""
     <script src=""
+    <script src=""
     <script src=""
     <script src=""
     <script src=""

Modified: trunk/Source/WebInspectorUI/UserInterface/Models/Resource.js (205210 => 205211)


--- trunk/Source/WebInspectorUI/UserInterface/Models/Resource.js	2016-08-31 00:15:50 UTC (rev 205210)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/Resource.js	2016-08-31 00:27:27 UTC (rev 205211)
@@ -59,6 +59,7 @@
         this._size = NaN;
         this._transferSize = NaN;
         this._cached = false;
+        this._timingData = new WebInspector.ResourceTimingData(this);
 
         if (this._initiatorSourceCodeLocation && this._initiatorSourceCodeLocation.sourceCode instanceof WebInspector.Resource)
             this._initiatorSourceCodeLocation.sourceCode.addInitiatedResource(this);
@@ -126,6 +127,8 @@
 
     // Public
 
+    get timingData() { return this._timingData; }
+
     get url()
     {
         return this._url;
@@ -317,27 +320,27 @@
 
     get firstTimestamp()
     {
-        return this.requestSentTimestamp || this.lastRedirectReceivedTimestamp || this.responseReceivedTimestamp || this.lastDataReceivedTimestamp || this.finishedOrFailedTimestamp;
+        return this.timingData.startTime || this.lastRedirectReceivedTimestamp || this.responseReceivedTimestamp || this.lastDataReceivedTimestamp || this.finishedOrFailedTimestamp;
     }
 
     get lastTimestamp()
     {
-        return this.finishedOrFailedTimestamp || this.lastDataReceivedTimestamp || this.responseReceivedTimestamp || this.lastRedirectReceivedTimestamp || this.requestSentTimestamp;
+        return this.timingData.responseEnd || this.lastDataReceivedTimestamp || this.responseReceivedTimestamp || this.lastRedirectReceivedTimestamp || this.requestSentTimestamp;
     }
 
     get duration()
     {
-        return this._finishedOrFailedTimestamp - this._requestSentTimestamp;
+        return this.timingData.responseEnd - this.timingData.requestStart;
     }
 
     get latency()
     {
-        return this._responseReceivedTimestamp - this._requestSentTimestamp;
+        return this.timingData.responseStart - this.timingData.requestStart;
     }
 
     get receiveDuration()
     {
-        return this._finishedOrFailedTimestamp - this._responseReceivedTimestamp;
+        return this.timingData.responseEnd - this.timingData.responseStart;
     }
 
     get cached()
@@ -447,7 +450,7 @@
         this.dispatchEventToListeners(WebInspector.Resource.Event.TimestampsDidChange);
     }
 
-    updateForResponse(url, mimeType, type, responseHeaders, statusCode, statusText, elapsedTime)
+    updateForResponse(url, mimeType, type, responseHeaders, statusCode, statusText, elapsedTime, timingData)
     {
         console.assert(!this._finished);
         console.assert(!this._failed);
@@ -467,6 +470,7 @@
         this._statusText = statusText;
         this._responseHeaders = responseHeaders || {};
         this._responseReceivedTimestamp = elapsedTime || NaN;
+        this._timingData = WebInspector.ResourceTimingData.fromPayload(timingData, this);
 
         this._responseHeadersSize = String(this._statusCode).length + this._statusText.length + 12; // Extra length is for "HTTP/1.1 ", " ", and "\r\n".
         for (var name in this._responseHeaders)
@@ -572,6 +576,7 @@
 
         this._finished = true;
         this._finishedOrFailedTimestamp = elapsedTime || NaN;
+        this._timingData.markResponseEndTime(elapsedTime || NaN);
 
         if (this._finishThenRequestContentPromise)
             this._finishThenRequestContentPromise = null;

Modified: trunk/Source/WebInspectorUI/UserInterface/Models/ResourceTimelineRecord.js (205210 => 205211)


--- trunk/Source/WebInspectorUI/UserInterface/Models/ResourceTimelineRecord.js	2016-08-31 00:15:50 UTC (rev 205210)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/ResourceTimelineRecord.js	2016-08-31 00:27:27 UTC (rev 205211)
@@ -52,17 +52,17 @@
 
     get startTime()
     {
-        return this._resource.requestSentTimestamp;
+        return this._resource.timingData.startTime;
     }
 
     get activeStartTime()
     {
-        return this._resource.responseReceivedTimestamp;
+        return this._resource.timingData.responseStart;
     }
 
     get endTime()
     {
-        return this._resource.finishedOrFailedTimestamp;
+        return this._resource.timingData.responseEnd;
     }
 
     // Private

Added: trunk/Source/WebInspectorUI/UserInterface/Models/ResourceTimingData.js (0 => 205211)


--- trunk/Source/WebInspectorUI/UserInterface/Models/ResourceTimingData.js	                        (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/ResourceTimingData.js	2016-08-31 00:27:27 UTC (rev 205211)
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+WebInspector.ResourceTimingData = class ResourceTimingData extends WebInspector.Object
+{
+    constructor(resource, data)
+    {
+        super();
+
+        data = "" || {};
+
+        console.assert(isNaN(data.domainLookupStart) === isNaN(data.domainLookupEnd));
+        console.assert(isNaN(data.connectStart) === isNaN(data.connectEnd));
+
+        this._resource = resource;
+
+        this._startTime = data.startTime || NaN;
+        this._domainLookupStart = data.domainLookupStart || NaN;
+        this._domainLookupEnd = data.domainLookupEnd || NaN;
+        this._connectStart = data.connectStart || NaN;
+        this._connectEnd = data.connectEnd || NaN;
+        this._secureConnectionStart = data.secureConnectionStart || NaN;
+        this._requestStart = data.requestStart || NaN;
+        this._responseStart = data.responseStart || NaN;
+        this._responseEnd = data.responseEnd || NaN;
+
+        if (this._domainLookupStart >= this._domainLookupEnd)
+            this._domainLookupStart = this._domainLookupEnd = NaN;
+
+        if (this._connectStart >= this._connectEnd)
+            this._connectStart = this._connectEnd = NaN;
+    }
+
+    // Static
+
+    static fromPayload(payload, resource)
+    {
+        payload = payload || {};
+
+        // COMPATIBILITY (iOS 10): Resource Timing data was incomplete and incorrect. Do not use it.
+        // iOS 7 sent a requestTime and iOS 8-9.3 sent a navigationStart time.
+        if (typeof payload.requestTime === "number" || typeof payload.navigationStart === "number")
+            payload = {};
+
+        function offsetToTimestamp(offset) {
+            return offset > 0 ? payload.startTime + offset / 1000 : NaN;
+        }
+
+        let data = {
+            startTime: payload.startTime,
+            domainLookupStart: offsetToTimestamp(payload.domainLookupStart),
+            domainLookupEnd: offsetToTimestamp(payload.domainLookupEnd),
+            connectStart: offsetToTimestamp(payload.connectStart),
+            connectEnd: offsetToTimestamp(payload.connectEnd),
+            secureConnectionStart: offsetToTimestamp(payload.secureConnectionStart),
+            requestStart: offsetToTimestamp(payload.requestStart),
+            responseStart: offsetToTimestamp(payload.responseStart),
+            responseEnd: offsetToTimestamp(payload.responseEnd)
+        };
+
+        // COMPATIBILITY (iOS 8): connectStart is zero if a secure connection is used.
+        if (isNaN(data.connectStart) && !isNaN(data.secureConnectionStart))
+            data.connectStart = data.secureConnectionStart;
+
+        return new WebInspector.ResourceTimingData(resource, data);
+    }
+
+    // Public
+
+    get startTime() { return this._startTime || this._resource.requestSentTimestamp; }
+    get domainLookupStart() { return this._domainLookupStart; }
+    get domainLookupEnd() { return this._domainLookupEnd; }
+    get connectStart() { return this._connectStart; }
+    get connectEnd() { return this._connectEnd; }
+    get secureConnectionStart() { return this._secureConnectionStart; }
+    get requestStart() { return this._requestStart || this._resource.requestSentTimestamp; }
+    get responseStart() { return this._responseStart || this._resource.responseReceivedTimestamp; }
+    get responseEnd() { return this._responseEnd || this._resource.finishedOrFailedTimestamp; }
+
+    markResponseEndTime(responseEnd)
+    {
+        console.assert(typeof responseEnd === "number");
+        this._responseEnd = responseEnd;
+    }
+};

Modified: trunk/Source/WebInspectorUI/UserInterface/Test.html (205210 => 205211)


--- trunk/Source/WebInspectorUI/UserInterface/Test.html	2016-08-31 00:15:50 UTC (rev 205210)
+++ trunk/Source/WebInspectorUI/UserInterface/Test.html	2016-08-31 00:27:27 UTC (rev 205211)
@@ -147,6 +147,7 @@
     <script src=""
     <script src=""
     <script src=""
+    <script src=""
     <script src=""
     <script src=""
     <script src=""
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to