blob: 13ae2423c8f123e93cb14865c8071824efbafcfe [file] [log] [blame]
/*
* Copyright (C) 2018 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.
*/
WI.AuditTestBase = class AuditTestBase extends WI.Object
{
constructor(name, {description, supports, setup, disabled} = {})
{
console.assert(typeof name === "string", name);
console.assert(!description || typeof description === "string", description);
console.assert(supports === undefined || typeof supports === "number", supports);
console.assert(!setup || typeof setup === "string", setup);
console.assert(disabled === undefined || typeof disabled === "boolean", disabled);
super();
// This class should not be instantiated directly. Create a concrete subclass instead.
console.assert(this.constructor !== WI.AuditTestBase && this instanceof WI.AuditTestBase, this);
this._name = name;
this._description = description || "";
this._supports = supports ?? NaN;
this._setup = setup || "";
this.determineIfSupported({warn: true});
this._runningState = disabled ? WI.AuditManager.RunningState.Disabled : WI.AuditManager.RunningState.Inactive;
this._result = null;
this._parent = null;
this._default = false;
}
// Public
get runningState() { return this._runningState; }
get result() { return this._result; }
get supported() { return this._supported; }
get name()
{
return this._name;
}
set name(name)
{
console.assert(this.editable);
console.assert(WI.auditManager.editing);
console.assert(name && typeof name === "string", name);
if (name === this._name)
return;
let oldName = this._name;
this._name = name;
this.dispatchEventToListeners(WI.AuditTestBase.Event.NameChanged, {oldName});
}
get description()
{
return this._description;
}
set description(description)
{
console.assert(this.editable);
console.assert(WI.auditManager.editing);
console.assert(typeof description === "string", description);
if (description === this._description)
return;
this._description = description;
}
get supports()
{
return this._supports;
}
set supports(supports)
{
console.assert(this.editable);
console.assert(WI.auditManager.editing);
console.assert(typeof supports === "number", supports);
if (supports === this._supports)
return;
this._supports = supports;
this.determineIfSupported();
}
get setup()
{
return this._setup;
}
set setup(setup)
{
console.assert(this.editable);
console.assert(typeof setup === "string", setup);
if (setup === this._setup)
return;
this._setup = setup;
this.clearResult();
}
get disabled()
{
return this._runningState === WI.AuditManager.RunningState.Disabled;
}
set disabled(disabled)
{
this.updateDisabled(disabled);
}
get editable()
{
return !this._default;
}
get default()
{
return this._default;
}
markAsDefault()
{
console.assert(!this._default);
this._default = true;
}
get topLevelTest()
{
let test = this;
while (test._parent)
test = test._parent;
return test;
}
async runSetup()
{
console.assert(this.topLevelTest === this);
if (!this._setup)
return;
let target = WI.assumingMainTarget();
let agentCommandFunction = null;
let agentCommandArguments = {};
if (target.hasDomain("Audit")) {
agentCommandFunction = target.AuditAgent.run;
agentCommandArguments.test = this._setup;
} else {
agentCommandFunction = target.RuntimeAgent.evaluate;
agentCommandArguments.expression = `(function() { "use strict"; return eval(\`(${this._setup.replace(/`/g, "\\`")})\`)(); })()`;
agentCommandArguments.objectGroup = AuditTestBase.ObjectGroup;
agentCommandArguments.doNotPauseOnExceptionsAndMuteConsole = true;
}
try {
let response = await agentCommandFunction.invoke(agentCommandArguments);
if (response.result.type === "object" && response.result.className === "Promise") {
if (WI.RuntimeManager.supportsAwaitPromise())
response = await target.RuntimeAgent.awaitPromise(response.result.objectId);
else {
response = null;
WI.AuditManager.synthesizeError(WI.UIString("Async audits are not supported."));
}
}
if (response) {
let remoteObject = WI.RemoteObject.fromPayload(response.result, WI.mainTarget);
if (response.wasThrown || (remoteObject.type === "object" && remoteObject.subtype === "error"))
WI.AuditManager.synthesizeError(remoteObject.description);
}
} catch (error) {
WI.AuditManager.synthesizeError(error.message);
}
}
async start()
{
// Called from WI.AuditManager.
if (!this._supported || this.disabled)
return;
console.assert(WI.auditManager.runningState === WI.AuditManager.RunningState.Active);
console.assert(this._runningState === WI.AuditManager.RunningState.Inactive);
if (this._runningState !== WI.AuditManager.RunningState.Inactive)
return;
this._runningState = WI.AuditManager.RunningState.Active;
this.dispatchEventToListeners(WI.AuditTestBase.Event.Scheduled);
await this.run();
this._runningState = WI.AuditManager.RunningState.Inactive;
this.dispatchEventToListeners(WI.AuditTestBase.Event.Completed);
}
stop()
{
// Called from WI.AuditManager.
// Overridden by sub-classes if needed.
if (!this._supported || this.disabled)
return;
console.assert(WI.auditManager.runningState === WI.AuditManager.RunningState.Stopping);
if (this._runningState !== WI.AuditManager.RunningState.Active)
return;
this._runningState = WI.AuditManager.RunningState.Stopping;
this.dispatchEventToListeners(WI.AuditTestBase.Event.Stopping);
}
clearResult(options = {})
{
// Overridden by sub-classes if needed.
if (!this._result)
return false;
this._result = null;
if (!options.suppressResultChangedEvent)
this.dispatchEventToListeners(WI.AuditTestBase.Event.ResultChanged);
return true;
}
async clone()
{
console.assert(WI.auditManager.editing);
return this.constructor.fromPayload(this.toJSON());
}
remove()
{
console.assert(WI.auditManager.editing);
if (!this._parent || this._default) {
WI.auditManager.removeTest(this);
return;
}
console.assert(this.editable);
console.assert(this._parent instanceof WI.AuditTestGroup);
this._parent.removeTest(this);
}
saveIdentityToCookie(cookie)
{
let path = [];
let test = this;
while (test) {
path.push(test.name);
test = test._parent;
}
path.reverse();
cookie["audit-path"] = path.join(",");
}
toJSON(key)
{
// Overridden by sub-classes if needed.
let json = {
type: this.constructor.TypeIdentifier,
name: this._name,
};
if (this._description)
json.description = this._description;
if (!isNaN(this._supports))
json.supports = Number.isFinite(this._supports) ? this._supports : WI.AuditTestBase.Version + 1;
if (this._setup)
json.setup = this._setup;
if (key === WI.ObjectStore.toJSONSymbol)
json.disabled = this.disabled;
return json;
}
// Protected
async run()
{
throw WI.NotImplementedError.subclassMustOverride();
}
determineIfSupported(options = {})
{
// Overridden by sub-classes if needed.
let supportedBefore = this._supported;
if (this._supports > WI.AuditTestBase.Version) {
this.updateSupported(false, options);
if (options.warn && supportedBefore !== this._supported && Number.isFinite(this._supports))
WI.AuditManager.synthesizeWarning(WI.UIString("\u0022%s\u0022 is too new to run in this Web Inspector").format(this.name));
} else if (InspectorBackend.hasDomain("Audit") && this._supports > InspectorBackend.getVersion("Audit")) {
this.updateSupported(false, options);
if (options.warn && supportedBefore !== this._supported && Number.isFinite(this._supports))
WI.AuditManager.synthesizeWarning(WI.UIString("\u0022%s\u0022 is too new to run in the inspected page").format(this.name));
} else
this.updateSupported(true, options);
return this._supported;
}
updateSupported(supported, options = {})
{
// Overridden by sub-classes if needed.
if (supported === this._supported)
return;
this._supported = supported;
if (!options.silent)
this.dispatchEventToListeners(WI.AuditTestBase.Event.SupportedChanged);
if (!this._supported)
this.clearResult();
}
updateDisabled(disabled, options = {})
{
// Overridden by sub-classes if needed.
console.assert(this._runningState === WI.AuditManager.RunningState.Disabled || this._runningState === WI.AuditManager.RunningState.Inactive);
if (this._runningState !== WI.AuditManager.RunningState.Disabled && this._runningState !== WI.AuditManager.RunningState.Inactive)
return;
let runningState = disabled ? WI.AuditManager.RunningState.Disabled : WI.AuditManager.RunningState.Inactive;
if (runningState === this._runningState)
return;
this._runningState = runningState;
if (!options.silent)
this.dispatchEventToListeners(WI.AuditTestBase.Event.DisabledChanged);
if (this.disabled)
this.clearResult();
}
updateResult(result)
{
// Overridden by sub-classes if needed.
console.assert(result instanceof WI.AuditTestResultBase, result);
this._result = result;
this.dispatchEventToListeners(WI.AuditTestBase.Event.ResultChanged);
}
};
// Keep this in sync with Inspector::Protocol::Audit::VERSION.
WI.AuditTestBase.Version = 4;
WI.AuditTestBase.ObjectGroup = "audit";
WI.AuditTestBase.Event = {
Completed: "audit-test-base-completed",
DisabledChanged: "audit-test-base-disabled-changed",
NameChanged: "audit-test-base-name-changed",
Progress: "audit-test-base-progress",
ResultChanged: "audit-test-base-result-changed",
Scheduled: "audit-test-base-scheduled",
Stopping: "audit-test-base-stopping",
SupportedChanged: "audit-test-base-supported-changed",
TestChanged: "audit-test-base-test-changed",
};