Added SDK

This commit is contained in:
Andrew Zambazos
2026-06-11 14:01:22 +12:00
commit c0395a49bd
2155 changed files with 451005 additions and 0 deletions
+165
View File
@@ -0,0 +1,165 @@
/*
* Copyright (C) 2019 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.
*/
// FIXME: AnimationManager lacks advanced multi-target support. (Animations per-target)
WI.AnimationManager = class AnimationManager
{
constructor()
{
this._enabled = false;
this._animationCollection = new WI.AnimationCollection;
this._animationIdMap = new Map;
WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._handleMainResourceDidChange, this);
}
// Agent
get domains() { return ["Animation"]; }
activateExtraDomain(domain)
{
// COMPATIBILITY (iOS 14.0): Inspector.activateExtraDomains was removed in favor of a declared debuggable type
console.assert(domain === "Animation");
for (let target of WI.targets)
this.initializeTarget(target);
}
// Target
initializeTarget(target)
{
if (!this._enabled)
return;
// COMPATIBILITY (iOS 13.1): Animation.enable did not exist yet.
if (target.hasCommand("Animation.enable"))
target.AnimationAgent.enable();
}
// Public
get animationCollection() { return this._animationCollection; }
get supported()
{
// COMPATIBILITY (iOS 13.1): Animation.enable did not exist yet.
return InspectorBackend.hasCommand("Animation.enable");
}
enable()
{
console.assert(!this._enabled);
this._enabled = true;
for (let target of WI.targets)
this.initializeTarget(target);
}
disable()
{
console.assert(this._enabled);
for (let target of WI.targets) {
// COMPATIBILITY (iOS 13.1): Animation.disable did not exist yet.
if (target.hasCommand("Animation.disable"))
target.AnimationAgent.disable();
}
this._animationCollection.clear();
this._animationIdMap.clear();
this._enabled = false;
}
// AnimationObserver
animationCreated(animationPayload)
{
console.assert(!this._animationIdMap.has(animationPayload.animationId), `Animation already exists with id ${animationPayload.animationId}.`);
let animation = WI.Animation.fromPayload(animationPayload);
this._animationCollection.add(animation);
this._animationIdMap.set(animation.animationId, animation);
}
nameChanged(animationId, name)
{
let animation = this._animationIdMap.get(animationId);
console.assert(animation);
if (!animation)
return;
animation.nameChanged(name);
}
effectChanged(animationId, effect)
{
let animation = this._animationIdMap.get(animationId);
console.assert(animation);
if (!animation)
return;
animation.effectChanged(effect);
}
targetChanged(animationId, effect)
{
let animation = this._animationIdMap.get(animationId);
console.assert(animation);
if (!animation)
return;
animation.targetChanged(effect);
}
animationDestroyed(animationId)
{
let animation = this._animationIdMap.take(animationId);
console.assert(animation);
if (!animation)
return;
this._animationCollection.remove(animation);
}
// Private
_handleMainResourceDidChange(event)
{
console.assert(event.target instanceof WI.Frame);
if (!event.target.isMainFrame())
return;
WI.Animation.resetUniqueDisplayNameNumbers();
this._animationCollection.clear();
this._animationIdMap.clear();
}
};
+110
View File
@@ -0,0 +1,110 @@
/*
* Copyright (C) 2014 Apple Inc. All rights reserved.
* Copyright (C) 2014 Saam Barati <saambarati1@gmail.com>
*
* 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.Annotator = class Annotator extends WI.Object
{
constructor(sourceCodeTextEditor)
{
super();
console.assert(sourceCodeTextEditor instanceof WI.SourceCodeTextEditor, sourceCodeTextEditor);
this._sourceCodeTextEditor = sourceCodeTextEditor;
this._timeoutIdentifier = null;
this._isActive = false;
}
// Public
get sourceCodeTextEditor()
{
return this._sourceCodeTextEditor;
}
isActive()
{
return this._isActive;
}
pause()
{
this._clearTimeoutIfNeeded();
this._isActive = false;
}
resume()
{
this._clearTimeoutIfNeeded();
this._isActive = true;
this.insertAnnotations();
}
refresh()
{
console.assert(this._isActive);
if (!this._isActive)
return;
this._clearTimeoutIfNeeded();
this.insertAnnotations();
}
reset()
{
this._clearTimeoutIfNeeded();
this._isActive = true;
this.clearAnnotations();
this.insertAnnotations();
}
clear()
{
this.pause();
this.clearAnnotations();
}
// Protected
insertAnnotations()
{
// Implemented by subclasses.
}
clearAnnotations()
{
// Implemented by subclasses.
}
// Private
_clearTimeoutIfNeeded()
{
if (this._timeoutIdentifier) {
clearTimeout(this._timeoutIdentifier);
this._timeoutIdentifier = null;
}
}
};
+76
View File
@@ -0,0 +1,76 @@
/*
* 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.
*/
WI.AppController = class AppController extends WI.AppControllerBase
{
constructor()
{
super();
this._debuggableType = WI.DebuggableType.fromString(InspectorFrontendHost.debuggableInfo.debuggableType);
console.assert(this._debuggableType);
if (!this._debuggableType)
this._debuggableType = WI.DebuggableType.JavaScript;
}
// Properties.
get debuggableType() { return this._debuggableType; }
// API.
activateExtraDomains(domains)
{
// COMPATIBILITY (iOS 14.0): Inspector.activateExtraDomains was removed in favor of a declared debuggable type
console.assert(this._debuggableType === WI.DebuggableType.JavaScript);
console.assert(WI.targets.length === 1);
let target = WI.mainTarget;
console.assert(target instanceof WI.DirectBackendTarget);
console.assert(target.type === WI.TargetType.JavaScript);
if (this._debuggableType === WI.DebuggableType.ITML || target.type === WI.TargetType.ITML)
throw new Error("Extra domains have already been activated, cannot activate again.");
this._debuggableType = WI.DebuggableType.ITML;
target._type = WI.TargetType.ITML;
for (let domain of domains) {
InspectorBackend.activateDomain(domain);
target.activateExtraDomain(domain);
let manager = WI.managers.find((manager) => manager.domains && manager.domains.includes(domain));
if (manager)
manager.activateExtraDomain(domain);
else if (target.hasCommand(domain + ".enable"))
target._agents[domain].enable();
}
// FIXME: all code within WI.activateExtraDomains should be distributed elsewhere.
WI.activateExtraDomains(domains);
}
};
@@ -0,0 +1,71 @@
/*
* 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.
*/
WI.NotImplementedError = class NotImplementedError extends Error
{
constructor(message = "This method is not implemented.")
{
super(message);
}
static subclassMustOverride()
{
return new WI.NotImplementedError("This method must be overridden by a subclass.");
}
};
WI.AppControllerBase = class AppControllerBase
{
constructor()
{
this._initialized = false;
this._extensionController = new WI.WebInspectorExtensionController;
}
// Public
get debuggableType() { throw WI.NotImplementedError.subclassMustOverride(); }
get extensionController() { return this._extensionController; }
// Since various members of the app controller depend on the global singleton to exist,
// some initialization needs to happen after the app controller has been constructed.
initialize()
{
if (this._initialized)
throw new Error("App controller is already initialized.");
this._initialized = true;
// FIXME: eventually all code within WI.loaded should be distributed elsewhere.
WI.loaded();
}
isWebDebuggable()
{
return this.debuggableType === WI.DebuggableType.Page
|| this.debuggableType === WI.DebuggableType.WebPage;
}
};
@@ -0,0 +1,274 @@
/*
* Copyright (C) 2013 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.
*/
// FIXME: ApplicationCacheManager lacks advanced multi-target support. (ApplciationCache objects per-target)
WI.ApplicationCacheManager = class ApplicationCacheManager extends WI.Object
{
constructor()
{
super();
this._enabled = false;
this._reset();
}
// Agent
get domains() { return ["ApplicationCache"]; }
activateExtraDomain(domain)
{
// COMPATIBILITY (iOS 14.0): Inspector.activateExtraDomains was removed in favor of a declared debuggable type
console.assert(domain === "ApplicationCache");
for (let target of WI.targets)
this.initializeTarget(target);
}
// Target
initializeTarget(target)
{
if (!this._enabled)
return;
if (target.hasDomain("ApplicationCache")) {
target.ApplicationCacheAgent.enable();
target.ApplicationCacheAgent.getFramesWithManifests(this._framesWithManifestsLoaded.bind(this));
}
}
// Public
get online() { return this._online; }
get applicationCacheObjects()
{
var applicationCacheObjects = [];
for (var id in this._applicationCacheObjects)
applicationCacheObjects.push(this._applicationCacheObjects[id]);
return applicationCacheObjects;
}
enable()
{
console.assert(!this._enabled);
this._enabled = true;
this._reset();
for (let target of WI.targets)
this.initializeTarget(target);
WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
WI.Frame.addEventListener(WI.Frame.Event.ChildFrameWasRemoved, this._childFrameWasRemoved, this);
}
disable()
{
console.assert(this._enabled);
this._enabled = false;
for (let target of WI.targets) {
// COMPATIBILITY (iOS 13): ApplicationCache.disable did not exist yet.
if (target.hasCommand("ApplicationCache.disable"))
target.ApplicationCacheAgent.disable();
}
WI.Frame.removeEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
WI.Frame.removeEventListener(WI.Frame.Event.ChildFrameWasRemoved, this._childFrameWasRemoved, this);
this._reset();
}
requestApplicationCache(frame, callback)
{
console.assert(this._enabled);
function callbackWrapper(error, applicationCache)
{
if (error) {
callback(null);
return;
}
callback(applicationCache);
}
let target = WI.assumingMainTarget();
target.ApplicationCacheAgent.getApplicationCacheForFrame(frame.id, callbackWrapper);
}
// ApplicationCacheObserver
networkStateUpdated(isNowOnline)
{
console.assert(this._enabled);
this._online = isNowOnline;
this.dispatchEventToListeners(WI.ApplicationCacheManager.Event.NetworkStateUpdated, {online: this._online});
}
applicationCacheStatusUpdated(frameId, manifestURL, status)
{
console.assert(this._enabled);
let frame = WI.networkManager.frameForIdentifier(frameId);
if (!frame)
return;
this._frameManifestUpdated(frame, manifestURL, status);
}
// Private
_reset()
{
this._online = true;
this._applicationCacheObjects = {};
this.dispatchEventToListeners(WI.ApplicationCacheManager.Event.Cleared);
}
_mainResourceDidChange(event)
{
console.assert(event.target instanceof WI.Frame);
if (event.target.isMainFrame()) {
this._reset();
return;
}
let target = WI.assumingMainTarget();
if (target.hasDomain("ApplicationCache"))
target.ApplicationCacheAgent.getManifestForFrame(event.target.id, this._manifestForFrameLoaded.bind(this, event.target.id));
}
_childFrameWasRemoved(event)
{
this._frameManifestRemoved(event.data.childFrame);
}
_manifestForFrameLoaded(frameId, error, manifestURL)
{
if (!this._enabled)
return;
var frame = WI.networkManager.frameForIdentifier(frameId);
if (!frame)
return;
// A frame can go away between `ApplicationCache.getManifestForFrame` being called and the
// response being received.
if (error) {
WI.reportInternalError(error);
return;
}
if (!manifestURL)
this._frameManifestRemoved(frame);
}
_framesWithManifestsLoaded(error, framesWithManifests)
{
if (error) {
WI.reportInternalError(error);
return;
}
if (!this._enabled)
return;
for (var i = 0; i < framesWithManifests.length; ++i) {
var frame = WI.networkManager.frameForIdentifier(framesWithManifests[i].frameId);
if (!frame)
continue;
this._frameManifestUpdated(frame, framesWithManifests[i].manifestURL, framesWithManifests[i].status);
}
}
_frameManifestUpdated(frame, manifestURL, status)
{
if (status === WI.ApplicationCacheManager.Status.Uncached) {
this._frameManifestRemoved(frame);
return;
}
if (!manifestURL)
return;
var manifestFrame = this._applicationCacheObjects[frame.id];
if (manifestFrame && manifestURL !== manifestFrame.manifest.manifestURL)
this._frameManifestRemoved(frame);
var oldStatus = manifestFrame ? manifestFrame.status : -1;
var statusChanged = manifestFrame && status !== oldStatus;
if (manifestFrame)
manifestFrame.status = status;
if (!this._applicationCacheObjects[frame.id]) {
var cacheManifest = new WI.ApplicationCacheManifest(manifestURL);
this._applicationCacheObjects[frame.id] = new WI.ApplicationCacheFrame(frame, cacheManifest, status);
this.dispatchEventToListeners(WI.ApplicationCacheManager.Event.FrameManifestAdded, {frameManifest: this._applicationCacheObjects[frame.id]});
}
if (statusChanged)
this.dispatchEventToListeners(WI.ApplicationCacheManager.Event.FrameManifestStatusChanged, {frameManifest: this._applicationCacheObjects[frame.id]});
}
_frameManifestRemoved(frame)
{
if (!this._applicationCacheObjects[frame.id])
return;
delete this._applicationCacheObjects[frame.id];
this.dispatchEventToListeners(WI.ApplicationCacheManager.Event.FrameManifestRemoved, {frame});
}
};
WI.ApplicationCacheManager.Event = {
Cleared: "application-cache-manager-cleared",
FrameManifestAdded: "application-cache-manager-frame-manifest-added",
FrameManifestRemoved: "application-cache-manager-frame-manifest-removed",
FrameManifestStatusChanged: "application-cache-manager-frame-manifest-status-changed",
NetworkStateUpdated: "application-cache-manager-network-state-updated"
};
WI.ApplicationCacheManager.Status = {
Uncached: 0,
Idle: 1,
Checking: 2,
Downloading: 3,
UpdateReady: 4,
Obsolete: 5
};
+452
View File
@@ -0,0 +1,452 @@
/*
* 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.AuditManager = class AuditManager extends WI.Object
{
constructor()
{
super();
this._tests = [];
this._results = [];
this._runningState = WI.AuditManager.RunningState.Inactive;
this._runningTests = [];
this._disabledDefaultTestsSetting = new WI.Setting("audit-disabled-default-tests", []);
WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._handleFrameMainResourceDidChange, this);
}
// Static
static synthesizeWarning(message)
{
message = WI.UIString("Audit Warning: %s").format(message);
if (window.InspectorTest) {
console.warn(message);
return;
}
let consoleMessage = new WI.ConsoleMessage(WI.mainTarget, WI.ConsoleMessage.MessageSource.Other, WI.ConsoleMessage.MessageLevel.Warning, message);
consoleMessage.shouldRevealConsole = true;
WI.consoleLogViewController.appendConsoleMessage(consoleMessage);
}
static synthesizeError(message)
{
message = WI.UIString("Audit Error: %s").format(message);
if (window.InspectorTest) {
console.error(message);
return;
}
let consoleMessage = new WI.ConsoleMessage(WI.mainTarget, WI.ConsoleMessage.MessageSource.Other, WI.ConsoleMessage.MessageLevel.Error, message);
consoleMessage.shouldRevealConsole = true;
WI.consoleLogViewController.appendConsoleMessage(consoleMessage);
}
// Public
get tests() { return this._tests; }
get results() { return this._results; }
get runningState() { return this._runningState; }
get editing()
{
return this._runningState === WI.AuditManager.RunningState.Disabled;
}
set editing(editing)
{
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 = editing ? WI.AuditManager.RunningState.Disabled : WI.AuditManager.RunningState.Inactive;
console.assert(runningState !== this._runningState);
if (runningState === this._runningState)
return;
this._runningState = runningState;
this.dispatchEventToListeners(WI.AuditManager.Event.EditingChanged);
if (!this.editing) {
WI.objectStores.audits.clear();
let disabledDefaultTests = [];
let saveDisabledDefaultTest = (test) => {
if (test.supported && test.disabled)
disabledDefaultTests.push(test.name);
if (test instanceof WI.AuditTestGroup) {
for (let child of test.tests)
saveDisabledDefaultTest(child);
}
};
for (let test of this._tests) {
if (test.default)
saveDisabledDefaultTest(test);
else
WI.objectStores.audits.putObject(test);
}
this._disabledDefaultTestsSetting.value = disabledDefaultTests;
}
}
async start(tests)
{
console.assert(this._runningState === WI.AuditManager.RunningState.Inactive);
if (this._runningState !== WI.AuditManager.RunningState.Inactive)
return null;
if (tests && tests.length)
tests = tests.filter((test) => typeof test === "object" && test instanceof WI.AuditTestBase);
else
tests = this._tests;
console.assert(tests.length);
if (!tests.length)
return null;
let mainResource = WI.networkManager.mainFrame.mainResource;
this._runningState = WI.AuditManager.RunningState.Active;
this.dispatchEventToListeners(WI.AuditManager.Event.RunningStateChanged);
this._runningTests = tests;
for (let test of this._runningTests)
test.clearResult();
this.dispatchEventToListeners(WI.AuditManager.Event.TestScheduled);
let target = WI.assumingMainTarget();
await Promise.chain(this._runningTests.map((test) => async () => {
if (this._runningState !== WI.AuditManager.RunningState.Active)
return;
if (target.hasDomain("Audit"))
await target.AuditAgent.setup();
let topLevelTest = test.topLevelTest;
console.assert(topLevelTest || window.InspectorTest, "No matching top-level test found", test);
if (topLevelTest)
await topLevelTest.runSetup();
await test.start();
if (target.hasDomain("Audit"))
await target.AuditAgent.teardown();
}));
let result = this._runningTests.map((test) => test.result).filter((result) => !!result);
this._runningState = WI.AuditManager.RunningState.Inactive;
this.dispatchEventToListeners(WI.AuditManager.Event.RunningStateChanged);
this._runningTests = [];
this._addResult(result);
if (mainResource !== WI.networkManager.mainFrame.mainResource) {
// Navigated while tests were running.
for (let test of this._tests)
test.clearResult();
}
return this._results.lastValue === result ? result : null;
}
stop()
{
console.assert(this._runningState === WI.AuditManager.RunningState.Active);
if (this._runningState !== WI.AuditManager.RunningState.Active)
return;
this._runningState = WI.AuditManager.RunningState.Stopping;
this.dispatchEventToListeners(WI.AuditManager.Event.RunningStateChanged);
for (let test of this._runningTests)
test.stop();
}
async processJSON({json, error})
{
if (error) {
WI.AuditManager.synthesizeError(error);
return;
}
if (typeof json !== "object" || json === null) {
WI.AuditManager.synthesizeError(WI.UIString("invalid JSON"));
return;
}
if (json.type !== WI.AuditTestCase.TypeIdentifier && json.type !== WI.AuditTestGroup.TypeIdentifier
&& json.type !== WI.AuditTestCaseResult.TypeIdentifier && json.type !== WI.AuditTestGroupResult.TypeIdentifier) {
WI.AuditManager.synthesizeError(WI.UIString("unknown %s \u0022%s\u0022").format(WI.unlocalizedString("type"), json.type));
return;
}
let object = await WI.AuditTestGroup.fromPayload(json) || await WI.AuditTestCase.fromPayload(json) || await WI.AuditTestGroupResult.fromPayload(json) || await WI.AuditTestCaseResult.fromPayload(json);
if (!object)
return;
if (object instanceof WI.AuditTestBase)
this.addTest(object, {save: true});
else if (object instanceof WI.AuditTestResultBase)
this._addResult(object);
WI.showRepresentedObject(object);
}
export(saveMode, object)
{
console.assert(object instanceof WI.AuditTestCase || object instanceof WI.AuditTestGroup || object instanceof WI.AuditTestCaseResult || object instanceof WI.AuditTestGroupResult, object);
function dataForObject(object) {
return {
displayType: object instanceof WI.AuditTestResultBase ? WI.UIString("Result") : WI.UIString("Audit"),
content: JSON.stringify(object),
suggestedName: object.name + (object instanceof WI.AuditTestResultBase ? ".result" : ".audit"),
};
}
let data = [dataForObject(object)];
if (saveMode === WI.FileUtilities.SaveMode.FileVariants && object instanceof WI.AuditTestBase && object.result)
data.push(dataForObject(object.result));
WI.FileUtilities.save(saveMode, data);
}
loadStoredTests()
{
if (this._tests.length)
return;
this._addDefaultTests();
WI.objectStores.audits.getAll().then(async (tests) => {
for (let payload of tests) {
let test = await WI.AuditTestGroup.fromPayload(payload) || await WI.AuditTestCase.fromPayload(payload);
if (!test)
continue;
const key = null;
WI.objectStores.audits.associateObject(test, key, payload);
this.addTest(test);
}
});
}
addTest(test, {save} = {})
{
console.assert(test instanceof WI.AuditTestBase, test);
console.assert(!this._tests.includes(test), test);
this._tests.push(test);
if (save)
WI.objectStores.audits.putObject(test);
this.dispatchEventToListeners(WI.AuditManager.Event.TestAdded, {test});
}
removeTest(test)
{
console.assert(this.editing);
console.assert(test instanceof WI.AuditTestBase, test);
console.assert(this._tests.includes(test) || test.default, test);
if (test.default) {
test.clearResult();
if (test.disabled) {
InspectorFrontendHost.beep();
return;
}
test.disabled = true;
let disabledTests = this._disabledDefaultTestsSetting.value.slice();
disabledTests.push(test.name);
this._disabledDefaultTestsSetting.value = disabledTests;
return;
}
console.assert(test.editable, test);
this._tests.remove(test);
this.dispatchEventToListeners(WI.AuditManager.Event.TestRemoved, {test});
WI.objectStores.audits.deleteObject(test);
}
// Private
_addResult(result)
{
if (!result || (Array.isArray(result) && !result.length))
return;
this._results.push(result);
this.dispatchEventToListeners(WI.AuditManager.Event.TestCompleted, {
result,
index: this._results.length - 1,
});
}
_handleFrameMainResourceDidChange(event)
{
if (!event.target.isMainFrame())
return;
if (this._runningState === WI.AuditManager.RunningState.Active)
this.stop();
else {
for (let test of this._tests)
test.clearResult();
}
}
_addDefaultTests()
{
console.assert(WI.DefaultAudits, "Default audits not loaded.");
if (!WI.DefaultAudits)
return;
const defaultTests = [
new WI.AuditTestGroup(WI.UIString("Demo Audit"), [
new WI.AuditTestGroup(WI.UIString("Result Levels"), [
new WI.AuditTestCase("level-pass", WI.DefaultAudits.levelPass.toString(), {description: WI.UIString("This is what the result of a passing test with no data looks like.")}),
new WI.AuditTestCase("level-warn", WI.DefaultAudits.levelWarn.toString(), {description: WI.UIString("This is what the result of a warning test with no data looks like.")}),
new WI.AuditTestCase("level-fail", WI.DefaultAudits.levelFail.toString(), {description: WI.UIString("This is what the result of a failing test with no data looks like.")}),
new WI.AuditTestCase("level-error", WI.DefaultAudits.levelError.toString(), {description: WI.UIString("This is what the result of a test that threw an error with no data looks like.")}),
new WI.AuditTestCase("level-unsupported", WI.DefaultAudits.levelUnsupported.toString(), {description: WI.UIString("This is what the result of an unsupported test with no data looks like.")}),
], {description: WI.UIString("These are all of the different test result levels.")}),
new WI.AuditTestGroup(WI.UIString("Result Data"), [
new WI.AuditTestCase("data-domNodes", WI.DefaultAudits.dataDOMNodes.toString(), {description: WI.UIString("This is an example of how result DOM nodes are shown. It will pass with the <body> element.")}),
new WI.AuditTestCase("data-domAttributes", WI.DefaultAudits.dataDOMAttributes.toString(), {description: WI.UIString("This is an example of how result DOM attributes are highlighted on any returned DOM nodes. It will pass with all elements with an id attribute.")}),
new WI.AuditTestCase("data-errors", WI.DefaultAudits.dataErrors.toString(), {description: WI.UIString("This is an example of how errors are shown. The error was thrown manually, but execution errors will appear in the same way.")}),
new WI.AuditTestCase("data-custom", WI.DefaultAudits.dataCustom.toString(), {description: WI.UIString("This is an example of how custom result data is shown."), supports: 3}),
], {description: WI.UIString("These are example tests that demonstrate all of the different types of data that can be returned with the test result.")}),
new WI.AuditTestGroup(WI.UIString("Specially Exposed Data"), [
new WI.AuditTestGroup(WI.UIString("Accessibility"), [
new WI.AuditTestCase("getElementsByComputedRole", WI.DefaultAudits.getElementsByComputedRole.toString(), {description: WI.UIString("This is an example test that uses %s to find elements with a computed role of \u201Clink\u201D.").format(WI.unlocalizedString("WebInspectorAudit.Accessibility.getElementsByComputedRole")), supports: 1}),
new WI.AuditTestCase("getActiveDescendant", WI.DefaultAudits.getActiveDescendant.toString(), {description: WI.UIString("This is an example test that uses %s to find any element that meets criteria for active descendant (\u201C%s\u201D) of the <body> element, if it exists.").format(WI.unlocalizedString("WebInspectorAudit.Accessibility.getActiveDescendant"), WI.unlocalizedString("aria-activedescendant")), supports: 1}),
new WI.AuditTestCase("getChildNodes", WI.DefaultAudits.getChildNodes.toString(), {description: WI.UIString("This is an example test that uses %s to find child nodes of the <body> element in the accessibility tree.").format(WI.unlocalizedString("WebInspectorAudit.Accessibility.getChildNodes")), supports: 1}),
new WI.AuditTestCase("getComputedProperties", WI.DefaultAudits.getComputedProperties.toString(), {description: WI.UIString("This is an example test that uses %s to find a variety of accessibility information about the <body> element.").format(WI.unlocalizedString("WebInspectorAudit.Accessibility.getComputedProperties")), supports: 3}),
new WI.AuditTestCase("getControlledNodes", WI.DefaultAudits.getControlledNodes.toString(), {description: WI.UIString("This is an example test that uses %s to find all nodes controlled (\u201C%s\u201D) by the <body> element, if any exist.").format(WI.unlocalizedString("WebInspectorAudit.Accessibility.getControlledNodes"), WI.unlocalizedString("aria-controls")), supports: 1}),
new WI.AuditTestCase("getFlowedNodes", WI.DefaultAudits.getFlowedNodes.toString(), {description: WI.UIString("This is an example test that uses %s to find all nodes flowed to (\u201C%s\u201D) from the <body> element, if any exist.").format(WI.unlocalizedString("WebInspectorAudit.Accessibility.getFlowedNodes"), WI.unlocalizedString("aria-flowto")), supports: 1}),
new WI.AuditTestCase("getMouseEventNode", WI.DefaultAudits.getMouseEventNode.toString(), {description: WI.UIString("This is an example test that uses %s to find the node that would handle mouse events for the <body> element, if applicable.").format(WI.unlocalizedString("WebInspectorAudit.Accessibility.getMouseEventNode")), supports: 1}),
new WI.AuditTestCase("getOwnedNodes", WI.DefaultAudits.getOwnedNodes.toString(), {description: WI.UIString("This is an example test that uses %s to find all nodes owned (\u201C%s\u201D) by the <body> element, if any exist.").format(WI.unlocalizedString("WebInspectorAudit.Accessibility.getOwnedNodes"), WI.unlocalizedString("aria-owns")), supports: 1}),
new WI.AuditTestCase("getParentNode", WI.DefaultAudits.getParentNode.toString(), {description: WI.UIString("This is an example test that uses %s to find the parent node of the <body> element in the accessibility tree.").format(WI.unlocalizedString("WebInspectorAudit.Accessibility.getParentNode")), supports: 1}),
new WI.AuditTestCase("getSelectedChildNodes", WI.DefaultAudits.getSelectedChildNodes.toString(), {description: WI.UIString("This is an example test that uses %s to find all child nodes that are selected (\u201C%s\u201D) of the <body> element in the accessibility tree.").format(WI.unlocalizedString("WebInspectorAudit.Accessibility.getSelectedChildNodes"), WI.unlocalizedString("aria-selected")), supports: 1}),
], {description: WI.UIString("These are example tests that demonstrate how to use %s to get information about the accessibility tree.").format(WI.unlocalizedString("WebInspectorAudit.Accessibility")), supports: 1}),
new WI.AuditTestGroup(WI.UIString("DOM"), [
new WI.AuditTestCase("hasEventListeners", WI.DefaultAudits.hasEventListeners.toString(), {description: WI.UIString("This is an example test that uses %s to find data indicating whether the <body> element has any event listeners.").format(WI.unlocalizedString("WebInspectorAudit.Accessibility.hasEventListeners")), supports: 3}),
new WI.AuditTestCase("hasEventListeners-click", WI.DefaultAudits.hasEventListenersClick.toString(), {description: WI.UIString("This is an example test that uses %s to find data indicating whether the <body> element has any click event listeners.").format(WI.unlocalizedString("WebInspectorAudit.Accessibility.hasEventListenersClick")), supports: 3}),
], {description: WI.UIString("These are example tests that demonstrate how to use %s to get information about DOM nodes.").format(WI.unlocalizedString("WebInspectorAudit.DOM")), supports: 1}),
new WI.AuditTestGroup(WI.UIString("Resources"), [
new WI.AuditTestCase("getResources", WI.DefaultAudits.getResources.toString(), {description: WI.UIString("This is an example test that uses %s to find basic information about each resource.").format(WI.unlocalizedString("WebInspectorAudit.Accessibility.getResources")), supports: 3}),
new WI.AuditTestCase("getResourceContent", WI.DefaultAudits.getResourceContent.toString(), {description: WI.UIString("This is an example test that uses %s to find the contents of the main resource.").format(WI.unlocalizedString("WebInspectorAudit.Accessibility.getResourceContent")), supports: 3}),
], {description: WI.UIString("These are example tests that demonstrate how to use %s to get information about loaded resources.").format(WI.unlocalizedString("WebInspectorAudit.Resources")), supports: 2}),
], {description: WI.UIString("These are example tests that demonstrate how to use %s to access information not normally available to JavaScript.").format(WI.unlocalizedString("WebInspectorAudit")), supports: 1}),
new WI.AuditTestCase("unsupported", WI.DefaultAudits.unsupported.toString(), {description: WI.UIString("This is an example of a test that will not run because it is unsupported."), supports: Infinity}),
], {description: WI.UIString("These are example tests that demonstrate the functionality and structure of audits.")}),
new WI.AuditTestGroup(WI.UIString("Accessibility"), [
new WI.AuditTestCase("testMenuRoleForRequiredChildren", WI.DefaultAudits.testMenuRoleForRequiredChildren.toString(), {description: WI.UIString("Ensure that elements of role \u201C%s\u201D and \u201C%s\u201D have required owned elements in accordance with WAI-ARIA.").format(WI.unlocalizedString("menu"), WI.unlocalizedString("menubar")), supports: 1}),
new WI.AuditTestCase("testGridRoleForRequiredChildren", WI.DefaultAudits.testGridRoleForRequiredChildren.toString(), {description: WI.UIString("Ensure that elements of role \u201C%s\u201D have required owned elements in accordance with WAI-ARIA.").format(WI.unlocalizedString("grid")), supports: 1}),
new WI.AuditTestCase("testForAriaLabelledBySpelling", WI.DefaultAudits.testForAriaLabelledBySpelling.toString(), {description: WI.UIString("Ensure that \u201C%s\u201D is spelled correctly.").format(WI.unlocalizedString("aria-labelledby")), supports: 1}),
new WI.AuditTestCase("testForMultipleBanners", WI.DefaultAudits.testForMultipleBanners.toString(), {description: WI.UIString("Ensure that only one banner is used on the page."), supports: 1}),
new WI.AuditTestCase("testForLinkLabels", WI.DefaultAudits.testForLinkLabels.toString(), {description: WI.UIString("Ensure that links have accessible labels for assistive technology."), supports: 1}),
new WI.AuditTestCase("testRowGroupRoleForRequiredChildren", WI.DefaultAudits.testRowGroupRoleForRequiredChildren.toString(), {description: WI.UIString("Ensure that elements of role \u201C%s\u201D have required owned elements in accordance with WAI-ARIA.").format(WI.unlocalizedString("rowgroup")), supports: 1}),
new WI.AuditTestCase("testTableRoleForRequiredChildren", WI.DefaultAudits.testTableRoleForRequiredChildren.toString(), {description: WI.UIString("Ensure that elements of role \u201C%s\u201D have required owned elements in accordance with WAI-ARIA.").format(WI.unlocalizedString("table")), supports: 1}),
new WI.AuditTestCase("testForMultipleLiveRegions", WI.DefaultAudits.testForMultipleLiveRegions.toString(), {description: WI.UIString("Ensure that only one live region is used on the page."), supports: 1}),
new WI.AuditTestCase("testListBoxRoleForRequiredChildren", WI.DefaultAudits.testListBoxRoleForRequiredChildren.toString(), {description: WI.UIString("Ensure that elements of role \u201C%s\u201D have required owned elements in accordance with WAI-ARIA.").format(WI.unlocalizedString("listbox")), supports: 1}),
new WI.AuditTestCase("testImageLabels", WI.DefaultAudits.testImageLabels.toString(), {description: WI.UIString("Ensure that elements of role \u201C%s\u201D have accessible labels for assistive technology.").format(WI.unlocalizedString("img")), supports: 1}),
new WI.AuditTestCase("testForAriaHiddenFalse", WI.DefaultAudits.testForAriaHiddenFalse.toString(), {description: WI.UIString("Ensure aria-hidden=\u0022%s\u0022 is not used.").format(WI.unlocalizedString("false")), supports: 1}),
new WI.AuditTestCase("testTreeRoleForRequiredChildren", WI.DefaultAudits.testTreeRoleForRequiredChildren.toString(), {description: WI.UIString("Ensure that elements of role \u201C%s\u201D have required owned elements in accordance with WAI-ARIA.").format(WI.unlocalizedString("tree")), supports: 1}),
new WI.AuditTestCase("testRadioGroupRoleForRequiredChildren", WI.DefaultAudits.testRadioGroupRoleForRequiredChildren.toString(), {description: WI.UIString("Ensure that elements of role \u201C%s\u201D have required owned elements in accordance with WAI-ARIA.").format(WI.unlocalizedString("radiogroup")), supports: 1}),
new WI.AuditTestCase("testFeedRoleForRequiredChildren", WI.DefaultAudits.testFeedRoleForRequiredChildren.toString(), {description: WI.UIString("Ensure that elements of role \u201C%s\u201D have required owned elements in accordance with WAI-ARIA.").format(WI.unlocalizedString("feed")), supports: 1}),
new WI.AuditTestCase("testTabListRoleForRequiredChildren", WI.DefaultAudits.testTabListRoleForRequiredChildren.toString(), {description: WI.UIString("Ensure that elements of role \u201C%s\u201D have required owned elements in accordance with WAI-ARIA.").format(WI.unlocalizedString("tablist")), supports: 1}),
new WI.AuditTestCase("testButtonLabels", WI.DefaultAudits.testButtonLabels.toString(), {description: WI.UIString("Ensure that buttons have accessible labels for assistive technology."), supports: 1}),
new WI.AuditTestCase("testRowRoleForRequiredChildren", WI.DefaultAudits.testRowRoleForRequiredChildren.toString(), {description: WI.UIString("Ensure that elements of role \u201C%s\u201D have required owned elements in accordance with WAI-ARIA.").format(WI.unlocalizedString("row")), supports: 1}),
new WI.AuditTestCase("testListRoleForRequiredChildren", WI.DefaultAudits.testListRoleForRequiredChildren.toString(), {description: WI.UIString("Ensure that elements of role \u201C%s\u201D have required owned elements in accordance with WAI-ARIA.").format(WI.unlocalizedString("list")), supports: 1}),
new WI.AuditTestCase("testComboBoxRoleForRequiredChildren", WI.DefaultAudits.testComboBoxRoleForRequiredChildren.toString(), {description: WI.UIString("Ensure that elements of role \u201C%s\u201D have required owned elements in accordance with WAI-ARIA.").format(WI.unlocalizedString("combobox")), supports: 1}),
new WI.AuditTestCase("testForMultipleMainContentSections", WI.DefaultAudits.testForMultipleMainContentSections.toString(), {description: WI.UIString("Ensure that only one main content section is used on the page."), supports: 1}),
new WI.AuditTestCase("testDialogsForLabels", WI.DefaultAudits.testDialogsForLabels.toString(), {description: WI.UIString("Ensure that dialogs have accessible labels for assistive technology."), supports: 1}),
new WI.AuditTestCase("testForInvalidAriaHiddenValue", WI.DefaultAudits.testForInvalidAriaHiddenValue.toString(), {description: WI.UIString("Ensure that values for \u201C%s\u201D are valid.").format(WI.unlocalizedString("aria-hidden")), supports: 1})
], {description: WI.UIString("Diagnoses common accessibility problems affecting screen readers and other assistive technology.")}),
];
let checkDisabledDefaultTest = (test) => {
test.markAsDefault();
if (this._disabledDefaultTestsSetting.value.includes(test.name))
test.disabled = true;
if (test instanceof WI.AuditTestGroup) {
for (let child of test.tests)
checkDisabledDefaultTest(child);
}
};
for (let test of defaultTests) {
checkDisabledDefaultTest(test);
this.addTest(test);
}
}
};
WI.AuditManager.RunningState = {
Disabled: "disabled",
Inactive: "inactive",
Active: "active",
Stopping: "stopping",
};
WI.AuditManager.Event = {
EditingChanged: "audit-manager-editing-changed",
RunningStateChanged: "audit-manager-running-state-changed",
TestAdded: "audit-manager-test-added",
TestCompleted: "audit-manager-test-completed",
TestRemoved: "audit-manager-test-removed",
TestScheduled: "audit-manager-test-scheduled",
};
@@ -0,0 +1,168 @@
/*
* Copyright (C) 2015 Apple Inc. All rights reserved.
* Copyright (C) 2015 Saam Barati <saambarati1@gmail.com>
*
* 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.BasicBlockAnnotator = class BasicBlockAnnotator extends WI.Annotator
{
constructor(sourceCodeTextEditor, script)
{
super(sourceCodeTextEditor);
this._script = script;
this._basicBlockMarkers = new Map; // Only contains unexecuted basic blocks.
}
// Protected
clearAnnotations()
{
for (var key of this._basicBlockMarkers.keys())
this._clearRangeForBasicBlockMarker(key);
}
insertAnnotations()
{
if (!this.isActive())
return;
this._script.requestContent().then(this._annotateBasicBlockExecutionRanges.bind(this));
}
// Private
_calculateBasicBlockPositions(basicBlocks, content)
{
if (!basicBlocks || !basicBlocks.length)
return;
let lineEndings = [];
let lineEndingLengths = [];
let pattern = /\r\n?|\n/g;
let match = pattern.exec(content);
while (match) {
lineEndings.push(match.index);
lineEndingLengths.push(match[0].length);
match = pattern.exec(content);
}
function offsetToPosition(offset) {
let lineNumber = lineEndings.upperBound(offset - 1);
if (lineNumber) {
let previousLine = lineNumber - 1;
var columnNumber = offset - lineEndings[previousLine] - lineEndingLengths[previousLine];
} else
var columnNumber = offset;
return new WI.SourceCodePosition(lineNumber, columnNumber);
}
for (let block of basicBlocks) {
block.startPosition = offsetToPosition(block.startOffset);
block.endPosition = offsetToPosition(block.endOffset);
}
}
_annotateBasicBlockExecutionRanges()
{
let content = this._script.content;
console.assert(content, "Missing script content for basic block annotations.");
if (!content)
return;
var sourceID = this._script.id;
var startTime = Date.now();
this._script.target.RuntimeAgent.getBasicBlocks(sourceID, function(error, basicBlocks) {
if (error) {
console.error("Error in getting basic block locations: " + error);
return;
}
if (!this.isActive())
return;
this._calculateBasicBlockPositions(basicBlocks, content);
let {startPosition, endPosition} = this.sourceCodeTextEditor.visibleRangePositions();
basicBlocks = basicBlocks.filter(function(block) {
// Viewport: [--]
// Block: [--]
if (block.startPosition.isAfter(endPosition))
return false;
// Viewport: [--]
// Block: [--]
if (block.endPosition.isBefore(startPosition))
return false;
return true;
});
for (var block of basicBlocks) {
var key = block.startOffset + ":" + block.endOffset;
var hasKey = this._basicBlockMarkers.has(key);
var hasExecuted = block.hasExecuted;
if (hasKey && hasExecuted)
this._clearRangeForBasicBlockMarker(key);
else if (!hasKey && !hasExecuted) {
var marker = this._highlightTextForBasicBlock(block);
this._basicBlockMarkers.set(key, marker);
}
}
var totalTime = Date.now() - startTime;
var timeoutTime = Number.constrain(30 * totalTime, 500, 5000);
this._timeoutIdentifier = setTimeout(this.insertAnnotations.bind(this), timeoutTime);
}.bind(this));
}
_highlightTextForBasicBlock(basicBlock)
{
console.assert(basicBlock.startOffset <= basicBlock.endOffset && basicBlock.startOffset >= 0 && basicBlock.endOffset >= 0, "" + basicBlock.startOffset + ":" + basicBlock.endOffset);
console.assert(!basicBlock.hasExecuted);
let startPosition = this.sourceCodeTextEditor.originalPositionToCurrentPosition(basicBlock.startPosition);
let endPosition = this.sourceCodeTextEditor.originalPositionToCurrentPosition(basicBlock.endPosition);
if (this._isTextRangeOnlyClosingBrace(startPosition, endPosition))
return null;
return this.sourceCodeTextEditor.addStyleToTextRange(startPosition, endPosition, WI.BasicBlockAnnotator.HasNotExecutedClassName);
}
_isTextRangeOnlyClosingBrace(startPosition, endPosition)
{
var isOnlyClosingBrace = /^\s*\}$/;
return isOnlyClosingBrace.test(this.sourceCodeTextEditor.getTextInRange(startPosition, endPosition));
}
_clearRangeForBasicBlockMarker(key)
{
console.assert(this._basicBlockMarkers.has(key));
var marker = this._basicBlockMarkers.get(key);
if (marker)
marker.clear();
this._basicBlockMarkers.delete(key);
}
};
WI.BasicBlockAnnotator.HasNotExecutedClassName = "basic-block-has-not-executed";
+110
View File
@@ -0,0 +1,110 @@
/*
* Copyright (C) 2013 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.BranchManager = class BranchManager extends WI.Object
{
constructor()
{
super();
WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
this.initialize();
}
// Public
initialize()
{
this._originalBranch = new WI.Branch(WI.UIString("Original"), null, true);
this._currentBranch = this._originalBranch.fork(WI.UIString("Working Copy"));
this._branches = [this._originalBranch, this._currentBranch];
}
get branches()
{
return this._branches;
}
get currentBranch()
{
return this._currentBranch;
}
set currentBranch(branch)
{
console.assert(branch instanceof WI.Branch);
if (!(branch instanceof WI.Branch))
return;
this._currentBranch.revert();
this._currentBranch = branch;
this._currentBranch.apply();
}
createBranch(displayName, fromBranch)
{
if (!fromBranch)
fromBranch = this._originalBranch;
console.assert(fromBranch instanceof WI.Branch);
if (!(fromBranch instanceof WI.Branch))
return null;
var newBranch = fromBranch.fork(displayName);
this._branches.push(newBranch);
return newBranch;
}
deleteBranch(branch)
{
console.assert(branch instanceof WI.Branch);
if (!(branch instanceof WI.Branch))
return;
console.assert(branch !== this._originalBranch);
if (branch === this._originalBranch)
return;
this._branches.remove(branch);
if (branch === this._currentBranch)
this._currentBranch = this._originalBranch;
}
// Private
_mainResourceDidChange(event)
{
console.assert(event.target instanceof WI.Frame);
if (!event.target.isMainFrame())
return;
this.initialize();
}
};
@@ -0,0 +1,197 @@
/*
* 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.
*/
WI.BreakpointLogMessageLexer = class BreakpointLogMessageLexer extends WI.Object
{
constructor()
{
super();
this._stateFunctions = {
[WI.BreakpointLogMessageLexer.State.Expression]: this._expression,
[WI.BreakpointLogMessageLexer.State.PlainText]: this._plainText,
[WI.BreakpointLogMessageLexer.State.PossiblePlaceholder]: this._possiblePlaceholder,
[WI.BreakpointLogMessageLexer.State.RegExpOrStringLiteral]: this._regExpOrStringLiteral,
};
this.reset();
}
// Public
tokenize(input)
{
this.reset();
this._input = input;
while (this._index < this._input.length) {
let stateFunction = this._stateFunctions[this._states.lastValue];
console.assert(stateFunction);
if (!stateFunction) {
this.reset();
return null;
}
stateFunction.call(this);
}
// Needed for trailing plain text.
this._finishPlainText();
return this._tokens;
}
reset()
{
this._input = "";
this._buffer = "";
this._index = 0;
this._states = [WI.BreakpointLogMessageLexer.State.PlainText];
this._literalStartCharacter = "";
this._curlyBraceDepth = 0;
this._tokens = [];
}
// Private
_finishPlainText()
{
this._appendToken(WI.BreakpointLogMessageLexer.TokenType.PlainText);
}
_finishExpression()
{
this._appendToken(WI.BreakpointLogMessageLexer.TokenType.Expression);
}
_appendToken(type)
{
if (!this._buffer)
return;
this._tokens.push({type, data: this._buffer});
this._buffer = "";
}
_consume()
{
console.assert(this._index < this._input.length);
let character = this._peek();
this._index++;
return character;
}
_peek()
{
return this._input[this._index] || null;
}
// States
_expression()
{
let character = this._consume();
if (character === "}") {
if (this._curlyBraceDepth === 0) {
this._finishExpression();
console.assert(this._states.lastValue === WI.BreakpointLogMessageLexer.State.Expression);
this._states.pop();
return;
}
this._curlyBraceDepth--;
}
this._buffer += character;
if (character === "/" || character === "\"" || character === "'") {
this._literalStartCharacter = character;
this._states.push(WI.BreakpointLogMessageLexer.State.RegExpOrStringLiteral);
} else if (character === "{")
this._curlyBraceDepth++;
}
_plainText()
{
let character = this._peek();
if (character === "$")
this._states.push(WI.BreakpointLogMessageLexer.State.PossiblePlaceholder);
else {
this._buffer += character;
this._consume();
}
}
_possiblePlaceholder()
{
let character = this._consume();
console.assert(character === "$");
let nextCharacter = this._peek();
console.assert(this._states.lastValue === WI.BreakpointLogMessageLexer.State.PossiblePlaceholder);
this._states.pop();
if (nextCharacter === "{") {
this._finishPlainText();
this._consume();
this._states.push(WI.BreakpointLogMessageLexer.State.Expression);
} else
this._buffer += character;
}
_regExpOrStringLiteral()
{
let character = this._consume();
this._buffer += character;
if (character === "\\") {
if (this._peek() !== null)
this._buffer += this._consume();
return;
}
if (character === this._literalStartCharacter) {
console.assert(this._states.lastValue === WI.BreakpointLogMessageLexer.State.RegExpOrStringLiteral);
this._states.pop();
}
}
};
WI.BreakpointLogMessageLexer.State = {
Expression: Symbol("expression"),
PlainText: Symbol("plain-text"),
PossiblePlaceholder: Symbol("possible-placeholder"),
RegExpOrStringLiteral: Symbol("regexp-or-string-literal"),
};
WI.BreakpointLogMessageLexer.TokenType = {
PlainText: "token-type-plain-text",
Expression: "token-type-expression",
};
@@ -0,0 +1,381 @@
/*
* Copyright (C) 2015 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.BreakpointPopoverController = class BreakpointPopoverController extends WI.Object
{
constructor()
{
super();
this._breakpoint = null;
this._popover = null;
this._popoverContentElement = null;
}
// Public
appendContextMenuItems(contextMenu, breakpoint, breakpointDisplayElement)
{
console.assert(document.body.contains(breakpointDisplayElement), "Breakpoint popover display element must be in the DOM.");
const editBreakpoint = () => {
console.assert(!this._popover, "Breakpoint popover already exists.");
if (this._popover)
return;
this._createPopoverContent(breakpoint);
this._popover = new WI.Popover(this);
this._popover.content = this._popoverContentElement;
let bounds = WI.Rect.rectFromClientRect(breakpointDisplayElement.getBoundingClientRect());
bounds.origin.x -= 1; // Move the anchor left one pixel so it looks more centered.
this._popover.present(bounds.pad(2), [WI.RectEdge.MAX_Y]);
};
const removeBreakpoint = () => {
WI.debuggerManager.removeBreakpoint(breakpoint);
};
const toggleBreakpoint = () => {
breakpoint.disabled = !breakpoint.disabled;
};
const toggleAutoContinue = () => {
breakpoint.autoContinue = !breakpoint.autoContinue;
};
const revealOriginalSourceCodeLocation = () => {
const options = {
ignoreNetworkTab: true,
ignoreSearchTab: true,
};
WI.showOriginalOrFormattedSourceCodeLocation(breakpoint.sourceCodeLocation, options);
};
if (WI.debuggerManager.isBreakpointEditable(breakpoint))
contextMenu.appendItem(WI.UIString("Edit Breakpoint\u2026"), editBreakpoint);
if (breakpoint.autoContinue && !breakpoint.disabled) {
contextMenu.appendItem(WI.UIString("Disable Breakpoint"), toggleBreakpoint);
contextMenu.appendItem(WI.UIString("Cancel Automatic Continue"), toggleAutoContinue);
} else if (!breakpoint.disabled)
contextMenu.appendItem(WI.UIString("Disable Breakpoint"), toggleBreakpoint);
else
contextMenu.appendItem(WI.UIString("Enable Breakpoint"), toggleBreakpoint);
if (!breakpoint.autoContinue && !breakpoint.disabled && breakpoint.actions.length)
contextMenu.appendItem(WI.UIString("Set to Automatically Continue"), toggleAutoContinue);
if (WI.debuggerManager.isBreakpointRemovable(breakpoint))
contextMenu.appendItem(WI.UIString("Delete Breakpoint"), removeBreakpoint);
if (breakpoint._sourceCodeLocation.hasMappedLocation()) {
contextMenu.appendSeparator();
contextMenu.appendItem(WI.UIString("Reveal in Original Resource"), revealOriginalSourceCodeLocation);
}
}
// CodeMirrorCompletionController delegate
completionControllerShouldAllowEscapeCompletion()
{
return false;
}
// Private
_createPopoverContent(breakpoint)
{
console.assert(!this._popoverContentElement, "Popover content element already exists.");
if (this._popoverContentElement)
return;
this._breakpoint = breakpoint;
this._popoverContentElement = document.createElement("div");
this._popoverContentElement.className = "edit-breakpoint-popover-content";
let checkboxElement = document.createElement("input");
checkboxElement.type = "checkbox";
checkboxElement.checked = !this._breakpoint.disabled;
checkboxElement.addEventListener("change", this._popoverToggleEnabledCheckboxChanged.bind(this));
let checkboxLabel = document.createElement("label");
checkboxLabel.className = "toggle";
checkboxLabel.appendChild(checkboxElement);
checkboxLabel.append(this._breakpoint.sourceCodeLocation.displayLocationString());
let table = document.createElement("table");
let conditionRow = table.appendChild(document.createElement("tr"));
let conditionHeader = conditionRow.appendChild(document.createElement("th"));
let conditionData = conditionRow.appendChild(document.createElement("td"));
let conditionLabel = conditionHeader.appendChild(document.createElement("label"));
conditionLabel.textContent = WI.UIString("Condition");
let conditionEditorElement = conditionData.appendChild(document.createElement("div"));
conditionEditorElement.classList.add("edit-breakpoint-popover-condition", WI.SyntaxHighlightedStyleClassName);
this._conditionCodeMirror = WI.CodeMirrorEditor.create(conditionEditorElement, {
extraKeys: {Tab: false},
lineWrapping: false,
mode: "text/javascript",
matchBrackets: true,
placeholder: WI.UIString("Conditional expression"),
scrollbarStyle: null,
value: this._breakpoint.condition || "",
});
let conditionCodeMirrorInputField = this._conditionCodeMirror.getInputField();
conditionCodeMirrorInputField.id = "codemirror-condition-input-field";
conditionLabel.setAttribute("for", conditionCodeMirrorInputField.id);
this._conditionCodeMirrorEscapeOrEnterKeyHandler = this._conditionCodeMirrorEscapeOrEnterKey.bind(this);
this._conditionCodeMirror.addKeyMap({
"Esc": this._conditionCodeMirrorEscapeOrEnterKeyHandler,
"Enter": this._conditionCodeMirrorEscapeOrEnterKeyHandler,
});
this._conditionCodeMirror.on("change", this._conditionCodeMirrorChanged.bind(this));
this._conditionCodeMirror.on("beforeChange", this._conditionCodeMirrorBeforeChange.bind(this));
let completionController = new WI.CodeMirrorCompletionController(this._conditionCodeMirror, this);
completionController.addExtendedCompletionProvider("javascript", WI.javaScriptRuntimeCompletionProvider);
// CodeMirror needs a refresh after the popover displays, to layout, otherwise it doesn't appear.
setTimeout(() => {
this._conditionCodeMirror.refresh();
this._conditionCodeMirror.focus();
}, 0);
// COMPATIBILITY (iOS 9): Legacy backends don't support breakpoint ignore count. Since support
// can't be tested directly, check for CSS.getSupportedSystemFontFamilyNames.
// FIXME: Use explicit version checking once https://webkit.org/b/148680 is fixed.
if (InspectorBackend.domains.CSS.getSupportedSystemFontFamilyNames) {
let ignoreCountRow = table.appendChild(document.createElement("tr"));
let ignoreCountHeader = ignoreCountRow.appendChild(document.createElement("th"));
let ignoreCountLabel = ignoreCountHeader.appendChild(document.createElement("label"));
let ignoreCountData = ignoreCountRow.appendChild(document.createElement("td"));
this._ignoreCountInput = ignoreCountData.appendChild(document.createElement("input"));
this._ignoreCountInput.id = "edit-breakpoint-popover-ignore";
this._ignoreCountInput.type = "number";
this._ignoreCountInput.min = 0;
this._ignoreCountInput.value = 0;
this._ignoreCountInput.addEventListener("change", this._popoverIgnoreInputChanged.bind(this));
ignoreCountLabel.setAttribute("for", this._ignoreCountInput.id);
ignoreCountLabel.textContent = WI.UIString("Ignore");
this._ignoreCountText = ignoreCountData.appendChild(document.createElement("span"));
this._updateIgnoreCountText();
}
let actionRow = table.appendChild(document.createElement("tr"));
let actionHeader = actionRow.appendChild(document.createElement("th"));
let actionData = this._actionsContainer = actionRow.appendChild(document.createElement("td"));
let actionLabel = actionHeader.appendChild(document.createElement("label"));
actionLabel.textContent = WI.UIString("Action");
if (!this._breakpoint.actions.length)
this._popoverActionsCreateAddActionButton();
else {
this._popoverContentElement.classList.add(WI.BreakpointPopoverController.WidePopoverClassName);
for (let i = 0; i < this._breakpoint.actions.length; ++i) {
let breakpointActionView = new WI.BreakpointActionView(this._breakpoint.actions[i], this, true);
this._popoverActionsInsertBreakpointActionView(breakpointActionView, i);
}
}
let optionsRow = this._popoverOptionsRowElement = table.appendChild(document.createElement("tr"));
if (!this._breakpoint.actions.length)
optionsRow.classList.add(WI.BreakpointPopoverController.HiddenStyleClassName);
let optionsHeader = optionsRow.appendChild(document.createElement("th"));
let optionsData = optionsRow.appendChild(document.createElement("td"));
let optionsLabel = optionsHeader.appendChild(document.createElement("label"));
let optionsCheckbox = this._popoverOptionsCheckboxElement = optionsData.appendChild(document.createElement("input"));
let optionsCheckboxLabel = optionsData.appendChild(document.createElement("label"));
optionsCheckbox.id = "edit-breakpoint-popover-auto-continue";
optionsCheckbox.type = "checkbox";
optionsCheckbox.checked = this._breakpoint.autoContinue;
optionsCheckbox.addEventListener("change", this._popoverToggleAutoContinueCheckboxChanged.bind(this));
optionsLabel.textContent = WI.UIString("Options");
optionsCheckboxLabel.setAttribute("for", optionsCheckbox.id);
optionsCheckboxLabel.textContent = WI.UIString("Automatically continue after evaluating");
this._popoverContentElement.appendChild(checkboxLabel);
this._popoverContentElement.appendChild(table);
}
_popoverToggleEnabledCheckboxChanged(event)
{
this._breakpoint.disabled = !event.target.checked;
}
_conditionCodeMirrorChanged(codeMirror, change)
{
this._breakpoint.condition = (codeMirror.getValue() || "").trim();
}
_conditionCodeMirrorBeforeChange(codeMirror, change)
{
if (change.update) {
let newText = change.text.join("").replace(/\n/g, "");
change.update(change.from, change.to, [newText]);
}
return true;
}
_conditionCodeMirrorEscapeOrEnterKey()
{
if (!this._popover)
return;
this._popover.dismiss();
}
_popoverIgnoreInputChanged(event)
{
let ignoreCount = 0;
if (event.target.value) {
ignoreCount = parseInt(event.target.value, 10);
if (isNaN(ignoreCount) || ignoreCount < 0)
ignoreCount = 0;
}
this._ignoreCountInput.value = ignoreCount;
this._breakpoint.ignoreCount = ignoreCount;
this._updateIgnoreCountText();
}
_popoverToggleAutoContinueCheckboxChanged(event)
{
this._breakpoint.autoContinue = event.target.checked;
}
_popoverActionsCreateAddActionButton()
{
this._popoverContentElement.classList.remove(WI.BreakpointPopoverController.WidePopoverClassName);
this._actionsContainer.removeChildren();
let addActionButton = this._actionsContainer.appendChild(document.createElement("button"));
addActionButton.textContent = WI.UIString("Add Action");
addActionButton.addEventListener("click", this._popoverActionsAddActionButtonClicked.bind(this));
}
_popoverActionsAddActionButtonClicked(event)
{
this._popoverContentElement.classList.add(WI.BreakpointPopoverController.WidePopoverClassName);
this._actionsContainer.removeChildren();
let newAction = this._breakpoint.createAction(WI.BreakpointAction.Type.Log);
let newBreakpointActionView = new WI.BreakpointActionView(newAction, this);
this._popoverActionsInsertBreakpointActionView(newBreakpointActionView, -1);
this._popoverOptionsRowElement.classList.remove(WI.BreakpointPopoverController.HiddenStyleClassName);
this._popover.update();
}
_popoverActionsInsertBreakpointActionView(breakpointActionView, index)
{
if (index === -1)
this._actionsContainer.appendChild(breakpointActionView.element);
else {
let nextElement = this._actionsContainer.children[index + 1] || null;
this._actionsContainer.insertBefore(breakpointActionView.element, nextElement);
}
}
_updateIgnoreCountText()
{
if (this._breakpoint.ignoreCount === 1)
this._ignoreCountText.textContent = WI.UIString("time before stopping");
else
this._ignoreCountText.textContent = WI.UIString("times before stopping");
}
breakpointActionViewAppendActionView(breakpointActionView, newAction)
{
let newBreakpointActionView = new WI.BreakpointActionView(newAction, this);
let index = 0;
let children = this._actionsContainer.children;
for (let i = 0; children.length; ++i) {
if (children[i] === breakpointActionView.element) {
index = i;
break;
}
}
this._popoverActionsInsertBreakpointActionView(newBreakpointActionView, index);
this._popoverOptionsRowElement.classList.remove(WI.BreakpointPopoverController.HiddenStyleClassName);
this._popover.update();
}
breakpointActionViewRemoveActionView(breakpointActionView)
{
breakpointActionView.element.remove();
if (!this._actionsContainer.children.length) {
this._popoverActionsCreateAddActionButton();
this._popoverOptionsRowElement.classList.add(WI.BreakpointPopoverController.HiddenStyleClassName);
this._popoverOptionsCheckboxElement.checked = false;
}
this._popover.update();
}
breakpointActionViewResized(breakpointActionView)
{
this._popover.update();
}
willDismissPopover(popover)
{
console.assert(this._popover === popover);
this._popoverContentElement = null;
this._popoverOptionsRowElement = null;
this._popoverOptionsCheckboxElement = null;
this._actionsContainer = null;
this._popover = null;
}
didDismissPopover(popover)
{
// Remove Evaluate and Probe actions that have no data.
let emptyActions = this._breakpoint.actions.filter(function(action) {
if (action.type !== WI.BreakpointAction.Type.Evaluate && action.type !== WI.BreakpointAction.Type.Probe)
return false;
return !(action.data && action.data.trim());
});
for (let action of emptyActions)
this._breakpoint.removeAction(action);
this._breakpoint = null;
}
};
WI.BreakpointPopoverController.WidePopoverClassName = "wide";
WI.BreakpointPopoverController.HiddenStyleClassName = "hidden";
+124
View File
@@ -0,0 +1,124 @@
/*
* Copyright (C) 2020 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.BrowserManager = class BrowserManager
{
constructor()
{
this._enabled = false;
this._extensionNameIdentifierMap = new Map;
}
// Target
initializeTarget(target)
{
if (!this._enabled)
return;
// COMPATIBILITY (iOS 13.4): Browser did not exist yet.
if (target.hasDomain("Browser"))
target.BrowserAgent.enable();
}
// Public
enable()
{
console.assert(!this._enabled);
this._enabled = true;
for (let target of WI.targetManager.allTargets)
this.initializeTarget(target);
}
disable()
{
console.assert(this._enabled);
for (let target of WI.targetManager.allTargets) {
// COMPATIBILITY (iOS 13.4): Browser did not exist yet.
if (target.hasDomain("Browser"))
target.BrowserAgent.disable();
}
this._extensionNameIdentifierMap.clear();
this._enabled = false;
}
isExtensionScheme(scheme)
{
return scheme && scheme.endsWith("-extension");
}
extensionNameForId(extensionId)
{
return this._extensionNameIdentifierMap.get(extensionId) || null;
}
extensionNameForURL(url)
{
console.assert(this.isExtensionScheme(parseURL(url).scheme));
let match = url.match(/^[a-z\-]*extension:\/\/([0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12})\//);
if (!match)
return null;
return this.extensionNameForId(match[1]);
}
extensionNameForExecutionContext(context)
{
console.assert(context instanceof WI.ExecutionContext);
console.assert(context.type === WI.ExecutionContext.Type.User);
let match = context.name.match(/^[A-Za-z]*ExtensionWorld-([0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12})$/);
if (!match)
return null;
return this.extensionNameForId(match[1]);
}
// BrowserObserver
extensionsEnabled(extensions)
{
for (let {extensionId, name} of extensions) {
console.assert(!this._extensionNameIdentifierMap.has(extensionId), `Extension already exists with id '${extensionId}'.`);
this._extensionNameIdentifierMap.set(extensionId, name);
}
}
extensionsDisabled(extensionIds)
{
for (let extensionId of extensionIds) {
let name = this._extensionNameIdentifierMap.take(extensionId);
console.assert(name, `Missing extension with id '${extensionId}'.`);
}
}
};
+894
View File
@@ -0,0 +1,894 @@
/*
* Copyright (C) 2013 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.
*/
// FIXME: CSSManager lacks advanced multi-target support. (Stylesheets per-target)
WI.CSSManager = class CSSManager extends WI.Object
{
constructor()
{
super();
WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
WI.Frame.addEventListener(WI.Frame.Event.ResourceWasAdded, this._resourceAdded, this);
WI.Resource.addEventListener(WI.SourceCode.Event.ContentDidChange, this._resourceContentDidChange, this);
WI.Resource.addEventListener(WI.Resource.Event.TypeDidChange, this._resourceTypeDidChange, this);
WI.DOMNode.addEventListener(WI.DOMNode.Event.AttributeModified, this._nodeAttributesDidChange, this);
WI.DOMNode.addEventListener(WI.DOMNode.Event.AttributeRemoved, this._nodeAttributesDidChange, this);
WI.DOMNode.addEventListener(WI.DOMNode.Event.EnabledPseudoClassesChanged, this._nodePseudoClassesDidChange, this);
this._colorFormatSetting = new WI.Setting("default-color-format", WI.Color.Format.Original);
this._styleSheetIdentifierMap = new Map;
this._styleSheetFrameURLMap = new Map;
this._nodeStylesMap = {};
this._modifiedStyles = new Map;
this._defaultUserPreferences = new Map;
this._overridenUserPreferences = new Map;
this._propertyNameCompletions = null;
}
// Target
initializeTarget(target)
{
if (target.hasDomain("CSS"))
target.CSSAgent.enable();
}
initializeCSSPropertyNameCompletions(target)
{
console.assert(target.hasDomain("CSS"));
if (this._propertyNameCompletions)
return;
target.CSSAgent.getSupportedCSSProperties((error, cssProperties) => {
if (error)
return;
this._propertyNameCompletions = new WI.CSSPropertyNameCompletions(cssProperties);
WI.CSSKeywordCompletions.addCustomCompletions(cssProperties);
// CodeMirror is not included by tests so we shouldn't assume it always exists.
// If it isn't available we skip MIME type associations.
if (!window.CodeMirror)
return;
let propertyNamesForCodeMirror = {};
let valueKeywordsForCodeMirror = {"inherit": true, "initial": true, "unset": true, "revert": true, "revert-layer": true, "var": true, "env": true};
let colorKeywordsForCodeMirror = {};
function nameForCodeMirror(name) {
// CodeMirror parses the vendor prefix separate from the property or keyword name,
// so we need to strip vendor prefixes from our names. Also strip function parenthesis.
return name.replace(/^-[^-]+-/, "").replace(/\(\)$/, "").toLowerCase();
}
for (let property of cssProperties) {
// Properties can also be value keywords, like when used in a transition.
// So we add them to both lists.
let codeMirrorPropertyName = nameForCodeMirror(property.name);
propertyNamesForCodeMirror[codeMirrorPropertyName] = true;
valueKeywordsForCodeMirror[codeMirrorPropertyName] = true;
}
for (let propertyName in WI.CSSKeywordCompletions._propertyKeywordMap) {
let keywords = WI.CSSKeywordCompletions._propertyKeywordMap[propertyName];
for (let keyword of keywords) {
// Skip numbers, like the ones defined for font-weight.
if (keyword === WI.CSSKeywordCompletions.AllPropertyNamesPlaceholder || !isNaN(Number(keyword)))
continue;
valueKeywordsForCodeMirror[nameForCodeMirror(keyword)] = true;
}
}
for (let color of WI.CSSKeywordCompletions._colors)
colorKeywordsForCodeMirror[nameForCodeMirror(color)] = true;
// TODO: Remove these keywords once they are built-in codemirror or once we get values from WebKit itself.
valueKeywordsForCodeMirror["conic-gradient"] = true;
valueKeywordsForCodeMirror["repeating-conic-gradient"] = true;
function updateCodeMirrorCSSMode(mimeType) {
let modeSpec = CodeMirror.resolveMode(mimeType);
console.assert(modeSpec.propertyKeywords);
console.assert(modeSpec.valueKeywords);
console.assert(modeSpec.colorKeywords);
modeSpec.propertyKeywords = propertyNamesForCodeMirror;
modeSpec.valueKeywords = valueKeywordsForCodeMirror;
modeSpec.colorKeywords = colorKeywordsForCodeMirror;
CodeMirror.defineMIME(mimeType, modeSpec);
}
updateCodeMirrorCSSMode("text/css");
updateCodeMirrorCSSMode("text/x-scss");
});
if (target.hasCommand("CSS.getSupportedSystemFontFamilyNames")) {
target.CSSAgent.getSupportedSystemFontFamilyNames((error, fontFamilyNames) =>{
if (error)
return;
WI.CSSKeywordCompletions.addPropertyCompletionValues("font-family", fontFamilyNames);
WI.CSSKeywordCompletions.addPropertyCompletionValues("font", fontFamilyNames);
});
}
}
// Static
static supportsInspectorStyleSheet()
{
return InspectorBackend.hasCommand("CSS.createStyleSheet");
}
static protocolStyleSheetOriginToEnum(origin)
{
switch (origin) {
case InspectorBackend.Enum.CSS.StyleSheetOrigin.User:
return WI.CSSStyleSheet.Type.User;
case InspectorBackend.Enum.CSS.StyleSheetOrigin.UserAgent:
return WI.CSSStyleSheet.Type.UserAgent;
case InspectorBackend.Enum.CSS.StyleSheetOrigin.Inspector:
return WI.CSSStyleSheet.Type.Inspector;
}
// COMPATIBILITY (iOS 14): CSS.StyleSheetOrigin.Regular was replaced with CSS.StyleSheetOrigin.Author.
console.assert(!InspectorBackend.Enum.CSS.StyleSheetOrigin.Author || origin === InspectorBackend.Enum.CSS.StyleSheetOrigin.Author);
console.assert(!InspectorBackend.Enum.CSS.StyleSheetOrigin.Regular || origin === InspectorBackend.Enum.CSS.StyleSheetOrigin.Regular);
return WI.CSSStyleSheet.Type.Author;
}
static protocolGroupingTypeToEnum(type)
{
// COMPATIBILITY (iOS 13): CSS.Grouping did not exist yet.
if (!InspectorBackend.Enum.CSS.Grouping) {
switch (type) {
case "mediaRule":
return WI.CSSGrouping.Type.MediaRule;
case "importRule":
return WI.CSSGrouping.Type.MediaImportRule;
case "linkedSheet":
return WI.CSSGrouping.Type.MediaLinkNode;
case "inlineSheet":
return WI.CSSGrouping.Type.MediaStyleNode;
}
}
return type;
}
static displayNameForPseudoId(pseudoId)
{
// Compatibility (iOS 12.2): CSS.PseudoId did not exist.
if (!InspectorBackend.Enum.CSS.PseudoId) {
switch (pseudoId) {
case 1: // PseudoId.FirstLine
return WI.unlocalizedString("::first-line");
case 2: // PseudoId.FirstLetter
return WI.unlocalizedString("::first-letter");
case 3: // PseudoId.Marker
return WI.unlocalizedString("::marker");
case 4: // PseudoId.Before
return WI.unlocalizedString("::before");
case 5: // PseudoId.After
return WI.unlocalizedString("::after");
case 6: // PseudoId.Selection
return WI.unlocalizedString("::selection");
case 7: // PseudoId.Scrollbar
return WI.unlocalizedString("::scrollbar");
case 8: // PseudoId.ScrollbarThumb
return WI.unlocalizedString("::scrollbar-thumb");
case 9: // PseudoId.ScrollbarButton
return WI.unlocalizedString("::scrollbar-button");
case 10: // PseudoId.ScrollbarTrack
return WI.unlocalizedString("::scrollbar-track");
case 11: // PseudoId.ScrollbarTrackPiece
return WI.unlocalizedString("::scrollbar-track-piece");
case 12: // PseudoId.ScrollbarCorner
return WI.unlocalizedString("::scrollbar-corner");
case 13: // PseudoId.Resizer
return WI.unlocalizedString("::resizer");
default:
console.error("Unknown pseudo id", pseudoId);
return "";
}
}
switch (pseudoId) {
case CSSManager.PseudoSelectorNames.FirstLine:
return WI.unlocalizedString("::first-line");
case CSSManager.PseudoSelectorNames.FirstLetter:
return WI.unlocalizedString("::first-letter");
case CSSManager.PseudoSelectorNames.Highlight:
return WI.unlocalizedString("::highlight");
case CSSManager.PseudoSelectorNames.Marker:
return WI.unlocalizedString("::marker");
case CSSManager.PseudoSelectorNames.Before:
return WI.unlocalizedString("::before");
case CSSManager.PseudoSelectorNames.After:
return WI.unlocalizedString("::after");
case CSSManager.PseudoSelectorNames.Selection:
return WI.unlocalizedString("::selection");
case CSSManager.PseudoSelectorNames.Backdrop:
return WI.unlocalizedString("::backdrop");
case CSSManager.PseudoSelectorNames.Scrollbar:
return WI.unlocalizedString("::scrollbar");
case CSSManager.PseudoSelectorNames.ScrollbarThumb:
return WI.unlocalizedString("::scrollbar-thumb");
case CSSManager.PseudoSelectorNames.ScrollbarButton:
return WI.unlocalizedString("::scrollbar-button");
case CSSManager.PseudoSelectorNames.ScrollbarTrack:
return WI.unlocalizedString("::scrollbar-track");
case CSSManager.PseudoSelectorNames.ScrollbarTrackPiece:
return WI.unlocalizedString("::scrollbar-track-piece");
case CSSManager.PseudoSelectorNames.ScrollbarCorner:
return WI.unlocalizedString("::scrollbar-corner");
case CSSManager.PseudoSelectorNames.Resizer:
return WI.unlocalizedString("::resizer");
default:
console.error("Unknown pseudo id", pseudoId);
return "";
}
}
static displayNameForForceablePseudoClass(pseudoClass)
{
switch (pseudoClass) {
case WI.CSSManager.ForceablePseudoClass.Active:
return WI.unlocalizedString(":active");
case WI.CSSManager.ForceablePseudoClass.Focus:
return WI.unlocalizedString(":focus");
case WI.CSSManager.ForceablePseudoClass.FocusVisible:
return WI.unlocalizedString(":focus-visible");
case WI.CSSManager.ForceablePseudoClass.FocusWithin:
return WI.unlocalizedString(":focus-within");
case WI.CSSManager.ForceablePseudoClass.Hover:
return WI.unlocalizedString(":hover");
case WI.CSSManager.ForceablePseudoClass.Target:
return WI.unlocalizedString(":target");
case WI.CSSManager.ForceablePseudoClass.Visited:
return WI.unlocalizedString(":visited");
}
console.assert(false, "Unknown pseudo class", pseudoClass);
return "";
}
// Public
get propertyNameCompletions() { return this._propertyNameCompletions; }
get overridenUserPreferences() { return this._overridenUserPreferences; }
get defaultUserPreferences() { return this._defaultUserPreferences; }
get overridenUserPreferences() { return this._overridenUserPreferences; }
get preferredColorFormat()
{
return this._colorFormatSetting.value;
}
get styleSheets()
{
return Array.from(this._styleSheetIdentifierMap.values());
}
get supportsOverrideUserPreference()
{
return InspectorBackend.hasCommand("Page.overrideUserPreference") && this._defaultUserPreferences.size;
}
get supportsOverrideColorScheme()
{
// A backend for a platform that does not support color schemes will not dispatch an initial event (Page.defaultAppearanceDidChange or Page.defaultUserPreferencesDidChange)
// with the default value for the color scheme preference which gets stored on the frontend.
// COMPATIBILITY (macOS 13.0, iOS 16.0): `PrefersColorScheme` value for `Page.UserPreferenceName` did not exist yet.
return this._defaultUserPreferences.has(InspectorBackend.Enum.Page.UserPreferenceName?.PrefersColorScheme) || this._defaultUserPreferences.has(WI.CSSManager.ForcedAppearancePreference);
}
// COMPATIBILITY (macOS 13, iOS 16.0): `Page.setForcedAppearance()` was removed in favor of `Page.overrideUserPreference()`
setForcedAppearance(name)
{
let commandArguments = {};
switch (name) {
case WI.CSSManager.Appearance.Light:
commandArguments.appearance = InspectorBackend.Enum.Page.Appearance.Light;
break;
case WI.CSSManager.Appearance.Dark:
commandArguments.appearance = InspectorBackend.Enum.Page.Appearance.Dark;
break;
case null:
// COMPATIBILITY (iOS 14): the `appearance`` parameter of `Page.setForcedAppearance` was not optional.
// Since support can't be tested directly, check for the `options`` parameter of `DOMDebugger.setDOMBreakpoint` (iOS 14.0+).
// FIXME: Use explicit version checking once https://webkit.org/b/148680 is fixed.
if (!InspectorBackend.hasCommand("DOMDebugger.setDOMBreakpoint", "options"))
commandArguments.appearance = "";
break;
default:
console.assert(false, "Unknown appearance", name);
return;
}
let target = WI.assumingMainTarget();
return target.PageAgent.setForcedAppearance.invoke(commandArguments);
}
set layoutContextTypeChangedMode(layoutContextTypeChangedMode)
{
for (let target of WI.targets) {
// COMPATIBILITY (iOS 14.5): CSS.setLayoutContextTypeChangedMode did not exist.
if (target.hasCommand("CSS.setLayoutContextTypeChangedMode"))
target.CSSAgent.setLayoutContextTypeChangedMode(layoutContextTypeChangedMode);
}
}
canForcePseudoClass(pseudoClass)
{
if (!InspectorBackend.hasCommand("CSS.forcePseudoState"))
return false;
if (!pseudoClass)
return true;
switch (pseudoClass) {
case WI.CSSManager.ForceablePseudoClass.Active:
case WI.CSSManager.ForceablePseudoClass.Focus:
case WI.CSSManager.ForceablePseudoClass.Hover:
case WI.CSSManager.ForceablePseudoClass.Visited:
return true;
case WI.CSSManager.ForceablePseudoClass.FocusVisible:
case WI.CSSManager.ForceablePseudoClass.FocusWithin:
case WI.CSSManager.ForceablePseudoClass.Target:
// COMPATIBILITY (macOS 12.3, iOS 15.4): CSS.ForceablePseudoClass did not exist yet.
return !!InspectorBackend.Enum.CSS.ForceablePseudoClass;
}
console.assert(false, "Unknown pseudo class", pseudoClass);
return false;
}
overrideUserPreference(preference, value)
{
let promises = [];
for (let target of WI.targets) {
// COMPATIBILITY (macOS 13.0, iOS 16.0): `Page.overrideUserPreference()` did not exist yet.
if (target.hasCommand("Page.overrideUserPreference") && InspectorBackend.Enum.Page.UserPreferenceName[preference])
promises.push(target.PageAgent.overrideUserPreference(preference, value));
// COMPATIBILITY (macOS 13, iOS 16.0): `Page.setForcedAppearance()` was removed in favor of `Page.overrideUserPreference()`
if (preference === WI.CSSManager.ForcedAppearancePreference && target.hasCommand("Page.setForcedAppearance"))
promises.push(this.setForcedAppearance(value || null));
}
if (value)
this._overridenUserPreferences.set(preference, value);
else
this._overridenUserPreferences.delete(preference);
Promise.allSettled(promises).then(() => {
this.mediaQueryResultChanged();
this.dispatchEventToListeners(WI.CSSManager.Event.OverridenUserPreferencesDidChange);
})
}
propertyNameHasOtherVendorPrefix(name)
{
if (!name || name.length < 4 || name.charAt(0) !== "-")
return false;
var match = name.match(/^(?:-moz-|-ms-|-o-|-epub-)/);
if (!match)
return false;
return true;
}
propertyValueHasOtherVendorKeyword(value)
{
var match = value.match(/(?:-moz-|-ms-|-o-|-epub-)[-\w]+/);
if (!match)
return false;
return true;
}
canonicalNameForPropertyName(name)
{
if (!name || name.length < 8 || name.charAt(0) !== "-")
return name;
// Keep in sync with prefix list from Source/WebInspectorUI/Scripts/update-inspector-css-documentation
var match = name.match(/^(?:-webkit-|-khtml-|-apple-)(.+)/);
if (!match)
return name;
return match[1];
}
styleSheetForIdentifier(id)
{
let styleSheet = this._styleSheetIdentifierMap.get(id);
if (styleSheet)
return styleSheet;
styleSheet = new WI.CSSStyleSheet(id);
this._styleSheetIdentifierMap.set(id, styleSheet);
return styleSheet;
}
stylesForNode(node)
{
if (node.id in this._nodeStylesMap)
return this._nodeStylesMap[node.id];
var styles = new WI.DOMNodeStyles(node);
this._nodeStylesMap[node.id] = styles;
return styles;
}
inspectorStyleSheetsForFrame(frame)
{
return this.styleSheets.filter((styleSheet) => styleSheet.isInspectorStyleSheet() && styleSheet.parentFrame === frame);
}
preferredInspectorStyleSheetForFrame(frame, callback)
{
var inspectorStyleSheets = this.inspectorStyleSheetsForFrame(frame);
for (let styleSheet of inspectorStyleSheets) {
if (styleSheet[WI.CSSManager.PreferredInspectorStyleSheetSymbol]) {
callback(styleSheet);
return;
}
}
let target = WI.assumingMainTarget();
target.CSSAgent.createStyleSheet(frame.id, function(error, styleSheetId) {
if (error || !styleSheetId) {
WI.reportInternalError(error || styleSheetId);
return;
}
const url = null;
let styleSheet = WI.cssManager.styleSheetForIdentifier(styleSheetId);
styleSheet.updateInfo(url, frame, styleSheet.origin, styleSheet.isInlineStyleTag(), styleSheet.startLineNumber, styleSheet.startColumnNumber);
styleSheet[WI.CSSManager.PreferredInspectorStyleSheetSymbol] = true;
callback(styleSheet);
});
}
mediaTypeChanged()
{
// Act the same as if media queries changed.
this.mediaQueryResultChanged();
}
get modifiedStyles()
{
return Array.from(this._modifiedStyles.values());
}
addModifiedStyle(style)
{
this._modifiedStyles.set(style.stringId, style);
this.dispatchEventToListeners(WI.CSSManager.Event.ModifiedStylesChanged);
}
getModifiedStyle(style)
{
return this._modifiedStyles.get(style.stringId);
}
removeModifiedStyle(style)
{
this._modifiedStyles.delete(style.stringId);
this.dispatchEventToListeners(WI.CSSManager.Event.ModifiedStylesChanged);
}
// PageObserver
// COMPATIBILITY (macOS 13, iOS 16.0): `Page.defaultAppearanceDidChange` was removed in favor of `Page.defaultUserPreferencesDidChange`
defaultAppearanceDidChange(protocolName)
{
let appearance = null;
switch (protocolName) {
case InspectorBackend.Enum.Page.Appearance.Light:
appearance = WI.CSSManager.Appearance.Light;
break;
case InspectorBackend.Enum.Page.Appearance.Dark:
appearance = WI.CSSManager.Appearance.Dark;
break;
default:
console.error("Unknown default appearance name:", protocolName);
break;
}
this.mediaQueryResultChanged();
this._defaultUserPreferences.set(WI.CSSManager.ForcedAppearancePreference, appearance);
this.dispatchEventToListeners(WI.CSSManager.Event.DefaultUserPreferencesDidChange);
}
defaultUserPreferencesDidChange(userPreferences)
{
this._defaultUserPreferences.clear();
for (let userPreference of userPreferences)
this._defaultUserPreferences.set(userPreference.name, userPreference.value)
this.dispatchEventToListeners(WI.CSSManager.Event.DefaultUserPreferencesDidChange);
}
// CSSObserver
mediaQueryResultChanged()
{
for (var key in this._nodeStylesMap)
this._nodeStylesMap[key].mediaQueryResultDidChange();
}
styleSheetChanged(styleSheetIdentifier)
{
var styleSheet = this.styleSheetForIdentifier(styleSheetIdentifier);
console.assert(styleSheet);
// Do not observe inline styles
if (styleSheet.isInlineStyleAttributeStyleSheet())
return;
if (!styleSheet.noteContentDidChange())
return;
this._updateResourceContent(styleSheet);
}
styleSheetAdded(styleSheetInfo)
{
console.assert(!this._styleSheetIdentifierMap.has(styleSheetInfo.styleSheetId), "Attempted to add a CSSStyleSheet but identifier was already in use");
let styleSheet = this.styleSheetForIdentifier(styleSheetInfo.styleSheetId);
let parentFrame = WI.networkManager.frameForIdentifier(styleSheetInfo.frameId);
let origin = WI.CSSManager.protocolStyleSheetOriginToEnum(styleSheetInfo.origin);
styleSheet.updateInfo(styleSheetInfo.sourceURL, parentFrame, origin, styleSheetInfo.isInline, styleSheetInfo.startLine, styleSheetInfo.startColumn);
this.dispatchEventToListeners(WI.CSSManager.Event.StyleSheetAdded, {styleSheet});
}
styleSheetRemoved(styleSheetIdentifier)
{
let styleSheet = this._styleSheetIdentifierMap.get(styleSheetIdentifier);
console.assert(styleSheet, "Attempted to remove a CSSStyleSheet that was not tracked");
if (!styleSheet)
return;
this._styleSheetIdentifierMap.delete(styleSheetIdentifier);
this.dispatchEventToListeners(WI.CSSManager.Event.StyleSheetRemoved, {styleSheet});
}
// Private
_nodePseudoClassesDidChange(event)
{
var node = event.target;
for (var key in this._nodeStylesMap) {
var nodeStyles = this._nodeStylesMap[key];
if (nodeStyles.node !== node && !nodeStyles.node.isDescendant(node))
continue;
nodeStyles.pseudoClassesDidChange(node);
}
}
_nodeAttributesDidChange(event)
{
var node = event.target;
for (var key in this._nodeStylesMap) {
var nodeStyles = this._nodeStylesMap[key];
if (nodeStyles.node !== node && !nodeStyles.node.isDescendant(node))
continue;
nodeStyles.attributeDidChange(node, event.data.name);
}
}
_mainResourceDidChange(event)
{
console.assert(event.target instanceof WI.Frame);
if (!event.target.isMainFrame())
return;
// Clear our maps when the main frame navigates.
this._styleSheetIdentifierMap.clear();
this._styleSheetFrameURLMap.clear();
this._modifiedStyles.clear();
this._nodeStylesMap = {};
}
_resourceAdded(event)
{
console.assert(event.target instanceof WI.Frame);
var resource = event.data.resource;
console.assert(resource);
if (resource.type !== WI.Resource.Type.StyleSheet)
return;
this._clearStyleSheetsForResource(resource);
}
_resourceTypeDidChange(event)
{
console.assert(event.target instanceof WI.Resource);
var resource = event.target;
if (resource.type !== WI.Resource.Type.StyleSheet)
return;
this._clearStyleSheetsForResource(resource);
}
_clearStyleSheetsForResource(resource)
{
// Clear known stylesheets for this URL and frame. This will cause the style sheets to
// be updated next time _fetchInfoForAllStyleSheets is called.
this._styleSheetIdentifierMap.delete(this._frameURLMapKey(resource.parentFrame, resource.url));
}
_frameURLMapKey(frame, url)
{
return frame.id + ":" + url;
}
_lookupStyleSheetForResource(resource, callback)
{
this._lookupStyleSheet(resource.parentFrame, resource.url, callback);
}
_lookupStyleSheet(frame, url, callback)
{
console.assert(frame instanceof WI.Frame);
let key = this._frameURLMapKey(frame, url);
function styleSheetsFetched()
{
callback(this._styleSheetFrameURLMap.get(key) || null);
}
let styleSheet = this._styleSheetFrameURLMap.get(key) || null;
if (styleSheet)
callback(styleSheet);
else
this._fetchInfoForAllStyleSheets(styleSheetsFetched.bind(this));
}
_fetchInfoForAllStyleSheets(callback)
{
console.assert(typeof callback === "function");
function processStyleSheets(error, styleSheets)
{
this._styleSheetFrameURLMap.clear();
if (error) {
callback();
return;
}
for (let styleSheetInfo of styleSheets) {
let parentFrame = WI.networkManager.frameForIdentifier(styleSheetInfo.frameId);
let origin = WI.CSSManager.protocolStyleSheetOriginToEnum(styleSheetInfo.origin);
let styleSheet = this.styleSheetForIdentifier(styleSheetInfo.styleSheetId);
styleSheet.updateInfo(styleSheetInfo.sourceURL, parentFrame, origin, styleSheetInfo.isInline, styleSheetInfo.startLine, styleSheetInfo.startColumn);
let key = this._frameURLMapKey(parentFrame, styleSheetInfo.sourceURL);
this._styleSheetFrameURLMap.set(key, styleSheet);
}
callback();
}
let target = WI.assumingMainTarget();
target.CSSAgent.getAllStyleSheets(processStyleSheets.bind(this));
}
_resourceContentDidChange(event)
{
var resource = event.target;
if (resource === this._ignoreResourceContentDidChangeEventForResource)
return;
// Ignore changes to resource overrides, those are not live on the page.
if (resource.localResourceOverride)
return;
// Ignore if it isn't a CSS style sheet.
if (resource.type !== WI.Resource.Type.StyleSheet || resource.syntheticMIMEType !== "text/css")
return;
function applyStyleSheetChanges()
{
function styleSheetFound(styleSheet)
{
resource.__pendingChangeTimeout.cancel();
console.assert(styleSheet);
if (!styleSheet)
return;
// To prevent updating a TextEditor's content while the user is typing in it we want to
// ignore the next _updateResourceContent call.
resource.__ignoreNextUpdateResourceContent = true;
let revision = styleSheet.editableRevision;
revision.updateRevisionContent(resource.content);
}
this._lookupStyleSheetForResource(resource, styleSheetFound.bind(this));
}
if (!resource.__pendingChangeTimeout)
resource.__pendingChangeTimeout = new Throttler(applyStyleSheetChanges.bind(this), 100);
resource.__pendingChangeTimeout.fire();
}
_updateResourceContent(styleSheet)
{
console.assert(styleSheet);
function fetchedStyleSheetContent(parameters)
{
styleSheet.__pendingChangeTimeout.cancel();
let representedObject = parameters.sourceCode;
console.assert(representedObject.url);
if (!representedObject.url)
return;
if (!styleSheet.isInspectorStyleSheet()) {
// Only try to update stylesheet resources. Other resources, like documents, can contain
// multiple stylesheets and we don't have the source ranges to update those.
representedObject = representedObject.parentFrame.resourcesForURL(representedObject.url).find((resource) => resource.type === WI.Resource.Type.StyleSheet);
if (!representedObject)
return;
}
if (representedObject.__ignoreNextUpdateResourceContent) {
representedObject.__ignoreNextUpdateResourceContent = false;
return;
}
this._ignoreResourceContentDidChangeEventForResource = representedObject;
let revision = representedObject.editableRevision;
if (styleSheet.isInspectorStyleSheet()) {
revision.updateRevisionContent(representedObject.content);
styleSheet.dispatchEventToListeners(WI.SourceCode.Event.ContentDidChange);
} else
revision.updateRevisionContent(parameters.content);
this._ignoreResourceContentDidChangeEventForResource = null;
}
function styleSheetReady()
{
styleSheet.requestContent().then(fetchedStyleSheetContent.bind(this));
}
function applyStyleSheetChanges()
{
if (styleSheet.url)
styleSheetReady.call(this);
else
this._fetchInfoForAllStyleSheets(styleSheetReady.bind(this));
}
if (!styleSheet.__pendingChangeTimeout)
styleSheet.__pendingChangeTimeout = new Throttler(applyStyleSheetChanges.bind(this), 100);
styleSheet.__pendingChangeTimeout.fire();
}
};
WI.CSSManager.Event = {
StyleSheetAdded: "css-manager-style-sheet-added",
StyleSheetRemoved: "css-manager-style-sheet-removed",
ModifiedStylesChanged: "css-manager-modified-styles-changed",
DefaultUserPreferencesDidChange: "css-manager-default-user-preferences-did-change",
OverridenUserPreferencesDidChange: "css-manager-overriden-user-preferences-did-change",
};
WI.CSSManager.UserPreferenceDefaultValue = "System";
// COMPATIBILITY (macOS 13, iOS 16.0): `Page.setForcedAppearance()` was removed in favor of `Page.overrideUserPreference()`
WI.CSSManager.ForcedAppearancePreference = "ForcedAppearancePreference";
WI.CSSManager.Appearance = {
Light: "Light",
Dark: "Dark",
};
WI.CSSManager.PseudoSelectorNames = {
After: "after",
Before: "before",
Backdrop: "backdrop",
FirstLetter: "first-letter",
FirstLine: "first-line",
Highlight: "highlight",
Marker: "marker",
Resizer: "resizer",
Scrollbar: "scrollbar",
ScrollbarButton: "scrollbar-button",
ScrollbarCorner: "scrollbar-corner",
ScrollbarThumb: "scrollbar-thumb",
ScrollbarTrack: "scrollbar-track",
ScrollbarTrackPiece: "scrollbar-track-piece",
Selection: "selection",
};
WI.CSSManager.LayoutContextTypeChangedMode = {
Observed: "observed",
All: "all",
};
WI.CSSManager.PseudoElementNames = ["before", "after"];
WI.CSSManager.ForceablePseudoClass = {
Active: "active",
Focus: "focus",
FocusVisible: "focus-visible",
FocusWithin: "focus-within",
Hover: "hover",
Target: "target",
Visited: "visited",
};
WI.CSSManager.PreferredInspectorStyleSheetSymbol = Symbol("css-manager-preferred-inspector-style-sheet");
@@ -0,0 +1,86 @@
/*
* Copyright (C) 2021 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.CSSQueryController = class CSSQueryController extends WI.QueryController
{
constructor(values)
{
console.assert(Array.isArray(values), values);
super();
this._values = values || [];
this._cachedSpecialCharacterIndicesForValueMap = new Map;
}
// Public
addValues(values)
{
console.assert(Array.isArray(values), values);
if (!values.length)
return;
this._values.pushAll(values);
}
reset()
{
this._values = [];
this._cachedSpecialCharacterIndicesForValueMap.clear();
}
executeQuery(query)
{
if (!query || !this._values.length)
return [];
query = query.toLowerCase();
let results = [];
for (let value of this._values) {
if (!this._cachedSpecialCharacterIndicesForValueMap.has(value))
this._cachedSpecialCharacterIndicesForValueMap.set(value, this._findSpecialCharacterIndicesInPropertyName(value));
let matches = this.findQueryMatches(query, value.toLowerCase(), this._cachedSpecialCharacterIndicesForValueMap.get(value));
if (matches.length)
results.push(new WI.QueryResult(value, matches));
}
return results.sort((a, b) => {
if (a.rank === b.rank)
return WI.CSSProperty.sortPreferringNonPrefixed(a.value, b.value);
return b.rank - a.rank;
});
}
// Private
_findSpecialCharacterIndicesInPropertyName(propertyName)
{
return this.findSpecialCharacterIndices(propertyName, "-_");
}
};
@@ -0,0 +1,93 @@
/*
* 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.
*/
WI.CallFrameTreeController = class CallFrameTreeController extends WI.Object
{
constructor(treeOutline)
{
console.assert(treeOutline instanceof WI.TreeOutline);
super();
this._treeOutline = treeOutline;
if (this._treeOutline.selectable)
this._treeOutline.addEventListener(WI.TreeOutline.Event.SelectionDidChange, this._treeSelectionDidChange, this);
else
this._treeOutline.addEventListener(WI.TreeOutline.Event.ElementClicked, this._treeElementClicked, this);
}
// Public
get treeOutline() { return this._treeOutline; }
get callFrames()
{
return this._callFrames;
}
set callFrames(callFrames)
{
callFrames = callFrames || [];
if (this._callFrames === callFrames)
return;
this._callFrames = callFrames;
this._treeOutline.removeChildren();
for (let callFrame of this._callFrames)
this._treeOutline.appendChild(new WI.CallFrameTreeElement(callFrame));
}
disconnect()
{
this._treeOutline.removeEventListener(null, null, this);
}
// Private
_treeElementClicked(event)
{
this._showSourceCodeLocation(event.data.treeElement);
}
_treeSelectionDidChange(event)
{
this._showSourceCodeLocation(this._treeOutline.selectedTreeElement);
}
_showSourceCodeLocation(treeElement)
{
let callFrame = treeElement.callFrame;
if (!callFrame.sourceCodeLocation)
return;
WI.showSourceCodeLocation(callFrame.sourceCodeLocation, {
ignoreNetworkTab: true,
ignoreSearchTab: true,
});
}
};
+313
View File
@@ -0,0 +1,313 @@
/*
* 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.
*/
// FIXME: CanvasManager lacks advanced multi-target support. (Canvases per-target)
WI.CanvasManager = class CanvasManager extends WI.Object
{
constructor()
{
super();
this._enabled = false;
this._canvasCollection = new WI.CanvasCollection;
this._canvasIdentifierMap = new Map;
this._shaderProgramIdentifierMap = new Map;
this._savedRecordings = new Set;
WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
}
// Agent
get domains() { return ["Canvas"]; }
activateExtraDomain(domain)
{
// COMPATIBILITY (iOS 14.0): Inspector.activateExtraDomains was removed in favor of a declared debuggable type
console.assert(domain === "Canvas");
for (let target of WI.targets)
this.initializeTarget(target);
}
// Target
initializeTarget(target)
{
if (!this._enabled)
return;
if (target.hasDomain("Canvas")) {
target.CanvasAgent.enable();
// COMPATIBILITY (iOS 12): Canvas.setRecordingAutoCaptureFrameCount did not exist yet.
if (target.hasCommand("Canvas.setRecordingAutoCaptureFrameCount") && WI.settings.canvasRecordingAutoCaptureEnabled.value && WI.settings.canvasRecordingAutoCaptureFrameCount.value)
target.CanvasAgent.setRecordingAutoCaptureFrameCount(WI.settings.canvasRecordingAutoCaptureFrameCount.value);
}
}
// Static
static supportsRecordingAutoCapture()
{
return InspectorBackend.hasCommand("Canvas.setRecordingAutoCaptureFrameCount");
}
// Public
get canvasCollection() { return this._canvasCollection; }
get savedRecordings() { return this._savedRecordings; }
get shaderPrograms()
{
return Array.from(this._shaderProgramIdentifierMap.values());
}
async processJSON({filename, json, error})
{
if (error) {
WI.Recording.synthesizeError(error);
return;
}
if (typeof json !== "object" || json === null) {
WI.Recording.synthesizeError(WI.UIString("invalid JSON"));
return;
}
let recording = WI.Recording.fromPayload(json);
if (!recording)
return;
let extensionStart = filename.lastIndexOf(".");
if (extensionStart !== -1)
filename = filename.substring(0, extensionStart);
recording.createDisplayName(filename);
this._savedRecordings.add(recording);
this.dispatchEventToListeners(WI.CanvasManager.Event.RecordingSaved, {recording, imported: true, initiatedByUser: true});
}
enable()
{
console.assert(!this._enabled);
this._enabled = true;
for (let target of WI.targets)
this.initializeTarget(target);
}
disable()
{
console.assert(this._enabled);
for (let target of WI.targets) {
if (target.hasDomain("Canvas"))
target.CanvasAgent.disable();
}
this._canvasCollection.clear();
this._canvasIdentifierMap.clear();
this._shaderProgramIdentifierMap.clear();
this._savedRecordings.clear();
this._enabled = false;
}
setRecordingAutoCaptureFrameCount(enabled, count)
{
console.assert(!isNaN(count) && count >= 0);
for (let target of WI.targets) {
// COMPATIBILITY (iOS 12): Canvas.setRecordingAutoCaptureFrameCount did not exist yet.
if (target.hasCommand("Canvas.setRecordingAutoCaptureFrameCount"))
target.CanvasAgent.setRecordingAutoCaptureFrameCount(enabled ? count : 0);
}
WI.settings.canvasRecordingAutoCaptureEnabled.value = enabled && count;
WI.settings.canvasRecordingAutoCaptureFrameCount.value = count;
}
// CanvasObserver
canvasAdded(canvasPayload)
{
console.assert(!this._canvasIdentifierMap.has(canvasPayload.canvasId), `Canvas already exists with id ${canvasPayload.canvasId}.`);
let canvas = WI.Canvas.fromPayload(canvasPayload);
this._canvasCollection.add(canvas);
this._canvasIdentifierMap.set(canvas.identifier, canvas);
}
canvasRemoved(canvasIdentifier)
{
let canvas = this._canvasIdentifierMap.take(canvasIdentifier);
console.assert(canvas);
if (!canvas)
return;
this._saveRecordings(canvas);
this._canvasCollection.remove(canvas);
for (let program of canvas.shaderProgramCollection)
this._shaderProgramIdentifierMap.delete(program.identifier);
canvas.shaderProgramCollection.clear();
}
canvasMemoryChanged(canvasIdentifier, memoryCost)
{
let canvas = this._canvasIdentifierMap.get(canvasIdentifier);
console.assert(canvas);
if (!canvas)
return;
canvas.memoryCost = memoryCost;
}
clientNodesChanged(canvasIdentifier)
{
let canvas = this._canvasIdentifierMap.get(canvasIdentifier);
console.assert(canvas);
if (!canvas)
return;
canvas.clientNodesChanged();
}
recordingStarted(canvasIdentifier, initiator)
{
let canvas = this._canvasIdentifierMap.get(canvasIdentifier);
console.assert(canvas);
if (!canvas)
return;
canvas.recordingStarted(initiator);
}
recordingProgress(canvasIdentifier, framesPayload, bufferUsed)
{
let canvas = this._canvasIdentifierMap.get(canvasIdentifier);
console.assert(canvas);
if (!canvas)
return;
canvas.recordingProgress(framesPayload, bufferUsed);
}
recordingFinished(canvasIdentifier, recordingPayload)
{
let canvas = this._canvasIdentifierMap.get(canvasIdentifier);
console.assert(canvas);
if (!canvas)
return;
canvas.recordingFinished(recordingPayload);
}
extensionEnabled(canvasIdentifier, extension)
{
let canvas = this._canvasIdentifierMap.get(canvasIdentifier);
console.assert(canvas);
if (!canvas)
return;
canvas.enableExtension(extension);
}
programCreated(shaderProgramPayload)
{
let canvas = this._canvasIdentifierMap.get(shaderProgramPayload.canvasId);
console.assert(canvas);
if (!canvas)
return;
let programId = shaderProgramPayload.programId;
console.assert(!this._shaderProgramIdentifierMap.has(programId), `ShaderProgram already exists with id ${programId}.`);
// COMPATIBILITY (iOS 13.0): `Canvas.ShaderProgram.programType` did not exist yet.
let programType = shaderProgramPayload.programType;
if (!programType)
programType = WI.ShaderProgram.ProgramType.Render;
let options = {};
// COMPATIBILITY (iOS 13.0): `Canvas.ShaderProgram.sharesVertexFragmentShader` did not exist yet.
if (shaderProgramPayload.sharesVertexFragmentShader)
options.sharesVertexFragmentShader = true;
let program = new WI.ShaderProgram(programId, programType, canvas, options);
this._shaderProgramIdentifierMap.set(program.identifier, program);
canvas.shaderProgramCollection.add(program);
}
programDeleted(programIdentifier)
{
let program = this._shaderProgramIdentifierMap.take(programIdentifier);
console.assert(program);
if (!program)
return;
program.canvas.shaderProgramCollection.remove(program);
}
// Private
_saveRecordings(canvas)
{
for (let recording of canvas.recordingCollection) {
recording.source = null;
recording.createDisplayName(recording.displayName);
this._savedRecordings.add(recording);
this.dispatchEventToListeners(WI.CanvasManager.Event.RecordingSaved, {recording});
}
}
_mainResourceDidChange(event)
{
console.assert(event.target instanceof WI.Frame);
if (!event.target.isMainFrame())
return;
WI.Canvas.resetUniqueDisplayNameNumbers();
for (let canvas of this._canvasIdentifierMap.values())
this._saveRecordings(canvas);
this._canvasCollection.clear();
this._canvasIdentifierMap.clear();
this._shaderProgramIdentifierMap.clear();
}
};
WI.CanvasManager.Event = {
RecordingSaved: "canvas-manager-recording-saved",
};
@@ -0,0 +1,63 @@
/*
* Copyright (C) 2015 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.CodeMirrorBezierEditingController = class CodeMirrorBezierEditingController extends WI.CodeMirrorEditingController
{
// Public
get initialValue()
{
return WI.CubicBezier.fromString(this.text);
}
get cssClassName()
{
return "cubic-bezier";
}
popoverWillPresent(popover)
{
this._bezierEditor = new WI.BezierEditor;
this._bezierEditor.addEventListener(WI.BezierEditor.Event.BezierChanged, this._bezierEditorBezierChanged, this);
popover.content = this._bezierEditor.element;
}
popoverDidPresent(popover)
{
this._bezierEditor.bezier = this.value;
}
popoverDidDismiss(popover)
{
this._bezierEditor.removeListeners();
}
// Private
_bezierEditorBezierChanged(event)
{
this.value = event.data.bezier;
}
};
@@ -0,0 +1,58 @@
/*
* Copyright (C) 2013 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.CodeMirrorColorEditingController = class CodeMirrorColorEditingController extends WI.CodeMirrorEditingController
{
// Public
get initialValue()
{
return WI.Color.fromString(this.text);
}
get cssClassName()
{
return "color";
}
popoverWillPresent(popover)
{
this._colorPicker = new WI.ColorPicker;
this._colorPicker.addEventListener(WI.ColorPicker.Event.ColorChanged, this._colorPickerColorChanged, this);
popover.content = this._colorPicker.element;
}
popoverDidPresent(popover)
{
this._colorPicker.color = this._value;
}
// Private
_colorPickerColorChanged(event)
{
this.value = event.target.color;
}
};
@@ -0,0 +1,29 @@
/*
* Copyright (C) 2013 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.
*/
.CodeMirror .CodeMirror-lines .completion-hint {
text-decoration: none !important;
opacity: 0.4;
}
@@ -0,0 +1,926 @@
/*
* Copyright (C) 2013 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.CodeMirrorCompletionController = class CodeMirrorCompletionController extends WI.Object
{
constructor(mode, codeMirror, delegate, stopCharactersRegex)
{
console.assert(Object.values(WI.CodeMirrorCompletionController.Mode).includes(mode), mode);
console.assert(codeMirror instanceof CodeMirror, codeMirror);
super();
this._mode = mode;
this._codeMirror = codeMirror;
this._stopCharactersRegex = stopCharactersRegex || null;
this._delegate = delegate || null;
this._startOffset = NaN;
this._endOffset = NaN;
this._lineNumber = NaN;
this._prefix = "";
this._noEndingSemicolon = false;
this._completions = [];
this._extendedCompletionProviders = {};
this._suggestionsView = new WI.CompletionSuggestionsView(this);
this._keyMap = {
"Up": this._handleUpKey.bind(this),
"Down": this._handleDownKey.bind(this),
"Right": this._handleRightOrEnterKey.bind(this),
"Esc": this._handleEscapeKey.bind(this),
"Enter": this._handleRightOrEnterKey.bind(this),
"Tab": this._handleTabKey.bind(this),
"Cmd-A": this._handleHideKey.bind(this),
"Cmd-Z": this._handleHideKey.bind(this),
"Shift-Cmd-Z": this._handleHideKey.bind(this),
"Cmd-Y": this._handleHideKey.bind(this)
};
this._handleChangeListener = this._handleChange.bind(this);
this._handleCursorActivityListener = this._handleCursorActivity.bind(this);
this._handleHideActionListener = this._handleHideAction.bind(this);
this._codeMirror.addKeyMap(this._keyMap);
this._codeMirror.on("change", this._handleChangeListener);
this._codeMirror.on("cursorActivity", this._handleCursorActivityListener);
this._codeMirror.on("blur", this._handleHideActionListener);
this._codeMirror.on("scroll", this._handleHideActionListener);
this._updatePromise = null;
}
// Public
get mode() { return this._mode; }
addExtendedCompletionProvider(modeName, provider)
{
this._extendedCompletionProviders[modeName] = provider;
}
updateCompletions(completions, implicitSuffix)
{
if (isNaN(this._startOffset) || isNaN(this._endOffset) || isNaN(this._lineNumber))
return;
if (!completions || !completions.length) {
this.hideCompletions();
return;
}
this._completions = completions;
if (typeof implicitSuffix === "string")
this._implicitSuffix = implicitSuffix;
var from = {line: this._lineNumber, ch: this._startOffset};
var to = {line: this._lineNumber, ch: this._endOffset};
var firstCharCoords = this._codeMirror.cursorCoords(from);
var lastCharCoords = this._codeMirror.cursorCoords(to);
var bounds = new WI.Rect(firstCharCoords.left, firstCharCoords.top, lastCharCoords.right - firstCharCoords.left, firstCharCoords.bottom - firstCharCoords.top);
// Try to restore the previous selected index, otherwise just select the first.
var index = this._currentCompletion ? completions.indexOf(this._currentCompletion) : 0;
if (index === -1)
index = 0;
if (this._forced || completions.length > 1 || completions[index] !== this._prefix) {
// Update and show the suggestion list.
this._suggestionsView.update(completions, index);
this._suggestionsView.show(bounds);
} else if (this._implicitSuffix) {
// The prefix and the completion exactly match, but there is an implicit suffix.
// Just hide the suggestion list and keep the completion hint for the implicit suffix.
this._suggestionsView.hide();
} else {
// The prefix and the completion exactly match, hide the completions. Return early so
// the completion hint isn't updated.
this.hideCompletions();
return;
}
this._applyCompletionHint(completions[index]);
this._resolveUpdatePromise(WI.CodeMirrorCompletionController.UpdatePromise.CompletionsFound);
}
isCompletionChange(change)
{
return this._ignoreChange || change.origin === WI.CodeMirrorCompletionController.CompletionOrigin || change.origin === WI.CodeMirrorCompletionController.DeleteCompletionOrigin;
}
isShowingCompletions()
{
return this._suggestionsView.visible || (this._completionHintMarker && this._completionHintMarker.find());
}
isHandlingClickEvent()
{
return this._suggestionsView.isHandlingClickEvent();
}
commitCurrentCompletion()
{
this._removeCompletionHint(true, true);
let replacementText = this._currentReplacementText;
if (!replacementText)
return;
let from = {line: this._lineNumber, ch: this._startOffset};
let cursor = {line: this._lineNumber, ch: this._endOffset};
let to = {line: this._lineNumber, ch: this._startOffset + replacementText.length};
let lastChar = this._currentCompletion.charAt(this._currentCompletion.length - 1);
let isClosing = ")]}".indexOf(lastChar);
if (isClosing !== -1)
to.ch -= 1 + this._implicitSuffix.length;
this._codeMirror.replaceRange(replacementText, from, cursor, WI.CodeMirrorCompletionController.CompletionOrigin);
// Don't call _removeLastChangeFromHistory here to allow the committed completion to be undone.
this._codeMirror.setCursor(to);
this.hideCompletions();
}
hideCompletions()
{
this._suggestionsView.hide();
this._removeCompletionHint();
this._startOffset = NaN;
this._endOffset = NaN;
this._lineNumber = NaN;
this._prefix = "";
this._completions = [];
this._implicitSuffix = "";
this._forced = false;
delete this._currentCompletion;
delete this._ignoreNextCursorActivity;
this._resolveUpdatePromise(WI.CodeMirrorCompletionController.UpdatePromise.NoCompletionsFound);
}
close()
{
this._codeMirror.removeKeyMap(this._keyMap);
this._codeMirror.off("change", this._handleChangeListener);
this._codeMirror.off("cursorActivity", this._handleCursorActivityListener);
this._codeMirror.off("blur", this._handleHideActionListener);
this._codeMirror.off("scroll", this._handleHideActionListener);
}
completeAtCurrentPositionIfNeeded(force)
{
this._resolveUpdatePromise(WI.CodeMirrorCompletionController.UpdatePromise.Canceled);
var update = this._updatePromise = new WI.WrappedPromise;
this._completeAtCurrentPosition(force);
return update.promise;
}
// Protected
completionSuggestionsSelectedCompletion(suggestionsView, completionText)
{
this._applyCompletionHint(completionText);
}
completionSuggestionsClickedCompletion(suggestionsView, completionText)
{
// The clicked suggestion causes the editor to loose focus. Restore it so the user can keep typing.
this._codeMirror.focus();
this._applyCompletionHint(completionText);
this._commitCompletionHint();
}
set noEndingSemicolon(noEndingSemicolon)
{
this._noEndingSemicolon = noEndingSemicolon;
}
// Private
_resolveUpdatePromise(message)
{
if (!this._updatePromise)
return;
this._updatePromise.resolve(message);
this._updatePromise = null;
}
get _currentReplacementText()
{
return (this._currentCompletion ?? "") + (this._implicitSuffix ?? "");
}
_hasPendingCompletion()
{
return !isNaN(this._startOffset) && !isNaN(this._endOffset) && !isNaN(this._lineNumber);
}
_notifyCompletionsHiddenSoon()
{
function notify()
{
if (this._completionHintMarker)
return;
if (this._delegate && typeof this._delegate.completionControllerCompletionsHidden === "function")
this._delegate.completionControllerCompletionsHidden(this);
}
if (this._notifyCompletionsHiddenIfNeededTimeout)
clearTimeout(this._notifyCompletionsHiddenIfNeededTimeout);
this._notifyCompletionsHiddenIfNeededTimeout = setTimeout(notify.bind(this), WI.CodeMirrorCompletionController.CompletionsHiddenDelay);
}
_createCompletionHintMarker(position, text)
{
var container = document.createElement("span");
container.classList.add(WI.CodeMirrorCompletionController.CompletionHintStyleClassName);
container.textContent = text;
container.addEventListener("mousedown", (event) => {
event.preventDefault();
this._commitCompletionHint();
// The clicked hint marker causes the editor to loose focus. Restore it so the user can keep typing.
setTimeout(() => { this._codeMirror.focus(); }, 0);
});
this._completionHintMarker = this._codeMirror.setUniqueBookmark(position, {widget: container, insertLeft: true});
}
_applyCompletionHint(completionText)
{
console.assert(completionText);
if (!completionText)
return;
function update()
{
this._currentCompletion = completionText;
this._removeCompletionHint(true, true);
var replacementText = this._currentReplacementText;
var from = {line: this._lineNumber, ch: this._startOffset};
var cursor = {line: this._lineNumber, ch: this._endOffset};
var currentText = this._codeMirror.getRange(from, cursor);
this._createCompletionHintMarker(cursor, replacementText.replace(currentText, ""));
}
this._ignoreChange = true;
this._ignoreNextCursorActivity = true;
this._codeMirror.operation(update.bind(this));
delete this._ignoreChange;
}
_commitCompletionHint()
{
this._ignoreChange = true;
this._ignoreNextCursorActivity = true;
this._codeMirror.operation(this.commitCurrentCompletion.bind(this));
delete this._ignoreChange;
}
_removeLastChangeFromHistory()
{
var history = this._codeMirror.getHistory();
// We don't expect a undone history. But if there is one clear it. If could lead to undefined behavior.
console.assert(!history.undone.length);
history.undone = [];
// Pop the last item from the done history.
console.assert(history.done.length);
history.done.pop();
this._codeMirror.setHistory(history);
}
_removeCompletionHint(nonatomic, dontRestorePrefix)
{
if (!this._completionHintMarker)
return;
this._notifyCompletionsHiddenSoon();
function clearMarker(marker)
{
if (!marker)
return;
var range = marker.find();
if (range)
marker.clear();
return null;
}
function update()
{
this._completionHintMarker = clearMarker(this._completionHintMarker);
if (dontRestorePrefix)
return;
console.assert(!isNaN(this._startOffset));
console.assert(!isNaN(this._endOffset));
console.assert(!isNaN(this._lineNumber));
var from = {line: this._lineNumber, ch: this._startOffset};
var to = {line: this._lineNumber, ch: this._endOffset};
this._codeMirror.replaceRange(this._prefix, from, to, WI.CodeMirrorCompletionController.DeleteCompletionOrigin);
this._removeLastChangeFromHistory();
}
if (nonatomic) {
update.call(this);
return;
}
this._ignoreChange = true;
this._codeMirror.operation(update.bind(this));
delete this._ignoreChange;
}
_scanStringForExpression(modeName, string, startOffset, direction, allowMiddleAndEmpty, includeStopCharacter, ignoreInitialUnmatchedOpenBracket, stopCharactersRegex)
{
console.assert(direction === -1 || direction === 1);
var stopCharactersRegex = stopCharactersRegex || this._stopCharactersRegex || WI.CodeMirrorCompletionController.DefaultStopCharactersRegexModeMap[modeName] || WI.CodeMirrorCompletionController.GenericStopCharactersRegex;
function isStopCharacter(character)
{
return stopCharactersRegex.test(character);
}
function isOpenBracketCharacter(character)
{
return WI.CodeMirrorCompletionController.OpenBracketCharactersRegex.test(character);
}
function isCloseBracketCharacter(character)
{
return WI.CodeMirrorCompletionController.CloseBracketCharactersRegex.test(character);
}
function matchingBracketCharacter(character)
{
return WI.CodeMirrorCompletionController.MatchingBrackets[character];
}
var endOffset = Math.min(startOffset, string.length);
var endOfLineOrWord = endOffset === string.length || isStopCharacter(string.charAt(endOffset));
if (!endOfLineOrWord && !allowMiddleAndEmpty)
return null;
var bracketStack = [];
var bracketOffsetStack = [];
var startOffset = endOffset;
var firstOffset = endOffset + direction;
for (var i = firstOffset; direction > 0 ? i < string.length : i >= 0; i += direction) {
var character = string.charAt(i);
// Ignore stop characters when we are inside brackets.
if (isStopCharacter(character) && !bracketStack.length)
break;
if (isCloseBracketCharacter(character)) {
bracketStack.push(character);
bracketOffsetStack.push(i);
} else if (isOpenBracketCharacter(character)) {
if ((!ignoreInitialUnmatchedOpenBracket || i !== firstOffset) && (!bracketStack.length || matchingBracketCharacter(character) !== bracketStack.lastValue))
break;
bracketOffsetStack.pop();
bracketStack.pop();
}
startOffset = i + (direction > 0 ? 1 : 0);
}
if (bracketOffsetStack.length)
startOffset = bracketOffsetStack.pop() + 1;
if (includeStopCharacter && startOffset > 0 && startOffset < string.length)
startOffset += direction;
if (direction > 0) {
var tempEndOffset = endOffset;
endOffset = startOffset;
startOffset = tempEndOffset;
}
return {string: string.substring(startOffset, endOffset), startOffset, endOffset};
}
_completeAtCurrentPosition(force)
{
if (this._codeMirror.somethingSelected()) {
this.hideCompletions();
return;
}
this._removeCompletionHint(true, true);
var cursor = this._codeMirror.getCursor();
var token = this._codeMirror.getTokenAt(cursor);
// Don't try to complete inside comments or strings.
if (token.type && /\b(comment|string)\b/.test(token.type)) {
this.hideCompletions();
return;
}
var mode = this._codeMirror.getMode();
var innerMode = CodeMirror.innerMode(mode, token.state).mode;
var modeName = innerMode.alternateName || innerMode.name;
var lineNumber = cursor.line;
var lineString = this._codeMirror.getLine(lineNumber);
var backwardScanResult = this._scanStringForExpression(modeName, lineString, cursor.ch, -1, force);
if (!backwardScanResult) {
this.hideCompletions();
return;
}
var forwardScanResult = this._scanStringForExpression(modeName, lineString, cursor.ch, 1, true, true);
var suffix = forwardScanResult.string;
this._ignoreNextCursorActivity = true;
this._startOffset = backwardScanResult.startOffset;
this._endOffset = backwardScanResult.endOffset;
this._lineNumber = lineNumber;
this._prefix = backwardScanResult.string;
this._completions = [];
this._implicitSuffix = "";
this._forced = force;
var baseExpressionStopCharactersRegex = WI.CodeMirrorCompletionController.BaseExpressionStopCharactersRegexModeMap[modeName];
if (baseExpressionStopCharactersRegex)
var baseScanResult = this._scanStringForExpression(modeName, lineString, this._startOffset, -1, true, false, true, baseExpressionStopCharactersRegex);
if (!force && !backwardScanResult.string && (!baseScanResult || !baseScanResult.string)) {
this.hideCompletions();
return;
}
var defaultCompletions = [];
switch (modeName) {
case "css":
defaultCompletions = this._generateCSSCompletions(token, baseScanResult ? baseScanResult.string : null, suffix);
break;
case "javascript":
defaultCompletions = this._generateJavaScriptCompletions(token, baseScanResult ? baseScanResult.string : null, suffix);
break;
}
var extendedCompletionsProvider = this._extendedCompletionProviders[modeName];
if (extendedCompletionsProvider) {
extendedCompletionsProvider.completionControllerCompletionsNeeded(this, defaultCompletions, baseScanResult ? baseScanResult.string : null, this._prefix, suffix, force);
return;
}
if (this._delegate && typeof this._delegate.completionControllerCompletionsNeeded === "function")
this._delegate.completionControllerCompletionsNeeded(this, this._prefix, defaultCompletions, baseScanResult ? baseScanResult.string : null, suffix, force);
else
this.updateCompletions(defaultCompletions);
}
_generateCSSCompletions(mainToken, base, suffix)
{
// We support completion inside CSS block context and functions.
if (mainToken.state.state === "media" || mainToken.state.state === "top")
return [];
// Don't complete in the middle of a property name.
if (/^[a-z]/i.test(suffix))
return [];
var token = mainToken;
var lineNumber = this._lineNumber;
let getPreviousToken = () => {
// Found the beginning of the line. Go to the previous line.
if (!token.start) {
--lineNumber;
// No more lines, stop.
if (lineNumber < 0)
return null;
}
return this._codeMirror.getTokenAt({line: lineNumber, ch: token.start ? token.start : Number.MAX_VALUE});
};
// Inside a function, determine the function name.
if (token.state.state === "parens") {
// Scan backwards looking for the function paren boundary.
while (token && token.state.state === "parens" && token.string !== "(")
token = getPreviousToken();
// The immediately preceding token should have the function name.
if (token)
token = getPreviousToken();
// No completions if no function name found.
if (!token)
return [];
let functionName = token.string;
if (!functionName)
return [];
let functionCompletions = WI.CSSKeywordCompletions.forFunction(functionName).startsWith(this._prefix);
if (this._delegate && this._delegate.completionControllerCSSFunctionValuesNeeded)
functionCompletions = this._delegate.completionControllerCSSFunctionValuesNeeded(this, functionName, functionCompletions);
return functionCompletions;
}
// Scan backwards looking for the current property.
while (token.state.state === "prop") {
let previousToken = getPreviousToken();
if (!previousToken)
break;
token = previousToken;
}
// If we have a property token and it's not the main token, then we are working on
// the value for that property and should complete allowed values.
if (mainToken !== token && token.type && /\bproperty\b/.test(token.type)) {
var propertyName = token.string;
// If there is a suffix and it isn't a semicolon, then we should use a space since
// the user is editing in the middle. Likewise if the suffix starts with an open
// paren we are changing a function name so don't add a suffix.
this._implicitSuffix = " ";
if (suffix === ";")
this._implicitSuffix = this._noEndingSemicolon ? "" : ";";
else if (suffix.startsWith("("))
this._implicitSuffix = "";
// Don't use an implicit suffix if it would be the same as the existing suffix.
if (this._implicitSuffix === suffix)
this._implicitSuffix = "";
let completions = WI.CSSKeywordCompletions.forProperty(propertyName).startsWith(this._prefix);
if (suffix.startsWith("("))
completions = completions.map((x) => x.replace(/\(\)$/, ""));
return completions;
}
this._implicitSuffix = suffix !== ":" ? ": " : "";
// Complete property names.
return WI.cssManager.propertyNameCompletions.startsWith(this._prefix);
}
_generateJavaScriptCompletions(mainToken, base, suffix)
{
// If there is a base expression then we should not attempt to match any keywords or variables.
// Allow only open bracket characters at the end of the base, otherwise leave completions with
// a base up to the delegate to figure out.
if (base && !/[({[]$/.test(base))
return [];
var matchingWords = [];
var prefix = this._prefix;
var localState = mainToken.state.localState ? mainToken.state.localState : mainToken.state;
var declaringVariable = localState.lexical.type === "vardef";
var insideSwitch = localState.lexical.prev ? localState.lexical.prev.info === "switch" : false;
var insideBlock = localState.lexical.prev ? localState.lexical.prev.type === "}" : false;
var insideParenthesis = localState.lexical.type === ")";
var insideBrackets = localState.lexical.type === "]";
// FIXME: Include module keywords if we know this is a module environment.
// var moduleKeywords = ["default", "export", "import"];
const allKeywords = [
"break", "case", "catch", "class", "const", "continue", "debugger", "default",
"delete", "do", "else", "extends", "false", "finally", "for", "function",
"if", "in", "Infinity", "instanceof", "let", "NaN", "new", "null", "of",
"return", "static", "super", "switch", "this", "throw", "true", "try",
"typeof", "undefined", "var", "void", "while", "with", "yield"
];
const valueKeywords = ["false", "Infinity", "NaN", "null", "this", "true", "undefined", "globalThis"];
const allowedKeywordsInsideBlocks = new Set(allKeywords);
const allowedKeywordsWhenDeclaringVariable = new Set(valueKeywords);
const allowedKeywordsInsideParenthesis = new Set(valueKeywords.concat(["class", "function"]));
const allowedKeywordsInsideBrackets = allowedKeywordsInsideParenthesis;
const allowedKeywordsOnlyInsideSwitch = new Set(["case", "default"]);
function matchKeywords(keywords)
{
for (let keyword of keywords) {
if (!insideSwitch && allowedKeywordsOnlyInsideSwitch.has(keyword))
continue;
if (insideBlock && !allowedKeywordsInsideBlocks.has(keyword))
continue;
if (insideBrackets && !allowedKeywordsInsideBrackets.has(keyword))
continue;
if (insideParenthesis && !allowedKeywordsInsideParenthesis.has(keyword))
continue;
if (declaringVariable && !allowedKeywordsWhenDeclaringVariable.has(keyword))
continue;
if (!keyword.startsWith(prefix))
continue;
matchingWords.push(keyword);
}
}
function matchVariables()
{
function filterVariables(variables)
{
for (var variable = variables; variable; variable = variable.next) {
// Don't match the variable if this token is in a variable declaration.
// Otherwise the currently typed text will always match and that isn't useful.
if (declaringVariable && variable.name === prefix)
continue;
if (variable.name.startsWith(prefix) && !matchingWords.includes(variable.name))
matchingWords.push(variable.name);
}
}
var context = localState.context;
while (context) {
if (context.vars)
filterVariables(context.vars);
context = context.prev;
}
if (localState.localVars)
filterVariables(localState.localVars);
if (localState.globalVars)
filterVariables(localState.globalVars);
}
switch (suffix.substring(0, 1)) {
case "":
case " ":
matchVariables();
matchKeywords(allKeywords);
break;
case ".":
case "[":
matchVariables();
matchKeywords(["false", "Infinity", "NaN", "this", "true"]);
break;
case "(":
matchVariables();
matchKeywords(["catch", "else", "for", "function", "if", "return", "switch", "throw", "while", "with", "yield"]);
break;
case "{":
matchKeywords(["do", "else", "finally", "return", "try", "yield"]);
break;
case ":":
if (insideSwitch)
matchKeywords(["case", "default"]);
break;
case ";":
matchVariables();
matchKeywords(valueKeywords);
matchKeywords(["break", "continue", "debugger", "return", "void"]);
break;
}
return matchingWords;
}
_handleUpKey(codeMirror)
{
if (!this._hasPendingCompletion())
return CodeMirror.Pass;
if (!this.isShowingCompletions())
return;
this._suggestionsView.selectPrevious();
}
_handleDownKey(codeMirror)
{
if (!this._hasPendingCompletion())
return CodeMirror.Pass;
if (!this.isShowingCompletions())
return;
this._suggestionsView.selectNext();
}
_handleRightOrEnterKey(codeMirror)
{
if (!this._hasPendingCompletion())
return CodeMirror.Pass;
if (!this.isShowingCompletions())
return;
this._commitCompletionHint();
}
_handleEscapeKey(codeMirror)
{
var delegateImplementsShouldAllowEscapeCompletion = this._delegate && typeof this._delegate.completionControllerShouldAllowEscapeCompletion === "function";
if (this._hasPendingCompletion())
this.hideCompletions();
else if (this._codeMirror.getOption("readOnly"))
return CodeMirror.Pass;
else if (!delegateImplementsShouldAllowEscapeCompletion || this._delegate.completionControllerShouldAllowEscapeCompletion(this))
this._completeAtCurrentPosition(true);
else
return CodeMirror.Pass;
}
_handleTabKey(codeMirror)
{
if (!this._hasPendingCompletion())
return CodeMirror.Pass;
if (!this.isShowingCompletions())
return;
console.assert(this._completions.length);
if (!this._completions.length)
return;
console.assert(this._currentCompletion);
if (!this._currentCompletion)
return;
// Commit the current completion if there is only one suggestion.
if (this._completions.length === 1) {
this._commitCompletionHint();
return;
}
var prefixLength = this._prefix.length;
var commonPrefix = this._completions[0];
for (var i = 1; i < this._completions.length; ++i) {
var completion = this._completions[i];
var lastIndex = Math.min(commonPrefix.length, completion.length);
for (var j = prefixLength; j < lastIndex; ++j) {
if (commonPrefix[j] !== completion[j]) {
commonPrefix = commonPrefix.substr(0, j);
break;
}
}
}
// Commit the current completion if there is no common prefix that is longer.
if (commonPrefix === this._prefix) {
this._commitCompletionHint();
return;
}
// Set the prefix to the common prefix so _applyCompletionHint will insert the
// common prefix as commited text. Adjust _endOffset to match the new prefix.
this._prefix = commonPrefix;
this._endOffset = this._startOffset + commonPrefix.length;
this._applyCompletionHint(this._currentCompletion);
}
_handleChange(codeMirror, change)
{
if (this.isCompletionChange(change))
return;
this._ignoreNextCursorActivity = true;
if (!change.origin || change.origin.charAt(0) !== "+") {
this.hideCompletions();
return;
}
// Only complete on delete if we are showing completions already.
if (change.origin === "+delete" && !this._hasPendingCompletion())
return;
this._completeAtCurrentPosition(false);
}
_handleCursorActivity(codeMirror)
{
if (this._ignoreChange)
return;
if (this._ignoreNextCursorActivity) {
delete this._ignoreNextCursorActivity;
return;
}
this.hideCompletions();
}
_handleHideKey(codeMirror)
{
this.hideCompletions();
return CodeMirror.Pass;
}
_handleHideAction(codeMirror)
{
// Clicking a suggestion causes the editor to blur. We don't want to hide completions in this case.
if (this.isHandlingClickEvent())
return;
this.hideCompletions();
}
};
WI.CodeMirrorCompletionController.Mode = {
Basic: "basic",
EventBreakpoint: "event-breakpoint",
ExceptionBreakpoint: "exception-breakpoint",
FullConsoleCommandLineAPI: "full-console-command-line-api",
PausedConsoleCommandLineAPI: "paused-console-command-line-api",
};
WI.CodeMirrorCompletionController.UpdatePromise = {
Canceled: "code-mirror-completion-controller-canceled",
CompletionsFound: "code-mirror-completion-controller-completions-found",
NoCompletionsFound: "code-mirror-completion-controller-no-completions-found"
};
WI.CodeMirrorCompletionController.GenericStopCharactersRegex = /[\s=:;,]/;
WI.CodeMirrorCompletionController.DefaultStopCharactersRegexModeMap = {"css": /[\s:;,{}()]/, "javascript": /[\s=:;,!+\-*/%&|^~?<>.{}()[\]]/};
WI.CodeMirrorCompletionController.BaseExpressionStopCharactersRegexModeMap = {"javascript": /[\s=:;,!+\-*/%&|^~?<>]/};
WI.CodeMirrorCompletionController.OpenBracketCharactersRegex = /[({[]/;
WI.CodeMirrorCompletionController.CloseBracketCharactersRegex = /[)}\]]/;
WI.CodeMirrorCompletionController.MatchingBrackets = {"{": "}", "(": ")", "[": "]", "}": "{", ")": "(", "]": "["};
WI.CodeMirrorCompletionController.CompletionHintStyleClassName = "completion-hint";
WI.CodeMirrorCompletionController.CompletionsHiddenDelay = 250;
WI.CodeMirrorCompletionController.CompletionTypingDelay = 250;
WI.CodeMirrorCompletionController.CompletionOrigin = "+completion";
WI.CodeMirrorCompletionController.DeleteCompletionOrigin = "+delete-completion";
@@ -0,0 +1,28 @@
/*
* Copyright (C) 2013 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.
*/
.CodeMirror.drag-to-adjust .CodeMirror-lines {
cursor: col-resize;
}
@@ -0,0 +1,121 @@
/*
* Copyright (C) 2013 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.CodeMirrorDragToAdjustNumberController = class CodeMirrorDragToAdjustNumberController extends WI.Object
{
constructor(codeMirror)
{
super();
this._codeMirror = codeMirror;
this._dragToAdjustController = new WI.DragToAdjustController(this);
}
// Public
get enabled()
{
return this._dragToAdjustController.enabled;
}
set enabled(enabled)
{
if (this.enabled === enabled)
return;
this._dragToAdjustController.element = this._codeMirror.getWrapperElement();
this._dragToAdjustController.enabled = enabled;
}
// Protected
dragToAdjustControllerActiveStateChanged(dragToAdjustController)
{
if (!dragToAdjustController.active)
this._hoveredTokenInfo = null;
}
dragToAdjustControllerCanBeActivated(dragToAdjustController)
{
return !this._codeMirror.getOption("readOnly");
}
dragToAdjustControllerCanBeAdjusted(dragToAdjustController)
{
return this._hoveredTokenInfo && this._hoveredTokenInfo.containsNumber;
}
dragToAdjustControllerWasAdjustedByAmount(dragToAdjustController, amount)
{
this._codeMirror.alterNumberInRange(amount, this._hoveredTokenInfo.startPosition, this._hoveredTokenInfo.endPosition, false);
}
dragToAdjustControllerDidReset(dragToAdjustController)
{
this._hoveredTokenInfo = null;
}
dragToAdjustControllerCanAdjustObjectAtPoint(dragToAdjustController, point)
{
var position = this._codeMirror.coordsChar({left: point.x, top: point.y});
var token = this._codeMirror.getTokenAt(position);
if (!token || !token.type || !token.string) {
if (this._hoveredTokenInfo)
dragToAdjustController.reset();
return false;
}
// Stop right here if we're hovering the same token as we were last time.
if (this._hoveredTokenInfo && this._hoveredTokenInfo.line === position.line &&
this._hoveredTokenInfo.token.start === token.start && this._hoveredTokenInfo.token.end === token.end)
return this._hoveredTokenInfo.token.type.indexOf("number") !== -1;
var containsNumber = token.type.indexOf("number") !== -1;
this._hoveredTokenInfo = {
token,
line: position.line,
containsNumber,
startPosition: {
ch: token.start,
line: position.line
},
endPosition: {
ch: token.end,
line: position.line
}
};
return containsNumber;
}
};
CodeMirror.defineOption("dragToAdjustNumbers", true, function(codeMirror, value, oldValue) {
if (!codeMirror.dragToAdjustNumberController)
codeMirror.dragToAdjustNumberController = new WI.CodeMirrorDragToAdjustNumberController(codeMirror);
codeMirror.dragToAdjustNumberController.enabled = value;
});
@@ -0,0 +1,200 @@
/*
* Copyright (C) 2014 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.CodeMirrorEditingController = class CodeMirrorEditingController extends WI.Object
{
constructor(codeMirror, marker)
{
super();
this._codeMirror = codeMirror;
this._marker = marker;
this._delegate = null;
this._range = marker.range;
// The value must support .toString() and .copy() methods.
this._value = this.initialValue;
this._keyboardShortcutEsc = new WI.KeyboardShortcut(null, WI.KeyboardShortcut.Key.Escape);
}
// Public
get marker()
{
return this._marker;
}
get range()
{
return this._range;
}
get value()
{
return this._value;
}
set value(value)
{
this.text = value.toString();
this._value = value;
}
get delegate()
{
return this._delegate;
}
set delegate(delegate)
{
this._delegate = delegate;
}
get text()
{
var from = {line: this._range.startLine, ch: this._range.startColumn};
var to = {line: this._range.endLine, ch: this._range.endColumn};
return this._codeMirror.getRange(from, to);
}
set text(text)
{
var from = {line: this._range.startLine, ch: this._range.startColumn};
var to = {line: this._range.endLine, ch: this._range.endColumn};
this._codeMirror.replaceRange(text, from, to);
var lines = text.split("\n");
var endLine = this._range.startLine + lines.length - 1;
var endColumn = lines.length > 1 ? lines.lastValue.length : this._range.startColumn + text.length;
this._range = new WI.TextRange(this._range.startLine, this._range.startColumn, endLine, endColumn);
}
get initialValue()
{
// Implemented by subclasses.
return this.text;
}
get cssClassName()
{
// Implemented by subclasses.
return "";
}
get popover()
{
return this._popover;
}
get popoverPreferredEdges()
{
// Best to display the popover to the left or above the edited range since its end position may change, but not its start
// position. This way we minimize the chances of overlaying the edited range as it changes.
return [WI.RectEdge.MIN_X, WI.RectEdge.MIN_Y, WI.RectEdge.MAX_Y, WI.RectEdge.MAX_X];
}
popoverTargetFrameWithRects(rects)
{
return WI.Rect.unionOfRects(rects);
}
presentHoverMenu()
{
if (!this.cssClassName)
return;
this._hoverMenu = new WI.HoverMenu(this);
this._hoverMenu.element.classList.add(this.cssClassName);
this._rects = this._marker.rects;
this._hoverMenu.present(this._rects);
}
dismissHoverMenu(discrete)
{
if (!this._hoverMenu)
return;
this._hoverMenu.dismiss(discrete);
}
popoverWillPresent(popover)
{
// Implemented by subclasses.
}
popoverDidPresent(popover)
{
// Implemented by subclasses.
}
popoverDidDismiss(popover)
{
// Implemented by subclasses.
}
// Protected
handleKeydownEvent(event)
{
if (!this._keyboardShortcutEsc.matchesEvent(event) || !this._popover.visible)
return false;
this.value = this._originalValue;
this._popover.dismiss();
return true;
}
hoverMenuButtonWasPressed(hoverMenu)
{
this._popover = new WI.Popover(this);
this.popoverWillPresent(this._popover);
this._popover.present(this.popoverTargetFrameWithRects(this._rects).pad(2), this.popoverPreferredEdges);
this.popoverDidPresent(this._popover);
WI.addWindowKeydownListener(this);
hoverMenu.dismiss();
if (this._delegate && typeof this._delegate.editingControllerDidStartEditing === "function")
this._delegate.editingControllerDidStartEditing(this);
this._originalValue = this._value.copy();
}
didDismissPopover(popover)
{
delete this._popover;
delete this._originalValue;
WI.removeWindowKeydownListener(this);
this.popoverDidDismiss();
if (this._delegate && typeof this._delegate.editingControllerDidFinishEditing === "function")
this._delegate.editingControllerDidFinishEditing(this);
}
};
@@ -0,0 +1,78 @@
/*
* Copyright (C) 2014 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.CodeMirrorGradientEditingController = class CodeMirrorGradientEditingController extends WI.CodeMirrorEditingController
{
// Public
get initialValue()
{
return WI.Gradient.fromString(this.text);
}
get cssClassName()
{
return "gradient";
}
get popoverPreferredEdges()
{
// Since the gradient editor can resize to be quite tall, let's avoid displaying the popover
// above the edited value so that it may not change which edge it attaches to upon editing a stop.
return [WI.RectEdge.MIN_X, WI.RectEdge.MAX_Y, WI.RectEdge.MAX_X];
}
popoverTargetFrameWithRects(rects)
{
// If a gradient is defined across several lines, we probably want to use the first line only
// as a target frame for the editor since we may reformat the gradient value to fit on a single line.
return rects[0];
}
popoverWillPresent(popover)
{
function handleColorPickerToggled(event)
{
popover.update();
}
this._gradientEditor = new WI.GradientEditor;
this._gradientEditor.addEventListener(WI.GradientEditor.Event.GradientChanged, this._gradientEditorGradientChanged, this);
this._gradientEditor.addEventListener(WI.GradientEditor.Event.ColorPickerToggled, handleColorPickerToggled, this);
popover.content = this._gradientEditor.element;
}
popoverDidPresent(popover)
{
this._gradientEditor.gradient = this.value;
}
// Private
_gradientEditorGradientChanged(event)
{
this.value = event.data.gradient;
}
};
@@ -0,0 +1,58 @@
/*
* Copyright (C) 2016 Devin Rousso <webkit@devinrousso.com>. 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.CodeMirrorSpringEditingController = class CodeMirrorSpringEditingController extends WI.CodeMirrorEditingController
{
// Public
get initialValue()
{
return WI.Spring.fromString(this.text);
}
get cssClassName()
{
return "spring";
}
popoverWillPresent(popover)
{
this._springEditor = new WI.SpringEditor;
this._springEditor.addEventListener(WI.SpringEditor.Event.SpringChanged, this._springEditorSpringChanged, this);
popover.content = this._springEditor.element;
}
popoverDidPresent(popover)
{
this._springEditor.spring = this.value;
}
// Private
_springEditorSpringChanged(event)
{
this.value = event.data.spring;
}
};
@@ -0,0 +1,138 @@
/*
* Copyright (C) 2015 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.CodeMirrorTextKillController = class CodeMirrorTextKillController extends WI.Object
{
constructor(codeMirror)
{
super();
console.assert(codeMirror);
this._codeMirror = codeMirror;
this._expectingChangeEventForKill = false;
this._nextKillStartsNewSequence = true;
this._shouldPrependToKillRing = false;
this._handleTextChangeListener = this._handleTextChange.bind(this);
this._handleEditorBlurListener = this._handleEditorBlur.bind(this);
this._handleSelectionOrCaretChangeListener = this._handleSelectionOrCaretChange.bind(this);
// FIXME: these keybindings match CodeMirror's default keymap for OS X.
// They should probably be altered for Windows / Linux someday.
this._codeMirror.addKeyMap({
// Overrides for the 'emacsy' keymap.
"Ctrl-K": this._handleTextKillCommand.bind(this, "killLine", false),
"Alt-D": this._handleTextKillCommand.bind(this, "delWordAfter", false),
// Overrides for the 'macDefault' keymap.
"Alt-Delete": this._handleTextKillCommand.bind(this, "delGroupAfter", false),
"Cmd-Backspace": this._handleTextKillCommand.bind(this, "delWrappedLineLeft", true),
"Cmd-Delete": this._handleTextKillCommand.bind(this, "delWrappedLineRight", false),
"Alt-Backspace": this._handleTextKillCommand.bind(this, "delGroupBefore", true),
"Ctrl-Alt-Backspace": this._handleTextKillCommand.bind(this, "delGroupAfter", false),
});
}
_handleTextKillCommand(command, prependsToKillRing, codeMirror)
{
// Read-only mode is dynamic in some editors, so check every time
// and ignore the shortcut if in read-only mode.
if (this._codeMirror.getOption("readOnly"))
return;
this._shouldPrependToKillRing = prependsToKillRing;
// Don't add the listener if it's still registered because
// a previous empty kill didn't generate change events.
if (!this._expectingChangeEventForKill)
this._codeMirror.on("changes", this._handleTextChangeListener);
this._expectingChangeEventForKill = true;
this._codeMirror.execCommand(command);
}
_handleTextChange(codeMirror, changes)
{
this._codeMirror.off("changes", this._handleTextChangeListener);
// Sometimes a second change event fires after removing the listener
// if you perform an "empty kill" and type after moving the caret.
if (!this._expectingChangeEventForKill)
return;
this._expectingChangeEventForKill = false;
// It doesn't make sense to get more than one change per kill.
console.assert(changes.length === 1);
let change = changes[0];
// If an "empty kill" is followed by up/down or typing,
// the empty kill won't fire a change event, then we'll get an
// unrelated change event that shouldn't be treated as a kill.
if (change.origin !== "+delete")
return;
// When killed text includes a newline, CodeMirror returns
// strange change objects. Special-case for when this could happen.
let killedText;
if (change.to.line === change.from.line + 1 && change.removed.length === 2) {
// An entire line was deleted, including newline (deleteLine).
if (change.removed[0].length && !change.removed[1].length)
killedText = change.removed[0] + "\n";
// A newline was killed by itself (Ctrl-K).
else
killedText = "\n";
} else {
console.assert(change.removed.length === 1);
killedText = change.removed[0];
}
InspectorFrontendHost.killText(killedText, this._shouldPrependToKillRing, this._nextKillStartsNewSequence);
// If the editor loses focus or the caret / selection changes
// (not as a result of the kill), then the next kill should
// start a new kill ring sequence.
this._nextKillStartsNewSequence = false;
this._codeMirror.on("blur", this._handleEditorBlurListener);
this._codeMirror.on("cursorActivity", this._handleSelectionOrCaretChangeListener);
}
_handleEditorBlur(codeMirror)
{
this._nextKillStartsNewSequence = true;
this._codeMirror.off("blur", this._handleEditorBlurListener);
this._codeMirror.off("cursorActivity", this._handleSelectionOrCaretChangeListener);
}
_handleSelectionOrCaretChange(codeMirror)
{
if (this._expectingChangeEventForKill)
return;
this._nextKillStartsNewSequence = true;
this._codeMirror.off("blur", this._handleEditorBlurListener);
this._codeMirror.off("cursorActivity", this._handleSelectionOrCaretChangeListener);
}
};
@@ -0,0 +1,31 @@
/*
* Copyright (C) 2013 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.
*/
.CodeMirror .jump-to-symbol-highlight {
color: blue !important;
text-decoration: underline !important;
cursor: pointer !important;
-webkit-text-stroke-width: 0 !important;
}
@@ -0,0 +1,636 @@
/*
* Copyright (C) 2013 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.CodeMirrorTokenTrackingController = class CodeMirrorTokenTrackingController extends WI.Object
{
constructor(codeMirror, delegate)
{
super();
console.assert(codeMirror);
this._codeMirror = codeMirror;
this._delegate = delegate || null;
this._mode = WI.CodeMirrorTokenTrackingController.Mode.None;
this._mouseOverDelayDuration = 0;
this._mouseOutReleaseDelayDuration = 0;
this._classNameForHighlightedRange = null;
this._enabled = false;
this._tracking = false;
this._previousTokenInfo = null;
this._hoveredMarker = null;
this._ignoreNextMouseMove = false;
const hidePopover = this._hidePopover.bind(this);
this._codeMirror.addKeyMap({
"Cmd-Enter": this._handleCommandEnterKey.bind(this),
"Esc": hidePopover
});
this._codeMirror.on("cursorActivity", hidePopover);
}
// Public
get delegate()
{
return this._delegate;
}
set delegate(x)
{
this._delegate = x;
}
get enabled()
{
return this._enabled;
}
set enabled(enabled)
{
if (this._enabled === enabled)
return;
this._enabled = enabled;
var wrapper = this._codeMirror.getWrapperElement();
if (enabled) {
wrapper.addEventListener("mouseenter", this);
wrapper.addEventListener("mouseleave", this);
this._updateHoveredTokenInfo({left: WI.mouseCoords.x, top: WI.mouseCoords.y});
this._startTracking();
} else {
wrapper.removeEventListener("mouseenter", this);
wrapper.removeEventListener("mouseleave", this);
this._stopTracking();
}
}
get mode()
{
return this._mode;
}
set mode(mode)
{
var oldMode = this._mode;
this._mode = mode || WI.CodeMirrorTokenTrackingController.Mode.None;
if (oldMode !== this._mode && this._tracking && this._previousTokenInfo)
this._processNewHoveredToken(this._previousTokenInfo);
}
get mouseOverDelayDuration()
{
return this._mouseOverDelayDuration;
}
set mouseOverDelayDuration(x)
{
console.assert(x >= 0);
this._mouseOverDelayDuration = Math.max(x, 0);
}
get mouseOutReleaseDelayDuration()
{
return this._mouseOutReleaseDelayDuration;
}
set mouseOutReleaseDelayDuration(x)
{
console.assert(x >= 0);
this._mouseOutReleaseDelayDuration = Math.max(x, 0);
}
get classNameForHighlightedRange()
{
return this._classNameForHighlightedRange;
}
set classNameForHighlightedRange(x)
{
this._classNameForHighlightedRange = x || null;
}
get candidate()
{
return this._candidate;
}
get hoveredMarker()
{
return this._hoveredMarker;
}
set hoveredMarker(hoveredMarker)
{
this._hoveredMarker = hoveredMarker;
}
highlightLastHoveredRange()
{
if (this._candidate)
this.highlightRange(this._candidate.hoveredTokenRange);
}
highlightRange(range)
{
// Nothing to do if we're trying to highlight the same range.
if (this._codeMirrorMarkedText && this._codeMirrorMarkedText.className === this._classNameForHighlightedRange) {
var highlightedRange = this._codeMirrorMarkedText.find();
if (!highlightedRange)
return;
if (WI.compareCodeMirrorPositions(highlightedRange.from, range.start) === 0 &&
WI.compareCodeMirrorPositions(highlightedRange.to, range.end) === 0)
return;
}
this.removeHighlightedRange();
var className = this._classNameForHighlightedRange || "";
this._codeMirrorMarkedText = this._codeMirror.markText(range.start, range.end, {className});
window.addEventListener("mousemove", this, true);
}
removeHighlightedRange()
{
if (!this._codeMirrorMarkedText)
return;
this._codeMirrorMarkedText.clear();
this._codeMirrorMarkedText = null;
window.removeEventListener("mousemove", this, true);
}
// Private
_startTracking()
{
if (this._tracking)
return;
this._tracking = true;
this._ignoreNextMouseMove = false;
var wrapper = this._codeMirror.getWrapperElement();
wrapper.addEventListener("mousemove", this, true);
wrapper.addEventListener("mouseout", this, false);
wrapper.addEventListener("mousedown", this, false);
wrapper.addEventListener("mouseup", this, false);
wrapper.addEventListener("mousewheel", this, false);
window.addEventListener("blur", this, true);
}
_stopTracking()
{
if (!this._tracking)
return;
this._tracking = false;
this._candidate = null;
var wrapper = this._codeMirror.getWrapperElement();
wrapper.removeEventListener("mousemove", this, true);
wrapper.removeEventListener("mouseout", this, false);
wrapper.removeEventListener("mousedown", this, false);
wrapper.removeEventListener("mouseup", this, false);
wrapper.removeEventListener("mousewheel", this, false);
window.removeEventListener("blur", this, true);
window.removeEventListener("mousemove", this, true);
this._resetTrackingStates();
}
handleEvent(event)
{
switch (event.type) {
case "mouseenter":
this._mouseEntered(event);
break;
case "mouseleave":
this._mouseLeft(event);
break;
case "mousemove":
if (event.currentTarget === window)
this._mouseMovedWithMarkedText(event);
else
this._mouseMovedOverEditor(event);
break;
case "mouseout":
// Only deal with a mouseout event that has the editor wrapper as the target.
if (!event.currentTarget.contains(event.relatedTarget))
this._mouseMovedOutOfEditor(event);
break;
case "mousedown":
this._mouseButtonWasPressedOverEditor(event);
break;
case "mouseup":
this._mouseButtonWasReleasedOverEditor(event);
break;
case "mousewheel":
this._ignoreNextMouseMove = true;
break;
case "blur":
this._windowLostFocus(event);
break;
}
}
_handleCommandEnterKey(codeMirror)
{
const tokenInfo = this._getTokenInfoForPosition(codeMirror.getCursor("head"));
tokenInfo.triggeredBy = WI.CodeMirrorTokenTrackingController.TriggeredBy.Keyboard;
this._processNewHoveredToken(tokenInfo);
}
_hidePopover()
{
if (!this._candidate)
return CodeMirror.Pass;
if (this._delegate && typeof this._delegate.tokenTrackingControllerHighlightedRangeReleased === "function") {
const forceHidePopover = true;
this._delegate.tokenTrackingControllerHighlightedRangeReleased(this, forceHidePopover);
}
}
_mouseEntered(event)
{
if (!this._tracking)
this._startTracking();
}
_mouseLeft(event)
{
this._stopTracking();
}
_mouseMovedWithMarkedText(event)
{
if (this._candidate && this._candidate.triggeredBy === WI.CodeMirrorTokenTrackingController.TriggeredBy.Keyboard)
return;
var shouldRelease = !event.target.classList.contains(this._classNameForHighlightedRange);
if (shouldRelease && this._delegate && typeof this._delegate.tokenTrackingControllerCanReleaseHighlightedRange === "function")
shouldRelease = this._delegate.tokenTrackingControllerCanReleaseHighlightedRange(this, event.target);
if (shouldRelease) {
if (!this._markedTextMouseoutTimer)
this._markedTextMouseoutTimer = setTimeout(this._markedTextIsNoLongerHovered.bind(this), this._mouseOutReleaseDelayDuration);
return;
}
if (this._markedTextMouseoutTimer)
clearTimeout(this._markedTextMouseoutTimer);
this._markedTextMouseoutTimer = 0;
}
_markedTextIsNoLongerHovered()
{
if (this._delegate && typeof this._delegate.tokenTrackingControllerHighlightedRangeReleased === "function")
this._delegate.tokenTrackingControllerHighlightedRangeReleased(this);
this._markedTextMouseoutTimer = 0;
}
_mouseMovedOverEditor(event)
{
if (this._ignoreNextMouseMove) {
this._ignoreNextMouseMove = false;
return;
}
this._updateHoveredTokenInfo({left: event.pageX, top: event.pageY});
}
_updateHoveredTokenInfo(mouseCoords)
{
// Get the position in the text and the token at that position.
var position = this._codeMirror.coordsChar(mouseCoords);
var token = this._codeMirror.getTokenAt(position);
if (!token || !token.type || !token.string) {
if (this._hoveredMarker && this._delegate && typeof this._delegate.tokenTrackingControllerMouseOutOfHoveredMarker === "function") {
if (!this._codeMirror.findMarksAt(position).includes(this._hoveredMarker.codeMirrorTextMarker))
this._delegate.tokenTrackingControllerMouseOutOfHoveredMarker(this, this._hoveredMarker);
}
this._resetTrackingStates();
return;
}
// Stop right here if we're hovering the same token as we were last time.
if (this._previousTokenInfo &&
this._previousTokenInfo.position.line === position.line &&
this._previousTokenInfo.token.start === token.start &&
this._previousTokenInfo.token.end === token.end)
return;
// We have a new hovered token.
var tokenInfo = this._previousTokenInfo = this._getTokenInfoForPosition(position);
if (/\bmeta\b/.test(token.type)) {
let nextTokenPosition = Object.shallowCopy(position);
nextTokenPosition.ch = tokenInfo.token.end + 1;
let nextToken = this._codeMirror.getTokenAt(nextTokenPosition);
if (nextToken && nextToken.type && !/\bmeta\b/.test(nextToken.type)) {
console.assert(tokenInfo.token.end === nextToken.start);
tokenInfo.token.type = nextToken.type;
tokenInfo.token.string = tokenInfo.token.string + nextToken.string;
tokenInfo.token.end = nextToken.end;
}
} else {
let previousTokenPosition = Object.shallowCopy(position);
previousTokenPosition.ch = tokenInfo.token.start - 1;
let previousToken = this._codeMirror.getTokenAt(previousTokenPosition);
if (previousToken && previousToken.type && /\bmeta\b/.test(previousToken.type)) {
console.assert(tokenInfo.token.start === previousToken.end);
tokenInfo.token.string = previousToken.string + tokenInfo.token.string;
tokenInfo.token.start = previousToken.start;
}
}
if (this._tokenHoverTimer)
clearTimeout(this._tokenHoverTimer);
this._tokenHoverTimer = 0;
if (this._codeMirrorMarkedText || !this._mouseOverDelayDuration)
this._processNewHoveredToken(tokenInfo);
else
this._tokenHoverTimer = setTimeout(this._processNewHoveredToken.bind(this, tokenInfo), this._mouseOverDelayDuration);
}
_getTokenInfoForPosition(position)
{
var token = this._codeMirror.getTokenAt(position);
var innerMode = CodeMirror.innerMode(this._codeMirror.getMode(), token.state);
var codeMirrorModeName = innerMode.mode.alternateName || innerMode.mode.name;
return {
token,
position,
innerMode,
modeName: codeMirrorModeName
};
}
_mouseMovedOutOfEditor(event)
{
if (this._tokenHoverTimer)
clearTimeout(this._tokenHoverTimer);
this._tokenHoverTimer = 0;
this._previousTokenInfo = null;
this._selectionMayBeInProgress = false;
}
_mouseButtonWasPressedOverEditor(event)
{
this._selectionMayBeInProgress = true;
}
_mouseButtonWasReleasedOverEditor(event)
{
this._selectionMayBeInProgress = false;
this._mouseMovedOverEditor(event);
if (this._codeMirrorMarkedText && this._previousTokenInfo) {
var position = this._codeMirror.coordsChar({left: event.pageX, top: event.pageY});
var marks = this._codeMirror.findMarksAt(position);
for (var i = 0; i < marks.length; ++i) {
if (marks[i] === this._codeMirrorMarkedText) {
if (this._delegate && typeof this._delegate.tokenTrackingControllerHighlightedRangeWasClicked === "function")
this._delegate.tokenTrackingControllerHighlightedRangeWasClicked(this);
break;
}
}
}
}
_windowLostFocus(event)
{
this._resetTrackingStates();
}
_processNewHoveredToken(tokenInfo)
{
console.assert(tokenInfo);
if (this._selectionMayBeInProgress)
return;
this._candidate = null;
switch (this._mode) {
case WI.CodeMirrorTokenTrackingController.Mode.NonSymbolTokens:
this._candidate = this._processNonSymbolToken(tokenInfo);
break;
case WI.CodeMirrorTokenTrackingController.Mode.JavaScriptExpression:
case WI.CodeMirrorTokenTrackingController.Mode.JavaScriptTypeInformation:
this._candidate = this._processJavaScriptExpression(tokenInfo);
break;
case WI.CodeMirrorTokenTrackingController.Mode.MarkedTokens:
this._candidate = this._processMarkedToken(tokenInfo);
break;
}
if (!this._candidate)
return;
this._candidate.triggeredBy = tokenInfo.triggeredBy;
if (this._markedTextMouseoutTimer)
clearTimeout(this._markedTextMouseoutTimer);
this._markedTextMouseoutTimer = 0;
if (this._delegate && typeof this._delegate.tokenTrackingControllerNewHighlightCandidate === "function")
this._delegate.tokenTrackingControllerNewHighlightCandidate(this, this._candidate);
}
_processNonSymbolToken(tokenInfo)
{
// Ignore any symbol tokens.
var type = tokenInfo.token.type;
if (!type)
return null;
var startPosition = {line: tokenInfo.position.line, ch: tokenInfo.token.start};
var endPosition = {line: tokenInfo.position.line, ch: tokenInfo.token.end};
return {
hoveredToken: tokenInfo.token,
hoveredTokenRange: {start: startPosition, end: endPosition},
};
}
_processJavaScriptExpression(tokenInfo)
{
// Only valid within JavaScript.
if (tokenInfo.modeName !== "javascript")
return null;
var startPosition = {line: tokenInfo.position.line, ch: tokenInfo.token.start};
var endPosition = {line: tokenInfo.position.line, ch: tokenInfo.token.end};
function tokenIsInRange(token, range)
{
return token.line >= range.start.line && token.ch >= range.start.ch &&
token.line <= range.end.line && token.ch <= range.end.ch;
}
// If the hovered token is within a selection, use the selection as our expression.
if (this._codeMirror.somethingSelected()) {
var selectionRange = {
start: this._codeMirror.getCursor("start"),
end: this._codeMirror.getCursor("end")
};
if (tokenIsInRange(startPosition, selectionRange) || tokenIsInRange(endPosition, selectionRange)) {
return {
hoveredToken: tokenInfo.token,
hoveredTokenRange: selectionRange,
expression: this._codeMirror.getSelection(),
expressionRange: selectionRange,
};
}
}
// We only handle vars, definitions, properties, and the keyword 'this'.
var type = tokenInfo.token.type;
var isProperty = type.indexOf("property") !== -1;
var isKeyword = type.indexOf("keyword") !== -1;
if (!isProperty && !isKeyword && type.indexOf("variable") === -1 && type.indexOf("def") === -1)
return null;
// Not object literal property names, but yes if an object literal shorthand property, which is a variable.
let state = tokenInfo.innerMode.state;
if (isProperty && state.lexical && state.lexical.type === "}") {
// Peek ahead to see if the next token is "}" or ",". If it is, we are a shorthand and therefore a variable.
let shorthand = false;
let mode = tokenInfo.innerMode.mode;
let position = {line: tokenInfo.position.line, ch: tokenInfo.token.end};
WI.walkTokens(this._codeMirror, mode, position, function(tokenType, string) {
if (tokenType)
return false;
if (string === "(")
return false;
if (string === "," || string === "}") {
shorthand = true;
return false;
}
return true;
});
if (!shorthand)
return null;
}
// Only the "this" keyword.
if (isKeyword && tokenInfo.token.string !== "this")
return null;
// Work out the full hovered expression.
var expression = tokenInfo.token.string;
var expressionStartPosition = {line: tokenInfo.position.line, ch: tokenInfo.token.start};
while (true) {
var token = this._codeMirror.getTokenAt(expressionStartPosition);
if (!token)
break;
var isDot = !token.type && token.string === ".";
var isExpression = token.type && token.type.includes("m-javascript");
if (!isDot && !isExpression)
break;
if (isExpression) {
// Disallow operators. We want the hovered expression to be just a single operand.
// Also, some operators can modify values, such as pre-increment and assignment operators.
if (token.type.includes("operator"))
break;
// Don't break out of a template string quasi group.
if (token.type.includes("string-2"))
break;
}
expression = token.string + expression;
expressionStartPosition.ch = token.start;
}
// Return the candidate for this token and expression.
return {
hoveredToken: tokenInfo.token,
hoveredTokenRange: {start: startPosition, end: endPosition},
expression,
expressionRange: {start: expressionStartPosition, end: endPosition},
};
}
_processMarkedToken(tokenInfo)
{
return this._processNonSymbolToken(tokenInfo);
}
_resetTrackingStates()
{
if (this._tokenHoverTimer)
clearTimeout(this._tokenHoverTimer);
this._tokenHoverTimer = 0;
this._selectionMayBeInProgress = false;
this._previousTokenInfo = null;
this.removeHighlightedRange();
}
};
WI.CodeMirrorTokenTrackingController.JumpToSymbolHighlightStyleClassName = "jump-to-symbol-highlight";
WI.CodeMirrorTokenTrackingController.Mode = {
None: "none",
NonSymbolTokens: "non-symbol-tokens",
JavaScriptExpression: "javascript-expression",
JavaScriptTypeInformation: "javascript-type-information",
MarkedTokens: "marked-tokens"
};
WI.CodeMirrorTokenTrackingController.TriggeredBy = {
Keyboard: "keyboard",
Hover: "hover"
};
+301
View File
@@ -0,0 +1,301 @@
/*
* Copyright (C) 2013 Apple Inc. All rights reserved.
* Copyright (C) 2015 Tobias Reiss <tobi+webkit@basecode.de>
*
* 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.ConsoleManager = class ConsoleManager extends WI.Object
{
constructor()
{
super();
this._warningCount = 0;
this._errorCount = 0;
this._issues = [];
this._lastMessageLevel = null;
this._clearMessagesRequested = false;
this._isNewPageOrReload = false;
this._remoteObjectsToRelease = null;
this._customLoggingChannels = [];
this._snippets = new Set;
this._restoringSnippets = false;
WI.ConsoleSnippet.addEventListener(WI.SourceCode.Event.ContentDidChange, this._handleSnippetContentChanged, this);
WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
WI.Target.registerInitializationPromise((async () => {
let serializedSnippets = await WI.objectStores.consoleSnippets.getAll();
this._restoringSnippets = true;
for (let serializedSnippet of serializedSnippets) {
let snippet = WI.ConsoleSnippet.fromJSON(serializedSnippet);
const key = null;
WI.objectStores.consoleSnippets.associateObject(snippet, key, serializedSnippet);
this.addSnippet(snippet);
}
this._restoringSnippets = false;
})());
}
// Static
static supportsLogChannels()
{
return InspectorBackend.hasCommand("Console.getLoggingChannels");
}
static issueMatchSourceCode(issue, sourceCode)
{
if (sourceCode instanceof WI.SourceMapResource)
return issue.sourceCodeLocation && issue.sourceCodeLocation.displaySourceCode === sourceCode;
if (sourceCode instanceof WI.Resource)
return issue.url === sourceCode.url && (!issue.sourceCodeLocation || issue.sourceCodeLocation.sourceCode === sourceCode);
if (sourceCode instanceof WI.Script)
return issue.sourceCodeLocation && issue.sourceCodeLocation.sourceCode === sourceCode;
return false;
}
// Public
get warningCount() { return this._warningCount; }
get errorCount() { return this._errorCount; }
get snippets() { return this._snippets; }
get customLoggingChannels() { return this._customLoggingChannels; }
issuesForSourceCode(sourceCode)
{
var issues = [];
for (var i = 0; i < this._issues.length; ++i) {
var issue = this._issues[i];
if (WI.ConsoleManager.issueMatchSourceCode(issue, sourceCode))
issues.push(issue);
}
return issues;
}
releaseRemoteObjectWithConsoleClear(remoteObject)
{
if (!this._remoteObjectsToRelease)
this._remoteObjectsToRelease = new Set;
this._remoteObjectsToRelease.add(remoteObject);
}
addSnippet(snippet)
{
console.assert(snippet instanceof WI.ConsoleSnippet, snippet);
console.assert(!this._snippets.has(snippet), snippet);
console.assert(!this._snippets.some((existingSnippet) => snippet.contentIdentifier === existingSnippet.contentIdentifier), snippet);
this._snippets.add(snippet);
if (!this._restoringSnippets)
WI.objectStores.consoleSnippets.putObject(snippet);
this.dispatchEventToListeners(WI.ConsoleManager.Event.SnippetAdded, {snippet});
}
removeSnippet(snippet)
{
console.assert(snippet instanceof WI.ConsoleSnippet, snippet);
console.assert(this._snippets.has(snippet), snippet);
this._snippets.delete(snippet);
if (!this._restoringSnippets)
WI.objectStores.consoleSnippets.deleteObject(snippet);
this.dispatchEventToListeners(WI.ConsoleManager.Event.SnippetRemoved, {snippet});
}
// ConsoleObserver
messageWasAdded(target, source, level, text, type, url, line, column, repeatCount, parameters, stackTrace, requestId, timestamp)
{
// FIXME: Get a request from request ID.
if (parameters)
parameters = parameters.map((x) => WI.RemoteObject.fromPayload(x, target));
// COMPATIBILITY (macOS 13.0, iOS 16.0): `stackTrace` was an array of `Console.CallFrame`.
if (Array.isArray(stackTrace))
stackTrace = {callFrames: stackTrace};
if (stackTrace)
stackTrace = WI.StackTrace.fromPayload(target, stackTrace);
const request = null;
let message = new WI.ConsoleMessage(target, source, level, text, type, url, line, column, repeatCount, parameters, stackTrace, request, timestamp);
this._incrementMessageLevelCount(message.level, message.repeatCount);
this.dispatchEventToListeners(WI.ConsoleManager.Event.MessageAdded, {message});
if (message.level === WI.ConsoleMessage.MessageLevel.Warning || message.level === WI.ConsoleMessage.MessageLevel.Error) {
let issue = new WI.IssueMessage(message);
this._issues.push(issue);
this.dispatchEventToListeners(WI.ConsoleManager.Event.IssueAdded, {issue});
}
}
messagesCleared()
{
if (this._remoteObjectsToRelease) {
for (let remoteObject of this._remoteObjectsToRelease)
remoteObject.release();
this._remoteObjectsToRelease = null;
}
WI.ConsoleCommandResultMessage.clearMaximumSavedResultIndex();
if (this._clearMessagesRequested) {
// Frontend requested "clear console" and Backend successfully completed the request.
this._clearMessagesRequested = false;
this._warningCount = 0;
this._errorCount = 0;
this._issues = [];
this._lastMessageLevel = null;
this.dispatchEventToListeners(WI.ConsoleManager.Event.Cleared);
} else {
// Received an unrequested clear console event.
// This could be for a navigation or other reasons (like console.clear()).
// If this was a reload, we may not want to dispatch WI.ConsoleManager.Event.Cleared.
// To detect if this is a reload we wait a turn and check if there was a main resource change reload.
setTimeout(this._delayedMessagesCleared.bind(this), 0);
}
}
messageRepeatCountUpdated(count, timestamp)
{
this._incrementMessageLevelCount(this._lastMessageLevel, 1);
this.dispatchEventToListeners(WI.ConsoleManager.Event.PreviousMessageRepeatCountUpdated, {count, timestamp});
}
requestClearMessages()
{
this._clearMessagesRequested = true;
for (let target of WI.targets)
target.ConsoleAgent.clearMessages();
}
initializeLogChannels(target)
{
console.assert(target.hasDomain("Console"));
if (!WI.ConsoleManager.supportsLogChannels())
return;
if (this._customLoggingChannels.length)
return;
target.ConsoleAgent.getLoggingChannels((error, channels) => {
if (error)
return;
this._customLoggingChannels = channels.map(WI.LoggingChannel.fromPayload);
});
}
// Private
_incrementMessageLevelCount(level, count)
{
switch (level) {
case WI.ConsoleMessage.MessageLevel.Warning:
this._warningCount += count;
break;
case WI.ConsoleMessage.MessageLevel.Error:
this._errorCount += count;
break;
}
this._lastMessageLevel = level;
}
_delayedMessagesCleared()
{
if (this._isNewPageOrReload) {
this._isNewPageOrReload = false;
if (!WI.settings.clearLogOnNavigate.value)
return;
}
this._warningCount = 0;
this._errorCount = 0;
this._issues = [];
this._lastMessageLevel = null;
// A console.clear() or command line clear() happened.
this.dispatchEventToListeners(WI.ConsoleManager.Event.Cleared);
}
_handleSnippetContentChanged(event)
{
let snippet = event.target;
console.assert(this._snippets.has(snippet), snippet);
WI.objectStores.consoleSnippets.putObject(snippet);
}
_mainResourceDidChange(event)
{
console.assert(event.target instanceof WI.Frame);
if (!event.target.isMainFrame())
return;
this._isNewPageOrReload = true;
let timestamp = Date.now();
let wasReloaded = event.data.oldMainResource && event.data.oldMainResource.url === event.target.mainResource.url;
this.dispatchEventToListeners(WI.ConsoleManager.Event.SessionStarted, {timestamp, wasReloaded});
WI.ConsoleCommandResultMessage.clearMaximumSavedResultIndex();
}
};
WI.ConsoleManager.Event = {
SessionStarted: "console-manager-session-was-started",
Cleared: "console-manager-cleared",
MessageAdded: "console-manager-message-added",
IssueAdded: "console-manager-issue-added",
PreviousMessageRepeatCountUpdated: "console-manager-previous-message-repeat-count-updated",
SnippetAdded: "console-manager-snippet-added",
SnippetRemoved: "console-manager-snippet-removed",
};
File diff suppressed because it is too large Load Diff
+899
View File
@@ -0,0 +1,899 @@
/*
* Copyright (C) 2009, 2010 Google Inc. All rights reserved.
* Copyright (C) 2009 Joseph Pecoraro
* Copyright (C) 2013 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:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT
* OWNER OR 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.
*/
// FIXME: DOMManager lacks advanced multi-target support. (DOMNodes per-target)
WI.DOMManager = class DOMManager extends WI.Object
{
constructor()
{
super();
this._idToDOMNode = {};
this._document = null;
this._documentPromise = null;
this._attributeLoadNodeIds = {};
this._restoreSelectedNodeIsAllowed = true;
this._loadNodeAttributesTimeout = 0;
this._inspectedNode = null;
this._breakpointsForEventListeners = new Map;
this._hasRequestedDocument = false;
this._pendingDocumentRequestCallbacks = null;
WI.EventBreakpoint.addEventListener(WI.Breakpoint.Event.DisabledStateDidChange, this._handleEventBreakpointDisabledStateChanged, this);
WI.EventBreakpoint.addEventListener(WI.Breakpoint.Event.ConditionDidChange, this._handleEventBreakpointEditablePropertyChanged, this);
WI.EventBreakpoint.addEventListener(WI.Breakpoint.Event.IgnoreCountDidChange, this._handleEventBreakpointEditablePropertyChanged, this);
WI.EventBreakpoint.addEventListener(WI.Breakpoint.Event.AutoContinueDidChange, this._handleEventBreakpointEditablePropertyChanged, this);
WI.EventBreakpoint.addEventListener(WI.Breakpoint.Event.ActionsDidChange, this._handleEventBreakpointActionsChanged, this);
WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
}
// Target
initializeTarget(target)
{
// FIXME: This should be improved when adding better DOM multi-target support since it is really per-target.
// This currently uses a setTimeout since it doesn't need to happen immediately, and DOMManager uses the
// global DOMAgent to request the document, so we want to make sure we've transitioned the global agents
// to this target if necessary.
if (target.hasDomain("DOM")) {
setTimeout(() => {
this.ensureDocument();
});
if (WI.engineeringSettingsAllowed()) {
if (DOMManager.supportsEditingUserAgentShadowTrees({target}))
target.DOMAgent.setAllowEditingUserAgentShadowTrees(WI.settings.engineeringAllowEditingUserAgentShadowTrees.value);
}
}
}
transitionPageTarget()
{
this._documentUpdated();
}
// Static
static buildHighlightConfig(mode)
{
mode = mode || "all";
let highlightConfig = {showInfo: mode === "all"};
if (mode === "all" || mode === "content")
highlightConfig.contentColor = {r: 111, g: 168, b: 220, a: 0.66};
if (mode === "all" || mode === "padding")
highlightConfig.paddingColor = {r: 147, g: 196, b: 125, a: 0.66};
if (mode === "all" || mode === "border")
highlightConfig.borderColor = {r: 255, g: 229, b: 153, a: 0.66};
if (mode === "all" || mode === "margin")
highlightConfig.marginColor = {r: 246, g: 178, b: 107, a: 0.66};
return highlightConfig;
}
static wrapClientCallback(callback)
{
if (!callback)
return null;
return function(error, result) {
if (error)
console.error("Error during DOMAgent operation: " + error);
callback(error ? null : result);
};
}
static supportsEventListenerBreakpoints()
{
return InspectorBackend.hasCommand("DOM.setBreakpointForEventListener")
&& InspectorBackend.hasCommand("DOM.removeBreakpointForEventListener");
}
static supportsEventListenerBreakpointConfiguration()
{
// COMPATIBILITY (iOS 14): DOM.setBreakpointForEventListener did not have an "options" parameter yet.
return InspectorBackend.hasCommand("DOM.setBreakpointForEventListener", "options");
}
static supportsEditingUserAgentShadowTrees({frontendOnly, target} = {})
{
target = target || InspectorBackend;
return WI.settings.engineeringAllowEditingUserAgentShadowTrees.value
&& (frontendOnly || target.hasCommand("DOM.setAllowEditingUserAgentShadowTrees"));
}
// Public
get inspectedNode() { return this._inspectedNode; }
get eventListenerBreakpoints()
{
return Array.from(this._breakpointsForEventListeners.values());
}
*attachedNodes({filter} = {})
{
if (!this._document)
return;
filter ??= (node) => true;
// Traverse the node tree in the same order items would appear if the entire tree were expanded in order to
// provide a predictable order for the results.
let currentBranch = [this._document];
while (currentBranch.length) {
let currentNode = currentBranch.at(-1);
if (filter(currentNode))
yield currentNode;
// The `::before` pseudo element is the first child of any node.
let beforePseudoElement = currentNode.beforePseudoElement();
if (beforePseudoElement && filter(beforePseudoElement))
yield beforePseudoElement;
let firstChild = currentNode.children?.[0];
if (firstChild) {
currentBranch.push(firstChild);
continue;
}
while (currentBranch.length) {
let parent = currentBranch.pop();
// The `::after` pseudo element is the last child of any node.
let parentAfterPseudoElement = parent.afterPseudoElement();
if (parentAfterPseudoElement && filter(parentAfterPseudoElement))
yield parentAfterPseudoElement;
if (parent.nextSibling) {
currentBranch.push(parent.nextSibling);
break;
}
}
}
}
requestDocument(callback)
{
if (typeof callback !== "function")
return this._requestDocumentWithPromise();
this._requestDocumentWithCallback(callback);
}
ensureDocument()
{
this.requestDocument(function(){});
}
pushNodeToFrontend(objectId, callback)
{
let target = WI.assumingMainTarget();
this._dispatchWhenDocumentAvailable((callbackWrapper) => {
target.DOMAgent.requestNode(objectId, callbackWrapper);
}, callback);
}
pushNodeByPathToFrontend(path, callback)
{
let target = WI.assumingMainTarget();
this._dispatchWhenDocumentAvailable((callbackWrapper) => {
target.DOMAgent.pushNodeByPathToFrontend(path, callbackWrapper);
}, callback);
}
// DOMObserver
willDestroyDOMNode(nodeId)
{
let node = this._idToDOMNode[nodeId];
node.markDestroyed();
delete this._idToDOMNode[nodeId];
this.dispatchEventToListeners(WI.DOMManager.Event.NodeRemoved, {node});
}
didAddEventListener(nodeId)
{
let node = this._idToDOMNode[nodeId];
if (!node)
return;
node.dispatchEventToListeners(WI.DOMNode.Event.EventListenersChanged);
}
willRemoveEventListener(nodeId)
{
let node = this._idToDOMNode[nodeId];
if (!node)
return;
node.dispatchEventToListeners(WI.DOMNode.Event.EventListenersChanged);
}
didFireEvent(nodeId, eventName, timestamp, data)
{
let node = this._idToDOMNode[nodeId];
if (!node)
return;
node.didFireEvent(eventName, timestamp, data);
}
powerEfficientPlaybackStateChanged(nodeId, timestamp, isPowerEfficient)
{
let node = this._idToDOMNode[nodeId];
if (!node)
return;
node.powerEfficientPlaybackStateChanged(timestamp, isPowerEfficient);
}
// CSSObserver
nodeLayoutFlagsChanged(nodeId, layoutFlags)
{
let domNode = this._idToDOMNode[nodeId];
console.assert(domNode instanceof WI.DOMNode, domNode, nodeId);
if (!domNode)
return;
domNode.layoutFlags = layoutFlags;
}
// Private
_dispatchWhenDocumentAvailable(func, callback)
{
var callbackWrapper = DOMManager.wrapClientCallback(callback);
function onDocumentAvailable()
{
if (this._document)
func(callbackWrapper);
else {
if (callbackWrapper)
callbackWrapper("No document");
}
}
this.requestDocument(onDocumentAvailable.bind(this));
}
_requestDocumentWithPromise()
{
if (this._documentPromise)
return this._documentPromise.promise;
this._documentPromise = new WI.WrappedPromise;
if (this._document)
this._documentPromise.resolve(this._document);
else {
this._requestDocumentWithCallback((doc) => {
this._documentPromise.resolve(doc);
});
}
return this._documentPromise.promise;
}
_requestDocumentWithCallback(callback)
{
if (this._document) {
callback(this._document);
return;
}
if (this._pendingDocumentRequestCallbacks)
this._pendingDocumentRequestCallbacks.push(callback);
else
this._pendingDocumentRequestCallbacks = [callback];
if (this._hasRequestedDocument)
return;
if (!WI.pageTarget)
return;
if (!WI.pageTarget.hasDomain("DOM"))
return;
this._hasRequestedDocument = true;
WI.pageTarget.DOMAgent.getDocument((error, root) => {
if (!error)
this._setDocument(root);
for (let callback of this._pendingDocumentRequestCallbacks)
callback(this._document);
this._pendingDocumentRequestCallbacks = null;
});
}
_attributeModified(nodeId, name, value)
{
var node = this._idToDOMNode[nodeId];
if (!node)
return;
node._setAttribute(name, value);
this.dispatchEventToListeners(WI.DOMManager.Event.AttributeModified, {node, name});
node.dispatchEventToListeners(WI.DOMNode.Event.AttributeModified, {name});
}
_attributeRemoved(nodeId, name)
{
var node = this._idToDOMNode[nodeId];
if (!node)
return;
node._removeAttribute(name);
this.dispatchEventToListeners(WI.DOMManager.Event.AttributeRemoved, {node, name});
node.dispatchEventToListeners(WI.DOMNode.Event.AttributeRemoved, {name});
}
_inlineStyleInvalidated(nodeIds)
{
for (var nodeId of nodeIds)
this._attributeLoadNodeIds[nodeId] = true;
if (this._loadNodeAttributesTimeout)
return;
this._loadNodeAttributesTimeout = setTimeout(this._loadNodeAttributes.bind(this), 0);
}
_loadNodeAttributes()
{
function callback(nodeId, error, attributes)
{
if (error) {
console.error("Error during DOMAgent operation: " + error);
return;
}
var node = this._idToDOMNode[nodeId];
if (node) {
node._setAttributesPayload(attributes);
this.dispatchEventToListeners(WI.DOMManager.Event.AttributeModified, {node, name: "style"});
node.dispatchEventToListeners(WI.DOMNode.Event.AttributeModified, {name: "style"});
}
}
this._loadNodeAttributesTimeout = 0;
let target = WI.assumingMainTarget();
for (var nodeId in this._attributeLoadNodeIds) {
if (!(nodeId in this._idToDOMNode))
continue;
var nodeIdAsNumber = parseInt(nodeId);
target.DOMAgent.getAttributes(nodeIdAsNumber, callback.bind(this, nodeIdAsNumber));
}
this._attributeLoadNodeIds = {};
}
_characterDataModified(nodeId, newValue)
{
var node = this._idToDOMNode[nodeId];
node._nodeValue = newValue;
this.dispatchEventToListeners(WI.DOMManager.Event.CharacterDataModified, {node});
}
nodeForId(nodeId)
{
return this._idToDOMNode[nodeId] || null;
}
_documentUpdated()
{
this._setDocument(null);
}
_setDocument(payload)
{
for (let node of Object.values(this._idToDOMNode))
node.markDestroyed();
this._idToDOMNode = {};
for (let breakpoint of this._breakpointsForEventListeners.values())
WI.domDebuggerManager.dispatchEventToListeners(WI.DOMDebuggerManager.Event.EventBreakpointRemoved, {breakpoint});
this._breakpointsForEventListeners.clear();
let newDocument = null;
if (payload && "nodeId" in payload)
newDocument = new WI.DOMNode(this, null, false, payload);
if (this._document === newDocument)
return;
this._document = newDocument;
// Force the promise to be recreated so that it resolves to the new document.
this._documentPromise = null;
if (!this._document)
this._hasRequestedDocument = false;
this.dispatchEventToListeners(WI.DOMManager.Event.DocumentUpdated, {document: this._document});
}
_setDetachedRoot(payload)
{
new WI.DOMNode(this, null, false, payload);
}
_setChildNodes(parentId, payloads)
{
if (!parentId && payloads.length) {
this._setDetachedRoot(payloads[0]);
return;
}
var parent = this._idToDOMNode[parentId];
if (parent.children) {
for (let node of parent.children)
this.dispatchEventToListeners(WI.DOMManager.Event.NodeRemoved, {node, parent});
}
parent._setChildrenPayload(payloads);
for (let node of parent.children)
this.dispatchEventToListeners(WI.DOMManager.Event.NodeInserted, {node, parent});
}
_childNodeCountUpdated(nodeId, newValue)
{
var node = this._idToDOMNode[nodeId];
node.childNodeCount = newValue;
this.dispatchEventToListeners(WI.DOMManager.Event.ChildNodeCountUpdated, node);
}
_childNodeInserted(parentId, prevId, payload)
{
var parent = this._idToDOMNode[parentId];
var prev = this._idToDOMNode[prevId];
var node = parent._insertChild(prev, payload);
this._idToDOMNode[node.id] = node;
this.dispatchEventToListeners(WI.DOMManager.Event.NodeInserted, {node, parent});
}
_childNodeRemoved(parentId, nodeId)
{
var parent = this._idToDOMNode[parentId];
var node = this._idToDOMNode[nodeId];
parent._removeChild(node);
this._unbind(node);
this.dispatchEventToListeners(WI.DOMManager.Event.NodeRemoved, {node, parent});
}
_customElementStateChanged(elementId, newState)
{
const node = this._idToDOMNode[elementId];
node._customElementState = newState;
this.dispatchEventToListeners(WI.DOMManager.Event.CustomElementStateChanged, {node});
}
_pseudoElementAdded(parentId, pseudoElement)
{
var parent = this._idToDOMNode[parentId];
if (!parent)
return;
var node = new WI.DOMNode(this, parent.ownerDocument, false, pseudoElement);
node.parentNode = parent;
this._idToDOMNode[node.id] = node;
console.assert(!parent.pseudoElements().get(node.pseudoType()));
parent.pseudoElements().set(node.pseudoType(), node);
this.dispatchEventToListeners(WI.DOMManager.Event.NodeInserted, {node, parent});
}
_pseudoElementRemoved(parentId, pseudoElementId)
{
var pseudoElement = this._idToDOMNode[pseudoElementId];
if (!pseudoElement)
return;
var parent = pseudoElement.parentNode;
console.assert(parent);
console.assert(parent.id === parentId);
if (!parent)
return;
parent._removeChild(pseudoElement);
this._unbind(pseudoElement);
this.dispatchEventToListeners(WI.DOMManager.Event.NodeRemoved, {node: pseudoElement, parent});
}
_unbind(node)
{
node.markDestroyed();
delete this._idToDOMNode[node.id];
for (let i = 0; node.children && i < node.children.length; ++i)
this._unbind(node.children[i]);
let templateContent = node.templateContent();
if (templateContent)
this._unbind(templateContent);
for (let pseudoElement of node.pseudoElements().values())
this._unbind(pseudoElement);
// FIXME: Handle shadow roots.
}
get restoreSelectedNodeIsAllowed()
{
return this._restoreSelectedNodeIsAllowed;
}
inspectElement(nodeId, options = {})
{
var node = this._idToDOMNode[nodeId];
if (!node || !node.ownerDocument)
return;
// This code path is hit by "Reveal in DOM Tree" and clicking element links/console widgets.
// Unless overridden by callers, assume that this is navigation is initiated by a Inspect mode.
let initiatorHint = options.initiatorHint || WI.TabBrowser.TabNavigationInitiator.Inspect;
this.dispatchEventToListeners(WI.DOMManager.Event.DOMNodeWasInspected, {node, initiatorHint});
this._inspectModeEnabled = false;
this.dispatchEventToListeners(WI.DOMManager.Event.InspectModeStateChanged);
}
inspectNodeObject(remoteObject)
{
this._restoreSelectedNodeIsAllowed = false;
function nodeAvailable(nodeId)
{
remoteObject.release();
console.assert(nodeId);
if (!nodeId)
return;
this.inspectElement(nodeId);
// Re-resolve the node in the console's object group when adding to the console.
let domNode = this.nodeForId(nodeId);
WI.RemoteObject.resolveNode(domNode, WI.RuntimeManager.ConsoleObjectGroup).then((remoteObject) => {
WI.consoleLogViewController.appendImmediateExecutionWithResult(WI.UIString("Selected Element"), remoteObject, {addSpecialUserLogClass: true});
});
}
remoteObject.pushNodeToFrontend(nodeAvailable.bind(this));
}
highlightDOMNodeList(nodes, mode)
{
if (this._hideDOMNodeHighlightTimeout) {
clearTimeout(this._hideDOMNodeHighlightTimeout);
this._hideDOMNodeHighlightTimeout = undefined;
}
let nodeIds = [];
for (let node of nodes) {
console.assert(node instanceof WI.DOMNode, node);
console.assert(!node.destroyed, node);
if (node.destroyed)
continue;
nodeIds.push(node.id);
}
let target = WI.assumingMainTarget();
target.DOMAgent.highlightNodeList(nodeIds, DOMManager.buildHighlightConfig(mode));
}
highlightSelector(selectorText, frameId, mode)
{
if (this._hideDOMNodeHighlightTimeout) {
clearTimeout(this._hideDOMNodeHighlightTimeout);
this._hideDOMNodeHighlightTimeout = undefined;
}
let target = WI.assumingMainTarget();
target.DOMAgent.highlightSelector(DOMManager.buildHighlightConfig(mode), selectorText, frameId);
}
highlightRect(rect, usePageCoordinates)
{
let target = WI.assumingMainTarget();
target.DOMAgent.highlightRect.invoke({
x: rect.x,
y: rect.y,
width: rect.width,
height: rect.height,
color: {r: 111, g: 168, b: 220, a: 0.66},
outlineColor: {r: 255, g: 229, b: 153, a: 0.66},
usePageCoordinates
});
}
hideDOMNodeHighlight()
{
for (let target of WI.targets) {
if (target.hasCommand("DOM.hideHighlight"))
target.DOMAgent.hideHighlight();
}
}
highlightDOMNodeForTwoSeconds(nodeId)
{
let node = this._idToDOMNode[nodeId];
if (!node)
return;
node.highlight();
this._hideDOMNodeHighlightTimeout = setTimeout(this.hideDOMNodeHighlight.bind(this), 2000);
}
get inspectModeEnabled()
{
return this._inspectModeEnabled;
}
set inspectModeEnabled(enabled)
{
if (enabled === this._inspectModeEnabled)
return;
let target = WI.assumingMainTarget();
let commandArguments = {
enabled,
highlightConfig: DOMManager.buildHighlightConfig(),
showRulers: WI.settings.showRulersDuringElementSelection.value,
};
target.DOMAgent.setInspectModeEnabled.invoke(commandArguments, (error) => {
if (error) {
WI.reportInternalError(error);
return;
}
this._inspectModeEnabled = enabled;
this.dispatchEventToListeners(WI.DOMManager.Event.InspectModeStateChanged);
});
}
setInspectedNode(node)
{
console.assert(node instanceof WI.DOMNode);
if (node === this._inspectedNode)
return;
console.assert(!node.destroyed, node);
if (node.destroyed)
return;
let callback = (error) => {
console.assert(!error, error);
if (error)
return;
let lastInspectedNode = this._inspectedNode;
this._inspectedNode = node;
this.dispatchEventToListeners(WI.DOMManager.Event.InspectedNodeChanged, {lastInspectedNode});
};
let target = WI.assumingMainTarget();
target.DOMAgent.setInspectedNode(node.id, callback);
}
getSupportedEventNames(callback)
{
let target = WI.assumingMainTarget();
if (!target.hasCommand("DOM.getSupportedEventNames"))
return Promise.resolve(new Set);
if (!this._getSupportedEventNamesPromise) {
this._getSupportedEventNamesPromise = target.DOMAgent.getSupportedEventNames()
.then(({eventNames}) => new Set(eventNames));
}
return this._getSupportedEventNamesPromise;
}
setEventListenerDisabled(eventListener, disabled)
{
let target = WI.assumingMainTarget();
target.DOMAgent.setEventListenerDisabled(eventListener.eventListenerId, disabled);
}
setBreakpointForEventListener(eventListener)
{
let breakpoint = this._breakpointsForEventListeners.get(eventListener.eventListenerId);
if (breakpoint) {
console.assert(breakpoint.disabled);
breakpoint.disabled = false;
return;
}
breakpoint = new WI.EventBreakpoint(WI.EventBreakpoint.Type.Listener, {eventName: eventListener.type, eventListener});
console.assert(!breakpoint.disabled);
this._breakpointsForEventListeners.set(eventListener.eventListenerId, breakpoint);
for (let target of WI.targets) {
if (target.hasDomain("DOM"))
this._setEventBreakpoint(breakpoint, target);
}
WI.debuggerManager.addProbesForBreakpoint(breakpoint);
WI.domDebuggerManager.dispatchEventToListeners(WI.DOMDebuggerManager.Event.EventBreakpointAdded, {breakpoint});
}
removeBreakpointForEventListener(eventListener)
{
let breakpoint = this._breakpointsForEventListeners.take(eventListener.eventListenerId);
if (!breakpoint)
return;
// Disable the breakpoint first, so removing actions doesn't re-add the breakpoint.
breakpoint.disabled = true;
breakpoint.clearActions();
WI.debuggerManager.removeProbesForBreakpoint(breakpoint);
WI.domDebuggerManager.dispatchEventToListeners(WI.DOMDebuggerManager.Event.EventBreakpointRemoved, {breakpoint});
}
removeEventListenerBreakpointsForNode(domNode)
{
for (let breakpoint of Array.from(this._breakpointsForEventListeners.values())) {
let eventListener = breakpoint.eventListener;
if (eventListener.nodeId === domNode.id)
this.removeBreakpointForEventListener(eventListener);
}
}
breakpointForEventListenerId(eventListenerId)
{
return this._breakpointsForEventListeners.get(eventListenerId) || null;
}
// Private
_setEventBreakpoint(breakpoint, target)
{
console.assert(!breakpoint.disabled, breakpoint);
let eventListener = breakpoint.eventListener;
console.assert(eventListener);
if (!WI.debuggerManager.breakpointsDisabledTemporarily)
WI.debuggerManager.breakpointsEnabled = true;
target.DOMAgent.setBreakpointForEventListener.invoke({
eventListenerId: eventListener.eventListenerId,
options: breakpoint.optionsToProtocol(),
});
}
_removeEventBreakpoint(breakpoint, target)
{
let eventListener = breakpoint.eventListener;
console.assert(eventListener);
target.DOMAgent.removeBreakpointForEventListener(eventListener.eventListenerId);
}
_handleEventBreakpointDisabledStateChanged(event)
{
let breakpoint = event.target;
// Non-specific event listener breakpoints are handled by `DOMDebuggerManager`.
if (!breakpoint.eventListener)
return;
for (let target of WI.targets) {
if (!target.hasDomain("DOM"))
continue;
if (breakpoint.disabled)
this._removeEventBreakpoint(breakpoint, target);
else
this._setEventBreakpoint(breakpoint, target);
}
}
_handleEventBreakpointEditablePropertyChanged(event)
{
let breakpoint = event.target;
// Non-specific event listener breakpoints are handled by `DOMDebuggerManager`.
if (!breakpoint.eventListener)
return;
if (breakpoint.disabled)
return;
for (let target of WI.targets) {
// Clear the old breakpoint from the backend before setting the new one.
this._removeEventBreakpoint(breakpoint, target);
this._setEventBreakpoint(breakpoint, target);
}
}
_handleEventBreakpointActionsChanged(event)
{
let breakpoint = event.target;
// Non-specific event listener breakpoints are handled by `DOMDebuggerManager`.
if (!breakpoint.eventListener)
return;
this._handleEventBreakpointEditablePropertyChanged(event);
WI.debuggerManager.updateProbesForBreakpoint(breakpoint);
}
_mainResourceDidChange(event)
{
if (!event.target.isMainFrame())
return;
this._restoreSelectedNodeIsAllowed = true;
this.ensureDocument();
WI.DOMNode.resetDefaultLayoutOverlayConfiguration();
}
};
WI.DOMManager.Event = {
AttributeModified: "dom-manager-attribute-modified",
AttributeRemoved: "dom-manager-attribute-removed",
CharacterDataModified: "dom-manager-character-data-modified",
NodeInserted: "dom-manager-node-inserted",
NodeRemoved: "dom-manager-node-removed",
CustomElementStateChanged: "dom-manager-custom-element-state-changed",
DocumentUpdated: "dom-manager-document-updated",
ChildNodeCountUpdated: "dom-manager-child-node-count-updated",
DOMNodeWasInspected: "dom-manager-dom-node-was-inspected",
InspectModeStateChanged: "dom-manager-inspect-mode-state-changed",
InspectedNodeChanged: "dom-manager-inspected-node-changed",
};
+258
View File
@@ -0,0 +1,258 @@
/*
* Copyright (C) 2013 Apple Inc. All rights reserved.
* Copyright (C) 2013 Samsung Electronics. 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.
*/
// FIXME: DOMStorageManager lacks advanced multi-target support. (DOMStorage per-target)
WI.DOMStorageManager = class DOMStorageManager extends WI.Object
{
constructor()
{
super();
this._enabled = false;
this._reset();
}
// Agent
get domains() { return ["DOMStorage"]; }
activateExtraDomain(domain)
{
// COMPATIBILITY (iOS 14.0): Inspector.activateExtraDomains was removed in favor of a declared debuggable type
console.assert(domain === "DOMStorage");
for (let target of WI.targets)
this.initializeTarget(target);
}
// Target
initializeTarget(target)
{
if (!this._enabled)
return;
if (target.hasDomain("DOMStorage"))
target.DOMStorageAgent.enable();
}
// Public
get domStorageObjects() { return this._domStorageObjects; }
get cookieStorageObjects()
{
var cookieStorageObjects = [];
for (var host in this._cookieStorageObjects)
cookieStorageObjects.push(this._cookieStorageObjects[host]);
return cookieStorageObjects;
}
enable()
{
console.assert(!this._enabled);
this._enabled = true;
this._reset();
for (let target of WI.targets)
this.initializeTarget(target);
WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
WI.Frame.addEventListener(WI.Frame.Event.SecurityOriginDidChange, this._securityOriginDidChange, this);
}
disable()
{
console.assert(this._enabled);
this._enabled = false;
for (let target of WI.targets) {
if (target.hasDomain("DOMStorage"))
target.DOMStorageAgent.disable();
}
WI.Frame.removeEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
WI.Frame.removeEventListener(WI.Frame.Event.SecurityOriginDidChange, this._securityOriginDidChange, this);
this._reset();
}
// DOMStorageObserver
itemsCleared(storageId)
{
console.assert(this._enabled);
let domStorage = this._domStorageForIdentifier(storageId);
if (domStorage)
domStorage.itemsCleared(storageId);
}
itemRemoved(storageId, key)
{
console.assert(this._enabled);
let domStorage = this._domStorageForIdentifier(storageId);
if (domStorage)
domStorage.itemRemoved(key);
}
itemAdded(storageId, key, value)
{
console.assert(this._enabled);
let domStorage = this._domStorageForIdentifier(storageId);
if (domStorage)
domStorage.itemAdded(key, value);
}
itemUpdated(storageId, key, oldValue, newValue)
{
console.assert(this._enabled);
let domStorage = this._domStorageForIdentifier(storageId);
if (domStorage)
domStorage.itemUpdated(key, oldValue, newValue);
}
// InspectorObserver
inspectDOMStorage(id)
{
console.assert(this._enabled);
var domStorage = this._domStorageForIdentifier(id);
console.assert(domStorage);
if (!domStorage)
return;
this.dispatchEventToListeners(WI.DOMStorageManager.Event.DOMStorageObjectWasInspected, {domStorage});
}
// Private
_reset()
{
this._domStorageObjects = [];
this._cookieStorageObjects = {};
this.dispatchEventToListeners(DOMStorageManager.Event.Cleared);
let mainFrame = WI.networkManager.mainFrame;
if (mainFrame) {
this._addDOMStorageIfNeeded(mainFrame);
this._addCookieStorageIfNeeded(mainFrame);
}
}
_domStorageForIdentifier(id)
{
for (var storageObject of this._domStorageObjects) {
// The id is an object, so we need to compare the properties using Object.shallowEqual.
if (Object.shallowEqual(storageObject.id, id))
return storageObject;
}
return null;
}
_addDOMStorageIfNeeded(frame)
{
if (!this._enabled)
return;
if (!InspectorBackend.hasDomain("DOMStorage"))
return;
// Don't show storage if we don't have a security origin (about:blank).
if (!frame.securityOrigin || frame.securityOrigin === "://")
return;
// FIXME: Consider passing the other parts of the origin along.
let addDOMStorage = (isLocalStorage) => {
let identifier = {securityOrigin: frame.securityOrigin, isLocalStorage};
if (this._domStorageForIdentifier(identifier))
return;
let domStorage = new WI.DOMStorageObject(identifier, frame.mainResource.urlComponents.host, identifier.isLocalStorage);
this._domStorageObjects.push(domStorage);
this.dispatchEventToListeners(DOMStorageManager.Event.DOMStorageObjectWasAdded, {domStorage});
};
addDOMStorage(true);
addDOMStorage(false);
}
_addCookieStorageIfNeeded(frame)
{
if (!this._enabled)
return;
if (!InspectorBackend.hasCommand("Page.getCookies"))
return;
// Add the host of the frame that changed the main resource to the list of hosts there could be cookies for.
let host = parseURL(frame.url).host;
if (!host)
return;
if (this._cookieStorageObjects[host])
return;
this._cookieStorageObjects[host] = new WI.CookieStorageObject(host);
this.dispatchEventToListeners(WI.DOMStorageManager.Event.CookieStorageObjectWasAdded, {cookieStorage: this._cookieStorageObjects[host]});
}
_mainResourceDidChange(event)
{
console.assert(event.target instanceof WI.Frame);
if (event.target.isMainFrame()) {
this._reset();
return;
}
this._addCookieStorageIfNeeded(event.target);
}
_securityOriginDidChange(event)
{
console.assert(event.target instanceof WI.Frame);
this._addDOMStorageIfNeeded(event.target);
}
};
WI.DOMStorageManager.Event = {
CookieStorageObjectWasAdded: "dom-storage-manager-cookie-storage-object-was-added",
DOMStorageObjectWasAdded: "dom-storage-manager-dom-storage-object-was-added",
DOMStorageObjectWasInspected: "dom-storage-manager-dom-storage-object-was-inspected",
Cleared: "dom-storage-manager-cleared",
};
+155
View File
@@ -0,0 +1,155 @@
/*
* Copyright (C) 2013 Apple Inc. All rights reserved.
* Copyright (C) 2013 Samsung Electronics. 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.
*/
// FIXME: DatabaseManager lacks advanced multi-target support. (DataBase per-target)
WI.DatabaseManager = class DatabaseManager extends WI.Object
{
constructor()
{
super();
this._enabled = false;
this._reset();
}
// Agent
get domains() { return ["Database"]; }
activateExtraDomain(domain)
{
// COMPATIBILITY (iOS 14.0): Inspector.activateExtraDomains was removed in favor of a declared debuggable type
console.assert(domain === "Database");
for (let target of WI.targets)
this.initializeTarget(target);
}
// Target
initializeTarget(target)
{
if (!this._enabled)
return;
if (target.hasDomain("Database"))
target.DatabaseAgent.enable();
}
// Public
get databases() { return this._databaseObjects; }
enable()
{
console.assert(!this._enabled);
this._enabled = true;
this._reset();
for (let target of WI.targets)
this.initializeTarget(target);
WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
}
disable()
{
console.assert(this._enabled);
this._enabled = false;
for (let target of WI.targets) {
if (target.hasDomain("Database"))
target.DatabaseAgent.disable();
}
WI.Frame.removeEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
this._reset();
}
// DatabaseObserver
databaseWasAdded(id, host, name, version)
{
console.assert(this._enabled);
var database = new WI.DatabaseObject(id, host, name, version);
this._databaseObjects.push(database);
this.dispatchEventToListeners(WI.DatabaseManager.Event.DatabaseWasAdded, {database});
}
// InspectorObserver
inspectDatabase(id)
{
console.assert(this._enabled);
var database = this._databaseForIdentifier(id);
console.assert(database);
if (!database)
return;
this.dispatchEventToListeners(WI.DatabaseManager.Event.DatabaseWasInspected, {database});
}
// Private
_reset()
{
this._databaseObjects = [];
this.dispatchEventToListeners(WI.DatabaseManager.Event.Cleared);
}
_mainResourceDidChange(event)
{
console.assert(event.target instanceof WI.Frame);
if (event.target.isMainFrame())
this._reset();
}
_databaseForIdentifier(id)
{
for (var i = 0; i < this._databaseObjects.length; ++i) {
if (this._databaseObjects[i].id === id)
return this._databaseObjects[i];
}
return null;
}
};
WI.DatabaseManager.Event = {
DatabaseWasAdded: "database-manager-database-was-added",
DatabaseWasInspected: "database-manager-database-was-inspected",
Cleared: "database-manager-cleared",
};
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,95 @@
/*
* Copyright (C) 2019 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.DiagnosticController = class DiagnosticController
{
constructor()
{
this._diagnosticLoggingAvailable = false;
this._recorders = new Set;
this._autoLogDiagnosticEventsToConsole = WI.settings.debugAutoLogDiagnosticEvents.value;
this._logToConsoleMethod = window.InspectorTest ? InspectorTest.log.bind(InspectorTest) : console.log;
WI.settings.debugEnableDiagnosticLogging.addEventListener(WI.Setting.Event.Changed, this._debugEnableDiagnosticLoggingSettingDidChange, this);
WI.settings.debugAutoLogDiagnosticEvents.addEventListener(WI.Setting.Event.Changed, this._debugAutoLogDiagnosticEventsSettingDidChange, this);
}
// Public
get diagnosticLoggingAvailable()
{
return this._diagnosticLoggingAvailable;
}
set diagnosticLoggingAvailable(available)
{
if (this._diagnosticLoggingAvailable === available)
return;
this._diagnosticLoggingAvailable = available;
this._updateRecorderStates();
}
addRecorder(recorder)
{
console.assert(!this._recorders.has(recorder), "Tried to add the same diagnostic recorder more than once.");
this._recorders.add(recorder);
this._updateRecorderStates();
}
logDiagnosticEvent(eventName, payload)
{
// Don't rely on a diagnostic logging delegate to unit test frontend diagnostics code.
if (window.InspectorTest) {
this._logToConsoleMethod(`Received diagnostic event: ${eventName} => ${JSON.stringify(payload)}`);
return;
}
if (this._autoLogDiagnosticEventsToConsole)
this._logToConsoleMethod(eventName, payload);
InspectorFrontendHost.logDiagnosticEvent(eventName, JSON.stringify(payload));
}
// Private
_debugEnableDiagnosticLoggingSettingDidChange()
{
this._updateRecorderStates();
}
_debugAutoLogDiagnosticEventsSettingDidChange()
{
this._autoLogDiagnosticEventsToConsole = WI.settings.debugAutoLogDiagnosticEvents.value;
}
_updateRecorderStates()
{
let isActive = this._diagnosticLoggingAvailable && WI.settings.debugEnableDiagnosticLogging.value;
for (let recorder of this._recorders)
recorder.active = isActive;
}
};
@@ -0,0 +1,76 @@
/*
* Copyright (C) 2019 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.DiagnosticEventRecorder = class DiagnosticEventRecorder
{
constructor(name, controller)
{
console.assert(controller instanceof WI.DiagnosticController, controller);
this._name = name;
this._active = false;
this._controller = controller;
}
// Public
get name() { return this._name; }
get active()
{
return this._active;
}
set active(value)
{
if (this._active === value)
return;
this._active = value;
if (this._active)
this.setup();
else
this.teardown();
}
// Protected
logDiagnosticEvent(eventName, payload)
{
if (this._active)
this._controller.logDiagnosticEvent(eventName, payload);
}
setup()
{
throw WI.NotImplementedError.subclassMustOverride();
}
teardown()
{
throw WI.NotImplementedError.subclassMustOverride();
}
};
@@ -0,0 +1,238 @@
/*
* Copyright (C) 2014 Antoine Quint
*
* 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.DragToAdjustController = class DragToAdjustController
{
constructor(delegate)
{
this._delegate = delegate;
this._element = null;
this._active = false;
this._enabled = false;
this._dragging = false;
this._tracksMouseClickAndDrag = false;
}
// Public
get element()
{
return this._element;
}
set element(element)
{
this._element = element;
}
get enabled()
{
return this._enabled;
}
set enabled(enabled)
{
if (this._enabled === enabled)
return;
if (enabled) {
this._element.addEventListener("mouseenter", this);
this._element.addEventListener("mouseleave", this);
} else {
this._element.removeEventListener("mouseenter", this);
this._element.removeEventListener("mouseleave", this);
}
}
get active()
{
return this._active;
}
set active(active)
{
if (!this._element)
return;
if (this._active === active)
return;
if (active) {
WI.notifications.addEventListener(WI.Notification.GlobalModifierKeysDidChange, this._modifiersDidChange, this);
this._element.addEventListener("mousemove", this);
} else {
WI.notifications.removeEventListener(WI.Notification.GlobalModifierKeysDidChange, this._modifiersDidChange, this);
this._element.removeEventListener("mousemove", this);
this._setTracksMouseClickAndDrag(false);
}
this._active = active;
if (this._delegate && typeof this._delegate.dragToAdjustControllerActiveStateChanged === "function")
this._delegate.dragToAdjustControllerActiveStateChanged(this);
}
reset()
{
this._setTracksMouseClickAndDrag(false);
this._element.classList.remove(WI.DragToAdjustController.StyleClassName);
if (this._delegate && typeof this._delegate.dragToAdjustControllerDidReset === "function")
this._delegate.dragToAdjustControllerDidReset(this);
}
// Protected
handleEvent(event)
{
switch (event.type) {
case "mouseenter":
if (!this._dragging) {
if (this._delegate && typeof this._delegate.dragToAdjustControllerCanBeActivated === "function")
this.active = this._delegate.dragToAdjustControllerCanBeActivated(this);
else
this.active = true;
}
break;
case "mouseleave":
if (!this._dragging)
this.active = false;
break;
case "mousemove":
if (this._dragging)
this._mouseWasDragged(event);
else
this._mouseMoved(event);
break;
case "mousedown":
this._mouseWasPressed(event);
break;
case "mouseup":
this._mouseWasReleased(event);
break;
case "contextmenu":
event.preventDefault();
break;
}
}
// Private
_setDragging(dragging)
{
if (this._dragging === dragging)
return;
console.assert(window.event);
if (dragging)
WI.elementDragStart(this._element, this, this, window.event, "col-resize", window);
else
WI.elementDragEnd(window.event);
this._dragging = dragging;
}
_setTracksMouseClickAndDrag(tracksMouseClickAndDrag)
{
if (this._tracksMouseClickAndDrag === tracksMouseClickAndDrag)
return;
if (tracksMouseClickAndDrag) {
this._element.classList.add(WI.DragToAdjustController.StyleClassName);
window.addEventListener("mousedown", this, true);
window.addEventListener("contextmenu", this, true);
} else {
this._element.classList.remove(WI.DragToAdjustController.StyleClassName);
window.removeEventListener("mousedown", this, true);
window.removeEventListener("contextmenu", this, true);
this._setDragging(false);
}
this._tracksMouseClickAndDrag = tracksMouseClickAndDrag;
}
_modifiersDidChange(event)
{
var canBeAdjusted = WI.modifierKeys.altKey;
if (canBeAdjusted && this._delegate && typeof this._delegate.dragToAdjustControllerCanBeAdjusted === "function")
canBeAdjusted = this._delegate.dragToAdjustControllerCanBeAdjusted(this);
this._setTracksMouseClickAndDrag(canBeAdjusted);
}
_mouseMoved(event)
{
var canBeAdjusted = event.altKey;
if (canBeAdjusted && this._delegate && typeof this._delegate.dragToAdjustControllerCanAdjustObjectAtPoint === "function")
canBeAdjusted = this._delegate.dragToAdjustControllerCanAdjustObjectAtPoint(this, WI.Point.fromEvent(event));
this._setTracksMouseClickAndDrag(canBeAdjusted);
}
_mouseWasPressed(event)
{
this._lastX = event.screenX;
this._setDragging(true);
event.preventDefault();
event.stopPropagation();
}
_mouseWasDragged(event)
{
var x = event.screenX;
var amount = x - this._lastX;
if (Math.abs(amount) < 1)
return;
this._lastX = x;
if (event.ctrlKey)
amount /= 10;
else if (event.shiftKey)
amount *= 10;
if (this._delegate && typeof this._delegate.dragToAdjustControllerWasAdjustedByAmount === "function")
this._delegate.dragToAdjustControllerWasAdjustedByAmount(this, amount);
event.preventDefault();
event.stopPropagation();
}
_mouseWasReleased(event)
{
this._setDragging(false);
event.preventDefault();
event.stopPropagation();
this.reset();
}
};
WI.DragToAdjustController.StyleClassName = "drag-to-adjust";
@@ -0,0 +1,71 @@
/*
* Copyright (C) 2021 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.ExtensionTabActivationDiagnosticEventRecorder = class ExtensionTabActivationDiagnosticEventRecorder extends WI.DiagnosticEventRecorder
{
constructor(controller)
{
super("ExtensionTabActivation", controller);
this._reportedExtensionTabIDs = new Set;
}
// Protected
setup()
{
WI.tabBrowser.addEventListener(WI.TabBrowser.Event.SelectedTabContentViewDidChange, this._selectedTabContentViewDidChange, this);
}
teardown()
{
WI.tabBrowser.removeEventListener(WI.TabBrowser.Event.SelectedTabContentViewDidChange, this._selectedTabContentViewDidChange, this);
}
// Private
_selectedTabContentViewDidChange(event)
{
let selectedTab = event.data.incomingTab;
if (!(selectedTab instanceof WI.WebInspectorExtensionTabContentView))
return;
let extension = selectedTab.extension;
console.assert(extension instanceof WI.WebInspectorExtension, "Extension tab should have an associated extension.");
// Only report the first selection of an extension tab.
if (this._reportedExtensionTabIDs.has(selectedTab.extensionTabID))
return;
this._reportedExtensionTabIDs.add(selectedTab.extensionTabID);
this.logDiagnosticEvent(this.name, {
extensionBundleIdentifier: extension.extensionBundleIdentifier,
extensionTabName: selectedTab.tabInfo().displayName,
activeExtensionTabCount: WI.sharedApp.extensionController.activeExtensionTabContentViews().length,
});
}
};
+167
View File
@@ -0,0 +1,167 @@
/*
* Copyright (C) 2013 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.Formatter = class Formatter
{
constructor(codeMirror, builder)
{
console.assert(codeMirror);
console.assert(builder);
this._codeMirror = codeMirror;
this._builder = builder;
this._lastToken = null;
this._lastContent = "";
}
// Public
format(from, to)
{
console.assert(this._builder.originalContent === null);
if (this._builder.originalContent !== null)
return;
var outerMode = this._codeMirror.getMode();
var content = this._codeMirror.getRange(from, to);
var state = CodeMirror.copyState(outerMode, this._codeMirror.getTokenAt(from).state);
this._builder.setOriginalContent(content);
var lineOffset = 0;
var lines = content.split("\n");
for (var i = 0; i < lines.length; ++i) {
var line = lines[i];
var startOfNewLine = true;
var firstTokenOnLine = true;
var stream = new CodeMirror.StringStream(line);
while (!stream.eol()) {
var innerMode = CodeMirror.innerMode(outerMode, state);
var token = outerMode.token(stream, state);
var isWhiteSpace = token === null && /^\s*$/.test(stream.current());
this._handleToken(innerMode.mode, token, state, stream, lineOffset + stream.start, isWhiteSpace, startOfNewLine, firstTokenOnLine);
stream.start = stream.pos;
startOfNewLine = false;
if (firstTokenOnLine && !isWhiteSpace)
firstTokenOnLine = false;
}
if (firstTokenOnLine)
this._handleEmptyLine();
lineOffset += line.length + 1; // +1 for the "\n" removed in split.
this._handleLineEnding(lineOffset - 1); // -1 for the index of the "\n".
}
this._builder.finish();
}
// Private
_handleToken(mode, token, state, stream, originalPosition, isWhiteSpace, startOfNewLine, firstTokenOnLine)
{
// String content of the token.
var content = stream.current();
// Start of a new line. Insert a newline to be safe if code was not-ASI safe. These are collapsed.
if (startOfNewLine)
this._builder.appendNewline();
// Whitespace. Remove all spaces or collapse to a single space.
if (isWhiteSpace) {
this._builder.appendSpace();
return;
}
// Avoid some hooks for content in comments.
var isComment = token && /\bcomment\b/.test(token);
if (mode.modifyStateForTokenPre)
mode.modifyStateForTokenPre(this._lastToken, this._lastContent, token, state, content, isComment);
// Should we remove the last whitespace?
if (this._builder.lastTokenWasWhitespace && mode.removeLastWhitespace(this._lastToken, this._lastContent, token, state, content, isComment))
this._builder.removeLastWhitespace();
// Should we remove the last newline?
if (this._builder.lastTokenWasNewline && mode.removeLastNewline(this._lastToken, this._lastContent, token, state, content, isComment, firstTokenOnLine))
this._builder.removeLastNewline();
// Add whitespace after the last token?
if (!this._builder.lastTokenWasWhitespace && mode.shouldHaveSpaceAfterLastToken(this._lastToken, this._lastContent, token, state, content, isComment))
this._builder.appendSpace();
// Add whitespace before this token?
if (!this._builder.lastTokenWasWhitespace && mode.shouldHaveSpaceBeforeToken(this._lastToken, this._lastContent, token, state, content, isComment))
this._builder.appendSpace();
// Should we dedent before this token?
var dedents = mode.dedentsBeforeToken(this._lastToken, this._lastContent, token, state, content, isComment);
while (dedents-- > 0)
this._builder.dedent();
// Should we add a newline before this token?
if (mode.newlineBeforeToken(this._lastToken, this._lastContent, token, state, content, isComment))
this._builder.appendNewline();
// Should we indent before this token?
if (mode.indentBeforeToken(this._lastToken, this._lastContent, token, state, content, isComment))
this._builder.indent();
// Append token.
this._builder.appendToken(content, originalPosition);
// Let the pretty printer update any state it keeps track of.
if (mode.modifyStateForTokenPost)
mode.modifyStateForTokenPost(this._lastToken, this._lastContent, token, state, content, isComment);
// Should we indent or dedent after this token?
if (!isComment && mode.indentAfterToken(this._lastToken, this._lastContent, token, state, content, isComment))
this._builder.indent();
// Should we add newlines after this token?
var newlines = mode.newlinesAfterToken(this._lastToken, this._lastContent, token, state, content, isComment);
if (newlines)
this._builder.appendMultipleNewlines(newlines);
// Record this token as the last token.
this._lastToken = token;
this._lastContent = content;
}
_handleEmptyLine()
{
// Preserve original whitespace only lines by adding a newline.
// However, don't do this if the builder just added multiple newlines.
if (!(this._builder.lastTokenWasNewline && this._builder.lastNewlineAppendWasMultiple))
this._builder.appendNewline(true);
}
_handleLineEnding(originalNewLinePosition)
{
// Record the original line ending.
this._builder.addOriginalLineEnding(originalNewLinePosition);
}
};
+107
View File
@@ -0,0 +1,107 @@
/*
* Copyright (C) 2013 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.FormatterSourceMap = class FormatterSourceMap extends WI.Object
{
constructor(originalLineEndings, formattedLineEndings, mapping)
{
super();
this._originalLineEndings = originalLineEndings;
this._formattedLineEndings = formattedLineEndings;
this._mapping = mapping;
}
// Static
static fromSourceMapData({originalLineEndings, formattedLineEndings, mapping})
{
return new WI.FormatterSourceMap(originalLineEndings, formattedLineEndings, mapping);
}
// Public
originalToFormatted(lineNumber, columnNumber)
{
var originalPosition = this._locationToPosition(this._originalLineEndings, lineNumber || 0, columnNumber || 0);
return this.originalPositionToFormatted(originalPosition);
}
originalPositionToFormatted(originalPosition)
{
var formattedPosition = this._convertPosition(this._mapping.original, this._mapping.formatted, originalPosition);
return this._positionToLocation(this._formattedLineEndings, formattedPosition);
}
originalPositionToFormattedPosition(originalPosition)
{
return this._convertPosition(this._mapping.original, this._mapping.formatted, originalPosition);
}
formattedToOriginal(lineNumber, columnNumber)
{
var originalPosition = this.formattedToOriginalOffset(lineNumber, columnNumber);
return this._positionToLocation(this._originalLineEndings, originalPosition);
}
formattedToOriginalOffset(lineNumber, columnNumber)
{
var formattedPosition = this._locationToPosition(this._formattedLineEndings, lineNumber || 0, columnNumber || 0);
var originalPosition = this._convertPosition(this._mapping.formatted, this._mapping.original, formattedPosition);
return originalPosition;
}
formattedPositionToOriginalPosition(formattedPosition)
{
return this._convertPosition(this._mapping.formatted, this._mapping.original, formattedPosition);
}
// Private
_locationToPosition(lineEndings, lineNumber, columnNumber)
{
var lineOffset = lineNumber ? lineEndings[lineNumber - 1] + 1 : 0;
return lineOffset + columnNumber;
}
_positionToLocation(lineEndings, position)
{
var lineNumber = lineEndings.upperBound(position - 1);
if (!lineNumber)
var columnNumber = position;
else
var columnNumber = position - lineEndings[lineNumber - 1] - 1;
return {lineNumber, columnNumber};
}
_convertPosition(positions1, positions2, positionInPosition1)
{
var index = positions1.upperBound(positionInPosition1) - 1;
var convertedPosition = positions2[index] + positionInPosition1 - positions1[index];
if (index < positions2.length - 1 && convertedPosition > positions2[index + 1])
convertedPosition = positions2[index + 1];
return convertedPosition;
}
};
+245
View File
@@ -0,0 +1,245 @@
/*
* Copyright (C) 2021 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.GestureController = class GestureController
{
constructor(target, delegate, {container, supportsScale, supportsTranslate})
{
console.assert(target instanceof Node, target);
console.assert(!container || container instanceof Node, container);
console.assert(!supportsScale || typeof delegate.gestureControllerDidScale === "function", delegate.gestureControllerDidScale);
console.assert(!supportsTranslate || typeof delegate.gestureControllerDidTranslate === "function", delegate.gestureControllerDidTranslate);
console.assert(supportsScale || supportsTranslate, "expects at least one gesture");
this._target = target;
this._delegate = delegate;
this._scale = 1;
this._translate = {x: 0, y: 0};
this._mouseWheelDelta = 0;
container ||= target;
this._supportsScale = supportsScale || false;
if (this._supportsScale) {
container.addEventListener("wheel", this._handleWheel.bind(this));
container.addEventListener("gesturestart", this._handleGestureStart.bind(this));
container.addEventListener("gesturechange", this._handleGestureChange.bind(this));
container.addEventListener("gestureend", this._handleGestureEnd.bind(this));
}
this._supportsTranslate = supportsTranslate || false;
if (this._supportsTranslate) {
console.assert(!container.draggable, "cannot have both a translate gesture and dragging");
container.addEventListener("mousedown", this._handleMouseDown.bind(this));
}
}
// Public
get scale()
{
return this._scale;
}
set scale(scale)
{
console.assert(this._supportsScale);
scale = Number.constrain(scale, 0.01, 100);
if (scale === this._scale)
return;
this._scale = scale;
this._delegate.gestureControllerDidScale(this);
}
get translate()
{
return this._translate;
}
set translate(translate)
{
console.assert(this._supportsTranslate);
if (translate.x === this._translate.x && translate.y === this._translate.y)
return;
this._translate = translate;
this._delegate.gestureControllerDidTranslate(this);
}
reset()
{
this.scale = 1;
this.translate = {x: 0, y: 0};
this._mouseWheelDelta = 0;
}
// Private
_startScaleInteraction(event)
{
this._scaleInteractionStartScale = this._scale;
if (this._supportsTranslate)
this._scaleInteractionStartTranslate = this._translate;
if (event.target === this._target) {
let elementBounds = this._target.getBoundingClientRect();
this._scaleInteractionStartPosition = {
x: (event.pageX - elementBounds.left - (elementBounds.width / 2)) / this._scaleInteractionStartScale,
y: (event.pageY - elementBounds.top - (elementBounds.height / 2)) / this._scaleInteractionStartScale,
};
} else
this._scaleInteractionStartPosition = {x: 0, y: 0};
}
_updateScaleInteraction(scale)
{
this.scale = this._scaleInteractionStartScale * scale;
if (this._supportsTranslate) {
this.translate = {
x: this._scaleInteractionStartTranslate.x - (this._scaleInteractionStartPosition.x * (this._scale - this._scaleInteractionStartScale)),
y: this._scaleInteractionStartTranslate.y - (this._scaleInteractionStartPosition.y * (this._scale - this._scaleInteractionStartScale)),
};
}
}
_endScaleInteraction() {
this._scaleInteractionStartScale = NaN;
if (this._supportsTranslate)
this._scaleInteractionStartTranslate = null;
this._scaleInteractionStartPosition = null;
}
_handleWheel(event)
{
// Ignore wheel events while handing gestures.
if (this._handlingGesture)
return;
// Require twice the vertical delta to overcome horizontal scrolling.
// This prevents most cases of inadvertent zooming for slightly diagonal scrolls.
if (Math.abs(event.deltaX) >= Math.abs(event.deltaY) * 0.5)
return;
let deviceDirection = event.webkitDirectionInvertedFromDevice ? -1 : 1;
let delta = (event.deltaZ || event.deltaY || event.deltaX) * deviceDirection / 1000;
// Reset accumulated wheel delta when direction changes.
if (delta < 0 && this._mouseWheelDelta >= 0 || delta >= 0 && this._mouseWheelDelta < 0)
this._mouseWheelDelta = 0;
this._mouseWheelDelta += delta;
this._startScaleInteraction(event);
this._updateScaleInteraction(1 - this._mouseWheelDelta);
this._endScaleInteraction();
event.preventDefault();
event.stopPropagation();
}
_handleGestureStart(event)
{
console.assert(!this._handlingGesture);
this._handlingGesture = true;
this._startScaleInteraction(event);
event.preventDefault();
event.stopPropagation();
}
_handleGestureChange(event)
{
console.assert(this._handlingGesture);
this._updateScaleInteraction(event.scale);
event.preventDefault();
event.stopPropagation();
}
_handleGestureEnd(event)
{
console.assert(this._handlingGesture);
this._handlingGesture = false;
this._endScaleInteraction();
}
_handleMouseDown(event)
{
if (event.target.draggable)
return;
if (event.button !== 0)
return;
this._translateInteractionStartTranslate = this._translate;
this._translateInteractionStartPosition = {
x: event.pageX,
y: event.pageY,
};
console.assert(!this._boundHandleMouseMove);
this._boundHandleMouseMove = this._handleMouseMove.bind(this);
window.addEventListener("mousemove", this._boundHandleMouseMove, {capture: true});
console.assert(!this._boundHandleMouseUp);
this._boundHandleMouseUp = this._handleMouseUp.bind(this);
window.addEventListener("mouseup", this._boundHandleMouseUp, {capture: true});
}
_handleMouseMove(event)
{
this.translate = {
x: this._translateInteractionStartTranslate.x + (event.pageX - this._translateInteractionStartPosition.x),
y: this._translateInteractionStartTranslate.y + (event.pageY - this._translateInteractionStartPosition.y),
};
}
_handleMouseUp(event)
{
window.removeEventListener("mousemove", this._boundHandleMouseMove, {capture: true});
this._boundHandleMouseMove = null;
window.removeEventListener("mouseup", this._boundHandleMouseUp, {capture: true});
this._boundHandleMouseUp = null;
this._translateInteractionStartTranslate = null;
this._translateInteractionStartPosition = null;
}
};
+428
View File
@@ -0,0 +1,428 @@
/*
* Copyright (C) 2017-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.
*/
// 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: "1.0",
};
}
static pages()
{
return [{
startedDateTime: HARBuilder.date(WI.networkManager.mainFrame.mainResource.requestSentDate),
id: "page_0",
title: WI.networkManager.mainFrame.url || "",
pageTimings: HARBuilder.pageTimings(),
}];
}
static pageTimings()
{
let result = {};
let domContentReadyEventTimestamp = WI.networkManager.mainFrame.domContentReadyEventTimestamp;
if (!isNaN(domContentReadyEventTimestamp))
result.onContentLoad = domContentReadyEventTimestamp * 1000;
let loadEventTimestamp = WI.networkManager.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);
// WebKit Custom Field `_serverPort`.
if (entry.serverIPAddress)
entry._serverPort = HARBuilder.port(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);
// WebKit Custom Field `_priority`.
if (resource.priority !== WI.Resource.NetworkPriority.Unknown)
entry._priority = HARBuilder.priority(resource.priority);
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;
if (cookie.sameSite !== WI.Cookie.SameSiteType.None)
json.sameSite = cookie.sameSite;
}
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)
{
// FIXME: <https://webkit.org/b/195694> Web Inspector: HAR Extension for Redirect Timing Info
// 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;
// If all the time before requestStart was included in blocked, then make send time zero
// as send time is essentially just blocked time after dns / connection time, and we
// do not want to double count it.
result.send = (domainLookupEnd || connectEnd) ? (requestStart - (connectEnd || domainLookupEnd)) * 1000 : 0;
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 port(remoteAddress)
{
// IP Address, without port.
if (!remoteAddress)
return undefined;
// NOTE: Resource.remoteAddress always includes the port at the end.
// So this always matches the last part.
let index = remoteAddress.lastIndexOf(":");
if (!index)
return undefined;
let portString = remoteAddress.substr(index + 1);
let port = parseInt(portString);
if (isNaN(port))
return undefined;
return port;
}
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";
case WI.Resource.ResponseSource.ServiceWorker:
return "Service Worker";
case WI.Resource.ResponseSource.InspectorOverride:
return "Inspector Override";
}
console.assert();
return undefined;
}
static priority(priority)
{
switch (priority) {
case WI.Resource.NetworkPriority.Low:
return "low";
case WI.Resource.NetworkPriority.Medium:
return "medium";
case WI.Resource.NetworkPriority.High:
return "high";
}
console.assert();
return undefined;
}
// Consuming.
static dateFromHARDate(isoString)
{
return Date.parse(isoString);
}
static protocolFromHARProtocol(protocol)
{
switch (protocol) {
case "HTTP/2":
return "h2";
case "HTTP/1.0":
return "http/1.0";
case "HTTP/1.1":
return "http/1.1";
case "SPDY/2":
return "spdy/2";
case "SPDY/3":
return "spdy/3";
case "SPDY/3.1":
return "spdy/3.1";
}
if (protocol)
console.warn("Unknown HAR protocol value", protocol);
return null;
}
static responseSourceFromHARFetchType(fetchType)
{
switch (fetchType) {
case "Network Load":
return WI.Resource.ResponseSource.Network;
case "Memory Cache":
return WI.Resource.ResponseSource.MemoryCache;
case "Disk Cache":
return WI.Resource.ResponseSource.DiskCache;
case "Service Worker":
return WI.Resource.ResponseSource.ServiceWorker;
case "Inspector Override":
return WI.Resource.ResponseSource.InspectorOverride;
}
if (fetchType)
console.warn("Unknown HAR _fetchType value", fetchType);
return WI.Resource.ResponseSource.Other;
}
static networkPriorityFromHARPriority(priority)
{
switch (priority) {
case "low":
return WI.Resource.NetworkPriority.Low;
case "medium":
return WI.Resource.NetworkPriority.Medium;
case "high":
return WI.Resource.NetworkPriority.High;
}
if (priority)
console.warn("Unknown HAR priority value", priority);
return WI.Resource.NetworkPriority.Unknown;
}
};
+144
View File
@@ -0,0 +1,144 @@
/*
* Copyright (C) 2015 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.
*/
// FIXME: HeapManager lacks advanced multi-target support. (Instruments/Profilers per-target)
WI.HeapManager = class HeapManager extends WI.Object
{
constructor()
{
super();
this._enabled = false;
}
// Agent
get domains() { return ["Heap"]; }
activateExtraDomain(domain)
{
// COMPATIBILITY (iOS 14.0): Inspector.activateExtraDomains was removed in favor of a declared debuggable type
console.assert(domain === "Heap");
for (let target of WI.targets)
this.initializeTarget(target);
}
// Target
initializeTarget(target)
{
if (!this._enabled)
return;
if (target.hasDomain("Heap"))
target.HeapAgent.enable();
}
// Public
enable()
{
if (this._enabled)
return;
this._enabled = true;
for (let target of WI.targets)
this.initializeTarget(target);
}
disable()
{
if (!this._enabled)
return;
for (let target of WI.targets) {
if (target.hasDomain("Heap"))
target.HeapAgent.disable();
}
this._enabled = false;
}
snapshot(callback)
{
console.assert(this._enabled);
let target = WI.assumingMainTarget();
target.HeapAgent.snapshot((error, timestamp, snapshotStringData) => {
if (error)
console.error(error);
callback(error, timestamp, snapshotStringData);
});
}
getPreview(node, callback)
{
console.assert(this._enabled);
console.assert(node instanceof WI.HeapSnapshotNodeProxy);
let target = WI.assumingMainTarget();
target.HeapAgent.getPreview(node.id, (error, string, functionDetails, preview) => {
if (error)
console.error(error);
callback(error, string, functionDetails, preview);
});
}
getRemoteObject(node, objectGroup, callback)
{
console.assert(this._enabled);
console.assert(node instanceof WI.HeapSnapshotNodeProxy);
let target = WI.assumingMainTarget();
target.HeapAgent.getRemoteObject(node.id, objectGroup, (error, result) => {
if (error)
console.error(error);
callback(error, result);
});
}
// HeapObserver
garbageCollected(target, payload)
{
if (!this._enabled)
return;
// FIXME: <https://webkit.org/b/167323> Web Inspector: Enable Memory profiling in Workers
if (target !== WI.mainTarget)
return;
let collection = WI.GarbageCollection.fromPayload(payload);
this.dispatchEventToListeners(WI.HeapManager.Event.GarbageCollected, {collection});
}
};
WI.HeapManager.Event = {
GarbageCollected: "heap-manager-garbage-collected"
};
+258
View File
@@ -0,0 +1,258 @@
/*
* Copyright (C) 2013 Apple Inc. All rights reserved.
* Copyright (C) 2013 Samsung Electronics. 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.
*/
// FIXME: IndexedDBManager lacks advanced multi-target support. (IndexedDatabase per-target)
WI.IndexedDBManager = class IndexedDBManager extends WI.Object
{
constructor()
{
super();
this._enabled = false;
this._requestedSecurityOrigins = new Set;
this._reset();
}
// Agent
get domains() { return ["IndexedDB"]; }
activateExtraDomain(domain)
{
// COMPATIBILITY (iOS 14.0): Inspector.activateExtraDomains was removed in favor of a declared debuggable type
console.assert(domain === "IndexedDB");
for (let target of WI.targets)
this.initializeTarget(target);
}
// Target
initializeTarget(target)
{
if (!this._enabled)
return;
if (target.hasDomain("IndexedDB"))
target.IndexedDBAgent.enable();
}
// Public
get indexedDatabases() { return this._indexedDatabases; }
enable()
{
console.assert(!this._enabled);
this._enabled = true;
this._reset();
for (let target of WI.targets)
this.initializeTarget(target);
WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
WI.Frame.addEventListener(WI.Frame.Event.SecurityOriginDidChange, this._securityOriginDidChange, this);
}
disable()
{
console.assert(this._enabled);
this._enabled = false;
for (let target of WI.targets) {
if (target.hasDomain("IndexedDB"))
target.IndexedDBAgent.disable();
}
WI.Frame.removeEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
WI.Frame.removeEventListener(WI.Frame.Event.SecurityOriginDidChange, this._securityOriginDidChange, this);
this._reset();
}
requestIndexedDatabaseData(objectStore, objectStoreIndex, startEntryIndex, maximumEntryCount, callback)
{
console.assert(this._enabled);
console.assert(InspectorBackend.hasDomain("IndexedDB"));
console.assert(objectStore);
console.assert(callback);
function processData(error, entryPayloads, moreAvailable)
{
if (error) {
callback(null, false);
return;
}
var entries = [];
for (var entryPayload of entryPayloads) {
var entry = {};
entry.primaryKey = WI.RemoteObject.fromPayload(entryPayload.primaryKey);
entry.key = WI.RemoteObject.fromPayload(entryPayload.key);
entry.value = WI.RemoteObject.fromPayload(entryPayload.value);
entries.push(entry);
}
callback(entries, moreAvailable);
}
var requestArguments = {
securityOrigin: objectStore.parentDatabase.securityOrigin,
databaseName: objectStore.parentDatabase.name,
objectStoreName: objectStore.name,
indexName: objectStoreIndex && objectStoreIndex.name || "",
skipCount: startEntryIndex || 0,
pageSize: maximumEntryCount || 100
};
let target = WI.assumingMainTarget();
target.IndexedDBAgent.requestData.invoke(requestArguments, processData);
}
clearObjectStore(objectStore)
{
console.assert(this._enabled);
let securityOrigin = objectStore.parentDatabase.securityOrigin;
let databaseName = objectStore.parentDatabase.name;
let objectStoreName = objectStore.name;
let target = WI.assumingMainTarget();
target.IndexedDBAgent.clearObjectStore(securityOrigin, databaseName, objectStoreName);
}
// Private
_reset()
{
this._indexedDatabases = [];
this._requestedSecurityOrigins.clear();
this.dispatchEventToListeners(WI.IndexedDBManager.Event.Cleared);
let mainFrame = WI.networkManager.mainFrame;
if (mainFrame)
this._addIndexedDBDatabasesIfNeeded(mainFrame);
}
_addIndexedDBDatabasesIfNeeded(frame)
{
if (!this._enabled)
return;
let target = WI.assumingMainTarget();
if (!target.hasDomain("IndexedDB"))
return;
var securityOrigin = frame.securityOrigin;
// Don't show storage if we don't have a security origin (about:blank).
if (!securityOrigin || securityOrigin === "://")
return;
if (this._requestedSecurityOrigins.has(securityOrigin))
return;
this._requestedSecurityOrigins.add(securityOrigin);
function processDatabaseNames(error, names)
{
if (error || !names)
return;
for (var name of names)
target.IndexedDBAgent.requestDatabase(securityOrigin, name, processDatabase.bind(this));
}
function processDatabase(error, databasePayload)
{
if (error || !databasePayload)
return;
var objectStores = databasePayload.objectStores.map(processObjectStore);
var indexedDatabase = new WI.IndexedDatabase(databasePayload.name, securityOrigin, databasePayload.version, objectStores);
this._indexedDatabases.push(indexedDatabase);
this.dispatchEventToListeners(WI.IndexedDBManager.Event.IndexedDatabaseWasAdded, {indexedDatabase});
}
function processKeyPath(keyPathPayload)
{
switch (keyPathPayload.type) {
case InspectorBackend.Enum.IndexedDB.KeyPathType.Null:
return null;
case InspectorBackend.Enum.IndexedDB.KeyPathType.String:
return keyPathPayload.string;
case InspectorBackend.Enum.IndexedDB.KeyPathType.Array:
return keyPathPayload.array;
default:
console.error("Unknown KeyPath type:", keyPathPayload.type);
return null;
}
}
function processObjectStore(objectStorePayload)
{
var keyPath = processKeyPath(objectStorePayload.keyPath);
var indexes = objectStorePayload.indexes.map(processObjectStoreIndex);
return new WI.IndexedDatabaseObjectStore(objectStorePayload.name, keyPath, objectStorePayload.autoIncrement, indexes);
}
function processObjectStoreIndex(objectStoreIndexPayload)
{
var keyPath = processKeyPath(objectStoreIndexPayload.keyPath);
return new WI.IndexedDatabaseObjectStoreIndex(objectStoreIndexPayload.name, keyPath, objectStoreIndexPayload.unique, objectStoreIndexPayload.multiEntry);
}
target.IndexedDBAgent.requestDatabaseNames(securityOrigin, processDatabaseNames.bind(this));
}
_mainResourceDidChange(event)
{
console.assert(event.target instanceof WI.Frame);
if (event.target.isMainFrame())
this._reset();
}
_securityOriginDidChange(event)
{
console.assert(event.target instanceof WI.Frame);
this._addIndexedDBDatabasesIfNeeded(event.target);
}
};
WI.IndexedDBManager.Event = {
IndexedDatabaseWasAdded: "indexed-db-manager-indexed-database-was-added",
Cleared: "indexed-db-manager-cleared",
};
@@ -0,0 +1,146 @@
/*
* Copyright (C) 2019 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.InspectedTargetTypesDiagnosticEventRecorder = class InspectedTargetTypesDiagnosticEventRecorder extends WI.DiagnosticEventRecorder
{
constructor(controller)
{
super("InspectedTargetTypes", controller);
this._initialDelayBeforeSamplingTimerIdentifier = undefined;
}
// Static
static get initialDelayBeforeSamplingInterval()
{
return 5 * 1000; // In milliseconds.
}
// Protected
setup()
{
// If it's been less than 5 seconds since the frontend loaded, wait a bit.
if (performance.now() - WI.frontendCompletedLoadTimestamp < InspectedTargetTypesDiagnosticEventRecorder.initialDelayBeforeSamplingInterval)
this._startInitialDelayBeforeSamplingTimer();
else
this._sampleInspectedTarget();
}
teardown()
{
this._stopInitialDelayBeforeSamplingTimer();
}
// Private
_startInitialDelayBeforeSamplingTimer()
{
if (this._initialDelayBeforeSamplingTimerIdentifier) {
clearTimeout(this._initialDelayBeforeSamplingTimerIdentifier);
this._initialDelayBeforeSamplingTimerIdentifier = undefined;
}
// All intervals are in milliseconds.
let maximumInitialDelay = InspectedTargetTypesDiagnosticEventRecorder.initialDelayBeforeSamplingInterval;
let elapsedTime = performance.now() - WI.frontendCompletedLoadTimestamp;
let remainingTime = maximumInitialDelay - elapsedTime;
let initialDelay = Number.constrain(remainingTime, 0, maximumInitialDelay);
this._initialDelayBeforeSamplingTimerIdentifier = setTimeout(this._sampleInspectedTarget.bind(this), initialDelay);
}
_stopInitialDelayBeforeSamplingTimer()
{
if (this._initialDelayBeforeSamplingTimerIdentifier) {
clearTimeout(this._initialDelayBeforeSamplingTimerIdentifier);
this._initialDelayBeforeSamplingTimerIdentifier = undefined;
}
}
_sampleInspectedTarget()
{
this._stopInitialDelayBeforeSamplingTimer();
this.logDiagnosticEvent(this.name, {
debuggableType: this._determineDebuggableType(),
targetPlatformName: this._determineTargetPlatformName(),
targetBuildVersion: this._determineTargetBuildVersion(),
targetProductVersion: this._determineTargetProductVersion(),
targetIsSimulator: this._determineTargetIsSimulator(),
});
}
_determineDebuggableType()
{
this._ensureCachedDebuggableInfo();
return this._cachedDebuggableInfo.debuggableType;
}
_determineTargetPlatformName()
{
this._ensureCachedDebuggableInfo();
return this._cachedDebuggableInfo.targetPlatformName;
}
_determineTargetBuildVersion()
{
this._ensureCachedDebuggableInfo();
return this._cachedDebuggableInfo.targetBuildVersion;
}
_determineTargetProductVersion()
{
this._ensureCachedDebuggableInfo();
return this._cachedDebuggableInfo.targetProductVersion;
}
_determineTargetIsSimulator()
{
this._ensureCachedDebuggableInfo();
return !!this._cachedDebuggableInfo.targetIsSimulator;
}
_ensureCachedDebuggableInfo()
{
if (this._cachedDebuggableInfo)
return;
let debuggableInfo = InspectorFrontendHost.debuggableInfo;
this._cachedDebuggableInfo = {
debuggableType: WI.DebuggableType.fromString(debuggableInfo.debuggableType) || WI.DebuggableType.JavaScript,
targetPlatformName: debuggableInfo.targetPlatformName || "Unknown",
targetBuildVersion: debuggableInfo.targetBuildVersion || "Unknown",
targetProductVersion: debuggableInfo.targetProductVersion || "Unknown",
targetIsSimulator: debuggableInfo.targetIsSimulator,
};
}
};
@@ -0,0 +1,394 @@
/*
* Copyright (C) 2013 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.JavaScriptLogViewController = class JavaScriptLogViewController extends WI.Object
{
constructor(element, scrollElement, textPrompt, delegate, historySettingIdentifier)
{
super();
console.assert(textPrompt instanceof WI.ConsolePrompt);
console.assert(historySettingIdentifier);
this._element = element;
this._scrollElement = scrollElement;
this._promptHistorySetting = new WI.Setting(historySettingIdentifier, null);
this._prompt = textPrompt;
this._prompt.delegate = this;
this._prompt.history = this._promptHistorySetting.value;
this.delegate = delegate;
this._cleared = true;
this._previousMessageView = null;
this._lastCommitted = {text: "", special: false};
this._repeatCountWasInterrupted = false;
this._sessions = [];
this._currentSessionOrGroup = null;
this.messagesAlternateClearKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.Control, "L", this.requestClearMessages.bind(this), this._element);
this._messagesFindNextKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl, "G", this._handleFindNextShortcut.bind(this), this._element);
this._messagesFindPreviousKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl | WI.KeyboardShortcut.Modifier.Shift, "G", this._handleFindPreviousShortcut.bind(this), this._element);
this._promptAlternateClearKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.Control, "L", this.requestClearMessages.bind(this), this._prompt.element);
this._promptFindNextKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl, "G", this._handleFindNextShortcut.bind(this), this._prompt.element);
this._promptFindPreviousKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl | WI.KeyboardShortcut.Modifier.Shift, "G", this._handleFindPreviousShortcut.bind(this), this._prompt.element);
WI.settings.showConsoleMessageTimestamps.addEventListener(WI.Setting.Event.Changed, this._handleShowConsoleMessageTimestampsSettingChanged, this);
this._pendingMessagesForSessionOrGroup = new Map;
this._scheduledRenderIdentifier = 0;
this._consoleMessageViews = [];
this._showTimestamps = WI.settings.showConsoleMessageTimestamps.value;
this.startNewSession();
}
// Public
clear()
{
this._cleared = true;
const clearPreviousSessions = true;
this.startNewSession(clearPreviousSessions, {newSessionReason: WI.ConsoleSession.NewSessionReason.ConsoleCleared});
}
startNewSession(clearPreviousSessions = false, data = {})
{
if (clearPreviousSessions) {
this._pendingMessagesForSessionOrGroup.clear();
if (this._sessions.length) {
for (let session of this._sessions)
session.element.remove();
this._sessions = [];
this._currentSessionOrGroup = null;
}
}
// First session shows the time when the console was opened.
if (!this._sessions.length)
data.timestamp = Date.now();
let lastSession = this._sessions.lastValue;
// Remove empty session.
if (lastSession && !lastSession.hasMessages() && !this._pendingMessagesForSessionOrGroup.has(lastSession)) {
this._sessions.pop();
lastSession.element.remove();
}
let consoleSession = new WI.ConsoleSession(data);
this._previousMessageView = null;
this._lastCommitted = {text: "", special: false};
this._repeatCountWasInterrupted = false;
this._sessions.push(consoleSession);
this._currentSessionOrGroup = consoleSession;
this._element.appendChild(consoleSession.element);
// Make sure the new session is visible.
consoleSession.element.scrollIntoView();
}
appendImmediateExecutionWithResult(text, result, {addSpecialUserLogClass, shouldRevealConsole, handleClick} = {})
{
console.assert(result instanceof WI.RemoteObject);
if (this._lastCommitted.text !== text || this._lastCommitted.special !== addSpecialUserLogClass) {
let classNames = [];
if (addSpecialUserLogClass)
classNames.push("special-user-log");
let commandMessageView = new WI.ConsoleCommandView(text, {classNames, handleClick});
this._appendConsoleMessageView(commandMessageView, true);
this._lastCommitted = {text, special: addSpecialUserLogClass};
}
function saveResultCallback(savedResultIndex)
{
let commandResultMessage = new WI.ConsoleCommandResultMessage(result.target, result, false, savedResultIndex, shouldRevealConsole);
let commandResultMessageView = new WI.ConsoleMessageView(commandResultMessage);
this._appendConsoleMessageView(commandResultMessageView, true);
}
WI.runtimeManager.saveResult(result, saveResultCallback.bind(this));
}
appendConsoleMessage(consoleMessage)
{
var consoleMessageView = new WI.ConsoleMessageView(consoleMessage);
this._appendConsoleMessageView(consoleMessageView);
return consoleMessageView;
}
updatePreviousMessageRepeatCount(count, timestamp)
{
console.assert(this._previousMessageView);
if (!this._previousMessageView)
return false;
var previousIgnoredCount = this._previousMessageView[WI.JavaScriptLogViewController.IgnoredRepeatCount] || 0;
var previousVisibleCount = this._previousMessageView.repeatCount;
this._previousMessageView.timestamp = timestamp;
if (!this._repeatCountWasInterrupted) {
this._previousMessageView.repeatCount = count - previousIgnoredCount;
return true;
}
var consoleMessage = this._previousMessageView.message;
var duplicatedConsoleMessageView = new WI.ConsoleMessageView(consoleMessage);
duplicatedConsoleMessageView[WI.JavaScriptLogViewController.IgnoredRepeatCount] = previousIgnoredCount + previousVisibleCount;
duplicatedConsoleMessageView.repeatCount = 1;
this._appendConsoleMessageView(duplicatedConsoleMessageView);
return true;
}
isScrolledToBottom()
{
// Lie about being scrolled to the bottom if we have a pending request to scroll to the bottom soon.
return this._scrollToBottomTimeout || this._scrollElement.isScrolledToBottom();
}
scrollToBottom()
{
if (this._scrollToBottomTimeout)
return;
function delayedWork()
{
this._scrollToBottomTimeout = null;
this._scrollElement.scrollTop = this._scrollElement.scrollHeight;
}
// Don't scroll immediately so we are not causing excessive layouts when there
// are many messages being added at once.
this._scrollToBottomTimeout = setTimeout(delayedWork.bind(this), 0);
}
requestClearMessages()
{
WI.consoleManager.requestClearMessages();
}
// Protected
consolePromptHistoryDidChange(prompt)
{
this._promptHistorySetting.value = this._prompt.history;
}
consolePromptShouldCommitText(prompt, text, cursorIsAtLastPosition, handler)
{
// Always commit the text if we are not at the last position.
if (!cursorIsAtLastPosition) {
handler(true);
return;
}
function parseFinished(error, result, message, range)
{
handler(result !== InspectorBackend.Enum.Runtime.SyntaxErrorType.Recoverable);
}
WI.runtimeManager.activeExecutionContext.target.RuntimeAgent.parse(text, parseFinished.bind(this));
}
consolePromptTextCommitted(prompt, text)
{
console.assert(text);
if (this._lastCommitted.text !== text || this._lastCommitted.special) {
let commandMessageView = new WI.ConsoleCommandView(text);
this._appendConsoleMessageView(commandMessageView, true);
this._lastCommitted = {text, special: false};
}
function printResult(result, wasThrown, savedResultIndex)
{
if (!result || this._cleared)
return;
let shouldRevealConsole = true;
let commandResultMessage = new WI.ConsoleCommandResultMessage(result.target, result, wasThrown, savedResultIndex, shouldRevealConsole);
let commandResultMessageView = new WI.ConsoleMessageView(commandResultMessage);
this._appendConsoleMessageView(commandResultMessageView, true);
}
let options = {
objectGroup: WI.RuntimeManager.ConsoleObjectGroup,
includeCommandLineAPI: true,
doNotPauseOnExceptionsAndMuteConsole: false,
returnByValue: false,
generatePreview: true,
saveResult: true,
emulateUserGesture: WI.settings.emulateInUserGesture.value,
sourceURLAppender: appendWebInspectorConsoleEvaluationSourceURL,
};
WI.runtimeManager.evaluateInInspectedWindow(text, options, printResult.bind(this));
}
// Private
_handleFindNextShortcut()
{
this.delegate.highlightNextSearchMatch();
}
_handleFindPreviousShortcut()
{
this.delegate.highlightPreviousSearchMatch();
}
_appendConsoleMessageView(messageView, repeatCountWasInterrupted)
{
let pendingMessagesForSession = this._pendingMessagesForSessionOrGroup.get(this._currentSessionOrGroup);
if (!pendingMessagesForSession) {
pendingMessagesForSession = [];
this._pendingMessagesForSessionOrGroup.set(this._currentSessionOrGroup, pendingMessagesForSession);
}
pendingMessagesForSession.push(messageView);
this._consoleMessageViews.push(messageView);
this._cleared = false;
this._repeatCountWasInterrupted = repeatCountWasInterrupted || false;
if (!repeatCountWasInterrupted)
this._previousMessageView = messageView;
if (messageView.message && messageView.message.source !== WI.ConsoleMessage.MessageSource.JS)
this._lastCommitted = {test: "", special: false};
if (WI.consoleContentView.isAttached)
this.renderPendingMessagesSoon();
if (!WI.isShowingConsoleTab() && messageView.message && messageView.message.shouldRevealConsole)
WI.showSplitConsole();
}
renderPendingMessages()
{
if (this._scheduledRenderIdentifier) {
cancelAnimationFrame(this._scheduledRenderIdentifier);
this._scheduledRenderIdentifier = 0;
}
if (!this._pendingMessagesForSessionOrGroup.size)
return;
let wasScrolledToBottom = this.isScrolledToBottom();
let savedCurrentConsoleGroup = this._currentSessionOrGroup;
let lastMessageView = null;
const maxMessagesPerFrame = 100;
let renderedMessages = 0;
for (let [session, messages] of this._pendingMessagesForSessionOrGroup) {
this._currentSessionOrGroup = session;
let messagesToRender = messages.splice(0, maxMessagesPerFrame - renderedMessages);
for (let message of messagesToRender) {
message.render();
this._didRenderConsoleMessageView(message);
}
lastMessageView = messagesToRender.lastValue;
if (!messages.length)
this._pendingMessagesForSessionOrGroup.delete(session);
renderedMessages += messagesToRender.length;
if (renderedMessages >= maxMessagesPerFrame)
break;
}
this._currentSessionOrGroup = savedCurrentConsoleGroup;
this._currentSessionOrGroup.element.classList.toggle("timestamps-visible", this._showTimestamps);
if (wasScrolledToBottom || lastMessageView instanceof WI.ConsoleCommandView || lastMessageView.message.type === WI.ConsoleMessage.MessageType.Result || lastMessageView.message.type === WI.ConsoleMessage.MessageType.Image)
this.scrollToBottom();
WI.quickConsole.needsLayout();
if (this._pendingMessagesForSessionOrGroup.size)
this.renderPendingMessagesSoon();
}
renderPendingMessagesSoon()
{
if (this._scheduledRenderIdentifier)
return;
this._scheduledRenderIdentifier = requestAnimationFrame(() => this.renderPendingMessages());
}
_didRenderConsoleMessageView(messageView)
{
var type = messageView instanceof WI.ConsoleCommandView ? null : messageView.message.type;
if (type === WI.ConsoleMessage.MessageType.EndGroup) {
var parentGroup = this._currentSessionOrGroup.parentGroup;
if (parentGroup)
this._currentSessionOrGroup = parentGroup;
} else {
if (type === WI.ConsoleMessage.MessageType.StartGroup || type === WI.ConsoleMessage.MessageType.StartGroupCollapsed) {
var group = new WI.ConsoleGroup(this._currentSessionOrGroup);
var groupElement = group.render(messageView);
this._currentSessionOrGroup.append(groupElement);
this._currentSessionOrGroup = group;
} else
this._currentSessionOrGroup.addMessageView(messageView);
}
if (this.delegate && typeof this.delegate.didAppendConsoleMessageView === "function")
this.delegate.didAppendConsoleMessageView(messageView);
}
_handleShowConsoleMessageTimestampsSettingChanged()
{
this._showTimestamps = WI.settings.showConsoleMessageTimestamps.value;
this._currentSessionOrGroup.element.classList.toggle("timestamps-visible", this._showTimestamps);
if (this._showTimestamps) {
for (let consoleMessageView of this._consoleMessageViews) {
if (consoleMessageView instanceof WI.ConsoleMessageView)
consoleMessageView.renderTimestamp();
}
}
}
};
WI.JavaScriptLogViewController.CachedPropertiesDuration = 30_000;
WI.JavaScriptLogViewController.IgnoredRepeatCount = Symbol("ignored-repeat-count");
@@ -0,0 +1,425 @@
/*
* Copyright (C) 2013 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.
*/
Object.defineProperty(WI, "javaScriptRuntimeCompletionProvider",
{
get: function()
{
if (!WI.JavaScriptRuntimeCompletionProvider._instance)
WI.JavaScriptRuntimeCompletionProvider._instance = new WI.JavaScriptRuntimeCompletionProvider;
return WI.JavaScriptRuntimeCompletionProvider._instance;
}
});
WI.JavaScriptRuntimeCompletionProvider = class JavaScriptRuntimeCompletionProvider extends WI.Object
{
constructor()
{
super();
console.assert(!WI.JavaScriptRuntimeCompletionProvider._instance);
this._ongoingCompletionRequests = 0;
WI.debuggerManager.addEventListener(WI.DebuggerManager.Event.ActiveCallFrameDidChange, this._clearLastProperties, this);
}
// Static
static get _commandLineAPIKeys()
{
if (!JavaScriptRuntimeCompletionProvider.__cachedCommandLineAPIKeys) {
JavaScriptRuntimeCompletionProvider.__cachedCommandLineAPIKeys = [
"$_",
"assert",
"clear",
"count",
"countReset",
"debug",
"dir",
"dirxml",
"error",
"group",
"groupCollapsed",
"groupEnd",
"info",
"inspect",
"keys",
"log",
"profile",
"profileEnd",
"queryHolders",
"queryInstances",
"queryObjects",
"record",
"recordEnd",
"screenshot",
"table",
"takeHeapSnapshot",
"time",
"timeEnd",
"timeLog",
"timeStamp",
"trace",
"values",
"warn",
];
}
return JavaScriptRuntimeCompletionProvider.__cachedCommandLineAPIKeys;
}
// Protected
completionControllerCompletionsNeeded(completionController, defaultCompletions, base, prefix, suffix, forced)
{
// Don't allow non-forced empty prefix completions unless the base is that start of property access.
if (!forced && !prefix && !/[.[]$/.test(base)) {
completionController.updateCompletions(null);
return;
}
// If the base ends with an open parentheses or open curly bracket then treat it like there is
// no base so we get global object completions.
if (/[({]$/.test(base))
base = "";
var lastBaseIndex = base.length - 1;
var dotNotation = base[lastBaseIndex] === ".";
var bracketNotation = base[lastBaseIndex] === "[";
if (dotNotation || bracketNotation) {
base = base.substring(0, lastBaseIndex);
// Don't suggest anything for an empty base that is using dot notation.
// Bracket notation with an empty base will be treated as an array.
if (!base && dotNotation) {
completionController.updateCompletions(defaultCompletions);
return;
}
// Don't allow non-forced empty prefix completions if the user is entering a number, since it might be a float.
// But allow number completions if the base already has a decimal, so "10.0." will suggest Number properties.
if (!forced && !prefix && dotNotation && base.indexOf(".") === -1 && parseInt(base, 10) == base) {
completionController.updateCompletions(null);
return;
}
// An empty base with bracket notation is not property access, it is an array.
// Clear the bracketNotation flag so completions are not quoted.
if (!base && bracketNotation)
bracketNotation = false;
}
// Start an completion request. We must now decrement before calling completionController.updateCompletions.
this._incrementOngoingCompletionRequests();
// If the base is the same as the last time, we can reuse the property names we have already gathered.
// Doing this eliminates delay caused by the async nature of the code below and it only calls getters
// and functions once instead of repetitively. Sure, there can be difference each time the base is evaluated,
// but this optimization gives us more of a win. We clear the cache after 30 seconds or when stepping in the
// debugger to make sure we don't use stale properties in most cases.
if (this._lastMode === completionController.mode && this._lastBase === base && this._lastPropertyNames) {
receivedPropertyNames.call(this, this._lastPropertyNames);
return;
}
this._lastMode = completionController.mode;
this._lastBase = base;
this._lastPropertyNames = null;
var activeCallFrame = WI.debuggerManager.activeCallFrame;
if (!base && activeCallFrame && !this._alwaysEvaluateInWindowContext)
activeCallFrame.collectScopeChainVariableNames(receivedPropertyNames.bind(this));
else {
let options = {objectGroup: "completion", includeCommandLineAPI: true, doNotPauseOnExceptionsAndMuteConsole: true, returnByValue: false, generatePreview: false, saveResult: false};
WI.runtimeManager.evaluateInInspectedWindow(base, options, evaluated.bind(this));
}
function updateLastPropertyNames(propertyNames)
{
if (this._clearLastPropertiesTimeout)
clearTimeout(this._clearLastPropertiesTimeout);
this._clearLastPropertiesTimeout = setTimeout(this._clearLastProperties.bind(this), WI.JavaScriptLogViewController.CachedPropertiesDuration);
this._lastPropertyNames = propertyNames || [];
}
function evaluated(result, wasThrown)
{
if (wasThrown || !result || result.type === "undefined" || (result.type === "object" && result.subtype === "null")) {
this._decrementOngoingCompletionRequests();
updateLastPropertyNames.call(this, []);
completionController.updateCompletions(defaultCompletions);
return;
}
function inspectedPage_evalResult_getArrayCompletions(primitiveType)
{
var array = this;
var arrayLength;
var resultSet = {};
for (var o = array; o; o = o.__proto__) {
try {
if (o === array && o.length) {
// If the array type has a length, don't include a list of all the indexes.
// Include it at the end and the frontend can build the list.
arrayLength = o.length;
} else {
var names = Object.getOwnPropertyNames(o);
for (var i = 0; i < names.length; ++i)
resultSet[names[i]] = true;
}
} catch { }
}
if (arrayLength)
resultSet["length"] = arrayLength;
return resultSet;
}
function inspectedPage_evalResult_getCompletions(primitiveType)
{
var object;
if (primitiveType === "string")
object = new String("");
else if (primitiveType === "number")
object = new Number(0);
else if (primitiveType === "boolean")
object = new Boolean(false);
else if (primitiveType === "symbol")
object = Symbol();
else
object = this;
var resultSet = {};
for (var o = object; o; o = o.__proto__) {
try {
var names = Object.getOwnPropertyNames(o);
for (var i = 0; i < names.length; ++i)
resultSet[names[i]] = true;
} catch (e) { }
}
return resultSet;
}
if (result.subtype === "array")
result.callFunctionJSON(inspectedPage_evalResult_getArrayCompletions, undefined, receivedArrayPropertyNames.bind(this));
else if (result.type === "object" || result.type === "function")
result.callFunctionJSON(inspectedPage_evalResult_getCompletions, undefined, receivedObjectPropertyNames.bind(this));
else if (result.type === "string" || result.type === "number" || result.type === "boolean" || result.type === "symbol") {
let options = {objectGroup: "completion", includeCommandLineAPI: false, doNotPauseOnExceptionsAndMuteConsole: true, returnByValue: true, generatePreview: false, saveResult: false};
WI.runtimeManager.evaluateInInspectedWindow("(" + inspectedPage_evalResult_getCompletions + ")(\"" + result.type + "\")", options, receivedPropertyNamesFromEvaluate.bind(this));
} else
console.error("Unknown result type: " + result.type);
}
function receivedPropertyNamesFromEvaluate(object, wasThrown, result)
{
receivedPropertyNames.call(this, result && !wasThrown ? Object.keys(result.value) : null);
}
function receivedObjectPropertyNames(propertyNames)
{
receivedPropertyNames.call(this, Object.keys(propertyNames));
}
function receivedArrayPropertyNames(propertyNames)
{
if (propertyNames && typeof propertyNames.length === "number") {
// FIXME <https://webkit.org/b/201909> Web Inspector: autocompletion of array indexes can't handle large arrays in a performant way
var max = Math.min(propertyNames.length, 1000);
for (var i = 0; i < max; ++i)
propertyNames[i] = true;
}
receivedObjectPropertyNames.call(this, propertyNames);
}
function receivedPropertyNames(propertyNames)
{
console.assert(!propertyNames || Array.isArray(propertyNames));
propertyNames = propertyNames || [];
updateLastPropertyNames.call(this, propertyNames);
this._decrementOngoingCompletionRequests();
if (!base) {
propertyNames.pushAll(JavaScriptRuntimeCompletionProvider._commandLineAPIKeys);
let savedResultAlias = WI.settings.consoleSavedResultAlias.value;
if (savedResultAlias)
propertyNames.push(savedResultAlias + "_");
let target = WI.runtimeManager.activeExecutionContext.target;
let targetData = WI.debuggerManager.paused ? WI.debuggerManager.dataForTarget(target) : {};
function shouldExposeEvent() {
switch (completionController.mode) {
case WI.CodeMirrorCompletionController.Mode.FullConsoleCommandLineAPI:
case WI.CodeMirrorCompletionController.Mode.EventBreakpoint:
return true;
case WI.CodeMirrorCompletionController.Mode.PausedConsoleCommandLineAPI:
return targetData.pauseReason === WI.DebuggerManager.PauseReason.Listener || targetData.pauseReason === WI.DebuggerManager.PauseReason.EventListener;
}
return false;
}
if (shouldExposeEvent()) {
propertyNames.push("$event");
if (savedResultAlias)
propertyNames.push(savedResultAlias + "event");
}
function shouldExposeException() {
switch (completionController.mode) {
case WI.CodeMirrorCompletionController.Mode.FullConsoleCommandLineAPI:
case WI.CodeMirrorCompletionController.Mode.ExceptionBreakpoint:
return true;
case WI.CodeMirrorCompletionController.Mode.PausedConsoleCommandLineAPI:
return targetData.pauseReason === WI.DebuggerManager.PauseReason.Exception;
}
return false;
}
if (shouldExposeException()) {
propertyNames.push("$exception");
if (savedResultAlias)
propertyNames.push(savedResultAlias + "exception");
}
switch (target.type) {
case WI.TargetType.Page:
propertyNames.push("$");
propertyNames.push("$$");
propertyNames.push("$0");
if (savedResultAlias)
propertyNames.push(savedResultAlias + "0");
propertyNames.push("$x");
// fallthrough
case WI.TargetType.ServiceWorker:
case WI.TargetType.Worker:
propertyNames.push("copy");
propertyNames.push("getEventListeners");
propertyNames.push("monitorEvents");
propertyNames.push("unmonitorEvents");
break;
}
// FIXME: Due to caching, sometimes old $n values show up as completion results even though they are not available. We should clear that proactively.
for (var i = 1; i <= WI.ConsoleCommandResultMessage.maximumSavedResultIndex; ++i) {
propertyNames.push("$" + i);
if (savedResultAlias)
propertyNames.push(savedResultAlias + i);
}
}
var implicitSuffix = "";
if (bracketNotation) {
var quoteUsed = prefix[0] === "'" ? "'" : "\"";
if (suffix !== "]" && suffix !== quoteUsed)
implicitSuffix = "]";
}
var completions = defaultCompletions;
let knownCompletions = new Set(completions);
for (var i = 0; i < propertyNames.length; ++i) {
var property = propertyNames[i];
if (dotNotation && !/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(property))
continue;
if (bracketNotation) {
if (parseInt(property) != property)
property = quoteUsed + property.escapeCharacters(quoteUsed + "\\") + (suffix !== quoteUsed ? quoteUsed : "");
}
if (!property.startsWith(prefix) || knownCompletions.has(property))
continue;
completions.push(property);
knownCompletions.add(property);
}
function compare(a, b)
{
// Try to sort in numerical order first.
let numericCompareResult = a - b;
if (!isNaN(numericCompareResult))
return numericCompareResult;
// Sort __defineGetter__, __lookupGetter__, and friends last.
let aRareProperty = a.startsWith("__") && a.endsWith("__");
let bRareProperty = b.startsWith("__") && b.endsWith("__");
if (aRareProperty && !bRareProperty)
return 1;
if (!aRareProperty && bRareProperty)
return -1;
// Not numbers, sort as strings.
return a.extendedLocaleCompare(b);
}
completions.sort(compare);
completionController.updateCompletions(completions, implicitSuffix);
}
}
// Private
_incrementOngoingCompletionRequests()
{
this._ongoingCompletionRequests++;
console.assert(this._ongoingCompletionRequests <= 50, "Ongoing requests probably should not get this high. We may be missing a balancing decrement.");
}
_decrementOngoingCompletionRequests()
{
this._ongoingCompletionRequests--;
console.assert(this._ongoingCompletionRequests >= 0, "Unbalanced increments / decrements.");
if (this._ongoingCompletionRequests <= 0)
WI.runtimeManager.activeExecutionContext.target.RuntimeAgent.releaseObjectGroup("completion");
}
_clearLastProperties()
{
if (this._clearLastPropertiesTimeout) {
clearTimeout(this._clearLastPropertiesTimeout);
delete this._clearLastPropertiesTimeout;
}
// Clear the cache of property names so any changes while stepping or sitting idle get picked up if the same
// expression is evaluated again.
this._lastPropertyNames = null;
}
};
+211
View File
@@ -0,0 +1,211 @@
/*
* Copyright (C) 2013 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.
*/
// FIXME: LayerTreeManager lacks advanced multi-target support. (Layers per-target)
WI.LayerTreeManager = class LayerTreeManager extends WI.Object
{
constructor()
{
super();
this._showPaintRects = false;
this._compositingBordersVisible = false;
}
// Target
initializeTarget(target)
{
if (target.hasDomain("LayerTree"))
target.LayerTreeAgent.enable();
if (target.hasDomain("Page")) {
if (target.hasCommand("Page.setShowPaintRects") && this._showPaintRects)
target.PageAgent.setShowPaintRects(this._showPaintRects);
if (this._compositingBordersVisible) {
// COMPATIBILITY(iOS 13.1): Page.setCompositingBordersVisible was replaced by Page.Setting.ShowDebugBorders and Page.Setting.ShowRepaintCounter.
if (target.hasCommand("Page.overrideSetting") && InspectorBackend.Enum.Page.Setting.ShowDebugBorders && InspectorBackend.Enum.Page.Setting.ShowRepaintCounter) {
target.PageAgent.overrideSetting(InspectorBackend.Enum.Page.Setting.ShowDebugBorders, this._compositingBordersVisible);
target.PageAgent.overrideSetting(InspectorBackend.Enum.Page.Setting.ShowRepaintCounter, this._compositingBordersVisible);
} else if (target.hasCommand("Page.setCompositingBordersVisible"))
target.PageAgent.setCompositingBordersVisible(this._compositingBordersVisible);
}
}
}
// Static
static supportsShowingPaintRects()
{
return InspectorBackend.hasCommand("Page.setShowPaintRects");
}
static supportsVisibleCompositingBorders()
{
return InspectorBackend.hasCommand("Page.setCompositingBordersVisible")
|| (InspectorBackend.hasCommand("Page.overrideSetting") && InspectorBackend.Enum.Page.Setting.ShowDebugBorders && InspectorBackend.Enum.Page.Setting.ShowRepaintCounter);
}
// Public
get supported()
{
return InspectorBackend.hasDomain("LayerTree");
}
get showPaintRects()
{
return this._showPaintRects;
}
set showPaintRects(showPaintRects)
{
if (this._showPaintRects === showPaintRects)
return;
this._showPaintRects = showPaintRects;
for (let target of WI.targets) {
if (target.hasCommand("Page.setShowPaintRects"))
target.PageAgent.setShowPaintRects(this._showPaintRects);
}
this.dispatchEventToListeners(LayerTreeManager.Event.ShowPaintRectsChanged);
}
get compositingBordersVisible()
{
return this._compositingBordersVisible;
}
set compositingBordersVisible(compositingBordersVisible)
{
if (this._compositingBordersVisible === compositingBordersVisible)
return;
this._compositingBordersVisible = compositingBordersVisible;
for (let target of WI.targets) {
// COMPATIBILITY(iOS 13.1): Page.setCompositingBordersVisible was replaced by Page.Setting.ShowDebugBorders and Page.Setting.ShowRepaintCounter.
if (target.hasCommand("Page.overrideSetting") && InspectorBackend.Enum.Page.Setting.ShowDebugBorders && InspectorBackend.Enum.Page.Setting.ShowRepaintCounter) {
target.PageAgent.overrideSetting(InspectorBackend.Enum.Page.Setting.ShowDebugBorders, this._compositingBordersVisible);
target.PageAgent.overrideSetting(InspectorBackend.Enum.Page.Setting.ShowRepaintCounter, this._compositingBordersVisible);
} else if (target.hasCommand("Page.setCompositingBordersVisible"))
target.PageAgent.setCompositingBordersVisible(this._compositingBordersVisible);
}
this.dispatchEventToListeners(LayerTreeManager.Event.CompositingBordersVisibleChanged);
}
updateCompositingBordersVisibleFromPageIfNeeded()
{
if (!WI.targetsAvailable()) {
WI.whenTargetsAvailable().then(() => {
this.updateCompositingBordersVisibleFromPageIfNeeded();
});
return;
}
let target = WI.assumingMainTarget();
// COMPATIBILITY(iOS 13.1): Page.getCompositingBordersVisible was replaced by Page.Setting.ShowDebugBorders and Page.Setting.ShowRepaintCounter.
if (!target.hasCommand("Page.getCompositingBordersVisible"))
return;
target.PageAgent.getCompositingBordersVisible((error, compositingBordersVisible) => {
if (error) {
WI.reportInternalError(error);
return;
}
this.compositingBordersVisible = compositingBordersVisible;
});
}
layerTreeMutations(previousLayers, newLayers)
{
console.assert(this.supported);
if (isEmptyObject(previousLayers))
return {preserved: [], additions: newLayers, removals: []};
let previousLayerIds = new Set;
let newLayerIds = new Set;
let preserved = [];
let additions = [];
for (let layer of previousLayers)
previousLayerIds.add(layer.layerId);
for (let layer of newLayers) {
newLayerIds.add(layer.layerId);
if (previousLayerIds.has(layer.layerId))
preserved.push(layer);
else
additions.push(layer);
}
let removals = previousLayers.filter((layer) => !newLayerIds.has(layer.layerId));
return {preserved, additions, removals};
}
layersForNode(node, callback)
{
console.assert(this.supported);
let target = WI.assumingMainTarget();
target.LayerTreeAgent.layersForNode(node.id, (error, layers) => {
callback(error ? [] : layers.map(WI.Layer.fromPayload));
});
}
reasonsForCompositingLayer(layer, callback)
{
console.assert(this.supported);
let target = WI.assumingMainTarget();
target.LayerTreeAgent.reasonsForCompositingLayer(layer.layerId, function(error, reasons) {
callback(error ? 0 : reasons);
});
}
// LayerTreeObserver
layerTreeDidChange()
{
this.dispatchEventToListeners(WI.LayerTreeManager.Event.LayerTreeDidChange);
}
};
WI.LayerTreeManager.Event = {
ShowPaintRectsChanged: "show-paint-rects-changed",
CompositingBordersVisibleChanged: "compositing-borders-visible-changed",
LayerTreeDidChange: "layer-tree-did-change",
};
+100
View File
@@ -0,0 +1,100 @@
/*
* 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.
*/
WI.MemoryManager = class MemoryManager extends WI.Object
{
constructor()
{
super();
this._enabled = false;
}
// Agent
get domains() { return ["Memory"]; }
activateExtraDomain(domain)
{
// COMPATIBILITY (iOS 14.0): Inspector.activateExtraDomains was removed in favor of a declared debuggable type
console.assert(domain === "Memory");
for (let target of WI.targets)
this.initializeTarget(target);
}
// Target
initializeTarget(target)
{
if (!this._enabled)
return;
if (target.hasDomain("Memory"))
target.MemoryAgent.enable();
}
// Public
enable()
{
if (this._enabled)
return;
this._enabled = true;
for (let target of WI.targets)
this.initializeTarget(target);
}
disable()
{
if (!this._enabled)
return;
for (let target of WI.targets) {
if (target.hasDomain("Memory"))
target.MemoryAgent.disable();
}
this._enabled = false;
}
// MemoryObserver
memoryPressure(timestamp, protocolSeverity)
{
if (!this._enabled)
return;
let memoryPressureEvent = WI.MemoryPressureEvent.fromPayload(timestamp, protocolSeverity);
this.dispatchEventToListeners(WI.MemoryManager.Event.MemoryPressure, {memoryPressureEvent});
}
};
WI.MemoryManager.Event = {
MemoryPressure: "memory-manager-memory-pressure",
};
File diff suppressed because it is too large Load Diff
+146
View File
@@ -0,0 +1,146 @@
/*
* Copyright (C) 2021 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.QueryController = class QueryController
{
// Public
executeQuery(query)
{
throw WI.NotImplementedError.subclassMustOverride();
}
findQueryMatches(query, searchString, specialCharacterIndices)
{
if (query.length > searchString.length)
return [];
let matches = [];
let queryIndex = 0;
let searchIndex = 0;
let specialIndex = 0;
let deadBranches = (new Array(query.length)).fill(Infinity);
let type = WI.QueryMatch.Type.Special;
function pushMatch(index) {
matches.push(new WI.QueryMatch(type, index, queryIndex));
searchIndex = index + 1;
queryIndex++;
}
function matchNextSpecialCharacter() {
if (specialIndex >= specialCharacterIndices.length)
return false;
let originalSpecialIndex = specialIndex;
while (specialIndex < specialCharacterIndices.length) {
// Normal character matching can move past special characters,
// so advance the special character index if it's before the
// current search string position.
let index = specialCharacterIndices[specialIndex++];
if (index < searchIndex)
continue;
if (query[queryIndex] === searchString[index]) {
pushMatch(index);
return true;
}
}
specialIndex = originalSpecialIndex;
return false;
}
function backtrack() {
while (matches.length) {
queryIndex--;
let lastMatch = matches.pop();
if (lastMatch.type !== WI.QueryMatch.Type.Special)
continue;
deadBranches[lastMatch.queryIndex] = lastMatch.index;
searchIndex = matches.lastValue ? matches.lastValue.index + 1 : 0;
return true;
}
return false;
}
while (queryIndex < query.length && searchIndex <= searchString.length) {
if (type === WI.QueryMatch.Type.Special && !matchNextSpecialCharacter())
type = WI.QueryMatch.Type.Normal;
if (type === WI.QueryMatch.Type.Normal) {
let index = searchString.indexOf(query[queryIndex], searchIndex);
if (index >= 0 && index < deadBranches[queryIndex]) {
pushMatch(index);
type = WI.QueryMatch.Type.Special;
} else if (!backtrack())
return [];
}
}
if (queryIndex < query.length)
return [];
return matches;
}
// Protected
findSpecialCharacterIndices(string, separators)
{
if (!string.length)
return [];
// Special characters include the following:
// - The first character.
// - Uppercase characters that follow a lowercase letter.
// - Separators and the first character following the separator.
let indices = [0];
for (let i = 1; i < string.length; ++i) {
let character = string[i];
let isSpecial = false;
if (separators.includes(character))
isSpecial = true;
else {
let previousCharacter = string[i - 1];
if (separators.includes(previousCharacter))
isSpecial = true;
else if (character.isUpperCase() && previousCharacter.isLowerCase())
isSpecial = true;
}
if (isSpecial)
indices.push(i);
}
return indices;
}
};
@@ -0,0 +1,122 @@
/*
* 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.
*/
WI.ResourceQueryController = class ResourceQueryController extends WI.QueryController
{
constructor()
{
super();
this._resourceDataMap = new Map;
}
// Public
addResource(resource)
{
this._resourceDataMap.set(resource, {});
}
removeResource(resource)
{
this._resourceDataMap.delete(resource);
}
reset()
{
this._resourceDataMap.clear();
}
executeQuery(query)
{
if (!query || !this._resourceDataMap.size)
return [];
query = query.removeWhitespace().toLowerCase();
let cookie = null;
if (query.includes(":")) {
let [newQuery, lineNumber, columnNumber] = query.split(":");
query = newQuery;
lineNumber = lineNumber ? parseInt(lineNumber, 10) - 1 : 0;
columnNumber = columnNumber ? parseInt(columnNumber, 10) - 1 : 0;
cookie = {lineNumber, columnNumber};
}
let results = [];
for (let [resource, cachedData] of this._resourceDataMap) {
if (isEmptyObject(cachedData)) {
let displayName = resource.displayName;
cachedData.displayName = {
searchString: displayName.toLowerCase(),
specialCharacterIndices: this._findSpecialCharacterIndicesInDisplayName(displayName),
};
let url = resource.url;
cachedData.url = {
searchString: url.toLowerCase(),
specialCharacterIndices: this._findSpecialCharacterIndicesInURL(url),
};
}
let resourceResult = null;
let findQueryMatches = ({searchString, specialCharacterIndices}) => {
let matches = this.findQueryMatches(query, searchString, specialCharacterIndices);
if (!matches.length)
return;
let queryResult = new WI.ResourceQueryResult(resource, searchString, matches, cookie);
if (!resourceResult || resourceResult.rank < queryResult.rank)
resourceResult = queryResult;
};
findQueryMatches(cachedData.displayName);
findQueryMatches(cachedData.url);
if (resourceResult)
results.push(resourceResult);
}
// Resources are sorted in descending order by rank. Resources of equal
// rank are sorted by display name.
return results.sort((a, b) => {
if (a.rank === b.rank)
return a.resource.displayName.extendedLocaleCompare(b.resource.displayName);
return b.rank - a.rank;
});
}
// Private
_findSpecialCharacterIndicesInDisplayName(displayName)
{
return this.findSpecialCharacterIndices(displayName, "_.-");
}
_findSpecialCharacterIndicesInURL(url)
{
return this.findSpecialCharacterIndices(url, "_.-/");
}
};
+291
View File
@@ -0,0 +1,291 @@
/*
* Copyright (C) 2013 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.RuntimeManager = class RuntimeManager extends WI.Object
{
constructor()
{
super();
this._activeExecutionContext = null;
WI.settings.consoleSavedResultAlias.addEventListener(WI.Setting.Event.Changed, function(event) {
for (let target of WI.targets) {
// COMPATIBILITY (iOS 12.2): Runtime.setSavedResultAlias did not exist.
if (target.hasCommand("Runtime.setSavedResultAlias"))
target.RuntimeAgent.setSavedResultAlias(WI.settings.consoleSavedResultAlias.value);
}
}, this);
}
// Static
static supportsAwaitPromise()
{
// COMPATIBILITY (iOS 12): Runtime.awaitPromise did not exist.
return InspectorBackend.hasCommand("Runtime.awaitPromise");
}
static preferredSavedResultPrefix()
{
// COMPATIBILITY (iOS 12.2): Runtime.setSavedResultAlias did not exist.
if (!InspectorBackend.hasCommand("Runtime.setSavedResultAlias"))
return "$";
return WI.settings.consoleSavedResultAlias.value || "$";
}
// Target
initializeTarget(target)
{
target.RuntimeAgent.enable();
if (WI.settings.showJavaScriptTypeInformation.value)
target.RuntimeAgent.enableTypeProfiler();
if (WI.settings.enableControlFlowProfiler.value)
target.RuntimeAgent.enableControlFlowProfiler();
// COMPATIBILITY (iOS 12.2): Runtime.setSavedResultAlias did not exist.
if (target.hasCommand("Runtime.setSavedResultAlias") && WI.settings.consoleSavedResultAlias.value)
target.RuntimeAgent.setSavedResultAlias(WI.settings.consoleSavedResultAlias.value);
}
// Public
get activeExecutionContext()
{
return this._activeExecutionContext;
}
set activeExecutionContext(executionContext)
{
if (this._activeExecutionContext === executionContext)
return;
this._activeExecutionContext = executionContext;
this.dispatchEventToListeners(WI.RuntimeManager.Event.ActiveExecutionContextChanged);
}
evaluateInInspectedWindow(expression, options, callback)
{
if (!this._activeExecutionContext) {
callback(null, false);
return;
}
let {objectGroup, includeCommandLineAPI, doNotPauseOnExceptionsAndMuteConsole, returnByValue, generatePreview, saveResult, emulateUserGesture, sourceURLAppender} = options;
includeCommandLineAPI = includeCommandLineAPI || false;
doNotPauseOnExceptionsAndMuteConsole = doNotPauseOnExceptionsAndMuteConsole || false;
returnByValue = returnByValue || false;
generatePreview = generatePreview || false;
saveResult = saveResult || false;
emulateUserGesture = emulateUserGesture || false;
sourceURLAppender = sourceURLAppender || appendWebInspectorSourceURL;
console.assert(objectGroup, "RuntimeManager.evaluateInInspectedWindow should always be called with an objectGroup");
console.assert(typeof sourceURLAppender === "function");
if (!expression) {
// There is no expression, so the completion should happen against global properties.
expression = "this";
} else if (/^\s*\{/.test(expression) && /\}\s*$/.test(expression)) {
// Transform {a:1} to ({a:1}) so it is treated like an object literal instead of a block with a label.
expression = "(" + expression + ")";
} else if (/\bawait\b/.test(expression)) {
// Transform `await <expr>` into an async function assignment.
expression = this._tryApplyAwaitConvenience(expression);
}
expression = sourceURLAppender(expression);
let target = this._activeExecutionContext.target;
let executionContextId = this._activeExecutionContext.id;
if (WI.debuggerManager.activeCallFrame) {
target = WI.debuggerManager.activeCallFrame.target;
executionContextId = target.executionContext.id;
}
function evalCallback(error, result, wasThrown, savedResultIndex)
{
this.dispatchEventToListeners(WI.RuntimeManager.Event.DidEvaluate, {objectGroup});
if (error) {
console.error(error);
callback(null, false);
return;
}
if (returnByValue)
callback(null, wasThrown, wasThrown ? null : result, savedResultIndex);
else
callback(WI.RemoteObject.fromPayload(result, target), wasThrown, savedResultIndex);
}
if (WI.debuggerManager.activeCallFrame) {
target.DebuggerAgent.evaluateOnCallFrame.invoke({
callFrameId: WI.debuggerManager.activeCallFrame.id,
expression,
objectGroup,
includeCommandLineAPI,
doNotPauseOnExceptionsAndMuteConsole,
returnByValue,
generatePreview,
saveResult,
emulateUserGesture, // COMPATIBILITY (iOS 13): "emulateUserGesture" did not exist yet.
}, evalCallback.bind(this));
return;
}
target.RuntimeAgent.evaluate.invoke({
expression,
objectGroup,
includeCommandLineAPI,
doNotPauseOnExceptionsAndMuteConsole,
contextId: executionContextId,
returnByValue,
generatePreview,
saveResult,
emulateUserGesture, // COMPATIBILITY (iOS 12.2): "emulateUserGesture" did not exist yet.
}, evalCallback.bind(this));
}
saveResult(remoteObject, callback)
{
console.assert(remoteObject instanceof WI.RemoteObject);
let target = this._activeExecutionContext.target;
let executionContextId = this._activeExecutionContext.id;
function mycallback(error, savedResultIndex)
{
callback(savedResultIndex);
}
if (remoteObject.objectId)
target.RuntimeAgent.saveResult(remoteObject.asCallArgument(), mycallback);
else
target.RuntimeAgent.saveResult(remoteObject.asCallArgument(), executionContextId, mycallback);
}
// Private
_tryApplyAwaitConvenience(originalExpression)
{
let esprimaSyntaxTree;
// Do not transform if the original code parses just fine.
try {
esprima.parse(originalExpression);
return originalExpression;
} catch { }
// Do not transform if the async function version does not parse.
try {
esprimaSyntaxTree = esprima.parse("(async function(){" + originalExpression + "})");
} catch {
return originalExpression;
}
// Assert expected AST produced by our wrapping code.
console.assert(esprimaSyntaxTree.type === "Program");
console.assert(esprimaSyntaxTree.body.length === 1);
console.assert(esprimaSyntaxTree.body[0].type === "ExpressionStatement");
console.assert(esprimaSyntaxTree.body[0].expression.type === "FunctionExpression");
console.assert(esprimaSyntaxTree.body[0].expression.async);
console.assert(esprimaSyntaxTree.body[0].expression.body.type === "BlockStatement");
// Do not transform if there is more than one statement.
let asyncFunctionBlock = esprimaSyntaxTree.body[0].expression.body;
if (asyncFunctionBlock.body.length !== 1)
return originalExpression;
// Extract the variable name for transformation.
let variableName;
let anonymous = false;
let declarationKind = "var";
let awaitPortion;
let statement = asyncFunctionBlock.body[0];
if (statement.type === "ExpressionStatement"
&& statement.expression.type === "AwaitExpression") {
// await <expr>
anonymous = true;
} else if (statement.type === "ExpressionStatement"
&& statement.expression.type === "AssignmentExpression"
&& statement.expression.right.type === "AwaitExpression"
&& statement.expression.left.type === "Identifier") {
// x = await <expr>
variableName = statement.expression.left.name;
awaitPortion = originalExpression.substring(originalExpression.indexOf("await"));
} else if (statement.type === "VariableDeclaration"
&& statement.declarations.length === 1
&& statement.declarations[0].init.type === "AwaitExpression"
&& statement.declarations[0].id.type === "Identifier") {
// var x = await <expr>
variableName = statement.declarations[0].id.name;
declarationKind = statement.kind;
awaitPortion = originalExpression.substring(originalExpression.indexOf("await"));
} else {
// Do not transform if this was not one of the simple supported syntaxes.
return originalExpression;
}
if (anonymous) {
return `
(async function() {
try {
let result = ${originalExpression};
console.info("%o", result);
} catch (e) {
console.error(e);
}
})();
undefined`;
}
return `${declarationKind} ${variableName};
(async function() {
try {
${variableName} = ${awaitPortion};
console.info("%o", ${variableName});
} catch (e) {
console.error(e);
}
})();
undefined;`;
}
};
WI.RuntimeManager.ConsoleObjectGroup = "console";
WI.RuntimeManager.TopLevelExecutionContextIdentifier = undefined;
WI.RuntimeManager.Event = {
DidEvaluate: "runtime-manager-did-evaluate",
DefaultExecutionContextChanged: "runtime-manager-default-execution-context-changed",
ActiveExecutionContextChanged: "runtime-manager-active-execution-context-changed",
};
@@ -0,0 +1,499 @@
/*
* Copyright (C) 2018, 2019 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.SelectionController = class SelectionController extends WI.Object
{
constructor(delegate, comparator)
{
super();
console.assert(delegate);
console.assert(typeof comparator === "function");
this._delegate = delegate;
this._comparator = comparator;
this._allowsEmptySelection = true;
this._allowsMultipleSelection = false;
this._lastSelectedItem = null;
this._shiftAnchorItem = null;
this._selectedItems = new Set;
this._suppressSelectionDidChange = false;
console.assert(this._delegate.selectionControllerFirstSelectableItem, "SelectionController delegate must implement selectionControllerFirstSelectableItem.");
console.assert(this._delegate.selectionControllerLastSelectableItem, "SelectionController delegate must implement selectionControllerLastSelectableItem.");
console.assert(this._delegate.selectionControllerNextSelectableItem, "SelectionController delegate must implement selectionControllerNextSelectableItem.");
console.assert(this._delegate.selectionControllerPreviousSelectableItem, "SelectionController delegate must implement selectionControllerPreviousSelectableItem.");
}
// Static
static createTreeComparator(itemForRepresentedObject)
{
return (a, b) => {
a = itemForRepresentedObject(a);
b = itemForRepresentedObject(b);
if (!a || !b)
return 0;
let getLevel = (item) => {
let level = 0;
while (item = item.parent)
level++;
return level;
};
let compareSiblings = (s, t) => {
return s.parent.children.indexOf(s) - s.parent.children.indexOf(t);
};
if (a.parent === b.parent)
return compareSiblings(a, b);
let aLevel = getLevel(a);
let bLevel = getLevel(b);
while (aLevel > bLevel) {
if (a.parent === b)
return 1;
a = a.parent;
aLevel--;
}
while (bLevel > aLevel) {
if (b.parent === a)
return -1;
b = b.parent;
bLevel--;
}
while (a.parent !== b.parent) {
a = a.parent;
b = b.parent;
}
console.assert(a.parent === b.parent, "Missing common ancestor.", a, b);
return compareSiblings(a, b);
};
}
static createListComparator(indexForRepresentedObject)
{
console.assert(indexForRepresentedObject);
return (a, b) => {
return indexForRepresentedObject(a) - indexForRepresentedObject(b);
};
}
// Public
get delegate() { return this._delegate; }
get lastSelectedItem() { return this._lastSelectedItem; }
get selectedItems() { return this._selectedItems; }
get allowsEmptySelection() { return this._allowsEmptySelection; }
set allowsEmptySelection(flag) { this._allowsEmptySelection = flag; }
get allowsMultipleSelection()
{
return this._allowsMultipleSelection;
}
set allowsMultipleSelection(flag)
{
if (this._allowsMultipleSelection === flag)
return;
this._allowsMultipleSelection = flag;
if (this._allowsMultipleSelection)
return;
if (this._selectedItems.size > 1)
this._updateSelectedItems(new Set([this._lastSelectedItem]));
}
hasSelectedItem(item)
{
return this._selectedItems.has(item);
}
selectItem(item, extendSelection = false)
{
console.assert(item, "Invalid item for selection.");
console.assert(!extendSelection || this._allowsMultipleSelection, "Cannot extend selection with multiple selection disabled.");
if (!this._allowsMultipleSelection)
extendSelection = false;
this._lastSelectedItem = item;
this._shiftAnchorItem = null;
let newItems = new Set(extendSelection ? this._selectedItems : null);
newItems.add(item);
this._updateSelectedItems(newItems);
}
selectItems(items)
{
console.assert(this._allowsMultipleSelection, "Cannot select multiple items with multiple selection disabled.");
if (!this._allowsMultipleSelection)
return;
if (!this._lastSelectedItem || !items.has(this._lastSelectedItem))
this._lastSelectedItem = items.lastValue;
if (!this._shiftAnchorItem || !items.has(this._shiftAnchorItem))
this._shiftAnchorItem = this._lastSelectedItem;
this._updateSelectedItems(items);
}
deselectItem(item)
{
console.assert(item, "Invalid item for selection.");
if (!this.hasSelectedItem(item))
return;
if (!this._allowsEmptySelection && this._selectedItems.size === 1)
return;
let newItems = new Set(this._selectedItems);
newItems.delete(item);
if (this._lastSelectedItem === item) {
this._lastSelectedItem = null;
if (newItems.size) {
console.assert(this._allowsMultipleSelection);
const operation = WI.SelectionController.Operation.Extend;
// Find selected item closest to deselected item.
let previous = item;
let next = item;
while (!this._lastSelectedItem && previous && next) {
previous = this._previousSelectableItem(previous, operation);
if (this.hasSelectedItem(previous)) {
this._lastSelectedItem = previous;
break;
}
next = this._nextSelectableItem(next, operation);
if (this.hasSelectedItem(next)) {
this._lastSelectedItem = next;
break;
}
}
}
}
if (this._shiftAnchorItem === item)
this._shiftAnchorItem = null;
this._updateSelectedItems(newItems);
}
selectAll()
{
if (!this._allowsMultipleSelection)
return;
const operation = WI.SelectionController.Operation.Extend;
let newItems = new Set;
this._addRange(newItems, this._firstSelectableItem(operation), this._lastSelectableItem(operation));
this.selectItems(newItems);
}
deselectAll()
{
this._deselectAllAndSelect(null);
}
removeSelectedItems()
{
if (!this._selectedItems.size)
return;
let operation = this._allowsMultipleSelection ? WI.SelectionController.Operation.Extend : WI.SelectionController.Operation.Direct;
let orderedSelection = Array.from(this._selectedItems).sort(this._comparator);
// Try selecting the item preceding the selection.
let firstSelectedItem = orderedSelection[0];
let itemToSelect = this._previousSelectableItem(firstSelectedItem, operation);
if (!itemToSelect) {
// If no item exists before the first item in the selection, try selecting
// a deselected item (hole) within the selection.
itemToSelect = firstSelectedItem;
while (itemToSelect && this.hasSelectedItem(itemToSelect))
itemToSelect = this._nextSelectableItem(itemToSelect, operation);
if (!itemToSelect || this.hasSelectedItem(itemToSelect)) {
// If the selection contains no holes, try selecting the item
// following the selection.
itemToSelect = this._nextSelectableItem(orderedSelection.lastValue, operation);
}
}
this._deselectAllAndSelect(itemToSelect);
}
reset()
{
this._lastSelectedItem = null;
this._shiftAnchorItem = null;
this._selectedItems.clear();
}
didRemoveItems(items)
{
console.assert(items instanceof Set);
if (!items.size || !this._selectedItems.size)
return;
this._updateSelectedItems(this._selectedItems.difference(items));
}
handleKeyDown(event)
{
if (event.key === "a" && event.commandOrControlKey) {
this.selectAll();
return true;
}
if (event.metaKey || event.ctrlKey)
return false;
if (event.keyIdentifier === "Up" || event.keyIdentifier === "Down") {
this._selectItemsFromArrowKey(event.keyIdentifier === "Up", event.shiftKey);
event.preventDefault();
event.stopPropagation();
return true;
}
return false;
}
handleItemMouseDown(item, event)
{
console.assert(item, "Invalid item for selection.");
if (event.button !== 0 || event.ctrlKey)
return;
// Command (macOS) or Control (Windows) key takes precedence over shift
// whether or not multiple selection is enabled, so handle it first.
if (event.commandOrControlKey) {
if (this.hasSelectedItem(item))
this.deselectItem(item);
else
this.selectItem(item, this._allowsMultipleSelection);
return;
}
let shiftExtendSelection = this._allowsMultipleSelection && event.shiftKey;
if (!shiftExtendSelection) {
this.selectItem(item);
return;
}
let newItems = new Set(this._selectedItems);
// Shift-clicking when nothing is selected should cause the first item
// through the clicked item to be selected.
if (!newItems.size) {
this._lastSelectedItem = item;
this._shiftAnchorItem = this._firstSelectableItem(WI.SelectionController.Operation.Extend);
this._addRange(newItems, this._shiftAnchorItem, this._lastSelectedItem);
this._updateSelectedItems(newItems);
return;
}
if (!this._shiftAnchorItem)
this._shiftAnchorItem = this._lastSelectedItem;
// Shift-clicking will add to or delete from the current selection, or
// pivot the selection around the anchor (a delete followed by an add).
// We could check for all three cases, and add or delete only those items
// that are necessary, but it is simpler to throw out the previous shift-
// selected range and add the new range between the anchor and clicked item.
let sortItemPair = (a, b) => {
return [a, b].sort(this._comparator);
};
if (this._shiftAnchorItem !== this._lastSelectedItem) {
let [startItem, endItem] = sortItemPair(this._shiftAnchorItem, this._lastSelectedItem);
this._deleteRange(newItems, startItem, endItem);
}
let [startItem, endItem] = sortItemPair(this._shiftAnchorItem, item);
this._addRange(newItems, startItem, endItem);
this._lastSelectedItem = item;
this._updateSelectedItems(newItems);
}
// Private
_deselectAllAndSelect(item)
{
if (!this._selectedItems.size && !item)
return;
if (this._selectedItems.size === 1 && this.hasSelectedItem(item))
return;
this._lastSelectedItem = item;
this._shiftAnchorItem = null;
let newItems = new Set;
if (item)
newItems.add(item);
this._updateSelectedItems(newItems);
}
_selectItemsFromArrowKey(goingUp, shiftKey)
{
let extendSelection = shiftKey && this._allowsMultipleSelection;
let operation = extendSelection ? WI.SelectionController.Operation.Extend : WI.SelectionController.Operation.Direct;
if (!this._selectedItems.size) {
this.selectItem(goingUp ? this._lastSelectableItem(operation) : this._firstSelectableItem(operation));
return;
}
let item = goingUp ? this._previousSelectableItem(this._lastSelectedItem, operation) : this._nextSelectableItem(this._lastSelectedItem, operation);
if (!item)
return;
if (!extendSelection || !this.hasSelectedItem(item)) {
this.selectItem(item, extendSelection);
return;
}
// Since the item in the direction of movement is selected, we are either
// extending the selection into the item, or deselecting. Determine which
// by checking whether the item opposite the anchor item is selected.
let priorItem = goingUp ? this._nextSelectableItem(this._lastSelectedItem, operation) : this._previousSelectableItem(this._lastSelectedItem, operation);
if (!priorItem || !this.hasSelectedItem(priorItem)) {
this.deselectItem(this._lastSelectedItem);
return;
}
// The selection is being extended into the item; make it the new
// anchor item then continue searching in the direction of movement
// for an unselected item to select.
while (item) {
if (!this.hasSelectedItem(item)) {
this.selectItem(item, extendSelection);
break;
}
this._lastSelectedItem = item;
item = goingUp ? this._previousSelectableItem(item, operation) : this._nextSelectableItem(item, operation);
}
}
_firstSelectableItem(operation)
{
return this._delegate.selectionControllerFirstSelectableItem(this, operation);
}
_lastSelectableItem(operation)
{
return this._delegate.selectionControllerLastSelectableItem(this, operation);
}
_previousSelectableItem(item, operation)
{
return this._delegate.selectionControllerPreviousSelectableItem(this, item, operation);
}
_nextSelectableItem(item, operation)
{
return this._delegate.selectionControllerNextSelectableItem(this, item, operation);
}
_updateSelectedItems(items)
{
let oldSelectedItems = this._selectedItems;
this._selectedItems = items;
if (this._suppressSelectionDidChange || !this._delegate.selectionControllerSelectionDidChange)
return;
let deselectedItems = oldSelectedItems.difference(items);
let selectedItems = items.difference(oldSelectedItems);
if (deselectedItems.size || selectedItems.size)
this._delegate.selectionControllerSelectionDidChange(this, deselectedItems, selectedItems);
}
_addRange(items, firstItem, lastItem)
{
console.assert(this._allowsMultipleSelection);
const operation = WI.SelectionController.Operation.Extend;
let current = firstItem;
while (current) {
items.add(current);
if (current === lastItem)
break;
current = this._nextSelectableItem(current, operation);
}
console.assert(!lastItem || items.has(lastItem), "End of range could not be reached.");
}
_deleteRange(items, firstItem, lastItem)
{
console.assert(this._allowsMultipleSelection);
const operation = WI.SelectionController.Operation.Extend;
let current = firstItem;
while (current) {
items.delete(current);
if (current === lastItem)
break;
current = this._nextSelectableItem(current, operation);
}
console.assert(!lastItem || !items.has(lastItem), "End of range could not be reached.");
}
};
WI.SelectionController.Operation = {
Direct: Symbol("selection-operation-direct"),
Extend: Symbol("selection-operation-extend"),
};
@@ -0,0 +1,217 @@
/*
* 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.
*/
WI.StackTraceTreeController = class StackTraceTreeController extends WI.Object
{
constructor(treeOutline)
{
console.assert(treeOutline instanceof WI.TreeOutline);
super();
this._stackTrace = null;
this._treeOutline = treeOutline;
if (this._treeOutline.selectable)
this._treeOutline.addEventListener(WI.TreeOutline.Event.SelectionDidChange, this._treeSelectionDidChange, this);
else
this._treeOutline.addEventListener(WI.TreeOutline.Event.ElementClicked, this._treeElementClicked, this);
}
// Static
static groupBlackboxedStackTrace(parent, stackTrace, {rememberBlackboxedCallFrameGroupToAutoExpand} = {})
{
let parentIsNode = parent instanceof Node;
console.assert(parentIsNode || parent instanceof WI.TreeOutline || parent instanceof WI.TreeElement, parent);
console.assert(stackTrace instanceof WI.StackTrace, stackTrace);
let CallFrameUIClass = parentIsNode ? WI.CallFrameView : WI.CallFrameTreeElement;
let activeCallFrameTreeElement = WI.StackTraceTreeController._groupBlackboxedCallFrames(parent, stackTrace.callFrames, {rememberBlackboxedCallFrameGroupToAutoExpand});
let parentStackTrace = stackTrace.parentStackTrace;
while (parentStackTrace) {
console.assert(parentStackTrace.callFrames.length, "StackTrace should have non-empty call frames array.");
if (!parentStackTrace.callFrames.length)
break;
let boundaryCallFrame;
if (parentStackTrace.topCallFrameIsBoundary) {
boundaryCallFrame = parentStackTrace.callFrames[0];
console.assert(boundaryCallFrame.nativeCode && !boundaryCallFrame.sourceCodeLocation);
} else {
// Create a generic native CallFrame for the asynchronous boundary.
boundaryCallFrame = new WI.CallFrame(parentStackTrace.callFrames[0].target, {
functionName: WI.UIString("(async)"),
nativeCode: true,
});
}
parent.appendChild(new CallFrameUIClass(boundaryCallFrame, {showFunctionName: true, isAsyncBoundaryCallFrame: true}));
let startIndex = parentStackTrace.topCallFrameIsBoundary ? 1 : 0;
let parentCallFrames = startIndex ? parentStackTrace.callFrames.slice(startIndex) : parentStackTrace.callFrames;
WI.StackTraceTreeController._groupBlackboxedCallFrames(parent, parentCallFrames, {rememberBlackboxedCallFrameGroupToAutoExpand});
if (parentStackTrace.truncated) {
let truncatedCallFrame = new WI.CallFrame(parentStackTrace.callFrames[0].target, {
functionName: WI.UIString("(call frames truncated)"),
nativeCode: true,
});
parent.appendChild(new CallFrameUIClass(truncatedCallFrame, {showFunctionName: true, isTruncatedBoundaryCallFrame: true}));
}
parentStackTrace = parentStackTrace.parentStackTrace;
}
return activeCallFrameTreeElement;
}
static _groupBlackboxedCallFrames(parent, callFrames, {rememberBlackboxedCallFrameGroupToAutoExpand} = {})
{
let parentIsNode = parent instanceof Node;
console.assert(parentIsNode || parent instanceof WI.TreeOutline || parent instanceof WI.TreeElement, parent);
console.assert(Array.isArray(callFrames) && callFrames.length && callFrames.every((callFrame) => callFrame instanceof WI.CallFrame), callFrames);
let BlackboxedGroupUIClass = parentIsNode ? WI.BlackboxedGroupView : WI.BlackboxedGroupTreeElement;
let blackboxedGroupUIOptions = {rememberBlackboxedCallFrameGroupToAutoExpand};
let CallFrameUIClass = parentIsNode ? WI.CallFrameView : WI.CallFrameTreeElement;
let callFrameUIOptions = {showFunctionName: true, indicateIfBlackboxed: true};
let activeCallFrame = WI.debuggerManager.activeCallFrame;
let activeCallFrameTreeElement = null;
let blackboxedCallFrameGroupStartIndex = undefined;
function displayable(callFrame) {
if (parentIsNode) {
if (!callFrame.sourceCodeLocation && callFrame.functionName === null)
return false;
if (callFrame.isConsoleEvaluation && !WI.settings.debugShowConsoleEvaluations.value)
return false;
}
return true;
}
// Add one extra iteration to handle call stacks that start blackboxed.
for (let i = 0; i < callFrames.length + 1; ++i) {
let callFrame = callFrames[i];
if (callFrame) {
if (callFrames.length > 1 && !displayable(callFrame))
continue;
if (callFrame.blackboxed) {
blackboxedCallFrameGroupStartIndex ??= i;
continue;
}
}
if (blackboxedCallFrameGroupStartIndex !== undefined) {
let blackboxedCallFrameGroup = callFrames.slice(blackboxedCallFrameGroupStartIndex, i).filter(displayable);
blackboxedCallFrameGroupStartIndex = undefined;
if (!rememberBlackboxedCallFrameGroupToAutoExpand || !WI.debuggerManager.shouldAutoExpandBlackboxedCallFrameGroup(blackboxedCallFrameGroup))
parent.appendChild(new BlackboxedGroupUIClass(blackboxedCallFrameGroup, blackboxedGroupUIOptions));
else {
for (let blackboxedCallFrame of blackboxedCallFrameGroup)
parent.appendChild(new CallFrameUIClass(blackboxedCallFrame, callFrameUIOptions));
}
}
if (!callFrame)
continue;
let callFrameTreeElement = new CallFrameUIClass(callFrame, callFrameUIOptions);
if (callFrame === activeCallFrame && !parentIsNode)
activeCallFrameTreeElement = callFrameTreeElement;
parent.appendChild(callFrameTreeElement);
}
return activeCallFrameTreeElement;
}
// Public
get treeOutline() { return this._treeOutline; }
get stackTrace()
{
return this._stackTrace;
}
set stackTrace(stackTrace)
{
stackTrace = stackTrace || null;
if (this._stackTrace === stackTrace)
return;
this._stackTrace = stackTrace;
this._treeOutline.removeChildren();
if (this._stackTrace)
WI.StackTraceTreeController.groupBlackboxedStackTrace(this._treeOutline, this._stackTrace);
}
disconnect()
{
if (this._treeOutline.selectable)
this._treeOutline.removeEventListener(WI.TreeOutline.Event.SelectionDidChange, this._treeSelectionDidChange, this);
else
this._treeOutline.removeEventListener(WI.TreeOutline.Event.ElementClicked, this._treeElementClicked, this);
}
// Private
_treeElementClicked(event)
{
this._showSourceCodeLocation(event.data.treeElement);
}
_treeSelectionDidChange(event)
{
this._showSourceCodeLocation(this._treeOutline.selectedTreeElement);
}
_showSourceCodeLocation(treeElement)
{
let callFrame = treeElement.callFrame;
if (!callFrame.sourceCodeLocation)
return;
WI.showSourceCodeLocation(callFrame.sourceCodeLocation, {
ignoreNetworkTab: true,
ignoreSearchTab: true,
// Treat call frame clicks as link clicks since it jumps to a source location.
initiatorHint: WI.TabBrowser.TabNavigationInitiator.LinkClick,
});
}
};
@@ -0,0 +1,201 @@
/*
* Copyright (C) 2019 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.TabActivityDiagnosticEventRecorder = class TabActivityDiagnosticEventRecorder extends WI.DiagnosticEventRecorder
{
constructor(controller)
{
super("TabActivity", controller);
this._inspectorHasFocus = true;
this._lastUserInteractionTimestamp = undefined;
this._eventSamplingTimerIdentifier = undefined;
this._initialDelayBeforeSamplingTimerIdentifier = undefined;
}
// Static
// In milliseconds.
static get eventSamplingInterval() { return 60 * 1000; }
static get initialDelayBeforeSamplingInterval() { return 10 * 1000; }
// Protected
setup()
{
const options = {
capture: true,
};
window.addEventListener("focus", this, options);
window.addEventListener("blur", this, options);
window.addEventListener("keydown", this, options);
window.addEventListener("mousedown", this, options);
// If it's been less than 10 seconds since the frontend loaded, wait a bit.
if (performance.now() - WI.frontendCompletedLoadTimestamp < TabActivityDiagnosticEventRecorder.initialDelayBeforeSamplingInterval)
this._startInitialDelayBeforeSamplingTimer();
else
this._startEventSamplingTimer();
}
teardown()
{
const options = {
capture: true,
};
window.removeEventListener("focus", this, options);
window.removeEventListener("blur", this, options);
window.removeEventListener("keydown", this, options);
window.removeEventListener("mousedown", this, options);
this._stopInitialDelayBeforeSamplingTimer();
this._stopEventSamplingTimer();
}
// Public
handleEvent(event)
{
switch (event.type) {
case "focus":
this._handleWindowFocus(event);
break;
case "blur":
this._handleWindowBlur(event);
break;
case "keydown":
this._handleWindowKeyDown(event);
break;
case "mousedown":
this._handleWindowMouseDown(event);
break;
}
}
// Private
_startInitialDelayBeforeSamplingTimer()
{
if (this._initialDelayBeforeSamplingTimerIdentifier) {
clearTimeout(this._initialDelayBeforeSamplingTimerIdentifier);
this._initialDelayBeforeSamplingTimerIdentifier = undefined;
}
// All intervals are in milliseconds.
let maximumInitialDelay = TabActivityDiagnosticEventRecorder.initialDelayBeforeSamplingInterval;
let elapsedTime = performance.now() - WI.frontendCompletedLoadTimestamp;
let remainingTime = maximumInitialDelay - elapsedTime;
let initialDelay = Number.constrain(remainingTime, 0, maximumInitialDelay);
this._initialDelayBeforeSamplingTimerIdentifier = setTimeout(this._sampleCurrentTabActivity.bind(this), initialDelay);
}
_stopInitialDelayBeforeSamplingTimer()
{
if (this._initialDelayBeforeSamplingTimerIdentifier) {
clearTimeout(this._initialDelayBeforeSamplingTimerIdentifier);
this._initialDelayBeforeSamplingTimerIdentifier = undefined;
}
}
_startEventSamplingTimer()
{
if (this._eventSamplingTimerIdentifier) {
clearTimeout(this._eventSamplingTimerIdentifier);
this._eventSamplingTimerIdentifier = undefined;
}
this._eventSamplingTimerIdentifier = setTimeout(this._sampleCurrentTabActivity.bind(this), TabActivityDiagnosticEventRecorder.eventSamplingInterval);
}
_stopEventSamplingTimer()
{
if (this._eventSamplingTimerIdentifier) {
clearTimeout(this._eventSamplingTimerIdentifier);
this._eventSamplingTimerIdentifier = undefined;
}
}
_sampleCurrentTabActivity()
{
// Set up the next timer first so later code can bail out if there's nothing to do.
this._stopEventSamplingTimer();
this._stopInitialDelayBeforeSamplingTimer();
this._startEventSamplingTimer();
let intervalSinceLastUserInteraction = performance.now() - this._lastUserInteractionTimestamp;
if (intervalSinceLastUserInteraction > TabActivityDiagnosticEventRecorder.eventSamplingInterval) {
if (WI.settings.debugAutoLogDiagnosticEvents.valueRespectingDebugUIAvailability)
console.log("TabActivity: sample not reported, last user interaction was %.1f seconds ago.".format(intervalSinceLastUserInteraction / 1000));
return;
}
let selectedTabContentView = WI.tabBrowser.selectedTabContentView;
console.assert(selectedTabContentView);
if (!selectedTabContentView)
return;
let tabType = selectedTabContentView.type;
let interval = TabActivityDiagnosticEventRecorder.eventSamplingInterval / 1000;
this.logDiagnosticEvent(this.name, {tabType, interval});
}
_didObserveUserInteraction()
{
if (!this._inspectorHasFocus)
return;
this._lastUserInteractionTimestamp = performance.now();
}
_handleWindowFocus(event)
{
if (event.target !== window)
return;
this._inspectorHasFocus = true;
}
_handleWindowBlur(event)
{
if (event.target !== window)
return;
this._inspectorHasFocus = false;
}
_handleWindowKeyDown(event)
{
this._didObserveUserInteraction();
}
_handleWindowMouseDown(event)
{
this._didObserveUserInteraction();
}
};
@@ -0,0 +1,90 @@
/*
* Copyright (C) 2019 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.TabNavigationDiagnosticEventRecorder = class TabNavigationDiagnosticEventRecorder extends WI.DiagnosticEventRecorder
{
constructor(controller)
{
super("TabNavigation", controller);
}
// Protected
setup()
{
WI.tabBrowser.addEventListener(WI.TabBrowser.Event.SelectedTabContentViewDidChange, this._selectedTabContentViewDidChange, this);
}
teardown()
{
WI.tabBrowser.removeEventListener(WI.TabBrowser.Event.SelectedTabContentViewDidChange, this._selectedTabContentViewDidChange, this);
}
// Private
_selectedTabContentViewDidChange(event)
{
let outgoingTabType = event.data.outgoingTab.identifier;
let incomingTabType = event.data.incomingTab.identifier;
let initiator = WI.TabNavigationDiagnosticEventRecorder.tabBrowserInitiatorToEventInitiator(event.data.initiator || WI.TabBrowser.TabNavigationInitiator.Unknown);
if (!initiator) {
// If initiator is null, then there is a missing value in the switch. This is a programming error.
WI.reportInternalError("Value of 'initiator' could not be parsed: " + event.data.initiator);
return;
}
this.logDiagnosticEvent(this.name, {outgoingTabType, incomingTabType, initiator});
}
static tabBrowserInitiatorToEventInitiator(tabBrowserInitiator)
{
switch (tabBrowserInitiator) {
case WI.TabBrowser.TabNavigationInitiator.TabClick:
return "tab-click";
case WI.TabBrowser.TabNavigationInitiator.LinkClick:
return "link-click";
case WI.TabBrowser.TabNavigationInitiator.ButtonClick:
return "button-click";
case WI.TabBrowser.TabNavigationInitiator.ContextMenu:
return "context-menu";
case WI.TabBrowser.TabNavigationInitiator.Dashboard:
return "dashboard";
case WI.TabBrowser.TabNavigationInitiator.Breakpoint:
return "breakpoint";
case WI.TabBrowser.TabNavigationInitiator.Inspect:
return "inspect";
case WI.TabBrowser.TabNavigationInitiator.KeyboardShortcut:
return "keyboard-shortcut";
case WI.TabBrowser.TabNavigationInitiator.FrontendAPI:
return "frontend-api";
case WI.TabBrowser.TabNavigationInitiator.Unknown:
return "unknown";
}
console.error("Unhandled initiator type: " + tabBrowserInitiator);
return null;
}
};
+312
View File
@@ -0,0 +1,312 @@
/*
* Copyright (C) 2016-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.TargetManager = class TargetManager extends WI.Object
{
constructor()
{
super();
this._targets = new Map;
this._cachedTargetsList = null;
this._seenPageTarget = false;
this._transitionTimeoutIdentifier = undefined;
}
// Target
initializeTarget(target)
{
// COMPATIBILITY (iOS 13): Target.setPauseOnStart did not exist yet.
if (target.hasCommand("Target.setPauseOnStart"))
target.TargetAgent.setPauseOnStart(true);
}
// Public
get targets()
{
if (!this._cachedTargetsList)
this._cachedTargetsList = Array.from(this._targets.values()).filter((target) => !(target instanceof WI.MultiplexingBackendTarget));
return this._cachedTargetsList;
}
get workerTargets()
{
return this.targets.filter((target) => target.type === WI.TargetType.Worker);
}
get allTargets()
{
return Array.from(this._targets.values());
}
targetForIdentifier(targetId)
{
if (!targetId)
return null;
for (let target of this._targets.values()) {
if (target.identifier === targetId)
return target;
}
return null;
}
addTarget(target)
{
console.assert(target);
console.assert(!this._targets.has(target.identifier));
this._cachedTargetsList = null;
this._targets.set(target.identifier, target);
this.dispatchEventToListeners(WI.TargetManager.Event.TargetAdded, {target});
}
removeTarget(target)
{
console.assert(target);
console.assert(target !== WI.mainTarget);
this._cachedTargetsList = null;
this._targets.delete(target.identifier);
target.destroy();
this.dispatchEventToListeners(WI.TargetManager.Event.TargetRemoved, {target});
}
createMultiplexingBackendTarget()
{
console.assert(WI.sharedApp.debuggableType === WI.DebuggableType.WebPage);
let target = new WI.MultiplexingBackendTarget;
target.initialize();
this._initializeBackendTarget(target);
// Add the target without dispatching an event.
this._targets.set(target.identifier, target);
}
createDirectBackendTarget()
{
console.assert(WI.sharedApp.debuggableType !== WI.DebuggableType.WebPage);
let target = new WI.DirectBackendTarget;
target.initialize();
this._initializeBackendTarget(target);
if (WI.sharedApp.debuggableType === WI.DebuggableType.ITML || WI.sharedApp.debuggableType === WI.DebuggableType.Page)
this._initializePageTarget(target);
this.addTarget(target);
}
// TargetObserver
targetCreated(parentTarget, targetInfo)
{
let connection = new InspectorBackend.TargetConnection(parentTarget, targetInfo.targetId);
let subTarget = this._createTarget(parentTarget, targetInfo, connection);
this._checkAndHandlePageTargetTransition(subTarget);
subTarget.initialize();
this.addTarget(subTarget);
}
didCommitProvisionalTarget(parentTarget, previousTargetId, newTargetId)
{
this.targetDestroyed(previousTargetId);
let target = this._targets.get(newTargetId);
console.assert(target);
if (!target)
return;
target.didCommitProvisionalTarget();
this._checkAndHandlePageTargetTransition(target);
target.connection.dispatchProvisionalMessages();
this.dispatchEventToListeners(WI.TargetManager.Event.DidCommitProvisionalTarget, {previousTargetId, target});
}
targetDestroyed(targetId)
{
let target = this._targets.get(targetId);
if (!target)
return;
this._checkAndHandlePageTargetTermination(target);
this.removeTarget(target);
}
dispatchMessageFromTarget(targetId, message)
{
let target = this._targets.get(targetId);
console.assert(target);
if (!target)
return;
if (target.isProvisional)
target.connection.addProvisionalMessage(message);
else
target.connection.dispatch(message);
}
// Private
_createTarget(parentTarget, targetInfo, connection)
{
// COMPATIBILITY (iOS 13.0): `Target.TargetInfo.isProvisional` and `Target.TargetInfo.isPaused` did not exist yet.
let {targetId, type, isProvisional, isPaused} = targetInfo;
switch (type) {
case InspectorBackend.Enum.Target.TargetInfoType.Page:
return new WI.PageTarget(parentTarget, targetId, WI.UIString("Page"), connection, {isProvisional, isPaused});
case InspectorBackend.Enum.Target.TargetInfoType.Worker:
return new WI.WorkerTarget(parentTarget, targetId, WI.UIString("Worker"), connection, {isPaused});
case "serviceworker": // COMPATIBILITY (iOS 13): "serviceworker" was renamed to "service-worker".
case InspectorBackend.Enum.Target.TargetInfoType.ServiceWorker:
return new WI.WorkerTarget(parentTarget, targetId, WI.UIString("ServiceWorker"), connection, {isPaused});
}
throw "Unknown Target type: " + type;
}
_checkAndHandlePageTargetTransition(target)
{
if (target.type !== WI.TargetType.Page)
return;
if (target.isProvisional)
return;
// First page target.
if (!WI.pageTarget && !this._seenPageTarget) {
this._seenPageTarget = true;
this._initializePageTarget(target);
return;
}
// Transitioning page target.
this._transitionPageTarget(target);
}
_checkAndHandlePageTargetTermination(target)
{
if (target.type !== WI.TargetType.Page)
return;
if (target.isProvisional)
return;
console.assert(target === WI.pageTarget);
console.assert(this._seenPageTarget);
// Terminating the page target.
this._terminatePageTarget(target);
// Ensure we transition in a reasonable amount of time, otherwise close.
const timeToTransition = 2000;
clearTimeout(this._transitionTimeoutIdentifier);
this._transitionTimeoutIdentifier = setTimeout(() => {
this._transitionTimeoutIdentifier = undefined;
if (WI.pageTarget)
return;
if (WI.isEngineeringBuild)
throw new Error("Error: No new pageTarget some time after last page target was terminated. Failed transition?");
WI.close();
}, timeToTransition);
}
_initializeBackendTarget(target)
{
console.assert(!WI.mainTarget);
WI.backendTarget = target;
this._resetMainExecutionContext();
WI._backendTargetAvailablePromise.resolve();
}
_initializePageTarget(target)
{
console.assert(WI.sharedApp.isWebDebuggable() || WI.sharedApp.debuggableType === WI.DebuggableType.ITML);
console.assert(target.type === WI.TargetType.Page || target instanceof WI.DirectBackendTarget);
WI.pageTarget = target;
this._resetMainExecutionContext();
WI._pageTargetAvailablePromise.resolve();
}
_transitionPageTarget(target)
{
console.assert(!WI.pageTarget);
console.assert(WI.sharedApp.debuggableType === WI.DebuggableType.WebPage);
console.assert(target.type === WI.TargetType.Page);
WI.pageTarget = target;
this._resetMainExecutionContext();
// Actions to transition the page target.
WI.notifications.dispatchEventToListeners(WI.Notification.TransitionPageTarget);
WI.domManager.transitionPageTarget();
WI.networkManager.transitionPageTarget();
WI.timelineManager.transitionPageTarget();
}
_terminatePageTarget(target)
{
console.assert(WI.pageTarget);
console.assert(WI.pageTarget === target);
console.assert(WI.sharedApp.debuggableType === WI.DebuggableType.WebPage);
// Remove any Worker targets associated with this page.
for (let workerTarget of this.workerTargets)
WI.workerManager.workerTerminated(workerTarget.identifier);
WI.pageTarget = null;
}
_resetMainExecutionContext()
{
if (WI.mainTarget instanceof WI.MultiplexingBackendTarget)
return;
if (WI.mainTarget.executionContext)
WI.runtimeManager.activeExecutionContext = WI.mainTarget.executionContext;
}
};
WI.TargetManager.Event = {
TargetAdded: "target-manager-target-added",
TargetRemoved: "target-manager-target-removed",
DidCommitProvisionalTarget: "target-manager-provisional-target-committed",
};
File diff suppressed because it is too large Load Diff
+199
View File
@@ -0,0 +1,199 @@
/*
* Copyright (C) 2014, 2015 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.TypeTokenAnnotator = class TypeTokenAnnotator extends WI.Annotator
{
constructor(sourceCodeTextEditor, script)
{
super(sourceCodeTextEditor);
this._script = script;
this._typeTokenNodes = [];
this._typeTokenBookmarks = [];
}
// Protected
insertAnnotations()
{
if (!this.isActive())
return;
var scriptSyntaxTree = this._script.scriptSyntaxTree;
if (!scriptSyntaxTree) {
this._script.requestScriptSyntaxTree((syntaxTree) => {
// After requesting the tree, we still might get a null tree from a parse error.
if (syntaxTree)
this.insertAnnotations();
});
return;
}
if (!scriptSyntaxTree.parsedSuccessfully)
return;
let {startPosition, endPosition} = this.sourceCodeTextEditor.visibleRangePositions();
let startTime = Date.now();
let allNodesInRange = scriptSyntaxTree.filterByRange(startPosition, endPosition);
scriptSyntaxTree.updateTypes(allNodesInRange, (nodesWithUpdatedTypes) => {
// Because this is an asynchronous call, we could have been deactivated before the callback function is called.
if (!this.isActive())
return;
nodesWithUpdatedTypes.forEach(this._insertTypeToken, this);
let totalTime = Date.now() - startTime;
let timeoutTime = Number.constrain(8 * totalTime, 500, 2000);
this._timeoutIdentifier = setTimeout(() => {
this._timeoutIdentifier = null;
this.insertAnnotations();
}, timeoutTime);
});
}
clearAnnotations()
{
this._clearTypeTokens();
}
// Private
_insertTypeToken(node)
{
if (node.type === WI.ScriptSyntaxTree.NodeType.Identifier) {
if (!node.attachments.__typeToken && node.attachments.types && node.attachments.types.valid)
this._insertToken(node, false, WI.TypeTokenView.TitleType.Variable, node.name);
if (node.attachments.__typeToken)
node.attachments.__typeToken.update(node.attachments.types);
return;
}
console.assert(node.type === WI.ScriptSyntaxTree.NodeType.FunctionDeclaration || node.type === WI.ScriptSyntaxTree.NodeType.FunctionExpression || node.type === WI.ScriptSyntaxTree.NodeType.ArrowFunctionExpression);
var functionReturnType = node.attachments.returnTypes;
if (!functionReturnType || !functionReturnType.valid)
return;
// If a function does not have an explicit return statement with an argument (i.e, "return x;" instead of "return;")
// then don't show a return type unless we think it's a constructor.
var scriptSyntaxTree = this._script._scriptSyntaxTree;
if (!node.attachments.__typeToken && (scriptSyntaxTree.containsNonEmptyReturnStatement(node.body) || !functionReturnType.typeSet.isContainedIn(WI.TypeSet.TypeBit.Undefined))) {
var functionName = node.id ? node.id.name : null;
this._insertToken(node, true, WI.TypeTokenView.TitleType.ReturnStatement, functionName);
}
if (node.attachments.__typeToken)
node.attachments.__typeToken.update(node.attachments.returnTypes);
}
_insertToken(node, shouldTranslateOffsetToAfterParameterList, typeTokenTitleType, functionOrVariableName)
{
let tokenPosition = this.sourceCodeTextEditor.originalPositionToCurrentPosition(node.startPosition);
let currentOffset = this.sourceCodeTextEditor.currentPositionToCurrentOffset(tokenPosition);
let sourceString = this.sourceCodeTextEditor.string;
if (shouldTranslateOffsetToAfterParameterList) {
// Translate the position to the closing parenthesis of the function arguments:
// translate from: [type-token] function foo() {} => to: function foo() [type-token] {}
currentOffset = this._translateToOffsetAfterFunctionParameterList(node, currentOffset, sourceString);
tokenPosition = this.sourceCodeTextEditor.currentOffsetToCurrentPosition(currentOffset);
}
// Note: bookmarks render to the left of the character they're being displayed next to.
// This is why right margin checks the current offset. And this is okay to do because JavaScript can't be written right-to-left.
var isSpaceRegexp = /\s/;
var shouldHaveLeftMargin = currentOffset !== 0 && !isSpaceRegexp.test(sourceString[currentOffset - 1]);
var shouldHaveRightMargin = !isSpaceRegexp.test(sourceString[currentOffset]);
var typeToken = new WI.TypeTokenView(this, shouldHaveRightMargin, shouldHaveLeftMargin, typeTokenTitleType, functionOrVariableName);
var bookmark = this.sourceCodeTextEditor.setInlineWidget(tokenPosition, typeToken.element);
node.attachments.__typeToken = typeToken;
this._typeTokenNodes.push(node);
this._typeTokenBookmarks.push(bookmark);
}
_translateToOffsetAfterFunctionParameterList(node, offset, sourceString)
{
// The assumption here is that we get the offset starting at the function keyword (or after the get/set keywords).
// We will return the offset for the closing parenthesis in the function declaration.
// All this code is just a way to find this parenthesis while ignoring comments.
var isMultiLineComment = false;
var isSingleLineComment = false;
var shouldIgnore = false;
const isArrowFunction = node.type === WI.ScriptSyntaxTree.NodeType.ArrowFunctionExpression;
function isLineTerminator(char)
{
// Reference EcmaScript 5 grammar for single line comments and line terminators:
// http://www.ecma-international.org/ecma-262/5.1/#sec-7.3
// http://www.ecma-international.org/ecma-262/5.1/#sec-7.4
return char === "\n" || char === "\r" || char === "\u2028" || char === "\u2029";
}
while (((!isArrowFunction && sourceString[offset] !== ")")
|| (isArrowFunction && sourceString[offset] !== ">")
|| shouldIgnore)
&& offset < sourceString.length) {
if (isSingleLineComment && isLineTerminator(sourceString[offset])) {
isSingleLineComment = false;
shouldIgnore = false;
} else if (isMultiLineComment && sourceString[offset] === "*" && sourceString[offset + 1] === "/") {
isMultiLineComment = false;
shouldIgnore = false;
offset++;
} else if (!shouldIgnore && sourceString[offset] === "/") {
offset++;
if (sourceString[offset] === "*")
isMultiLineComment = true;
else if (sourceString[offset] === "/")
isSingleLineComment = true;
else
throw new Error("Bad parsing. Couldn't parse comment preamble.");
shouldIgnore = true;
}
offset++;
}
return offset + 1;
}
_clearTypeTokens()
{
this._typeTokenNodes.forEach(function(node) {
node.attachments.__typeToken = null;
});
this._typeTokenBookmarks.forEach(function(bookmark) {
bookmark.clear();
});
this._typeTokenNodes = [];
this._typeTokenBookmarks = [];
}
};
@@ -0,0 +1,464 @@
/*
* Copyright (C) 2020-2021 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.WebInspectorExtensionController = class WebInspectorExtensionController extends WI.Object
{
constructor()
{
super();
this._extensionForExtensionIDMap = new Map;
this._extensionTabContentViewForExtensionTabIDMap = new Map;
this._tabIDsForExtensionIDMap = new Multimap;
this._nextExtensionTabID = 1;
this._extensionTabPositions = null;
this._saveTabPositionsDebouncer = null;
WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._handleMainResourceDidChange, this);
}
// Static
static get extensionTabPositionsObjectStoreKey()
{
return "extension-tab-positions";
}
// Public
get registeredExtensionIDs()
{
return new Set(this._extensionForExtensionIDMap.keys());
}
registerExtension(extensionID, extensionBundleIdentifier, displayName)
{
if (this._extensionForExtensionIDMap.has(extensionID)) {
WI.reportInternalError("Unable to register extension, it's already registered: " + extensionID);
return WI.WebInspectorExtension.ErrorCode.RegistrationFailed;
}
if (!this._extensionForExtensionIDMap.size) {
WI.tabBrowser.tabBar.addEventListener(WI.TabBar.Event.TabBarItemAdded, this._saveExtensionTabPositions, this);
WI.tabBrowser.tabBar.addEventListener(WI.TabBar.Event.TabBarItemRemoved, this._saveExtensionTabPositions, this);
WI.tabBrowser.tabBar.addEventListener(WI.TabBar.Event.TabBarItemsReordered, this._saveExtensionTabPositions, this);
}
let extension = new WI.WebInspectorExtension(extensionID, extensionBundleIdentifier, displayName);
this._extensionForExtensionIDMap.set(extensionID, extension);
this.dispatchEventToListeners(WI.WebInspectorExtensionController.Event.ExtensionAdded, {extension});
}
unregisterExtension(extensionID)
{
let extension = this._extensionForExtensionIDMap.take(extensionID);
if (!extension) {
WI.reportInternalError("Unable to unregister extension with unknown ID: " + extensionID);
return WI.WebInspectorExtension.ErrorCode.InvalidRequest;
}
if (!this._extensionForExtensionIDMap.size) {
WI.tabBrowser.tabBar.removeEventListener(WI.TabBar.Event.TabBarItemAdded, this._saveExtensionTabPositions, this);
WI.tabBrowser.tabBar.removeEventListener(WI.TabBar.Event.TabBarItemRemoved, this._saveExtensionTabPositions, this);
WI.tabBrowser.tabBar.removeEventListener(WI.TabBar.Event.TabBarItemsReordered, this._saveExtensionTabPositions, this);
}
let extensionTabIDsToRemove = this._tabIDsForExtensionIDMap.take(extensionID) || [];
for (let extensionTabID of extensionTabIDsToRemove) {
let tabContentView = this._extensionTabContentViewForExtensionTabIDMap.take(extensionTabID);
// Ensure that the iframe is actually detached and does not leak.
WI.tabBrowser.closeTabForContentView(tabContentView, {suppressAnimations: true});
tabContentView.dispose();
}
this.dispatchEventToListeners(WI.WebInspectorExtensionController.Event.ExtensionRemoved, {extension});
}
async createTabForExtension(extensionID, tabName, tabIconURL, sourceURL)
{
let extension = this._extensionForExtensionIDMap.get(extensionID);
if (!extension) {
WI.reportInternalError("Unable to create tab for extension with unknown ID: " + extensionID + " sourceURL: " + sourceURL);
return WI.WebInspectorExtension.ErrorCode.InvalidRequest;
}
let extensionTabID = `WebExtensionTab-${extensionID}-${this._nextExtensionTabID++}`;
let tabContentView = new WI.WebInspectorExtensionTabContentView(extension, extensionTabID, tabName, tabIconURL, sourceURL);
this._tabIDsForExtensionIDMap.add(extensionID, extensionTabID);
this._extensionTabContentViewForExtensionTabIDMap.set(extensionTabID, tabContentView);
if (!this._extensionTabPositions)
await this._loadExtensionTabPositions();
WI.tabBrowser.addTabForContentView(tabContentView, {
suppressAnimations: true,
insertionIndex: this._insertionIndexForExtensionTab(tabContentView),
});
// The calling convention is to return an error string or a result object.
return {"result": extensionTabID};
}
evaluateScriptForExtension(extensionID, scriptSource, {frameURL, contextSecurityOrigin, useContentScriptContext} = {})
{
let extension = this._extensionForExtensionIDMap.get(extensionID);
if (!extension) {
WI.reportInternalError("Unable to evaluate script for extension with unknown ID: " + extensionID);
return WI.WebInspectorExtension.ErrorCode.InvalidRequest;
}
let frame = this._frameForFrameURL(frameURL);
if (!frame) {
WI.reportInternalError("evaluateScriptForExtension: No frame matched provided frameURL: " + frameURL);
return WI.WebInspectorExtension.ErrorCode.InvalidRequest;
}
if (contextSecurityOrigin) {
WI.reportInternalError("evaluateScriptForExtension: the 'contextSecurityOrigin' option is not yet implemented.");
return WI.WebInspectorExtension.ErrorCode.NotImplemented;
}
if (useContentScriptContext) {
WI.reportInternalError("evaluateScriptForExtension: the 'useContentScriptContext' option is not yet implemented.");
return WI.WebInspectorExtension.ErrorCode.NotImplemented;
}
let evaluationContext = frame.pageExecutionContext;
if (!evaluationContext) {
WI.reportInternalError("evaluateScriptForExtension: No 'pageExecutionContext' was present for frame with URL: " + frame.url);
return WI.WebInspectorExtension.ErrorCode.ContextDestroyed;
}
return evaluationContext.target.RuntimeAgent.evaluate.invoke({
expression: scriptSource,
objectGroup: "extension-evaluation",
includeCommandLineAPI: true,
returnByValue: true,
generatePreview: false,
saveResult: false,
contextId: evaluationContext.id,
}).then((payload) => {
let resultOrError = payload.result;
let wasThrown = payload.wasThrown;
let {type, value} = resultOrError;
return wasThrown ? {"error": resultOrError.description} : {"result": value};
}).catch((error) => error.description);
}
reloadForExtension(extensionID, {ignoreCache, userAgent, injectedScript} = {})
{
let extension = this._extensionForExtensionIDMap.get(extensionID);
if (!extension) {
WI.reportInternalError("Unable to evaluate script for extension with unknown ID: " + extensionID);
return WI.WebInspectorExtension.ErrorCode.InvalidRequest;
}
// FIXME: <webkit.org/b/222328> Implement `userAgent` and `injectedScript` options for `devtools.inspectedWindow.reload` command
if (userAgent) {
WI.reportInternalError("reloadForExtension: the 'userAgent' option is not yet implemented.");
return WI.WebInspectorExtension.ErrorCode.NotImplemented;
}
if (injectedScript) {
WI.reportInternalError("reloadForExtension: the 'injectedScript' option is not yet implemented.");
return WI.WebInspectorExtension.ErrorCode.NotImplemented;
}
let target = WI.assumingMainTarget();
if (!target.hasCommand("Page.reload"))
return WI.WebInspectorExtension.ErrorCode.InvalidRequest;
return target.PageAgent.reload.invoke({ignoreCache});
}
navigateTabForExtension(extensionTabID, sourceURL)
{
let tabContentView = this._extensionTabContentViewForExtensionTabIDMap.get(extensionTabID);
if (!tabContentView) {
WI.reportInternalError("Unable to navigate extension tab with unknown extensionTabID: " + extensionTabID);
return WI.WebInspectorExtension.ErrorCode.InvalidRequest;
}
tabContentView.iframeURL = sourceURL;
}
showExtensionTab(extensionTabID, options = {})
{
let tabContentView = this._extensionTabContentViewForExtensionTabIDMap.get(extensionTabID);
if (!tabContentView) {
WI.reportInternalError("Unable to show extension tab with unknown extensionTabID: " + extensionTabID);
return WI.WebInspectorExtension.ErrorCode.InvalidRequest;
}
tabContentView.visible = true;
let success = WI.tabBrowser.showTabForContentView(tabContentView, {
...options,
insertionIndex: this._insertionIndexForExtensionTab(tabContentView),
initiatorHint: WI.TabBrowser.TabNavigationInitiator.FrontendAPI,
});
if (!success) {
WI.reportInternalError("Unable to show extension tab with extensionTabID: " + extensionTabID);
return WI.WebInspectorExtension.ErrorCode.InternalError;
}
tabContentView.visible = true;
// Clients expect to be able to use evaluateScriptInExtensionTab() when this method
// returns, so wait for the extension tab to finish its loading sequence. Wrap the result.
return tabContentView.whenPageAvailable().then((sourceURL) => { return {"result": sourceURL}; });
}
hideExtensionTab(extensionTabID, options = {})
{
let tabContentView = this._extensionTabContentViewForExtensionTabIDMap.get(extensionTabID);
if (!tabContentView) {
WI.reportInternalError("Unable to show extension tab with unknown extensionTabID: " + extensionTabID);
return WI.WebInspectorExtension.ErrorCode.InvalidRequest;
}
tabContentView.visible = false;
WI.tabBrowser.closeTabForContentView(tabContentView, options);
console.assert(!tabContentView.visible);
console.assert(!tabContentView.isClosed);
}
addContextMenuItemsForClosedExtensionTabs(contextMenu)
{
contextMenu.appendSeparator();
for (let tabContentView of this._extensionTabContentViewForExtensionTabIDMap.values()) {
// If the extension tab has been unchecked in the TabBar context menu, then the tabBarItem
// for the extension tab will not be connected to a parent TabBar.
let shouldIncludeTab = !tabContentView.visible || !tabContentView.tabBarItem.parentTabBar;
if (!shouldIncludeTab)
continue;
contextMenu.appendItem(tabContentView.tabInfo().displayName, () => {
this.showExtensionTab(tabContentView.extensionTabID);
});
}
}
addContextMenuItemsForAllExtensionTabs(contextMenu)
{
contextMenu.appendSeparator();
for (let tabContentView of this._extensionTabContentViewForExtensionTabIDMap.values()) {
let checked = tabContentView.visible || !!tabContentView.tabBarItem.parentTabBar;
contextMenu.appendCheckboxItem(tabContentView.tabInfo().displayName, () => {
if (!checked)
this.showExtensionTab(tabContentView.extensionTabID);
else
this.hideExtensionTab(tabContentView.extensionTabID);
}, checked);
}
}
activeExtensionTabContentViews()
{
return Array.from(this._extensionTabContentViewForExtensionTabIDMap.values()).filter((tab) => tab.visible || tab.tabBarItem.parentTabBar);
}
evaluateScriptInExtensionTab(extensionTabID, scriptSource)
{
let tabContentView = this._extensionTabContentViewForExtensionTabIDMap.get(extensionTabID);
if (!tabContentView) {
WI.reportInternalError("Unable to evaluate with unknown extensionTabID: " + extensionTabID);
return WI.WebInspectorExtension.ErrorCode.InvalidRequest;
}
let iframe = tabContentView.iframeElement;
if (!(iframe instanceof HTMLIFrameElement)) {
WI.reportInternalError("Unable to evaluate without an <iframe> for extensionTabID: " + extensionTabID);
return WI.WebInspectorExtension.ErrorCode.InvalidRequest;
}
return new Promise((resolve, reject) => {
try {
// If `result` is a promise, then it came from a different frame and `instanceof Promise` won't work.
let result = InspectorFrontendHost.evaluateScriptInExtensionTab(iframe, scriptSource);
if (result?.then) {
result.then((resolvedValue) => resolve({result: resolvedValue}), (errorValue) => reject({error: errorValue}));
return;
}
resolve({result});
} catch (error) {
// Include more context in the stringification of the error.
const stackIndent = " ";
let stackLines = (error.stack?.split("\n") || []).map((line) => `${stackIndent}${line}`);
let formattedMessage = [
`Caught Exception: ${error.name}`,
`at ${error.sourceURL || "(unknown)"}:${error.line || 0}:${error.column || 0}:`,
error.message,
"",
"Backtrace:",
...stackLines,
].join("\n");
reject({error: formattedMessage});
}
});
}
// Private
async _loadExtensionTabPositions()
{
let savedTabPositions = await WI.objectStores.general.get(WebInspectorExtensionController.extensionTabPositionsObjectStoreKey);
this._extensionTabPositions = savedTabPositions || {};
}
_saveExtensionTabPositions()
{
if (!this._extensionTabPositions)
return;
this._saveTabPositionsDebouncer ||= new Debouncer(() => {
for (let tabBarItem of WI.tabBrowser.tabBar.visibleTabBarItemsFromLeftToRight) {
if (!(tabBarItem.representedObject instanceof WI.WebInspectorExtensionTabContentView))
continue;
let {anchorTabType, anchorTabIndex, distanceFromAnchorTab} = this._computeIndicesForExtensionTab(tabBarItem.representedObject, {recomputePositions: true});
this._extensionTabPositions[tabBarItem.representedObject.savedTabPositionKey] = {anchorTabType, distanceFromAnchorTab};
}
WI.objectStores.general.put(this._extensionTabPositions, WebInspectorExtensionController.extensionTabPositionsObjectStoreKey);
});
this._saveTabPositionsDebouncer.delayForTime(5000);
}
_insertionIndexForExtensionTab(tabContentView, options = {})
{
let {anchorTabType, anchorTabIndex, distanceFromAnchorTab} = this._computeIndicesForExtensionTab(tabContentView, options);
return anchorTabIndex + distanceFromAnchorTab + 1;
}
_computeIndicesForExtensionTab(tabContentView, {recomputePositions} = {})
{
// Note: pinned tabs always appear on the trailing edge, so we can ignore them
// for the purposes of computing an `insertionIndex`` for `tabContentView`.
let anchorTabIndex = 0;
let savedPositions = this._extensionTabPositions[tabContentView.savedTabPositionKey] || {};
let anchorTabType = (recomputePositions && savedPositions.anchorTabType) || null;
let distanceFromAnchorTab = (recomputePositions && savedPositions.distanceFromAnchorTab) || 0;
let visibleTabBarItems = WI.tabBrowser.tabBar.visibleTabBarItemsFromLeftToRight;
for (let i = 0; i < visibleTabBarItems.length; ++i) {
let visibleTab = visibleTabBarItems[i].representedObject;
if (!visibleTab)
continue;
if (visibleTab === tabContentView)
break;
if (visibleTab instanceof WI.WebInspectorExtensionTabContentView)
continue;
if (recomputePositions) {
anchorTabType = visibleTab.type || null;
continue;
}
if (visibleTab.type !== anchorTabType)
continue;
anchorTabIndex = i;
break;
}
// Find the count of extension tabs after the anchor tab to compute the real distanceFromAnchorTab.
// Adding `distanceFromAnchorTab` to `anchorTabIndex` should not insert the tab after a different anchor tab.
for (let i = 1; i < visibleTabBarItems.length - anchorTabIndex; ++i) {
if (visibleTabBarItems[anchorTabIndex + i].representedObject?.constructor?.shouldSaveTab?.()) {
distanceFromAnchorTab = Number.constrain(distanceFromAnchorTab, 0, Math.max(0, i - 1));
return {anchorTabType, anchorTabIndex, distanceFromAnchorTab};
}
}
// If the anchor tab is now hidden upon restoring, place the extension at the end.
// This could happen if a smaller set of tabs are enabled for the inspection target.
anchorTabIndex = visibleTabBarItems.length - 1;
return {anchorTabType, anchorTabIndex, distanceFromAnchorTab};
}
_frameForFrameURL(frameURL)
{
if (!frameURL)
return WI.networkManager.mainFrame;
function findFrame(frameURL, adjustKnownFrameURL) {
return WI.networkManager.frames.find((knownFrame) => {
let knownFrameURL = new URL(knownFrame.url);
adjustKnownFrameURL?.(knownFrameURL);
return knownFrameURL.toString() === frameURL;
});
}
let frame = findFrame(frameURL);
if (frame)
return frame;
let frameURLParts = new URL(frameURL);
if (frameURLParts.hash.length)
return null;
frame = findFrame(frameURL, (knownFrameURL) => {
knownFrameURL.hash = "";
});
if (frame)
return frame;
if (frameURLParts.search.length)
return null;
return findFrame(frameURL, (knownFrameURL) => {
knownFrameURL.hash = "";
knownFrameURL.search = "";
});
}
_handleMainResourceDidChange(event)
{
if (!event.target.isMainFrame())
return;
// Don't fire the event unless one or more extensions are registered.
if (!this._extensionForExtensionIDMap.size)
return;
InspectorFrontendHost.inspectedPageDidNavigate(WI.networkManager.mainFrame.url);
}
};
WI.WebInspectorExtensionController.Event = {
ExtensionAdded: "extension-added",
ExtensionRemoved: "extension-removed",
};
+78
View File
@@ -0,0 +1,78 @@
/*
* 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.
*/
WI.WorkerManager = class WorkerManager extends WI.Object
{
constructor()
{
super();
this._connections = new Map;
}
// Target
initializeTarget(target)
{
if (target.hasDomain("Worker"))
target.WorkerAgent.enable();
}
// WorkerObserver
workerCreated(target, workerId, url, name)
{
console.assert(target.hasCommand("Worker.sendMessageToWorker"));
let connection = new InspectorBackend.WorkerConnection;
let workerTarget = new WI.WorkerTarget(target, workerId, url, name, connection);
workerTarget.initialize();
WI.targetManager.addTarget(workerTarget);
this._connections.set(workerId, connection);
// Unpause the worker now that we have sent all initialization messages.
// Ignore errors if a worker went away quickly.
target.WorkerAgent.initialized(workerId).catch(function(){});
}
workerTerminated(workerId)
{
let connection = this._connections.take(workerId);
WI.targetManager.removeTarget(connection.target);
}
dispatchMessageFromWorker(workerId, message)
{
let connection = this._connections.get(workerId);
console.assert(connection);
if (!connection)
return;
connection.dispatch(message);
}
};