Diff
Modified: trunk/LayoutTests/ChangeLog (223855 => 223856)
--- trunk/LayoutTests/ChangeLog 2017-10-23 21:00:45 UTC (rev 223855)
+++ trunk/LayoutTests/ChangeLog 2017-10-23 21:34:59 UTC (rev 223856)
@@ -1,3 +1,23 @@
+2017-10-23 Joseph Pecoraro <[email protected]>
+
+ Web Inspector: Please support HAR Export for network traffic
+ https://bugs.webkit.org/show_bug.cgi?id=146692
+ <rdar://problem/7463672>
+
+ Reviewed by Brian Burg.
+
+ * http/tests/inspector/network/har/har-basic-expected.txt: Added.
+ * http/tests/inspector/network/har/har-basic.html: Added.
+ * http/tests/inspector/network/har/har-page-expected.txt: Added.
+ * http/tests/inspector/network/har/har-page.html: Added.
+ Tests with mock resources / data and real resources.
+
+ * platform/mac-wk1/TestExpectations:
+ * platform/mac/TestExpectations:
+ * platform/win/TestExpectations:
+ Skip on platforms that cannot provide complete metrics, so some optional
+ fields may be missing.
+
2017-10-23 Andy Estes <[email protected]>
[Payment Request] Resolve PaymentRequest.show()'s accept promise when a payment is authorized
Added: trunk/LayoutTests/http/tests/inspector/network/har/har-basic-expected.txt (0 => 223856)
--- trunk/LayoutTests/http/tests/inspector/network/har/har-basic-expected.txt (rev 0)
+++ trunk/LayoutTests/http/tests/inspector/network/har/har-basic-expected.txt 2017-10-23 21:34:59 UTC (rev 223856)
@@ -0,0 +1,144 @@
+Basic tests for HAR.
+
+
+== Running test suite: HAR.Basic
+-- Running test case: HAR.Basic.Empty
+{
+ "log": {
+ "version": "1.2",
+ "creator": {
+ "name": "WebKit Web Inspector",
+ "version": "<filtered>"
+ },
+ "pages": [
+ {
+ "startedDateTime": "",
+ "id": "page_0",
+ "title": "http://127.0.0.1:8000/inspector/network/har/har-basic.html",
+ "pageTimings": {}
+ }
+ ],
+ "entries": []
+ }
+}
+
+-- Running test case: HAR.Basic.FakeResources
+{
+ "log": {
+ "version": "1.2",
+ "creator": {
+ "name": "WebKit Web Inspector",
+ "version": "<filtered>"
+ },
+ "pages": [
+ {
+ "startedDateTime": "",
+ "id": "page_0",
+ "title": "http://127.0.0.1:8000/inspector/network/har/har-basic.html",
+ "pageTimings": {}
+ }
+ ],
+ "entries": [
+ {
+ "pageref": "page_0",
+ "startedDateTime": "2017-10-23T01:55:52.694Z",
+ "time": 0,
+ "request": {
+ "method": "GET",
+ "url": "https://example.com/fake.js",
+ "httpVersion": "",
+ "cookies": [],
+ "headers": [
+ {
+ "name": "Test-Request-Header",
+ "value": "Test Request Header Value"
+ }
+ ],
+ "queryString": [],
+ "headersSize": -1,
+ "bodySize": -1
+ },
+ "response": {
+ "status": 0,
+ "statusText": "",
+ "httpVersion": "",
+ "cookies": [],
+ "headers": [],
+ "content": {
+ "size": 0,
+ "compression": 0,
+ "mimeType": "text/_javascript_"
+ },
+ "redirectURL": "",
+ "headersSize": -1,
+ "bodySize": -1
+ },
+ "cache": {},
+ "timings": {
+ "blocked": -1,
+ "dns": -1,
+ "connect": -1,
+ "ssl": -1,
+ "send": 0,
+ "wait": 0,
+ "receive": 0
+ }
+ },
+ {
+ "pageref": "page_0",
+ "startedDateTime": "2017-10-23T01:55:52.694Z",
+ "time": 700,
+ "request": {
+ "method": "GET",
+ "url": "https://example.com/fake.js",
+ "httpVersion": "HTTP/1.1",
+ "cookies": [],
+ "headers": [
+ {
+ "name": "Test-Request-Header",
+ "value": "Test Request Header Value"
+ }
+ ],
+ "queryString": [],
+ "headersSize": 100,
+ "bodySize": 0
+ },
+ "response": {
+ "status": 200,
+ "statusText": "OK",
+ "httpVersion": "HTTP/1.1",
+ "cookies": [],
+ "headers": [
+ {
+ "name": "Test-Response-Header",
+ "value": "Test Response Header Value"
+ }
+ ],
+ "content": {
+ "size": 1234,
+ "compression": 434,
+ "mimeType": "text/_javascript_"
+ },
+ "redirectURL": "",
+ "headersSize": 200,
+ "bodySize": 800,
+ "_transferSize": 1000
+ },
+ "cache": {},
+ "timings": {
+ "blocked": 100.00000000000009,
+ "dns": 99.99999999999987,
+ "connect": 99.99999999999987,
+ "ssl": 49.99999999999982,
+ "send": 100.00000000000009,
+ "wait": 100.00000000000009,
+ "receive": 99.99999999999987
+ },
+ "serverIPAddress": "12.34.56.78",
+ "connection": "1",
+ "_fetchType": "Network Load"
+ }
+ ]
+ }
+}
+
Added: trunk/LayoutTests/http/tests/inspector/network/har/har-basic.html (0 => 223856)
--- trunk/LayoutTests/http/tests/inspector/network/har/har-basic.html (rev 0)
+++ trunk/LayoutTests/http/tests/inspector/network/har/har-basic.html 2017-10-23 21:34:59 UTC (rev 223856)
@@ -0,0 +1,96 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<script src=""
+<script>
+function test()
+{
+ function HARJSONFilter(key, value) {
+ // Filter out the creator.version / browser.version but leave a top level version.
+ if ((key === "creator" || key === "browser") && value.version) {
+ value.version = "<filtered>";
+ return value;
+ }
+ return value;
+ }
+
+ let suite = InspectorTest.createAsyncSuite("HAR.Basic");
+
+ suite.addTestCase({
+ name: "HAR.Basic.Empty",
+ description: "Should be able to generate a HAR with no resources.",
+ async test() {
+ let har = await WI.HARBuilder.buildArchive([]);
+ InspectorTest.json(har, HARJSONFilter);
+ }
+ });
+
+ suite.addTestCase({
+ name: "HAR.Basic.FakeResources",
+ description: "Should be able to generate a HAR with resources.",
+ async test() {
+ // FIXME: We should have an easier way to construct a Resource with mock data.
+ const url = ""
+ const mimeType = "text/_javascript_";
+ const type = WI.Resource.Type.Script;
+ const loaderIdentifier = undefined;
+ const targetId = undefined;
+ const requestIdentifier = undefined;
+ const requestMethod = "GET";
+ const requestHeaders = {"Test-Request-Header": "Test Request Header Value"};
+ const responseHeaders = {"Test-Response-Header": "Test Response Header Value"};
+ const statusCode = 200;
+ const statusText = "OK";
+ const source = "network";
+ const requestData = null;
+ const requestSentWalltime = 1508723752694 / 1000; // Sun Oct 22 2017 18:55:52 GMT-0700, when this test was written.
+ const initiatorSourceCodeLocation = null;
+ const timestamp = undefined;
+ const size = 1234;
+ const timingData = {
+ startTime: 1,
+ domainLookupStart: 100,
+ domainLookupEnd: 200,
+ connectStart: 300,
+ connectEnd: 400,
+ secureConnectionStart: 350,
+ requestStart: 500,
+ responseStart: 600,
+ responseEnd: 700,
+ };
+ const metrics = {
+ protocol: "http/1.1",
+ priority: "medium",
+ remoteAddress: "12.34.56.78:443",
+ connectionIdentifier: 123,
+ requestHeaderBytesSent: 100,
+ requestBodyBytesSent: 0,
+ responseHeaderBytesReceived: 200,
+ responseBodyBytesReceived: 800,
+ responseBodyDecodedSize: 1234,
+ requestHeaders,
+ };
+
+ let bareResource = new WI.Resource(url, mimeType, type, loaderIdentifier, targetId, requestIdentifier, requestMethod, requestHeaders, requestData, timestamp, requestSentWalltime, initiatorSourceCodeLocation, timestamp);
+ bareResource.markAsFinished(undefined);
+
+ let fullResource = new WI.Resource(url, mimeType, type, loaderIdentifier, targetId, requestIdentifier, requestMethod, requestHeaders, requestData, timestamp, requestSentWalltime, initiatorSourceCodeLocation, timestamp);
+ fullResource.updateForResponse(url, mimeType, type, responseHeaders, statusCode, statusText, timestamp, timingData, source);
+ fullResource.increaseSize(size);
+ fullResource.updateWithMetrics(metrics);
+ fullResource.markAsFinished(1.7);
+
+ let har = await WI.HARBuilder.buildArchive([bareResource, fullResource]);
+ InspectorTest.json(har, HARJSONFilter);
+ }
+ });
+
+ suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body _onload_="runTest()">
+<p>Basic tests for HAR.</p>
+</body>
+</html>
Added: trunk/LayoutTests/http/tests/inspector/network/har/har-page-expected.txt (0 => 223856)
--- trunk/LayoutTests/http/tests/inspector/network/har/har-page-expected.txt (rev 0)
+++ trunk/LayoutTests/http/tests/inspector/network/har/har-page-expected.txt 2017-10-23 21:34:59 UTC (rev 223856)
@@ -0,0 +1,116 @@
+HAR Page Test.
+
+
+== Running test suite: HAR.Page
+-- Running test case: HAR.Basic.Page
+{
+ "log": {
+ "version": "1.2",
+ "creator": {
+ "name": "WebKit Web Inspector",
+ "version": "<filtered>"
+ },
+ "pages": [
+ {
+ "startedDateTime": "<filtered>",
+ "id": "page_0",
+ "title": "http://127.0.0.1:8000/inspector/network/har/har-page.html",
+ "pageTimings": {
+ "onContentLoad": "<filtered>",
+ "onLoad": "<filtered>"
+ }
+ }
+ ],
+ "entries": [
+ {
+ "pageref": "page_0",
+ "startedDateTime": "<filtered>",
+ "time": "<filtered>",
+ "request": {
+ "method": "GET",
+ "url": "http://127.0.0.1:8000/inspector/network/har/har-page.html",
+ "httpVersion": "HTTP/1.1",
+ "cookies": [],
+ "headers": "<filtered>",
+ "queryString": [],
+ "headersSize": "<filtered>",
+ "bodySize": "<filtered>"
+ },
+ "response": {
+ "status": 200,
+ "statusText": "OK",
+ "httpVersion": "HTTP/1.1",
+ "cookies": [],
+ "headers": "<filtered>",
+ "content": {
+ "size": 2658,
+ "compression": 0,
+ "mimeType": "text/html",
+ "text": "<!DOCTYPE html>\n<html>\n<head>\n<meta charset=\"utf-8\">\n<script src="" test()\n{\n function HARJSONFilter(key, value) {\n // Filter out the creator.version / browser.version but leave a top level version.\n if ((key === \"creator\" || key === \"browser\") && value.version) {\n value.version = \"<filtered>\";\n return value;\n }\n\n // Headers include dynamic data.\n if (key === \"headers\")\n return \"<filtered>\";\n\n // Dates would change between test runs.\n if (key.endsWith(\"DateTime\"))\n return \"<filtered>\";\n\n // Size data may or may not be available, but could change based on headers.\n if (key.endsWith(\&
quot;Size\"))\n return \"<filtered>\";\n\n // Connection identifier could be different.\n if (key === \"connection\")\n return \"<filtered>\";\n\n // Cache may or may not have been used.\n if (key === \"_fetchType\")\n return \"<filtered>\";\n\n // Since cache may or may not be used, timing data may be variable.\n // NOTE: SSL should always be -1 for this test case.\n if (key === \"time\")\n return \"<filtered>\";\n if (key === \"timings\") {\n value.blocked = \"<filtered>\";\n value.dns = \"<filtered>\";\n value.connect = \"<filtered>\";\n value.send = \"<filtered>\";\n value.wait = \"<filtered>\";\n value.receive = \"<filter
ed>\";\n }\n\n // PageTimings can be variable.\n if (key === \"onContentLoad\" || key === \"onLoad\")\n return \"<filtered>\";\n\n return value;\n }\n\n let suite = InspectorTest.createAsyncSuite(\"HAR.Page\");\n\n suite.addTestCase({\n name: \"HAR.Basic.Page\",\n description: \"Should be able to generate a HAR with all of this test page's resources.\",\n async test() {\n InspectorTest.reloadPage({ignoreCache: true});\n await InspectorTest.awaitEvent(\"LoadComplete\");\n\n let resources = [];\n resources.push(WI.frameResourceManager.mainFrame.mainResource);\n for (let resource of WI.frameResourceManager.mainFrame.resourceCollection.items)\n resources.push(resource);\n\n let har = await WI.HARBuilder.buildArchive(resources);\n InspectorTest.js
on(har, HARJSONFilter);\n }\n });\n\n suite.runTestCasesAndFinish();\n}\n</script>\n</head>\n<body _onload_=\"runTest()\">\n<p>HAR Page Test.</p>\n<script>\nwindow.addEventListener(\"load\", () => {\n TestPage.dispatchEventToFrontend(\"LoadComplete\");\n});\n</script>\n</body>\n</html>\n"
+ },
+ "redirectURL": "",
+ "headersSize": "<filtered>",
+ "bodySize": "<filtered>",
+ "_transferSize": "<filtered>"
+ },
+ "cache": {},
+ "timings": {
+ "blocked": "<filtered>",
+ "dns": "<filtered>",
+ "connect": "<filtered>",
+ "ssl": -1,
+ "send": "<filtered>",
+ "wait": "<filtered>",
+ "receive": "<filtered>"
+ },
+ "serverIPAddress": "127.0.0.1",
+ "connection": "<filtered>",
+ "_fetchType": "<filtered>"
+ },
+ {
+ "pageref": "page_0",
+ "startedDateTime": "<filtered>",
+ "time": "<filtered>",
+ "request": {
+ "method": "GET",
+ "url": "http://127.0.0.1:8000/inspector/resources/inspector-test.js",
+ "httpVersion": "",
+ "cookies": [],
+ "headers": "<filtered>",
+ "queryString": [],
+ "headersSize": "<filtered>",
+ "bodySize": "<filtered>"
+ },
+ "response": {
+ "status": 200,
+ "statusText": "OK",
+ "httpVersion": "",
+ "cookies": [],
+ "headers": "<filtered>",
+ "content": {
+ "size": 0,
+ "compression": 0,
+ "mimeType": "application/x-_javascript_",
+ "text": "/*\n * Copyright (C) 2013-2015 Apple Inc. All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n * 1. Redistributions of source code must retain the above copyright\n * notice, this list of conditions and the following disclaimer.\n * 2. Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n * IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED\n * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A\n * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENT
AL,\n * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n// This namespace is injected into every test page. Its functions are invoked by\n// InspectorTest methods on the inspector page via a TestHarness subclass.\nTestPage = {};\nTestPage._initializers = [];\n\n// Helper scripts like `debugger-test.js` must register their initialization\n// function with this method so it will be marshalled to the inspector page.\nTestPage.registerInitializer = function(initializer)\n{\n if (typeof initializer === \"function\")\n this._initializers.push(initializer.toString());\n}\n\n// This fun
ction is called by the test document's body onload handler.\n\n// It initializes the inspector and loads any `*-test.js` helper scripts\n// into the inspector page context.\nfunction runTest()\n{\n // Don't try to use testRunner if running through the browser.\n if (!window.testRunner)\n return;\n\n // Set up the test page before the load event fires.\n testRunner.dumpAsText();\n testRunner.waitUntilDone();\n\n window.internals.setInspectorIsUnderTest(true);\n testRunner.showWebInspector();\n\n let testFunction = window.test;\n if (typeof testFunction !== \"function\") {\n alert(\"Failed to send test() because it is not a function.\");\n testRunner.notifyDone();\n }\n\n function runInitializationMethodsInFrontend(initializersArray)\n {\n InspectorTest.testPageDidLoad();\n\n // If the test page reloaded but we started running the test in a previous\n // navigation, then don't initialize
the inspector frontend again.\n if (InspectorTest.didInjectTestCode)\n return;\n\n for (let initializer of initializersArray) {\n try {\n initializer();\n } catch (e) {\n console.error(\"Exception in test initialization: \" + e, e.stack || \"(no stack trace)\");\n InspectorTest.completeTest();\n }\n }\n }\n\n function runTestMethodInFrontend(testFunction)\n {\n if (InspectorTest.didInjectTestCode)\n return;\n\n InspectorTest.didInjectTestCode = true;\n\n try {\n testFunction();\n } catch (e) {\n // Using this instead of window.onerror will preserve the stack trace.\n e.code = testFunction.toString();\n InspectorTest.reportUncaughtException(e);\n }\n }\n\n let initializationCodeString = `(${runInitializationMethodsInFrontend.toString()})([${TestPage._in
itializers}]);`;\n let testFunctionCodeString = `(${runTestMethodInFrontend.toString()})(${testFunction.toString()});`;\n\n testRunner.evaluateInWebInspector(initializationCodeString);\n testRunner.evaluateInWebInspector(testFunctionCodeString);\n}\n\nfunction runTestHTTPS()\n{\n if (window.testRunner) {\n testRunner.dumpAsText();\n testRunner.waitUntilDone();\n }\n\n let url = "" URL(document.URL);\n if (url.protocol !== \"https:\") {\n url.protocol = \"https:\";\n url.port = \"8443\";\n window.location.href = "" return;\n }\n\n runTest();\n}\n\nTestPage.completeTest = function()\n{\n // Don't try to use testRunner if running through the browser.\n if (!window.testRunner)\n return;\n\n // Close inspector asynchrously in case we want to test tear-down behavior.\n setTimeout(() => {\n testRunner.closeWebInspector();\n setTimeout(() =&g
t; { testRunner.notifyDone(); }, 0);\n }, 0);\n}\n\n// Logs message to unbuffered process stdout, avoiding timeouts.\n// only be used to debug tests and not to produce normal test output.\nTestPage.debugLog = function(message)\n{\n window.alert(message);\n}\n\n// Add and clear test output from the results window.\nTestPage.addResult = function(text)\n{\n // For early errors triggered when loading the test page, write to stderr.\n if (!document.body) {\n this.debugLog(text);\n this.completeTest();\n }\n\n if (!this._resultElement) {\n this._resultElement = document.createElement(\"pre\");\n this._resultElement.id = \"output\";\n document.body.appendChild(this._resultElement);\n }\n\n this._resultElement.append(text, document.createElement(\"br\"));\n}\n\nTestPage.log = TestPage.addResult;\n\nTestPage.dispatchEventToFrontend = function(eventName, data)\n{\n let dispatchEventCodeString = `Inspec
torTest.dispatchEventToListeners(${JSON.stringify(eventName)}, ${JSON.stringify(data)});`;\n testRunner.evaluateInWebInspector(dispatchEventCodeString);\n};\n\nTestPage.allowUncaughtExceptions = false;\nTestPage.needToSanitizeUncaughtExceptionURLs = false;\n\nTestPage.reportUncaughtException = function(message, url, lineNumber)\n{\n if (TestPage.needToSanitizeUncaughtExceptionURLs) {\n if (typeof url ="" \"string\") {\n let lastSlash = url.lastIndexOf(\"/\");\n let lastBackSlash = url.lastIndexOf(\"\\\\\");\n let lastPathSeparator = Math.max(lastSlash, lastBackSlash);\n if (lastPathSeparator > 0)\n url = "" + 1);\n }\n }\n\n let result = `Uncaught exception in test page: ${message} [${url}:${lineNumber}]`;\n TestPage.addResult(result);\n\n if (!TestPage.allowUncaughtExceptions)\n TestPage.completeTest();\n}\n\n// Catch syntax er
rors, type errors, and other exceptions. Run this before loading other files.\nwindow._onerror_ = TestPage.reportUncaughtException.bind(TestPage);\n"
+ },
+ "redirectURL": "",
+ "headersSize": "<filtered>",
+ "bodySize": "<filtered>",
+ "_transferSize": "<filtered>"
+ },
+ "cache": {},
+ "timings": {
+ "blocked": "<filtered>",
+ "dns": "<filtered>",
+ "connect": "<filtered>",
+ "ssl": -1,
+ "send": "<filtered>",
+ "wait": "<filtered>",
+ "receive": "<filtered>"
+ },
+ "_fetchType": "<filtered>"
+ }
+ ]
+ }
+}
+
Added: trunk/LayoutTests/http/tests/inspector/network/har/har-page.html (0 => 223856)
--- trunk/LayoutTests/http/tests/inspector/network/har/har-page.html (rev 0)
+++ trunk/LayoutTests/http/tests/inspector/network/har/har-page.html 2017-10-23 21:34:59 UTC (rev 223856)
@@ -0,0 +1,87 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<script src=""
+<script>
+function test()
+{
+ function HARJSONFilter(key, value) {
+ // Filter out the creator.version / browser.version but leave a top level version.
+ if ((key === "creator" || key === "browser") && value.version) {
+ value.version = "<filtered>";
+ return value;
+ }
+
+ // Headers include dynamic data.
+ if (key === "headers")
+ return "<filtered>";
+
+ // Dates would change between test runs.
+ if (key.endsWith("DateTime"))
+ return "<filtered>";
+
+ // Size data may or may not be available, but could change based on headers.
+ if (key.endsWith("Size"))
+ return "<filtered>";
+
+ // Connection identifier could be different.
+ if (key === "connection")
+ return "<filtered>";
+
+ // Cache may or may not have been used.
+ if (key === "_fetchType")
+ return "<filtered>";
+
+ // Since cache may or may not be used, timing data may be variable.
+ // NOTE: SSL should always be -1 for this test case.
+ if (key === "time")
+ return "<filtered>";
+ if (key === "timings") {
+ value.blocked = "<filtered>";
+ value.dns = "<filtered>";
+ value.connect = "<filtered>";
+ value.send = "<filtered>";
+ value.wait = "<filtered>";
+ value.receive = "<filtered>";
+ }
+
+ // PageTimings can be variable.
+ if (key === "onContentLoad" || key === "onLoad")
+ return "<filtered>";
+
+ return value;
+ }
+
+ let suite = InspectorTest.createAsyncSuite("HAR.Page");
+
+ suite.addTestCase({
+ name: "HAR.Basic.Page",
+ description: "Should be able to generate a HAR with all of this test page's resources.",
+ async test() {
+ InspectorTest.reloadPage({ignoreCache: true});
+ await InspectorTest.awaitEvent("LoadComplete");
+
+ let resources = [];
+ resources.push(WI.frameResourceManager.mainFrame.mainResource);
+ for (let resource of WI.frameResourceManager.mainFrame.resourceCollection.items)
+ resources.push(resource);
+
+ let har = await WI.HARBuilder.buildArchive(resources);
+ InspectorTest.json(har, HARJSONFilter);
+ }
+ });
+
+ suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body _onload_="runTest()">
+<p>HAR Page Test.</p>
+<script>
+window.addEventListener("load", () => {
+ TestPage.dispatchEventToFrontend("LoadComplete");
+});
+</script>
+</body>
+</html>
Modified: trunk/LayoutTests/platform/mac/TestExpectations (223855 => 223856)
--- trunk/LayoutTests/platform/mac/TestExpectations 2017-10-23 21:00:45 UTC (rev 223855)
+++ trunk/LayoutTests/platform/mac/TestExpectations 2017-10-23 21:34:59 UTC (rev 223856)
@@ -1429,6 +1429,7 @@
# Request Header networking data not available without Network Session.
[ ElCapitan ] http/tests/inspector/network/resource-request-headers.html [ Failure ]
+[ ElCapitan ] http/tests/inspector/network/har/har-page.html [ Failure ]
[ ElCapitan ] http/tests/inspector/network/ping-type.html [ Skip ]
Modified: trunk/LayoutTests/platform/mac-wk1/TestExpectations (223855 => 223856)
--- trunk/LayoutTests/platform/mac-wk1/TestExpectations 2017-10-23 21:00:45 UTC (rev 223855)
+++ trunk/LayoutTests/platform/mac-wk1/TestExpectations 2017-10-23 21:34:59 UTC (rev 223856)
@@ -340,6 +340,7 @@
http/tests/inspector/network/resource-request-headers.html [ Failure ]
http/tests/inspector/network/resource-sizes-network.html [ Failure ]
http/tests/inspector/network/resource-sizes-memory-cache.html [ Failure ]
+http/tests/inspector/network/har/har-page.html [ Failure ]
webkit.org/b/164491 [ Yosemite ElCapitan ] fast/visual-viewport/rtl-zoomed-rects.html [ Failure ]
Modified: trunk/LayoutTests/platform/win/TestExpectations (223855 => 223856)
--- trunk/LayoutTests/platform/win/TestExpectations 2017-10-23 21:00:45 UTC (rev 223855)
+++ trunk/LayoutTests/platform/win/TestExpectations 2017-10-23 21:34:59 UTC (rev 223856)
@@ -3599,6 +3599,7 @@
fast/text/font-weight-fallback.html [ Skip ]
http/tests/cache/cache-control-immutable-https.html [ Skip ]
http/tests/inspector/network/resource-request-headers.html [ Skip ]
+http/tests/inspector/network/har/har-page.html [ Skip ]
http/tests/local/blob/send-hybrid-blob-using-open-panel.html [ Skip ]
http/tests/security/contentSecurityPolicy/cross-origin-plugin-document-allowed-in-child-window.html [ Skip ]
http/tests/security/contentSecurityPolicy/same-origin-plugin-document-blocked-in-child-window.html [ Skip ]
Modified: trunk/Source/_javascript_Core/ChangeLog (223855 => 223856)
--- trunk/Source/_javascript_Core/ChangeLog 2017-10-23 21:00:45 UTC (rev 223855)
+++ trunk/Source/_javascript_Core/ChangeLog 2017-10-23 21:34:59 UTC (rev 223856)
@@ -1,3 +1,14 @@
+2017-10-23 Joseph Pecoraro <[email protected]>
+
+ Web Inspector: Please support HAR Export for network traffic
+ https://bugs.webkit.org/show_bug.cgi?id=146692
+ <rdar://problem/7463672>
+
+ Reviewed by Brian Burg.
+
+ * inspector/protocol/Network.json:
+ Add a walltime to each send request.
+
2017-10-23 Matt Lewis <[email protected]>
Unreviewed, rolling out r223820.
Modified: trunk/Source/_javascript_Core/inspector/protocol/Network.json (223855 => 223856)
--- trunk/Source/_javascript_Core/inspector/protocol/Network.json 2017-10-23 21:00:45 UTC (rev 223855)
+++ trunk/Source/_javascript_Core/inspector/protocol/Network.json 2017-10-23 21:34:59 UTC (rev 223856)
@@ -216,7 +216,8 @@
{ "name": "loaderId", "$ref": "LoaderId", "description": "Loader identifier." },
{ "name": "documentURL", "type": "string", "description": "URL of the document this request is loaded for." },
{ "name": "request", "$ref": "Request", "description": "Request data." },
- { "name": "timestamp", "$ref": "Timestamp", "description": "Timestamp." },
+ { "name": "timestamp", "$ref": "Timestamp" },
+ { "name": "walltime", "$ref": "Walltime" },
{ "name": "initiator", "$ref": "Initiator", "description": "Request initiator." },
{ "name": "redirectResponse", "optional": true, "$ref": "Response", "description": "Redirect response data." },
{ "name": "type", "$ref": "Page.ResourceType", "optional": true, "description": "Resource type." },
Modified: trunk/Source/WebCore/ChangeLog (223855 => 223856)
--- trunk/Source/WebCore/ChangeLog 2017-10-23 21:00:45 UTC (rev 223855)
+++ trunk/Source/WebCore/ChangeLog 2017-10-23 21:34:59 UTC (rev 223856)
@@ -1,3 +1,20 @@
+2017-10-23 Joseph Pecoraro <[email protected]>
+
+ Web Inspector: Please support HAR Export for network traffic
+ https://bugs.webkit.org/show_bug.cgi?id=146692
+ <rdar://problem/7463672>
+
+ Reviewed by Brian Burg.
+
+ Tests: http/tests/inspector/network/har/har-basic.html
+ http/tests/inspector/network/har/har-page.html
+
+ * inspector/InspectorNetworkAgent.cpp:
+ (WebCore::InspectorNetworkAgent::willSendRequest):
+ Include the wall time when sending a request. This is needed for HAR to
+ include a wall time, and can be used for Cookie expiration time calculation
+ as well.
+
2017-10-23 Andy Estes <[email protected]>
[Payment Request] Resolve PaymentRequest.show()'s accept promise when a payment is authorized
Modified: trunk/Source/WebCore/inspector/InspectorNetworkAgent.cpp (223855 => 223856)
--- trunk/Source/WebCore/inspector/InspectorNetworkAgent.cpp 2017-10-23 21:00:45 UTC (rev 223855)
+++ trunk/Source/WebCore/inspector/InspectorNetworkAgent.cpp 2017-10-23 21:34:59 UTC (rev 223856)
@@ -345,6 +345,9 @@
return;
}
+ double sendTimestamp = timestamp();
+ double walltime = currentTime();
+
String requestId = IdentifiersFactory::requestId(identifier);
m_resourcesData->resourceCreated(requestId, m_pageAgent->loaderId(&loader));
@@ -373,7 +376,7 @@
RefPtr<Inspector::Protocol::Network::Initiator> initiatorObject = buildInitiatorObject(loader.frame() ? loader.frame()->document() : nullptr);
String targetId = request.initiatorIdentifier();
- m_frontendDispatcher->requestWillBeSent(requestId, m_pageAgent->frameId(loader.frame()), m_pageAgent->loaderId(&loader), loader.url().string(), buildObjectForResourceRequest(request), timestamp(), initiatorObject, buildObjectForResourceResponse(redirectResponse, nullptr), type != InspectorPageAgent::OtherResource ? &protocolResourceType : nullptr, targetId.isEmpty() ? nullptr : &targetId);
+ m_frontendDispatcher->requestWillBeSent(requestId, m_pageAgent->frameId(loader.frame()), m_pageAgent->loaderId(&loader), loader.url().string(), buildObjectForResourceRequest(request), sendTimestamp, walltime, initiatorObject, buildObjectForResourceResponse(redirectResponse, nullptr), type != InspectorPageAgent::OtherResource ? &protocolResourceType : nullptr, targetId.isEmpty() ? nullptr : &targetId);
}
static InspectorPageAgent::ResourceType resourceTypeForCachedResource(CachedResource* resource)
Modified: trunk/Source/WebInspectorUI/ChangeLog (223855 => 223856)
--- trunk/Source/WebInspectorUI/ChangeLog 2017-10-23 21:00:45 UTC (rev 223855)
+++ trunk/Source/WebInspectorUI/ChangeLog 2017-10-23 21:34:59 UTC (rev 223856)
@@ -1,3 +1,91 @@
+2017-10-23 Joseph Pecoraro <[email protected]>
+
+ Web Inspector: Please support HAR Export for network traffic
+ https://bugs.webkit.org/show_bug.cgi?id=146692
+ <rdar://problem/7463672>
+
+ Reviewed by Brian Burg.
+
+ * UserInterface/Main.html:
+ * UserInterface/Test.html:
+ New resources.
+
+ * UserInterface/Base/Platform.js:
+ Include a build number as well.
+
+ * UserInterface/Base/URLUtilities.js:
+ (parseLocationQueryParameters): Deleted.
+ Remove unused function.
+
+ * UserInterface/Controllers/FrameResourceManager.js:
+ (WI.FrameResourceManager.prototype.frameDidNavigate):
+ (WI.FrameResourceManager.prototype.resourceRequestWillBeSent):
+ (WI.FrameResourceManager.prototype.resourceRequestWasServedFromMemoryCache):
+ (WI.FrameResourceManager.prototype.resourceRequestDidReceiveResponse):
+ (WI.FrameResourceManager.prototype._addNewResourceToFrameOrTarget):
+ Pass along a walltime.
+
+ * UserInterface/Protocol/NetworkObserver.js:
+ (WI.NetworkObserver.prototype.requestWillBeSent):
+ Pass along a walltime. This new parameter shifts old parameters.
+
+ * UserInterface/Controllers/HARBuilder.js: Added.
+ (WI.HARBuilder.async.buildArchive):
+ (WI.HARBuilder.creator):
+ (WI.HARBuilder.pages):
+ (WI.HARBuilder.pageTimings):
+ (WI.HARBuilder.entry):
+ (WI.HARBuilder.request):
+ (WI.HARBuilder.response):
+ (WI.HARBuilder.cookies):
+ (WI.HARBuilder.headers):
+ (WI.HARBuilder.content):
+ (WI.HARBuilder.postData):
+ (WI.HARBuilder.cache):
+ (WI.HARBuilder.timings):
+ (WI.HARBuilder.ipAddress):
+ (WI.HARBuilder.date):
+ (WI.HARBuilder.fetchType):
+ HAR construction and helpers.
+
+ * UserInterface/Models/Cookie.js:
+ (WI.Cookie.prototype.expirationDate):
+ * UserInterface/Models/Resource.js:
+ (WI.Resource.prototype.get queryStringParameters):
+ (WI.Resource.prototype.get requestFormParameters):
+ (WI.Resource.prototype.get requestSentWalltime):
+ (WI.Resource.prototype.get requestSentDate):
+ (WI.Resource.prototype.hasRequestFormParameters):
+ Helpers for HAR generation and sub-sets of data.
+
+ * UserInterface/Models/SourceCode.js:
+ (WI.SourceCode.prototype._processContent):
+ Capture the raw, unmodified, base64 encoded flag and content. This ends
+ up getting used by HAR generation and is otherwise lost.
+
+ * UserInterface/Test/TestHarness.js:
+ (TestHarness.prototype.json):
+ Helper for just logging JSON data with a filter. This defaults to
+ a reasonable 2 space indent for JSON logs in our test output.
+
+ * UserInterface/Views/DOMTreeContentView.js:
+ (WI.DOMTreeContentView.prototype.get saveData):
+ (WI.DOMTreeContentView.get saveData.saveHandler): Deleted.
+ Drive-by simplify while looking at other save handlers.
+
+ * UserInterface/Views/NetworkTableContentView.js:
+ (WI.NetworkTableContentView.prototype.get supportsSave):
+ (WI.NetworkTableContentView.prototype.get saveData):
+ (WI.NetworkTableContentView.prototype.tableCellContextMenuClicked):
+ (WI.NetworkTableContentView.prototype._HARResources):
+ (WI.NetworkTableContentView.prototype._exportHAR):
+ Provide a context menu and save keyboard handler to export a HAR.
+ This matches other browsers.
+
+ * UserInterface/Views/ResourceClusterContentView.js:
+ (WI.ResourceClusterContentView.prototype._canShowRequestContentView):
+ Use code that is now available in Resource.
+
2017-10-20 Matt Baker <[email protected]>
Web Inspector: scrolling the editor while debugging shouldn't trigger popovers
Modified: trunk/Source/WebInspectorUI/UserInterface/Base/Platform.js (223855 => 223856)
--- trunk/Source/WebInspectorUI/UserInterface/Base/Platform.js 2017-10-23 21:00:45 UTC (rev 223855)
+++ trunk/Source/WebInspectorUI/UserInterface/Base/Platform.js 2017-10-23 21:34:59 UTC (rev 223856)
@@ -29,22 +29,30 @@
version: {
base: 0,
release: 0,
- name: ""
+ name: "",
+ build: "",
}
};
(function () {
- // Check for a nightly build by looking for a plus in the version number and a small number of stylesheets (indicating combined resources).
- var versionMatch = / AppleWebKit\/([^ ]+)/.exec(navigator.userAgent);
- if (versionMatch && versionMatch[1].indexOf("+") !== -1 && document.styleSheets.length < 10)
- WI.Platform.isNightlyBuild = true;
+ let versionMatch = / AppleWebKit\/([^ ]+)/.exec(navigator.userAgent);
+ if (versionMatch) {
+ WI.Platform.version.build = versionMatch[1];
- var osVersionMatch = / Mac OS X (\d+)_(\d+)/.exec(navigator.appVersion);
+ // Check for a nightly build by looking for a plus in the version number and a small number of stylesheets (indicating combined resources).
+ if (versionMatch[1].indexOf("+") !== -1 && document.styleSheets.length < 10)
+ WI.Platform.isNightlyBuild = true;
+ }
+
+ let osVersionMatch = / Mac OS X (\d+)_(\d+)/.exec(navigator.appVersion);
if (osVersionMatch && osVersionMatch[1] === "10") {
WI.Platform.version.base = 10;
WI.Platform.version.release = parseInt(osVersionMatch[2]);
switch (osVersionMatch[2]) {
case "12":
+ WI.Platform.version.name = "high-sierra";
+ break;
+ case "12":
WI.Platform.version.name = "sierra";
break;
case "11":
Modified: trunk/Source/WebInspectorUI/UserInterface/Base/URLUtilities.js (223855 => 223856)
--- trunk/Source/WebInspectorUI/UserInterface/Base/URLUtilities.js 2017-10-23 21:00:45 UTC (rev 223855)
+++ trunk/Source/WebInspectorUI/UserInterface/Base/URLUtilities.js 2017-10-23 21:34:59 UTC (rev 223856)
@@ -186,12 +186,6 @@
return baseURLPrefix + resolveDotsInPath(basePath + partialURL);
}
-function parseLocationQueryParameters(arrayResult)
-{
- // The first character is always the "?".
- return parseQueryString(window.location.search.substring(1), arrayResult);
-}
-
function parseQueryString(queryString, arrayResult)
{
if (!queryString)
Modified: trunk/Source/WebInspectorUI/UserInterface/Controllers/FrameResourceManager.js (223855 => 223856)
--- trunk/Source/WebInspectorUI/UserInterface/Controllers/FrameResourceManager.js 2017-10-23 21:00:45 UTC (rev 223855)
+++ trunk/Source/WebInspectorUI/UserInterface/Controllers/FrameResourceManager.js 2017-10-23 21:34:59 UTC (rev 223856)
@@ -85,7 +85,7 @@
// If the frame wasn't known before now, then the main resource was loaded instantly (about:blank, etc.)
// Make a new resource (which will make the frame). Mark will mark it as loaded at the end too since we
// don't expect any more events about the load finishing for these frames.
- var frameResource = this._addNewResourceToFrameOrTarget(null, framePayload.id, framePayload.loaderId, framePayload.url, null, null, null, null, null, framePayload.name, framePayload.securityOrigin);
+ var frameResource = this._addNewResourceToFrameOrTarget(null, framePayload.id, framePayload.loaderId, framePayload.url, null, null, null, null, null, null, framePayload.name, framePayload.securityOrigin);
frame = frameResource.parentFrame;
frameWasLoadedInstantly = true;
@@ -163,7 +163,7 @@
this._mainFrameDidChange(oldMainFrame);
}
- resourceRequestWillBeSent(requestIdentifier, frameIdentifier, loaderIdentifier, request, type, redirectResponse, timestamp, initiator, targetId)
+ resourceRequestWillBeSent(requestIdentifier, frameIdentifier, loaderIdentifier, request, type, redirectResponse, timestamp, walltime, initiator, targetId)
{
// Called from WI.NetworkObserver.
@@ -191,7 +191,7 @@
var initiatorSourceCodeLocation = this._initiatorSourceCodeLocationFromPayload(initiator);
// This is a new request, make a new resource and add it to the right frame.
- resource = this._addNewResourceToFrameOrTarget(requestIdentifier, frameIdentifier, loaderIdentifier, request.url, type, request.method, request.headers, request.postData, elapsedTime, null, null, initiatorSourceCodeLocation, originalRequestWillBeSentTimestamp, targetId);
+ resource = this._addNewResourceToFrameOrTarget(requestIdentifier, frameIdentifier, loaderIdentifier, request.url, type, request.method, request.headers, request.postData, elapsedTime, walltime, null, null, initiatorSourceCodeLocation, originalRequestWillBeSentTimestamp, targetId);
// Associate the resource with the requestIdentifier so it can be found in future loading events.
this._resourceRequestIdentifierMap.set(requestIdentifier, resource);
@@ -327,7 +327,7 @@
let response = cachedResourcePayload.response;
const responseSource = NetworkAgent.ResponseSource.MemoryCache;
- let resource = this._addNewResourceToFrameOrTarget(requestIdentifier, frameIdentifier, loaderIdentifier, cachedResourcePayload.url, cachedResourcePayload.type, "GET", null, null, elapsedTime, null, null, initiatorSourceCodeLocation);
+ let resource = this._addNewResourceToFrameOrTarget(requestIdentifier, frameIdentifier, loaderIdentifier, cachedResourcePayload.url, cachedResourcePayload.type, "GET", null, null, elapsedTime, null, null, null, initiatorSourceCodeLocation);
resource.updateForResponse(cachedResourcePayload.url, response.mimeType, cachedResourcePayload.type, response.headers, response.status, response.statusText, elapsedTime, response.timing, responseSource);
resource.increaseSize(cachedResourcePayload.bodySize, elapsedTime);
resource.increaseTransferSize(cachedResourcePayload.bodySize);
@@ -374,7 +374,7 @@
// If we haven't found an existing Resource by now, then it is a resource that was loading when the inspector
// opened and we just missed the resourceRequestWillBeSent for it. So make a new resource and add it.
if (!resource) {
- resource = this._addNewResourceToFrameOrTarget(requestIdentifier, frameIdentifier, loaderIdentifier, response.url, type, null, response.requestHeaders, null, elapsedTime, null, null, null);
+ resource = this._addNewResourceToFrameOrTarget(requestIdentifier, frameIdentifier, loaderIdentifier, response.url, type, null, response.requestHeaders, null, elapsedTime, null, null, null, null);
// Associate the resource with the requestIdentifier so it can be found in future loading events.
this._resourceRequestIdentifierMap.set(requestIdentifier, resource);
@@ -498,7 +498,7 @@
// Private
- _addNewResourceToFrameOrTarget(requestIdentifier, frameIdentifier, loaderIdentifier, url, type, requestMethod, requestHeaders, requestData, elapsedTime, frameName, frameSecurityOrigin, initiatorSourceCodeLocation, originalRequestWillBeSentTimestamp, targetId)
+ _addNewResourceToFrameOrTarget(requestIdentifier, frameIdentifier, loaderIdentifier, url, type, requestMethod, requestHeaders, requestData, elapsedTime, walltime, frameName, frameSecurityOrigin, initiatorSourceCodeLocation, originalRequestWillBeSentTimestamp, targetId)
{
console.assert(!this._waitingForMainFrameResourceTreePayload);
@@ -512,7 +512,7 @@
else if (type === PageAgent.ResourceType.Document && frame.provisionalMainResource && frame.provisionalMainResource.url ="" url && frame.provisionalLoaderIdentifier === loaderIdentifier)
resource = frame.provisionalMainResource;
else {
- resource = new WI.Resource(url, null, type, loaderIdentifier, targetId, requestIdentifier, requestMethod, requestHeaders, requestData, elapsedTime, initiatorSourceCodeLocation, originalRequestWillBeSentTimestamp);
+ resource = new WI.Resource(url, null, type, loaderIdentifier, targetId, requestIdentifier, requestMethod, requestHeaders, requestData, elapsedTime, walltime, initiatorSourceCodeLocation, originalRequestWillBeSentTimestamp);
if (resource.target === WI.mainTarget)
this._addResourceToFrame(frame, resource);
else if (resource.target)
@@ -523,7 +523,7 @@
} else {
// This is a new request for a new frame, which is always the main resource.
console.assert(!targetId);
- resource = new WI.Resource(url, null, type, loaderIdentifier, targetId, requestIdentifier, requestMethod, requestHeaders, requestData, elapsedTime, initiatorSourceCodeLocation, originalRequestWillBeSentTimestamp);
+ resource = new WI.Resource(url, null, type, loaderIdentifier, targetId, requestIdentifier, requestMethod, requestHeaders, requestData, elapsedTime, walltime, initiatorSourceCodeLocation, originalRequestWillBeSentTimestamp);
frame = new WI.Frame(frameIdentifier, frameName, frameSecurityOrigin, loaderIdentifier, resource);
this._frameIdentifierMap.set(frame.id, frame);
Added: trunk/Source/WebInspectorUI/UserInterface/Controllers/HARBuilder.js (0 => 223856)
--- trunk/Source/WebInspectorUI/UserInterface/Controllers/HARBuilder.js (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Controllers/HARBuilder.js 2017-10-23 21:34:59 UTC (rev 223856)
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+// HTTP Archive (HAR) format - Version 1.2
+// https://dvcs.w3.org/hg/webperf/raw-file/tip/specs/HAR/Overview.html#sec-har-object-types-creator
+// http://www.softwareishard.com/blog/har-12-spec/
+
+WI.HARBuilder = class HARBuilder
+{
+ static async buildArchive(resources)
+ {
+ let promises = [];
+ for (let resource of resources) {
+ console.assert(resource.finished);
+ promises.push(new Promise((resolve, reject) => {
+ // Always resolve.
+ resource.requestContent().then(
+ (x) => resolve(x),
+ () => resolve(null)
+ );
+ }));
+ }
+
+ let contents = await Promise.all(promises);
+ console.assert(contents.length === resources.length);
+
+ return {
+ log: {
+ version: "1.2",
+ creator: HARBuilder.creator(),
+ pages: HARBuilder.pages(),
+ entries: resources.map((resource, index) => HARBuilder.entry(resource, contents[index])),
+ }
+ };
+ }
+
+ static creator()
+ {
+ return {
+ name: "WebKit Web Inspector",
+ version: WI.Platform.version.build || "1.0",
+ };
+ }
+
+ static pages()
+ {
+ return [{
+ startedDateTime: HARBuilder.date(WI.frameResourceManager.mainFrame.mainResource.requestSentDate),
+ id: "page_0",
+ title: WI.frameResourceManager.mainFrame.url || "",
+ pageTimings: HARBuilder.pageTimings(),
+ }];
+ }
+
+ static pageTimings()
+ {
+ let result = {};
+
+ let domContentReadyEventTimestamp = WI.frameResourceManager.mainFrame.domContentReadyEventTimestamp;
+ if (!isNaN(domContentReadyEventTimestamp))
+ result._onContentLoad_ = domContentReadyEventTimestamp * 1000;
+
+ let loadEventTimestamp = WI.frameResourceManager.mainFrame.loadEventTimestamp;
+ if (!isNaN(loadEventTimestamp))
+ result._onLoad_ = loadEventTimestamp * 1000;
+
+ return result;
+ }
+
+ static entry(resource, content)
+ {
+ let entry = {
+ pageref: "page_0",
+ startedDateTime: HARBuilder.date(resource.requestSentDate),
+ time: 0,
+ request: HARBuilder.request(resource),
+ response: HARBuilder.response(resource, content),
+ cache: HARBuilder.cache(resource),
+ timings: HARBuilder.timings(resource),
+ };
+
+ if (resource.timingData.startTime && resource.timingData.responseEnd)
+ entry.time = (resource.timingData.responseEnd - resource.timingData.startTime) * 1000;
+ if (resource.remoteAddress)
+ entry.serverIPAddress = HARBuilder.ipAddress(resource.remoteAddress);
+ if (resource.connectionIdentifier)
+ entry.connection = "" + resource.connectionIdentifier;
+
+ // CFNetwork Custom Field `_fetchType`.
+ if (resource.responseSource !== WI.Resource.ResponseSource.Unknown)
+ entry._fetchType = HARBuilder.fetchType(resource.responseSource);
+
+ return entry;
+ }
+
+ static request(resource)
+ {
+ let result = {
+ method: resource.requestMethod || "",
+ url: resource.url || "",
+ httpVersion: WI.Resource.displayNameForProtocol(resource.protocol) || "",
+ cookies: HARBuilder.cookies(resource.requestCookies, null),
+ headers: HARBuilder.headers(resource.requestHeaders),
+ queryString: resource.queryStringParameters || [],
+ headersSize: !isNaN(resource.requestHeadersTransferSize) ? resource.requestHeadersTransferSize : -1,
+ bodySize: !isNaN(resource.requestBodyTransferSize) ? resource.requestBodyTransferSize : -1,
+ };
+
+ if (resource.requestData)
+ result.postData = HARBuilder.postData(resource);
+
+ return result;
+ }
+
+ static response(resource, content)
+ {
+ let result = {
+ status: resource.statusCode || 0,
+ statusText: resource.statusText || "",
+ httpVersion: WI.Resource.displayNameForProtocol(resource.protocol) || "",
+ cookies: HARBuilder.cookies(resource.responseCookies, resource.requestSentDate),
+ headers: HARBuilder.headers(resource.responseHeaders),
+ content: HARBuilder.content(resource, content),
+ redirectURL: resource.responseHeaders.valueForCaseInsensitiveKey("Location") || "",
+ headersSize: !isNaN(resource.responseHeadersTransferSize) ? resource.responseHeadersTransferSize : -1,
+ bodySize: !isNaN(resource.responseBodyTransferSize) ? resource.responseBodyTransferSize : -1,
+ };
+
+ // Chrome Custom Field `_transferSize`.
+ if (!isNaN(resource.networkTotalTransferSize))
+ result._transferSize = resource.networkTotalTransferSize;
+
+ // Chrome Custom Field `_error`.
+ if (resource.failureReasonText)
+ result._error = resource.failureReasonText;
+
+ return result;
+ }
+
+ static cookies(cookies, requestSentDate)
+ {
+ let result = [];
+
+ for (let cookie of cookies) {
+ let json = {
+ name: cookie.name,
+ value: cookie.value,
+ };
+
+ if (cookie.type === WI.Cookie.Type.Response) {
+ if (cookie.path)
+ json.path = cookie.path;
+ if (cookie.domain)
+ json.domain = cookie.domain;
+ json.expires = HARBuilder.date(cookie.expirationDate(requestSentDate));
+ json.httpOnly = cookie.httpOnly;
+ json.secure = cookie.secure;
+ }
+
+ result.push(json);
+ }
+
+ return result;
+ }
+
+ static headers(headers)
+ {
+ let result = [];
+
+ for (let key in headers)
+ result.push({name: key, value: headers[key]});
+
+ return result;
+ }
+
+ static content(resource, content)
+ {
+ let encodedSize = !isNaN(resource.networkEncodedSize) ? resource.networkEncodedSize : resource.estimatedNetworkEncodedSize;
+ let decodedSize = !isNaN(resource.networkDecodedSize) ? resource.networkDecodedSize : resource.size;
+
+ if (isNaN(decodedSize))
+ decodedSize = 0;
+ if (isNaN(encodedSize))
+ encodedSize = 0;
+
+ let result = {
+ size: decodedSize,
+ compression: decodedSize - encodedSize,
+ mimeType: resource.mimeType || "x-unknown",
+ };
+
+ if (content) {
+ if (content.rawContent)
+ result.text = content.rawContent;
+ if (content.rawBase64Encoded)
+ result.encoding = "base64";
+ }
+
+ return result;
+ }
+
+ static postData(resource)
+ {
+ return {
+ mimeType: resource.requestDataContentType || "",
+ text: resource.requestData,
+ params: resource.requestFormParameters || [],
+ };
+ }
+
+ static cache(resource)
+ {
+ // FIXME: <https://webkit.org/b/178682> Web Inspector: Include <cache> details in HAR Export
+ // http://www.softwareishard.com/blog/har-12-spec/#cache
+ return {};
+ }
+
+ static timings(resource)
+ {
+ // Chrome has Custom Fields `_blocked_queueing` and `_blocked_proxy`.
+
+ let result = {
+ blocked: -1,
+ dns: -1,
+ connect: -1,
+ ssl: -1,
+ send: 0,
+ wait: 0,
+ receive: 0,
+ };
+
+ if (resource.timingData.startTime && resource.timingData.responseEnd) {
+ let {startTime, domainLookupStart, domainLookupEnd, connectStart, connectEnd, secureConnectionStart, requestStart, responseStart, responseEnd} = resource.timingData;
+ result.blocked = ((domainLookupStart || connectStart || requestStart) - startTime) * 1000;
+ if (domainLookupStart)
+ result.dns = ((domainLookupEnd || connectStart || requestStart) - domainLookupStart) * 1000;
+ if (connectStart)
+ result.connect = ((connectEnd || requestStart) - connectStart) * 1000;
+ if (secureConnectionStart)
+ result.ssl = ((connectEnd || requestStart) - secureConnectionStart) * 1000;
+ result.send = (requestStart - (connectEnd || domainLookupEnd || startTime)) * 1000;
+ result.wait = (responseStart - requestStart) * 1000;
+ result.receive = (responseEnd - responseStart) * 1000;
+ }
+
+ return result;
+ }
+
+ // Helpers
+
+ static ipAddress(remoteAddress)
+ {
+ // IP Address, without port.
+ if (!remoteAddress)
+ return "";
+
+ // NOTE: Resource.remoteAddress always includes the port at the end.
+ // So this always strips the last part.
+ return remoteAddress.replace(/:\d+$/, "");
+ }
+
+ static date(date)
+ {
+ // ISO 8601
+ if (!date)
+ return "";
+
+ return date.toISOString();
+ }
+
+ static fetchType(responseSource)
+ {
+ switch (responseSource) {
+ case WI.Resource.ResponseSource.Network:
+ return "Network Load";
+ case WI.Resource.ResponseSource.MemoryCache:
+ return "Memory Cache";
+ case WI.Resource.ResponseSource.DiskCache:
+ return "Disk Cache";
+ }
+
+ console.assert(false);
+ return undefined;
+ }
+}
Modified: trunk/Source/WebInspectorUI/UserInterface/Main.html (223855 => 223856)
--- trunk/Source/WebInspectorUI/UserInterface/Main.html 2017-10-23 21:00:45 UTC (rev 223855)
+++ trunk/Source/WebInspectorUI/UserInterface/Main.html 2017-10-23 21:34:59 UTC (rev 223856)
@@ -820,6 +820,7 @@
<script src=""
<script src=""
<script src=""
+ <script src=""
<script src=""
<script src=""
<script src=""
Modified: trunk/Source/WebInspectorUI/UserInterface/Models/Cookie.js (223855 => 223856)
--- trunk/Source/WebInspectorUI/UserInterface/Models/Cookie.js 2017-10-23 21:00:45 UTC (rev 223855)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/Cookie.js 2017-10-23 21:34:59 UTC (rev 223856)
@@ -53,6 +53,18 @@
}
}
+ // Public
+
+ expirationDate(requestSentDate)
+ {
+ if (this.maxAge) {
+ let startDate = requestSentDate || new Date;
+ return new Date(startDate.getTime() + (this.maxAge * 1000));
+ }
+
+ return this.expires;
+ }
+
// Static
// RFC 6265 defines the HTTP Cookie and Set-Cookie header fields:
Modified: trunk/Source/WebInspectorUI/UserInterface/Models/Resource.js (223855 => 223856)
--- trunk/Source/WebInspectorUI/UserInterface/Models/Resource.js 2017-10-23 21:00:45 UTC (rev 223855)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/Resource.js 2017-10-23 21:34:59 UTC (rev 223856)
@@ -26,7 +26,7 @@
WI.Resource = class Resource extends WI.SourceCode
{
- constructor(url, mimeType, type, loaderIdentifier, targetId, requestIdentifier, requestMethod, requestHeaders, requestData, requestSentTimestamp, initiatorSourceCodeLocation, originalRequestWillBeSentTimestamp)
+ constructor(url, mimeType, type, loaderIdentifier, targetId, requestIdentifier, requestMethod, requestHeaders, requestData, requestSentTimestamp, requestSentWalltime, initiatorSourceCodeLocation, originalRequestWillBeSentTimestamp)
{
super();
@@ -42,6 +42,8 @@
this._type = type || WI.Resource.typeFromMIMEType(mimeType);
this._loaderIdentifier = loaderIdentifier || null;
this._requestIdentifier = requestIdentifier || null;
+ this._queryStringParameters = undefined;
+ this._requestFormParameters = undefined;
this._requestMethod = requestMethod || null;
this._requestData = requestData || null;
this._requestHeaders = requestHeaders || {};
@@ -53,6 +55,7 @@
this._initiatedResources = [];
this._originalRequestWillBeSentTimestamp = originalRequestWillBeSentTimestamp || null;
this._requestSentTimestamp = requestSentTimestamp || NaN;
+ this._requestSentWalltime = requestSentWalltime || NaN;
this._responseReceivedTimestamp = NaN;
this._lastRedirectReceivedTimestamp = NaN;
this._lastDataReceivedTimestamp = NaN;
@@ -410,6 +413,20 @@
return this._failureReasonText;
}
+ get queryStringParameters()
+ {
+ if (this._queryStringParameters === undefined)
+ this._queryStringParameters = parseQueryString(this.urlComponents.queryString, true);
+ return this._queryStringParameters;
+ }
+
+ get requestFormParameters()
+ {
+ if (this._requestFormParameters === undefined)
+ this._requestFormParameters = this.hasRequestFormParameters() ? parseQueryString(this.requestData, true) : null;
+ return this._requestFormParameters;
+ }
+
get requestDataContentType()
{
return this._requestHeaders.valueForCaseInsensitiveKey("Content-Type") || null;
@@ -460,6 +477,16 @@
return this._requestSentTimestamp;
}
+ get requestSentWalltime()
+ {
+ return this._requestSentWalltime;
+ }
+
+ get requestSentDate()
+ {
+ return isNaN(this._requestSentWalltime) ? null : new Date(this._requestSentWalltime * 1000);
+ }
+
get lastRedirectReceivedTimestamp()
{
return this._lastRedirectReceivedTimestamp;
@@ -656,6 +683,12 @@
return !isNaN(this._statusCode) || this._finished;
}
+ hasRequestFormParameters()
+ {
+ let requestDataContentType = this.requestDataContentType;
+ return requestDataContentType && requestDataContentType.match(/^application\/x-www-form-urlencoded\s*(;.*)?$/i);
+ }
+
updateForResponse(url, mimeType, type, responseHeaders, statusCode, statusText, elapsedTime, timingData, source)
{
console.assert(!this._finished);
Modified: trunk/Source/WebInspectorUI/UserInterface/Models/SourceCode.js (223855 => 223856)
--- trunk/Source/WebInspectorUI/UserInterface/Models/SourceCode.js 2017-10-23 21:00:45 UTC (rev 223855)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/SourceCode.js 2017-10-23 21:34:59 UTC (rev 223856)
@@ -194,12 +194,13 @@
_processContent(parameters)
{
// Different backend APIs return one of `content, `body`, `text`, or `scriptSource`.
- var content = parameters.content || parameters.body || parameters.text || parameters.scriptSource;
- var error = parameters.error;
+ let rawContent = parameters.content || parameters.body || parameters.text || parameters.scriptSource;
+ let content = rawContent;
+ let error = parameters.error;
if (parameters.base64Encoded)
content = content ? decodeBase64ToBlob(content, this.mimeType) : "";
- var revision = this.revisionForRequestedContent;
+ let revision = this.revisionForRequestedContent;
this._ignoreRevisionContentDidChangeEvent = true;
revision.content = content || null;
@@ -213,6 +214,8 @@
error,
sourceCode: this,
content,
+ rawContent,
+ rawBase64Encoded: parameters.base64Encoded,
});
}
};
Modified: trunk/Source/WebInspectorUI/UserInterface/Protocol/NetworkObserver.js (223855 => 223856)
--- trunk/Source/WebInspectorUI/UserInterface/Protocol/NetworkObserver.js 2017-10-23 21:00:45 UTC (rev 223855)
+++ trunk/Source/WebInspectorUI/UserInterface/Protocol/NetworkObserver.js 2017-10-23 21:34:59 UTC (rev 223856)
@@ -27,9 +27,18 @@
{
// Events defined by the "Network" domain.
- requestWillBeSent(requestId, frameId, loaderId, documentURL, request, timestamp, initiator, redirectResponse, type, targetId)
+ requestWillBeSent(requestId, frameId, loaderId, documentURL, request, timestamp, walltime, initiator, redirectResponse, type, targetId)
{
- WI.frameResourceManager.resourceRequestWillBeSent(requestId, frameId, loaderId, request, type, redirectResponse, timestamp, initiator, targetId);
+ // COMPATIBILITY(iOS 11.0): `walltime` did not exist in 11.0 and earlier.
+ if (!NetworkAgent.hasEventParameter("requestWillBeSent", "walltime")) {
+ walltime = undefined;
+ initiator = arguments[6];
+ redirectResponse = arguments[7];
+ type = arguments[8];
+ targetId = arguments[9];
+ }
+
+ WI.frameResourceManager.resourceRequestWillBeSent(requestId, frameId, loaderId, request, type, redirectResponse, timestamp, walltime, initiator, targetId);
}
requestServedFromCache(requestId)
Modified: trunk/Source/WebInspectorUI/UserInterface/Test/TestHarness.js (223855 => 223856)
--- trunk/Source/WebInspectorUI/UserInterface/Test/TestHarness.js 2017-10-23 21:00:45 UTC (rev 223855)
+++ trunk/Source/WebInspectorUI/UserInterface/Test/TestHarness.js 2017-10-23 21:34:59 UTC (rev 223856)
@@ -90,6 +90,11 @@
this.addResult(message);
}
+ json(object, filter)
+ {
+ this.log(JSON.stringify(object, filter || null, 2));
+ }
+
assert(condition, message)
{
if (condition)
Modified: trunk/Source/WebInspectorUI/UserInterface/Test.html (223855 => 223856)
--- trunk/Source/WebInspectorUI/UserInterface/Test.html 2017-10-23 21:00:45 UTC (rev 223855)
+++ trunk/Source/WebInspectorUI/UserInterface/Test.html 2017-10-23 21:34:59 UTC (rev 223856)
@@ -202,6 +202,7 @@
<script src=""
<script src=""
<script src=""
+ <script src=""
<script src=""
<script src=""
<script src=""
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/DOMTreeContentView.js (223855 => 223856)
--- trunk/Source/WebInspectorUI/UserInterface/Views/DOMTreeContentView.js 2017-10-23 21:00:45 UTC (rev 223855)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/DOMTreeContentView.js 2017-10-23 21:34:59 UTC (rev 223856)
@@ -214,12 +214,7 @@
get saveData()
{
- function saveHandler(forceSaveAs)
- {
- WI.archiveMainFrame();
- }
-
- return {customSaveHandler: saveHandler};
+ return {customSaveHandler: () => { WI.archiveMainFrame(); }};
}
get supportsSearch()
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/NetworkTableContentView.js (223855 => 223856)
--- trunk/Source/WebInspectorUI/UserInterface/Views/NetworkTableContentView.js 2017-10-23 21:00:45 UTC (rev 223855)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/NetworkTableContentView.js 2017-10-23 21:34:59 UTC (rev 223856)
@@ -184,6 +184,16 @@
return items;
}
+ get supportsSave()
+ {
+ return this._filteredEntries.some((entry) => entry.resource.finished);
+ }
+
+ get saveData()
+ {
+ return {customSaveHandler: () => { this._exportHAR(); }};
+ }
+
shown()
{
super.shown();
@@ -295,6 +305,9 @@
let entry = this._filteredEntries[rowIndex];
let contextMenu = WI.ContextMenu.createFromEvent(event);
WI.appendContextMenuItemsForSourceCode(contextMenu, entry.resource);
+
+ contextMenu.appendSeparator();
+ contextMenu.appendItem(WI.UIString("Export HAR"), this._exportHAR);
}
tableSelectedRowChanged(table, rowIndex)
@@ -1360,6 +1373,33 @@
this._table.selectRow(rowIndex);
}
+ _HARResources()
+ {
+ let resources = this._filteredEntries.map((x) => x.resource);
+ const supportedHARSchemes = new Set(["http", "https", "ws", "wss"]);
+ return resources.filter((resource) => resource.finished && supportedHARSchemes.has(resource.urlComponents.scheme));
+ }
+
+ _exportHAR()
+ {
+ let resources = this._HARResources();
+ if (!resources.length) {
+ InspectorFrontendHost.beep();
+ return;
+ }
+
+ WI.HARBuilder.buildArchive(resources).then((har) => {
+ let mainFrame = WI.frameResourceManager.mainFrame;
+ let archiveName = mainFrame.mainResource.urlComponents.host || mainFrame.mainResource.displayName || "Archive";
+ let url = "" + encodeURI(archiveName) + ".har";
+ WI.saveDataToFile({
+ url,
+ content: JSON.stringify(har, null, 2),
+ forceSaveAs: true,
+ });
+ }).catch(handlePromiseException);
+ }
+
_waterfallPopoverContentForResource(resource)
{
let contentElement = document.createElement("div");
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/ResourceClusterContentView.js (223855 => 223856)
--- trunk/Source/WebInspectorUI/UserInterface/Views/ResourceClusterContentView.js 2017-10-23 21:00:45 UTC (rev 223855)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/ResourceClusterContentView.js 2017-10-23 21:34:59 UTC (rev 223856)
@@ -213,8 +213,7 @@
if (!requestData)
return false;
- var requestDataContentType = this._resource.requestDataContentType;
- if (requestDataContentType && requestDataContentType.match(/^application\/x-www-form-urlencoded\s*(;.*)?$/i))
+ if (!this._resource.hasRequestFormParameters())
return false;
return true;