moved to root
This commit is contained in:
@@ -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();
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
@@ -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";
|
||||
@@ -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";
|
||||
@@ -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}'.`);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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"
|
||||
};
|
||||
@@ -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
@@ -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",
|
||||
};
|
||||
@@ -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",
|
||||
};
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
@@ -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"
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
@@ -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",
|
||||
};
|
||||
@@ -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
@@ -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, "_.-/");
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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
@@ -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",
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user