1/*
2 * Copyright (C) 2013, 2014 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
14 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
15 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
16 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
17 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
18 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
19 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26WebInspector.loaded = function()
27{
28    // Register observers for events from the InspectorBackend.
29    // The initialization order should match the same in Main.js.
30    InspectorBackend.registerInspectorDispatcher(new WebInspector.InspectorObserver);
31    InspectorBackend.registerPageDispatcher(new WebInspector.PageObserver);
32    InspectorBackend.registerDOMDispatcher(new WebInspector.DOMObserver);
33    InspectorBackend.registerNetworkDispatcher(new WebInspector.NetworkObserver);
34    InspectorBackend.registerDebuggerDispatcher(new WebInspector.DebuggerObserver);
35    InspectorBackend.registerTimelineDispatcher(new WebInspector.TimelineObserver);
36    InspectorBackend.registerCSSDispatcher(new WebInspector.CSSObserver);
37    InspectorBackend.registerRuntimeDispatcher(new WebInspector.RuntimeObserver);
38    if (InspectorBackend.registerReplayDispatcher)
39        InspectorBackend.registerReplayDispatcher(new WebInspector.ReplayObserver);
40
41    // Instantiate controllers used by tests.
42    this.frameResourceManager = new WebInspector.FrameResourceManager;
43    this.domTreeManager = new WebInspector.DOMTreeManager;
44    this.cssStyleManager = new WebInspector.CSSStyleManager;
45    this.runtimeManager = new WebInspector.RuntimeManager;
46    this.timelineManager = new WebInspector.TimelineManager;
47    this.debuggerManager = new WebInspector.DebuggerManager;
48    this.probeManager = new WebInspector.ProbeManager;
49    this.replayManager = new WebInspector.ReplayManager;
50
51    document.addEventListener("DOMContentLoaded", this.contentLoaded.bind(this));
52
53    // Enable agents.
54    InspectorAgent.enable();
55
56    // Establish communication with the InspectorBackend.
57    InspectorFrontendHost.loaded();
58}
59
60WebInspector.contentLoaded = function() {
61    // Signal that the frontend is now ready to receive messages.
62    InspectorFrontendAPI.loadCompleted();
63}
64
65// Add stubs that are called by the frontend API.
66WebInspector.updateDockedState = function()
67{
68}
69
70// InspectorTest contains extra methods that are only available to test code running
71// in the Web Inspector page. They rely on equivalents in the actual test page
72// which are provided by `inspector-test.js`.
73InspectorTest = {};
74
75// This is a workaround for the fact that it would be hard to set up a constructor,
76// prototype, and prototype chain for the singleton InspectorTest.
77InspectorTest.EventDispatcher = function()
78{
79    WebInspector.Object.call(this);
80};
81
82InspectorTest.EventDispatcher.Event = {
83    TestPageDidLoad: "inspector-test-test-page-did-load"
84};
85
86InspectorTest.EventDispatcher.prototype = {
87    __proto__: WebInspector.Object.prototype,
88    constructor: InspectorTest.EventDispatcher,
89
90    dispatchEvent: function(event)
91    {
92        this.dispatchEventToListeners(event);
93    }
94};
95
96InspectorTest.eventDispatcher = new InspectorTest.EventDispatcher;
97
98// Note: Additional InspectorTest methods are included on a per-test basis from
99// files like `debugger-test.js`.
100
101// Appends a log message in the test document.
102InspectorTest.log = function(message)
103{
104    var stringifiedMessage = typeof message !== "object" ? message : JSON.stringify(message);
105    InspectorTest.addResult(stringifiedMessage);
106}
107
108// Appends a message in the test document only if the condition is false.
109InspectorTest.assert = function(condition, message)
110{
111    if (condition)
112        return;
113
114    var stringifiedMessage = typeof message !== "object" ? message : JSON.stringify(message);
115    InspectorTest.addResult("ASSERT: " + stringifiedMessage);
116}
117
118// Appends a message in the test document whether the condition is true or not.
119InspectorTest.expectThat = function(condition, message)
120{
121    var prefix = condition ? "PASS" : "FAIL";
122    var stringifiedMessage = typeof message !== "object" ? message : JSON.stringify(message);
123    InspectorTest.addResult(prefix + ": " + stringifiedMessage);
124}
125
126// This function should only be used to debug tests and not to produce normal test output.
127InspectorTest.debugLog = function(message)
128{
129    this.evaluateInPage("InspectorTestProxy.debugLog(unescape('" + escape(JSON.stringify(message)) + "'))");
130}
131
132InspectorTest.completeTest = function()
133{
134    function signalCompletionToTestPage() {
135        InspectorBackend.runAfterPendingDispatches(this.evaluateInPage.bind(this, "InspectorTestProxy.completeTest()"));
136    }
137
138    // Wait for results to be resent before requesting completeTest(). Otherwise, messages will be
139    // queued after pending dispatches run to zero and the test page will quit before processing them.
140    if (this._shouldResendResults)
141        this._resendResults(signalCompletionToTestPage.bind(this));
142    else
143        signalCompletionToTestPage.call(this);
144}
145
146InspectorTest.evaluateInPage = function(codeString, callback)
147{
148    // If we load this page outside of the inspector, or hit an early error when loading
149    // the test frontend, then defer evaluating the commands (indefinitely in the former case).
150    if (!RuntimeAgent) {
151        this._originalConsoleMethods["error"]("Tried to evaluate in test page, but connection not yet established:", codeString);
152        return;
153    }
154
155    RuntimeAgent.evaluate.invoke({expression: codeString, objectGroup: "test", includeCommandLineAPI: false}, callback);
156}
157
158InspectorTest.addResult = function(text)
159{
160    this._results.push(text);
161
162    if (!this._testPageIsReloading)
163        this.evaluateInPage("InspectorTestProxy.addResult(unescape('" + escape(text) + "'))");
164}
165
166InspectorTest._resendResults = function(callback)
167{
168    console.assert(this._shouldResendResults);
169    delete this._shouldResendResults;
170
171    var pendingResponseCount = 1 + this._results.length;
172    function decrementPendingResponseCount() {
173        pendingResponseCount--;
174        if (!pendingResponseCount && typeof callback === "function")
175            callback();
176    }
177
178    this.evaluateInPage("InspectorTestProxy.clearResults()", decrementPendingResponseCount);
179    for (var result of this._results)
180        this.evaluateInPage("InspectorTestProxy.addResult(unescape('" + escape(result) + "'))", decrementPendingResponseCount);
181}
182
183InspectorTest.testPageDidLoad = function()
184{
185    this._testPageIsReloading = false;
186    this._resendResults();
187
188    this.eventDispatcher.dispatchEvent(InspectorTest.EventDispatcher.Event.TestPageDidLoad);
189}
190
191InspectorTest.reloadPage = function(shouldIgnoreCache)
192{
193    return PageAgent.reload.promise(!!shouldIgnoreCache)
194        .then(function() {
195            this._shouldResendResults = true;
196            this._testPageIsReloading = true;
197
198            return Promise.resolve(null);
199        });
200}
201
202InspectorTest.reportUncaughtException = function(message, url, lineNumber)
203{
204    var result = "Uncaught exception in inspector page: " + message + " [" + url + ":" + lineNumber + "]";
205
206    // If the connection to the test page is not set up, then just dump to console and give up.
207    // Errors encountered this early can be debugged by loading Test.html in a normal browser page.
208    if (!InspectorFrontendHost || !InspectorBackend) {
209        this._originalConsoleMethods["error"](result);
210        return false;
211    }
212
213    this.addResult(result);
214    this.completeTest();
215    // Stop default handler so we can empty InspectorBackend's message queue.
216    return true;
217}
218
219// Initialize reporting mechanisms before loading the rest of the inspector page.
220InspectorTest._results = [];
221InspectorTest._shouldResendResults = true;
222InspectorTest._originalConsoleMethods = {};
223
224// Catch syntax errors, type errors, and other exceptions.
225window.onerror = InspectorTest.reportUncaughtException.bind(InspectorTest);
226
227for (var logType of ["log", "error", "info"]) {
228    // Redirect console methods to log messages into the test page's DOM.
229    InspectorTest._originalConsoleMethods[logType] = console[logType].bind(console);
230    console[logType] = function() {
231        InspectorTest.addResult(logType.toUpperCase() + ": " + Array.prototype.slice.call(arguments).toString());
232    };
233}
234